use async_trait::async_trait; use cm_dashboard_shared::{Metric, MetricValue, Status, StatusTracker}; use std::process::Command; use tracing::debug; use super::{Collector, CollectorError}; use crate::config::NixOSConfig; /// NixOS system information collector /// /// Collects NixOS-specific system information including: /// - NixOS version and build information pub struct NixOSCollector { } impl NixOSCollector { pub fn new(_config: NixOSConfig) -> Self { Self {} } /// Get agent hash from binary path fn get_agent_hash(&self) -> Result> { // Get the path of the current executable let exe_path = std::env::current_exe()?; let exe_str = exe_path.to_string_lossy(); // Extract Nix store hash from path like /nix/store/fn804fh332mp8gz06qawminpj20xl25h-cm-dashboard-0.1.0/bin/cm-dashboard-agent if let Some(store_path) = exe_str.strip_prefix("/nix/store/") { if let Some(dash_pos) = store_path.find('-') { return Ok(store_path[..dash_pos].to_string()); } } // Fallback to "unknown" if not in Nix store Ok("unknown".to_string()) } /// Get configuration hash from deployed nix store system fn get_config_hash(&self) -> Result> { // Read the symlink target of /run/current-system to get nix store path let output = Command::new("readlink") .arg("/run/current-system") .output()?; if !output.status.success() { return Err("readlink command failed".into()); } let binding = String::from_utf8_lossy(&output.stdout); let store_path = binding.trim(); // Extract hash from nix store path // Format: /nix/store/HASH-nixos-system-HOSTNAME-VERSION if let Some(hash_part) = store_path.strip_prefix("/nix/store/") { if let Some(hash) = hash_part.split('-').next() { if hash.len() >= 8 { // Return first 8 characters of nix store hash return Ok(hash[..8].to_string()); } } } Err("Could not extract hash from nix store path".into()) } } #[async_trait] impl Collector for NixOSCollector { async fn collect(&self, _status_tracker: &mut StatusTracker) -> Result, CollectorError> { debug!("Collecting NixOS system information"); let mut metrics = Vec::new(); let timestamp = chrono::Utc::now().timestamp() as u64; // Collect NixOS build information (config hash) match self.get_config_hash() { Ok(config_hash) => { metrics.push(Metric { name: "system_nixos_build".to_string(), value: MetricValue::String(config_hash), unit: None, description: Some("NixOS deployed configuration hash".to_string()), status: Status::Ok, timestamp, }); } Err(e) => { debug!("Failed to get config hash: {}", e); metrics.push(Metric { name: "system_nixos_build".to_string(), value: MetricValue::String("unknown".to_string()), unit: None, description: Some("NixOS config hash (failed to detect)".to_string()), status: Status::Unknown, timestamp, }); } } // Collect config hash match self.get_config_hash() { Ok(hash) => { metrics.push(Metric { name: "system_config_hash".to_string(), value: MetricValue::String(hash), unit: None, description: Some("NixOS deployed configuration hash".to_string()), status: Status::Ok, timestamp, }); } Err(e) => { debug!("Failed to get config hash: {}", e); metrics.push(Metric { name: "system_config_hash".to_string(), value: MetricValue::String("unknown".to_string()), unit: None, description: Some("Deployed config hash (failed to detect)".to_string()), status: Status::Unknown, timestamp, }); } } // Collect agent hash match self.get_agent_hash() { Ok(hash) => { metrics.push(Metric { name: "system_agent_hash".to_string(), value: MetricValue::String(hash), unit: None, description: Some("Agent Nix store hash".to_string()), status: Status::Ok, timestamp, }); } Err(e) => { debug!("Failed to get agent hash: {}", e); metrics.push(Metric { name: "system_agent_hash".to_string(), value: MetricValue::String("unknown".to_string()), unit: None, description: Some("Agent hash (failed to detect)".to_string()), status: Status::Unknown, timestamp, }); } } debug!("Collected {} NixOS metrics", metrics.len()); Ok(metrics) } }