//! Local CA-bundle freshness task. Reads the trust-anchor package and bundle on //! the host PoM runs on, and alerts on ok -> not-ok transitions. use tokio::task::JoinHandle; use tracing::{info, warn}; use pom::alerts::Alerter; use pom::checks::ca_bundle; use pom::config::Config; use pom::db; use super::{CheckInterval, configured_targets}; pub(crate) fn spawn_ca_bundle_tasks( config: &Config, pool: &sqlx::SqlitePool, cancel: &tokio_util::sync::CancellationToken, alerter: Option<&Alerter>, ) -> Vec> { let mut handles = Vec::new(); for (name, target_config) in configured_targets(config) { let Some(ca_config) = target_config.ca_bundle else { continue; }; let name = name.clone(); let label = target_config.label.clone(); let alerter = alerter.cloned(); let pool = pool.clone(); let cancel = cancel.clone(); info!( "{name}: CA bundle check every {}s ({}, floor {} certs)", ca_config.interval_secs, ca_config.package, ca_config.min_certs, ); handles.push(tokio::spawn(async move { let mut ticks = CheckInterval::new(ca_config.interval_secs, cancel); // Seed from the ledger for the same reason systemd's task does: a // restart while already stale must not re-fire the alert. let mut previous_status: Option = db::get_latest_alert_matching(&pool, &format!("ca_bundle:{name}"), "ca_bundle_%") .await .ok() .flatten() .filter(|a| a.alert_type == "ca_bundle_stale") .and_then(|a| a.to_status); while ticks.next().await { let result = ca_bundle::check_ca_bundle(&name, &ca_config).await; if result.status == "ok" { info!( "{name}: CA bundle current ({} {})", result.package, result.installed.as_deref().unwrap_or("unknown"), ); } else { warn!( target = %name, status = %result.status, issues = ?result.issues, "CA bundle not current" ); } if let Err(e) = db::insert_ca_bundle_check(&pool, &result).await { tracing::error!("{name}: failed to store CA bundle check: {e}"); } if let Some(ref alerter) = alerter { let prev_ok = previous_status.as_deref().is_none_or(|s| s == "ok"); let now_ok = result.status == "ok"; if prev_ok && !now_ok { alerter .send_ca_bundle_alert(&name, &label, &result.status, &result.issues) .await; } else if !prev_ok && now_ok { alerter.send_ca_bundle_recovery(&name, &label).await; } } previous_status = Some(result.status); } })); } handles }