mod query; mod runner; mod types; use anyhow::Result; use std::collections::HashSet; use std::env; use std::sync::Arc; use rustls; use tokio::sync::Mutex; use tokio::time::{sleep, Duration}; use tracing::{error, info}; #[tokio::main] async fn main() -> Result<()> { // Install default rustls crypto provider (required for cert expiry checks) rustls::crypto::ring::default_provider() .install_default() .ok(); // ok() — ignore error if already installed tracing_subscriber::fmt() .with_env_filter(env::var("RUST_LOG").unwrap_or_else(|_| "info".into())) .init(); let coordinator_url = env::var("COORDINATOR_URL") .unwrap_or_else(|_| "http://localhost:3000".into()); let monitor_token = env::var("MONITOR_TOKEN") .expect("MONITOR_TOKEN must be set"); let region = env::var("REGION").unwrap_or_default(); info!("PingQL monitor starting, coordinator: {coordinator_url}, region: {}", if region.is_empty() { "all" } else { ®ion }); let client = reqwest::Client::builder() .user_agent("PingQL-Monitor/0.1") .connect_timeout(std::time::Duration::from_secs(10)) .build()?; let in_flight: Arc>> = Arc::new(Mutex::new(HashSet::new())); let shutdown = tokio::signal::ctrl_c(); tokio::pin!(shutdown); loop { tokio::select! { _ = &mut shutdown => { info!("Shutdown signal received, waiting for in-flight checks..."); let deadline = tokio::time::Instant::now() + Duration::from_secs(35); while !in_flight.lock().await.is_empty() && tokio::time::Instant::now() < deadline { sleep(Duration::from_millis(500)).await; } info!("Shutdown complete"); break; } _ = sleep(Duration::from_millis(500)) => { match runner::fetch_and_run(&client, &coordinator_url, &monitor_token, ®ion, &in_flight).await { Ok(n) => { if n > 0 { info!("Spawned {n} checks"); } }, Err(e) => error!("Check cycle failed: {e}"), } } } } Ok(()) }