Christoffer Martinsson 3d187c9220 Make dashboard -V show actual config hash for rebuild verification
Replace hardcoded version with first 8 characters of current system's
nix store hash. This makes it easy to verify when rebuilds complete
as the hash changes with each deployment.

No fallback - fails hard if config hash cannot be determined.
2025-10-25 14:31:20 +02:00

108 lines
3.1 KiB
Rust

use anyhow::Result;
use clap::Parser;
use tracing::{error, info};
use tracing_subscriber::EnvFilter;
mod app;
mod communication;
mod config;
mod metrics;
mod ui;
use app::Dashboard;
/// Get version showing current system config hash for easy rebuild verification
fn get_version() -> &'static str {
use std::process::Command;
// Get config hash from current system
let output = Command::new("readlink").arg("/run/current-system").output().expect("Failed to run readlink");
assert!(output.status.success(), "readlink command failed");
let store_path = String::from_utf8_lossy(&output.stdout);
let store_path = store_path.trim();
// Extract hash from nix store path
let hash_part = store_path.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")]
#[command(about = "CM Dashboard TUI with individual metric consumption")]
#[command(version = get_version())]
struct Cli {
/// Increase logging verbosity (-v, -vv)
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
/// Configuration file path (defaults to /etc/cm-dashboard/dashboard.toml)
#[arg(short, long)]
config: Option<String>,
/// Run in headless mode (no TUI, just logging)
#[arg(long)]
headless: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
// Setup logging - only if headless or verbose
if cli.headless || cli.verbose > 0 {
let log_level = match cli.verbose {
0 => "warn", // Only warnings and errors when not verbose
1 => "info",
2 => "debug",
_ => "trace",
};
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(log_level.parse()?))
.init();
} else {
// No logging output when running TUI mode
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive("off".parse()?))
.init();
}
if cli.headless || cli.verbose > 0 {
info!("CM Dashboard starting with individual metrics architecture...");
}
// Create and run dashboard
let mut dashboard = Dashboard::new(cli.config, cli.headless).await?;
// Setup graceful shutdown
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
// Run dashboard with graceful shutdown
tokio::select! {
result = dashboard.run() => {
if let Err(e) = result {
error!("Dashboard error: {}", e);
return Err(e);
}
}
_ = ctrl_c => {
info!("Shutdown signal received");
}
}
if cli.headless || cli.verbose > 0 {
info!("Dashboard shutdown complete");
}
Ok(())
}