use nanometrics_agent::{config, metrics, payload, transport}; use sysinfo::{Components, Networks, System}; use std::time::{Duration, Instant}; use std::sync::mpsc; use std::sync::atomic::{AtomicBool, Ordering}; static RUNNING: AtomicBool = AtomicBool::new(true); extern "C" fn handle_signal(_: libc::c_int) { RUNNING.store(false, Ordering::Relaxed); } fn get_local_ip(server_ip: &str) -> String { use std::net::UdpSocket; // Try server IP first (always reachable), then internet fallback for target in &[format!("{}:80", server_ip), "8.8.8.8:80".to_string()] { if let Ok(s) = UdpSocket::bind("0.0.0.0:0") { if s.connect(target.as_str()).is_ok() { if let Ok(addr) = s.local_addr() { let ip = addr.ip().to_string(); if ip != "0.0.0.0" { return ip; } } } } } "0.0.0.0".to_string() } fn apply_config_update(cfg: &mut config::Config, data: &[u8]) { if let Ok(new_cfg) = serde_json::from_slice::(data) { cfg.metrics = new_cfg.metrics; eprintln!("[config] mis à jour depuis le serveur"); } } fn main() { let cfg_path = std::env::args() .nth(1) .unwrap_or_else(|| "config.toml".to_string()); let mut cfg = config::load(std::path::Path::new(&cfg_path)) .expect("Impossible de charger config.toml"); let hostname = System::host_name().unwrap_or_else(|| "unknown".to_string()); let ip = get_local_ip(&cfg.server.ip); let mut sys = System::new(); let mut networks = Networks::new_with_refreshed_list(); let mut components = Components::new_with_refreshed_list(); let udp_sender = if cfg.protocols.udp.enabled { Some(transport::udp::UdpSender::new(&cfg.server.ip, cfg.server.port)) } else { None }; let (incoming_tx, incoming_rx) = mpsc::channel::(); let mqtt_client = if cfg.protocols.mqtt.enabled { Some(transport::mqtt::start(&hostname, &cfg.protocols.mqtt, incoming_tx)) } else { None }; unsafe { libc::signal(libc::SIGTERM, handle_signal as libc::sighandler_t); libc::signal(libc::SIGINT, handle_signal as libc::sighandler_t); } let mut last_slow = Instant::now(); let mut last_medium = Instant::now(); let mut first_medium = true; let mut first_slow = true; // Scheduler métriques lentes (startup + 1×/jour à l'heure configurée) let slow_time: (u32, u32) = { let parts: Vec<&str> = cfg.metrics.slow_daily_time.splitn(2, ':').collect(); let h = parts.first().and_then(|s| s.parse().ok()).unwrap_or(3u32); let m = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0u32); (h, m) }; let mut slow_daily_done = false; let mut slow_last_yday = metrics::network_info::current_yday().wrapping_sub(1); // Collecte immédiate au démarrage let mut startup_net: Option> = None; let mut startup_hw: Option = None; if cfg.metrics.network_info.udp || cfg.metrics.network_info.mqtt { let ni = metrics::network_info::collect(&cfg.server.ip, cfg.server.iperf3_port); if !ni.is_empty() { startup_net = Some(ni); } } if cfg.metrics.hardware_info.udp || cfg.metrics.hardware_info.mqtt { startup_hw = metrics::hardware::collect(); } while RUNNING.load(Ordering::Relaxed) { let now = Instant::now(); while let Ok(transport::mqtt::MqttIncoming::ConfigUpdate(data)) = incoming_rx.try_recv() { apply_config_update(&mut cfg, &data); } sys.refresh_cpu_usage(); sys.refresh_memory(); // Métriques lentes quotidiennes let cur_yday = metrics::network_info::current_yday(); if cur_yday != slow_last_yday { slow_last_yday = cur_yday; slow_daily_done = false; } let mut daily_net: Option> = None; let mut daily_hw: Option = None; if !slow_daily_done { let (ch, cm) = metrics::network_info::current_hhmm(); if ch == slow_time.0 && cm == slow_time.1 { slow_daily_done = true; if cfg.metrics.network_info.udp || cfg.metrics.network_info.mqtt { let ni = metrics::network_info::collect(&cfg.server.ip, cfg.server.iperf3_port); if !ni.is_empty() { daily_net = Some(ni); } } if cfg.metrics.hardware_info.udp || cfg.metrics.hardware_info.mqtt { daily_hw = metrics::hardware::collect(); } } } let mut m = payload::AgentMetrics { hostname: hostname.clone(), ip: ip.clone(), status: "online".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), network_info: daily_net.or_else(|| startup_net.take()), hardware_info: daily_hw.or_else(|| startup_hw.take()), ..Default::default() }; if cfg.metrics.cpu.udp || cfg.metrics.cpu.mqtt { m.cpu_percent = Some(metrics::cpu::get(&sys)); } let (mem_used, mem_free, mem_total) = metrics::memory::get(&sys); if cfg.metrics.memory.udp || cfg.metrics.memory.mqtt { m.memory_used = Some(mem_used); m.memory_free = Some(mem_free); m.memory_total = Some(mem_total); } if first_medium || now.duration_since(last_medium).as_secs() >= 10 { networks.refresh(); components.refresh(); let (rx, tx) = metrics::network::get(&networks); if cfg.metrics.network.udp || cfg.metrics.network.mqtt { m.network_rx = Some(rx); m.network_tx = Some(tx); } if cfg.metrics.uptime.udp || cfg.metrics.uptime.mqtt { m.uptime = Some(metrics::uptime::get()); } if cfg.metrics.temperature.udp || cfg.metrics.temperature.mqtt { m.temperature = metrics::temperature::get(&components); } last_medium = now; first_medium = false; } if first_slow || now.duration_since(last_slow).as_secs() >= 60 { if cfg.metrics.disk.udp || cfg.metrics.disk.mqtt { let (used, free, total) = metrics::disk::get(); m.hdd_used = Some(used); m.hdd_free = Some(free); m.hdd_total = Some(total); } if cfg.metrics.smart.udp || cfg.metrics.smart.mqtt { m.smart = metrics::smart::collect(); } last_slow = now; first_slow = false; } let json = serde_json::to_string(&m).expect("sérialisation JSON impossible"); if let Some(ref udp) = udp_sender { udp.send(&json); } if let Some(ref client) = mqtt_client { if cfg.protocols.mqtt.enabled { transport::mqtt::publish_metrics( client, &cfg.protocols.mqtt.topic_base, &hostname, &json, ); } } std::thread::sleep(Duration::from_secs(2)); } // Déconnexion propre : notifier le serveur avant de quitter let offline = serde_json::to_string(&payload::AgentMetrics { hostname: hostname.clone(), ip: ip.clone(), status: "offline".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), ..Default::default() }).unwrap_or_default(); if let Some(ref udp) = udp_sender { udp.send(&offline); } if let Some(ref client) = mqtt_client { transport::mqtt::publish_status( client, &cfg.protocols.mqtt.topic_base, &hostname, "offline", ); std::thread::sleep(Duration::from_millis(200)); // laisser le temps au broker de recevoir let _ = client.disconnect(); } }