Christoffer Martinsson e7200fb1b0 Implement UUID-based disk detection for CMTEC infrastructure
Replace df-based auto-discovery with UUID-based detection using NixOS
hardware configuration data. Each host now has predefined filesystem
configurations with predictable metric names.

- Add FilesystemConfig struct with UUID, mount point, and filesystem type
- Remove auto_discover and devices fields from DiskConfig
- Add host-specific UUID defaults for cmbox, srv01, srv02, simonbox, steambox
- Remove legacy get_mounted_disks() df-based detection method
- Update DiskCollector to use UUID resolution via /dev/disk/by-uuid/
- Generate predictable metric names: disk_root_*, disk_boot_*, etc.
- Maintain fallback for labbox/wslbox (no UUIDs configured yet)

Provides consistent metric names across reboots and reliable detection
aligned with NixOS deployments without dependency on mount order.
2025-10-20 09:50:10 +02:00

418 lines
13 KiB
Rust

use anyhow::Result;
use cm_dashboard_shared::CacheConfig;
use gethostname::gethostname;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub mod defaults;
pub mod loader;
pub mod validation;
use defaults::*;
/// Main agent configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub zmq: ZmqConfig,
pub collectors: CollectorConfig,
pub cache: CacheConfig,
pub notifications: NotificationConfig,
pub collection_interval_seconds: u64,
}
/// ZMQ communication configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZmqConfig {
pub publisher_port: u16,
pub command_port: u16,
pub bind_address: String,
pub timeout_ms: u64,
pub heartbeat_interval_ms: u64,
}
/// Collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectorConfig {
pub cpu: CpuConfig,
pub memory: MemoryConfig,
pub disk: DiskConfig,
pub processes: ProcessConfig,
pub systemd: SystemdConfig,
pub smart: SmartConfig,
pub backup: BackupConfig,
pub network: NetworkConfig,
}
/// CPU collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub load_warning_threshold: f32,
pub load_critical_threshold: f32,
pub temperature_warning_threshold: f32,
pub temperature_critical_threshold: f32,
}
/// Memory collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub usage_warning_percent: f32,
pub usage_critical_percent: f32,
}
/// Disk collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub usage_warning_percent: f32,
pub usage_critical_percent: f32,
pub filesystems: Vec<FilesystemConfig>,
}
/// Filesystem configuration entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilesystemConfig {
pub name: String, // Human-readable name (e.g., "root", "boot", "home")
pub uuid: String, // UUID for /dev/disk/by-uuid/ resolution
pub mount_point: String, // Expected mount point (e.g., "/", "/boot")
pub fs_type: String, // Filesystem type (e.g., "ext4", "vfat")
pub monitor: bool, // Whether to monitor this filesystem
}
/// Process collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub top_processes_count: usize,
}
/// Systemd services collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemdConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub auto_discover: bool,
pub services: Vec<String>,
pub memory_warning_mb: f32,
pub memory_critical_mb: f32,
}
/// SMART collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SmartConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub temperature_warning_celsius: f32,
pub temperature_critical_celsius: f32,
pub wear_warning_percent: f32,
pub wear_critical_percent: f32,
}
/// Backup collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub backup_paths: Vec<String>,
pub max_age_hours: u64,
}
/// Network collector configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub interfaces: Vec<String>,
pub auto_discover: bool,
}
/// Notification configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationConfig {
pub enabled: bool,
pub smtp_host: String,
pub smtp_port: u16,
pub from_email: String,
pub to_email: String,
pub rate_limit_minutes: u64,
}
impl AgentConfig {
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
loader::load_config(path)
}
pub fn validate(&self) -> Result<()> {
validation::validate_config(self)
}
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
zmq: ZmqConfig::default(),
collectors: CollectorConfig::default(),
cache: CacheConfig::default(),
notifications: NotificationConfig::default(),
collection_interval_seconds: DEFAULT_COLLECTION_INTERVAL_SECONDS,
}
}
}
impl Default for ZmqConfig {
fn default() -> Self {
Self {
publisher_port: DEFAULT_ZMQ_PUBLISHER_PORT,
command_port: DEFAULT_ZMQ_COMMAND_PORT,
bind_address: DEFAULT_ZMQ_BIND_ADDRESS.to_string(),
timeout_ms: DEFAULT_ZMQ_TIMEOUT_MS,
heartbeat_interval_ms: DEFAULT_ZMQ_HEARTBEAT_INTERVAL_MS,
}
}
}
impl Default for CollectorConfig {
fn default() -> Self {
Self {
cpu: CpuConfig::default(),
memory: MemoryConfig::default(),
disk: DiskConfig::default(),
processes: ProcessConfig::default(),
systemd: SystemdConfig::default(),
smart: SmartConfig::default(),
backup: BackupConfig::default(),
network: NetworkConfig::default(),
}
}
}
impl Default for CpuConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_CPU_INTERVAL_SECONDS,
load_warning_threshold: DEFAULT_CPU_LOAD_WARNING,
load_critical_threshold: DEFAULT_CPU_LOAD_CRITICAL,
temperature_warning_threshold: DEFAULT_CPU_TEMP_WARNING,
temperature_critical_threshold: DEFAULT_CPU_TEMP_CRITICAL,
}
}
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_MEMORY_INTERVAL_SECONDS,
usage_warning_percent: DEFAULT_MEMORY_WARNING_PERCENT,
usage_critical_percent: DEFAULT_MEMORY_CRITICAL_PERCENT,
}
}
}
impl Default for DiskConfig {
fn default() -> Self {
let hostname = gethostname::gethostname().to_string_lossy().to_string();
let filesystems = get_default_filesystems_for_host(&hostname);
Self {
enabled: true,
interval_seconds: DEFAULT_DISK_INTERVAL_SECONDS,
usage_warning_percent: DEFAULT_DISK_WARNING_PERCENT,
usage_critical_percent: DEFAULT_DISK_CRITICAL_PERCENT,
filesystems,
}
}
}
/// Get default filesystem configurations for known CMTEC hosts
fn get_default_filesystems_for_host(hostname: &str) -> Vec<FilesystemConfig> {
match hostname {
"cmbox" => vec![
FilesystemConfig {
name: "root".to_string(),
uuid: "4cade5ce-85a5-4a03-83c8-dfd1d3888d79".to_string(),
mount_point: "/".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
FilesystemConfig {
name: "boot".to_string(),
uuid: "AB4D-62EC".to_string(),
mount_point: "/boot".to_string(),
fs_type: "vfat".to_string(),
monitor: true,
},
],
"srv02" => vec![
FilesystemConfig {
name: "root".to_string(),
uuid: "5a880608-c79f-458f-a031-30206aa27ca7".to_string(),
mount_point: "/".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
FilesystemConfig {
name: "boot".to_string(),
uuid: "6B2E-2AD9".to_string(),
mount_point: "/boot".to_string(),
fs_type: "vfat".to_string(),
monitor: true,
},
],
"simonbox" => vec![
FilesystemConfig {
name: "root".to_string(),
uuid: "b74284a9-2899-4f71-bdb0-fd07dc4baab3".to_string(),
mount_point: "/".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
FilesystemConfig {
name: "boot".to_string(),
uuid: "F6A3-AD2B".to_string(),
mount_point: "/boot".to_string(),
fs_type: "vfat".to_string(),
monitor: true,
},
FilesystemConfig {
name: "steampool_1".to_string(),
uuid: "09300cb7-0938-4dba-8a42-7a7aaf60db51".to_string(),
mount_point: "/steampool_1".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
FilesystemConfig {
name: "steampool_2".to_string(),
uuid: "a2d61a41-3f2a-4760-b62e-5eb8caf50d1a".to_string(),
mount_point: "/steampool_2".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
],
"steambox" => vec![
FilesystemConfig {
name: "root".to_string(),
uuid: "4514ca9f-2d0a-40df-b14b-e342f39c3e6a".to_string(),
mount_point: "/".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
FilesystemConfig {
name: "boot".to_string(),
uuid: "8FD2-1B13".to_string(),
mount_point: "/boot".to_string(),
fs_type: "vfat".to_string(),
monitor: true,
},
FilesystemConfig {
name: "steampool".to_string(),
uuid: "0ebe8abb-bbe7-4224-947b-86bf38981f60".to_string(),
mount_point: "/mnt/steampool".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
],
"srv01" => vec![
FilesystemConfig {
name: "root".to_string(),
uuid: "cd98df34-03a3-4d68-8338-d90d2920f9f8".to_string(),
mount_point: "/".to_string(),
fs_type: "ext4".to_string(),
monitor: true,
},
FilesystemConfig {
name: "boot".to_string(),
uuid: "13E1-4DDE".to_string(),
mount_point: "/boot".to_string(),
fs_type: "vfat".to_string(),
monitor: true,
},
],
// labbox and wslbox have no UUIDs configured yet
"labbox" | "wslbox" => {
Vec::new()
},
_ => {
// Unknown hosts use auto-discovery
Vec::new()
}
}
}
impl Default for ProcessConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_PROCESS_INTERVAL_SECONDS,
top_processes_count: DEFAULT_TOP_PROCESSES_COUNT,
}
}
}
impl Default for SystemdConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_SYSTEMD_INTERVAL_SECONDS,
auto_discover: true,
services: Vec::new(),
memory_warning_mb: DEFAULT_SERVICE_MEMORY_WARNING_MB,
memory_critical_mb: DEFAULT_SERVICE_MEMORY_CRITICAL_MB,
}
}
}
impl Default for SmartConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_SMART_INTERVAL_SECONDS,
temperature_warning_celsius: DEFAULT_SMART_TEMP_WARNING,
temperature_critical_celsius: DEFAULT_SMART_TEMP_CRITICAL,
wear_warning_percent: DEFAULT_SMART_WEAR_WARNING,
wear_critical_percent: DEFAULT_SMART_WEAR_CRITICAL,
}
}
}
impl Default for BackupConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_BACKUP_INTERVAL_SECONDS,
backup_paths: Vec::new(),
max_age_hours: DEFAULT_BACKUP_MAX_AGE_HOURS,
}
}
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: DEFAULT_NETWORK_INTERVAL_SECONDS,
interfaces: Vec::new(),
auto_discover: true,
}
}
}
impl Default for NotificationConfig {
fn default() -> Self {
Self {
enabled: true,
smtp_host: DEFAULT_SMTP_HOST.to_string(),
smtp_port: DEFAULT_SMTP_PORT,
from_email: DEFAULT_FROM_EMAIL.to_string(),
to_email: DEFAULT_TO_EMAIL.to_string(),
rate_limit_minutes: DEFAULT_NOTIFICATION_RATE_LIMIT_MINUTES,
}
}
}