#![feature(exit_status_error)] 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, Check}; use chrono::{DateTime, Utc}; use mail::MailConfig; use serde::Deserialize; use std::{collections::BTreeMap, net::SocketAddr, process::exit, sync::Arc}; 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:?}"); exit(1); } } #[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, Clone)] pub struct Status { pub time: DateTime, pub status: Result, } 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(()) }