pub mod check; pub mod log; pub mod mail; pub mod web; use ::log::error; use anyhow::{anyhow, Result}; use axum::{routing::get, Router}; use check::check_loop; use mail::MailConfig; use serde::Deserialize; use std::{ collections::BTreeMap, net::SocketAddr, sync::Arc, time::{Duration, SystemTime}, }; use tokio::{fs::read_to_string, sync::RwLock}; use web::send_html_page; pub static GLOBAL_ERROR: RwLock> = RwLock::const_new(None); #[tokio::main] async fn main() { env_logger::init_from_env("LOG"); if let Err(e) = run().await { error!("{e:?}") } } #[derive(Debug, Deserialize)] pub struct Config { mail: Option, title: String, bind: SocketAddr, interval: u64, services: Vec, } #[derive(Debug, Deserialize)] pub struct Service { title: String, url: Option, checks: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Check { Systemd(String), Http { title: Option, url: String }, Shell { title: String, command: String }, } #[derive(Debug, Clone)] pub struct Success { pub latency: Option, pub updated: SystemTime, } impl Default for Success { fn default() -> Self { Self { latency: None, updated: SystemTime::now(), } } } static STATUS: RwLock>> = RwLock::const_new(BTreeMap::new()); async fn run() -> anyhow::Result<()> { let config = std::env::args() .nth(1) .ok_or(anyhow!("expected config path as first argument"))?; let config = read_to_string(config).await?; let config = Arc::::new(serde_yaml::from_str(&config)?); for i in 0..config.services.len() { tokio::task::spawn(check_loop(config.clone(), i)); } let app = Router::new().route( "/", get({ let config = config.clone(); move || send_html_page(config.clone()) }), ); let listener = tokio::net::TcpListener::bind(config.bind).await?; axum::serve(listener, app).await?; Ok(()) }