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 /// Get git commit hash from rebuild process fn get_git_commit(&self) -> Result> { let commit_file = "/var/lib/cm-dashboard/git-commit"; match std::fs::read_to_string(commit_file) { Ok(content) => { let commit_hash = content.trim(); if commit_hash.len() >= 7 { Ok(commit_hash.to_string()) } else { Err("Git commit hash too short".into()) } } Err(e) => Err(format!("Failed to read git commit file: {}", e).into()) } } 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 git commit information (shows what's actually deployed) match self.get_git_commit() { Ok(git_commit) => { metrics.push(Metric { name: "system_nixos_build".to_string(), value: MetricValue::String(git_commit), unit: None, description: Some("Git commit hash of deployed configuration".to_string()), status: Status::Ok, timestamp, }); } Err(e) => { debug!("Failed to get git commit: {}", e); metrics.push(Metric { name: "system_nixos_build".to_string(), value: MetricValue::String("unknown".to_string()), unit: None, description: Some("Git commit 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) } }