- Change from showing version to build format: 'hash dd/mm/yy H:M:S' - Parse nixos-version output to extract short hash and format date - Update system widget to display 'Build:' instead of 'Version:' - Remove version/build_date fields in favor of single build string - Follow TODO.md specification for NixOS section layout
158 lines
5.8 KiB
Rust
158 lines
5.8 KiB
Rust
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
|
|
/// - Currently active/logged in users
|
|
pub struct NixOSCollector {
|
|
config: NixOSConfig,
|
|
}
|
|
|
|
impl NixOSCollector {
|
|
pub fn new(config: NixOSConfig) -> Self {
|
|
Self { config }
|
|
}
|
|
|
|
/// Get NixOS build information (short hash and timestamp)
|
|
fn get_nixos_build_info(&self) -> Result<(String, String), Box<dyn std::error::Error>> {
|
|
// Try nixos-version command first
|
|
if let Ok(output) = Command::new("nixos-version").output() {
|
|
if output.status.success() {
|
|
let version_line = String::from_utf8_lossy(&output.stdout);
|
|
let version = version_line.trim();
|
|
|
|
// Parse format: "24.05.20241023.abcdef (Vicuna)"
|
|
if let Some(parts) = version.split('.').collect::<Vec<&str>>().get(2) {
|
|
if parts.len() >= 14 { // 8 digits + 6 hex chars minimum
|
|
let date_part = &parts[..8]; // YYYYMMDD
|
|
let hash_part = &parts[8..]; // remaining is hash
|
|
|
|
// Extract short hash (first 6 characters)
|
|
let short_hash = if hash_part.len() >= 6 {
|
|
&hash_part[..6]
|
|
} else {
|
|
hash_part
|
|
};
|
|
|
|
// Format date from YYYYMMDD to dd/mm/yy
|
|
if date_part.len() == 8 {
|
|
let year = &date_part[2..4]; // YY
|
|
let month = &date_part[4..6]; // MM
|
|
let day = &date_part[6..8]; // DD
|
|
let formatted_date = format!("{}/{}/{}", day, month, year);
|
|
|
|
return Ok((short_hash.to_string(), formatted_date));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Err("Could not parse NixOS build information".into())
|
|
}
|
|
|
|
/// Get currently active users
|
|
fn get_active_users(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
|
let output = Command::new("who").output()?;
|
|
|
|
if !output.status.success() {
|
|
return Err("who command failed".into());
|
|
}
|
|
|
|
let who_output = String::from_utf8_lossy(&output.stdout);
|
|
let mut users = std::collections::HashSet::new();
|
|
|
|
for line in who_output.lines() {
|
|
if let Some(username) = line.split_whitespace().next() {
|
|
if !username.is_empty() {
|
|
users.insert(username.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(users.into_iter().collect())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Collector for NixOSCollector {
|
|
fn name(&self) -> &str {
|
|
"nixos"
|
|
}
|
|
|
|
async fn collect(&self, _status_tracker: &mut StatusTracker) -> Result<Vec<Metric>, CollectorError> {
|
|
debug!("Collecting NixOS system information");
|
|
let mut metrics = Vec::new();
|
|
let timestamp = chrono::Utc::now().timestamp() as u64;
|
|
|
|
// Collect NixOS build information
|
|
match self.get_nixos_build_info() {
|
|
Ok((short_hash, formatted_date)) => {
|
|
// Create combined build string: "hash dd/mm/yy H:M:S"
|
|
// For now, use current time for H:M:S (could be enhanced to get actual build time)
|
|
let now = chrono::Local::now();
|
|
let build_string = format!("{} {} {}",
|
|
short_hash,
|
|
formatted_date,
|
|
now.format("%H:%M:%S")
|
|
);
|
|
|
|
metrics.push(Metric {
|
|
name: "system_nixos_build".to_string(),
|
|
value: MetricValue::String(build_string),
|
|
unit: None,
|
|
description: Some("NixOS build information".to_string()),
|
|
status: Status::Ok,
|
|
timestamp,
|
|
});
|
|
}
|
|
Err(e) => {
|
|
debug!("Failed to get NixOS build info: {}", e);
|
|
metrics.push(Metric {
|
|
name: "system_nixos_build".to_string(),
|
|
value: MetricValue::String("unknown".to_string()),
|
|
unit: None,
|
|
description: Some("NixOS build (failed to detect)".to_string()),
|
|
status: Status::Unknown,
|
|
timestamp,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Collect active users
|
|
match self.get_active_users() {
|
|
Ok(users) => {
|
|
let users_str = users.join(", ");
|
|
metrics.push(Metric {
|
|
name: "system_active_users".to_string(),
|
|
value: MetricValue::String(users_str),
|
|
unit: None,
|
|
description: Some("Currently active users".to_string()),
|
|
status: Status::Ok,
|
|
timestamp,
|
|
});
|
|
}
|
|
Err(e) => {
|
|
debug!("Failed to get active users: {}", e);
|
|
metrics.push(Metric {
|
|
name: "system_active_users".to_string(),
|
|
value: MetricValue::String("unknown".to_string()),
|
|
unit: None,
|
|
description: Some("Active users (failed to detect)".to_string()),
|
|
status: Status::Unknown,
|
|
timestamp,
|
|
});
|
|
}
|
|
}
|
|
|
|
debug!("Collected {} NixOS metrics", metrics.len());
|
|
Ok(metrics)
|
|
}
|
|
} |