From bb72c427260434da6a44e4b9aea10fbb074eeb4d Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Sun, 26 Oct 2025 10:38:20 +0100 Subject: [PATCH] Add agent version reporting and display - Agent reports version via agent_version metric using nix store hash - Dashboard displays agent version in system widget - Foundation for cross-host version comparison - Both agent -V and dashboard show versions --- agent/src/agent.rs | 39 +++++++++++++++++++++++++++++- agent/src/main.rs | 18 +++++++++++++- dashboard/src/ui/widgets/system.rs | 6 ++--- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/agent/src/agent.rs b/agent/src/agent.rs index baba1e4..bf91d88 100644 --- a/agent/src/agent.rs +++ b/agent/src/agent.rs @@ -9,7 +9,7 @@ use crate::config::AgentConfig; use crate::metrics::MetricCollectionManager; use crate::notifications::NotificationManager; use crate::status::HostStatusManager; -use cm_dashboard_shared::{Metric, MetricMessage}; +use cm_dashboard_shared::{Metric, MetricMessage, MetricValue, Status}; pub struct Agent { hostname: String, @@ -162,6 +162,10 @@ impl Agent { let host_status_metric = self.host_status_manager.get_host_status_metric(); metrics.push(host_status_metric); + // Add agent version metric for cross-host version comparison + let version_metric = self.get_agent_version_metric(); + metrics.push(version_metric); + if metrics.is_empty() { debug!("No metrics to broadcast"); return Ok(()); @@ -183,6 +187,39 @@ impl Agent { } } + /// Create agent version metric for cross-host version comparison + fn get_agent_version_metric(&self) -> Metric { + // Get version from executable path (same logic as main.rs get_version) + let version = self.get_agent_version(); + + Metric::new( + "agent_version".to_string(), + MetricValue::String(version), + Status::Ok, + ) + } + + /// Get agent version from executable path + fn get_agent_version(&self) -> String { + match std::env::current_exe() { + Ok(exe_path) => { + let exe_str = exe_path.to_string_lossy(); + + // Extract Nix store hash from path + if let Some(hash_part) = exe_str.strip_prefix("/nix/store/") { + if let Some(hash) = hash_part.split('-').next() { + if hash.len() >= 8 { + return hash[..8].to_string(); + } + } + } + + "unknown".to_string() + }, + Err(_) => "unknown".to_string() + } + } + async fn handle_commands(&mut self) -> Result<()> { // Try to receive commands (non-blocking) match self.zmq_handler.try_receive_command() { diff --git a/agent/src/main.rs b/agent/src/main.rs index e238866..7fc3f7a 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -13,10 +13,26 @@ mod status; use agent::Agent; +/// Get version showing cm-dashboard-agent package hash for easy deployment verification +fn get_version() -> &'static str { + // Get the path of the current executable + let exe_path = std::env::current_exe().expect("Failed to get executable path"); + let exe_str = exe_path.to_string_lossy(); + + // Extract Nix store hash from path like /nix/store/HASH-cm-dashboard-v0.1.8/bin/cm-dashboard-agent + let hash_part = exe_str.strip_prefix("/nix/store/").expect("Not a nix store path"); + let hash = hash_part.split('-').next().expect("Invalid nix store path format"); + assert!(hash.len() >= 8, "Hash too short"); + + // Return first 8 characters of nix store hash + let short_hash = hash[..8].to_string(); + Box::leak(short_hash.into_boxed_str()) +} + #[derive(Parser)] #[command(name = "cm-dashboard-agent")] #[command(about = "CM Dashboard metrics agent with individual metric collection")] -#[command(version)] +#[command(version = get_version())] struct Cli { /// Increase logging verbosity (-v, -vv) #[arg(short, long, action = clap::ArgAction::Count)] diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index 18f0800..f84d585 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -339,9 +339,9 @@ impl Widget for SystemWidget { self.active_users = Some(users.clone()); } } - "system_agent_hash" => { - if let MetricValue::String(hash) = &metric.value { - self.agent_hash = Some(hash.clone()); + "agent_version" => { + if let MetricValue::String(version) = &metric.value { + self.agent_hash = Some(version.clone()); } }