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<()> { rustls::crypto::ring::default_provider().install_default().ok(); 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"); // Region label this runner reports on every ping. "default" means the operator // didn't pin this runner to a named region — it's still a meaningful label so // alerts say where they came from instead of being blank. let region = env::var("REGION").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| "default".to_string()); info!("PingQL monitor starting, coordinator: {coordinator_url}, region: {region}"); 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(()) }