use tokio::task::JoinHandle; use tracing::info; use pom::alerts::Alerter; use pom::checks::tls; use pom::config::Config; use pom::db; pub(crate) fn spawn_tls_tasks( config: &Config, pool: &sqlx::SqlitePool, cancel: &tokio_util::sync::CancellationToken, alerter: &Option, ) -> Vec> { let tls_interval_secs = config.serve.tls_check_interval_secs; let mut handles = Vec::new(); for name in config.target_names() { let target_config = config.get_target(&name).unwrap().clone(); if let Some(tls_config) = target_config.tls { let pool = pool.clone(); let name = name.clone(); let label = target_config.label.clone(); let alerter = alerter.clone(); let warn_days = tls_config.warn_days; let cancel = cancel.clone(); info!("{name}: TLS check every {tls_interval_secs}s (host={})", tls_config.host); handles.push(tokio::spawn(async move { let mut interval = tokio::time::interval( std::time::Duration::from_secs(tls_interval_secs), ); interval.tick().await; // consume immediate first tick loop { tokio::select! { _ = cancel.cancelled() => break, _ = interval.tick() => {} } let previous = db::get_latest_tls_check(&pool, &name).await.ok().flatten(); let status = tls::check_tls(&name, &tls_config).await; info!("{}: TLS {} — {}d remaining", name, if status.valid { "valid" } else { "invalid" }, status.days_remaining); if let Err(e) = db::insert_tls_check(&pool, &status).await { tracing::error!("{name}: failed to store TLS check: {e}"); } // Fire alerts on TLS state transitions if let Some(ref alerter) = alerter { let was_ok = previous.as_ref().is_none_or(|p| p.valid && p.error.is_none()); let now_warn = status.valid && status.days_remaining <= warn_days as i64; let now_error = !status.valid || status.error.is_some(); if was_ok && now_error { alerter.send_tls_error_alert( &name, &tls_config.host, status.error.as_deref().unwrap_or("certificate invalid"), ).await; } else if was_ok && now_warn { alerter.send_tls_expiry_alert( &name, &tls_config.host, status.days_remaining, &status.not_after, ).await; } else if let Some(ref prev) = previous { let was_bad = !prev.valid || prev.error.is_some() || prev.days_remaining <= warn_days as i64; let now_ok = status.valid && status.error.is_none() && status.days_remaining > warn_days as i64; if was_bad && now_ok { alerter.send_tls_recovery( &name, &label, status.days_remaining, ).await; } } } } })); } } handles }