use tokio::task::JoinHandle; use tracing::info; use pom::alerts::Alerter; use pom::checks::backup; use pom::config::Config; use pom::db; pub(crate) fn spawn_backup_tasks( config: &Config, pool: &sqlx::SqlitePool, cancel: &tokio_util::sync::CancellationToken, alerter: &Option, ) -> Vec> { let mut handles = Vec::new(); for name in config.target_names() { let target_config = config.get_target(&name).unwrap().clone(); if let Some(backup_config) = target_config.backups { let pool = pool.clone(); let name = name.clone(); let label = target_config.label.clone(); let alerter = alerter.clone(); let cancel = cancel.clone(); let interval_secs = backup_config.interval_secs; info!( "{name}: backup check every {interval_secs}s (dir={}, databases={:?})", backup_config.directory, backup_config.databases ); handles.push(tokio::spawn(async move { let mut interval = tokio::time::interval( std::time::Duration::from_secs(interval_secs), ); interval.tick().await; // consume immediate first tick loop { tokio::select! { _ = cancel.cancelled() => break, _ = interval.tick() => {} } for database in &backup_config.databases { let result = backup::check_backup( &name, &backup_config.directory, database, backup_config.max_age_hours, ); info!( "{}: backup {} — {} (age: {}h)", name, database, result.status, result.age_hours.unwrap_or(-1), ); // Check for status transitions before storing let previous = db::get_latest_backup_check(&pool, &name, database) .await .ok() .flatten(); if let Err(e) = db::insert_backup_check(&pool, &result).await { tracing::error!("{name}: failed to store backup check for {database}: {e}"); } // Fire alerts on status transitions if let Some(ref alerter) = alerter { let prev_status = previous.as_ref().map(|p| p.status.as_str()); let was_ok = prev_status.is_none_or(|s| s == "ok"); let now_ok = result.status == "ok"; if was_ok && !now_ok { // Transitioned to a bad state alerter.send_backup_stale_alert( &name, &label, database, &result.status, result.age_hours, ).await; } else if !was_ok && now_ok { // Recovered alerter.send_backup_recovery( &name, &label, database, ).await; } } } } })); } } handles }