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, } /// 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, 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, 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, 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>(path: P) -> Result { 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 { 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, } } }