diff --git a/agent/src/collectors/nixos.rs b/agent/src/collectors/nixos.rs index 7db6aa2..982d298 100644 --- a/agent/src/collectors/nixos.rs +++ b/agent/src/collectors/nixos.rs @@ -63,6 +63,29 @@ impl NixOSCollector { Ok("unknown".to_string()) } + /// Get configuration hash from cloned nixos-config git repository + fn get_config_hash(&self) -> Result> { + // Get git hash from the cloned nixos-config directory + let config_path = "/var/lib/cm-dashboard/nixos-config"; + + let output = Command::new("git") + .args(&["log", "-1", "--format=%h"]) + .current_dir(config_path) + .output()?; + + if !output.status.success() { + return Err("git log command failed".into()); + } + + let hash = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if hash.is_empty() { + return Err("Empty git hash output".into()); + } + + Ok(hash) + } + /// Get currently active users fn get_active_users(&self) -> Result, Box> { let output = Command::new("who").output()?; @@ -148,6 +171,31 @@ impl Collector for NixOSCollector { } } + // 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 configuration git 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("Config hash (failed to detect)".to_string()), + status: Status::Unknown, + timestamp, + }); + } + } + // Collect agent hash match self.get_agent_hash() { Ok(hash) => { diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index b6527b4..0216998 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -14,6 +14,7 @@ use crate::ui::theme::{StatusIcons, Typography}; pub struct SystemWidget { // NixOS information nixos_build: Option, + config_hash: Option, active_users: Option, agent_hash: Option, @@ -64,6 +65,7 @@ impl SystemWidget { pub fn new() -> Self { Self { nixos_build: None, + config_hash: None, active_users: None, agent_hash: None, cpu_load_1min: None, @@ -322,6 +324,11 @@ impl Widget for SystemWidget { self.nixos_build = Some(build.clone()); } } + "system_config_hash" => { + if let MetricValue::String(hash) = &metric.value { + self.config_hash = Some(hash.clone()); + } + } "system_active_users" => { if let MetricValue::String(users) = &metric.value { self.active_users = Some(users.clone()); @@ -418,6 +425,11 @@ impl SystemWidget { Span::styled(format!("Build: {}", build_text), Typography::secondary()) ])); + let config_text = self.config_hash.as_deref().unwrap_or("unknown"); + lines.push(Line::from(vec![ + Span::styled(format!("Config: {}", config_text), Typography::secondary()) + ])); + let agent_hash_text = self.agent_hash.as_deref().unwrap_or("unknown"); let short_hash = if agent_hash_text.len() > 8 && agent_hash_text != "unknown" { &agent_hash_text[..8]