use axum::{ routing::get, Router, response::{ IntoResponse, Response, }, http::StatusCode, }; use std::net::SocketAddr; use std::str::FromStr; #[tokio::main] async fn main() { let mut listen = String::from("[::]:9152"); let mut args = std::env::args(); args.next(); loop { let arg = if let Some(arg) = args.next() { arg } else { break }; match arg.as_str() { "--help" | "-h" => { println!("Prometheus exporter for NixOS systems"); println!("Use --listen bind the web service."); println!("Output will be on /metrics endpoint. HTTP 500 if something broke while scraping."); std::process::exit(0); }, "--listen" => { listen = args.next().unwrap(); }, unknown => { println!("unknown option: {}", unknown); std::process::exit(1) } } } let app = Router::new().route("/metrics", get(metrics)); let addr = SocketAddr::from_str(&listen).unwrap(); println!("listening on http://{}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); } fn get_current_system() -> Result<(String, String), String> { let symlink = match std::fs::read_link("/run/current-system") { Ok(symlink) => symlink, Err(err) => return Err(err.to_string()) }; let (hash, name) = symlink.file_name() .ok_or_else(String::default)? .to_str() .ok_or_else(String::default)? .split_once("-") .ok_or_else(String::default)?; Ok((String::from(hash), String::from(name))) } async fn metrics() -> Response { let current_system = get_current_system(); let (hash, name) = match current_system { Ok((hash, name)) => (hash, name), Err(err) => { println!("failed: {}", err); return (StatusCode::INTERNAL_SERVER_ERROR, "").into_response() } }; ( StatusCode::OK, format!("nixos_nixos_current_system_hash{{hash=\"{}\"}}\nnixos_current_system_name{{name=\"{}\"}}", hash, name) ).into_response() }