Skip to main content

max / makenotwork

3.8 KB · 99 lines History Blame Raw
1 use tokio::task::JoinHandle;
2 use tracing::info;
3
4 use pom::alerts::Alerter;
5 use pom::checks::backup;
6 use pom::config::Config;
7 use pom::db;
8
9 pub(crate) fn spawn_backup_tasks(
10 config: &Config,
11 pool: &sqlx::SqlitePool,
12 cancel: &tokio_util::sync::CancellationToken,
13 alerter: &Option<Alerter>,
14 ) -> Vec<JoinHandle<()>> {
15 let mut handles = Vec::new();
16
17 for name in config.target_names() {
18 let target_config = config.get_target(&name).unwrap().clone();
19 if let Some(backup_config) = target_config.backups {
20 let pool = pool.clone();
21 let name = name.clone();
22 let label = target_config.label.clone();
23 let alerter = alerter.clone();
24 let cancel = cancel.clone();
25 let interval_secs = backup_config.interval_secs;
26
27 info!(
28 "{name}: backup check every {interval_secs}s (dir={}, databases={:?})",
29 backup_config.directory, backup_config.databases
30 );
31
32 handles.push(tokio::spawn(async move {
33 let mut interval = tokio::time::interval(
34 std::time::Duration::from_secs(interval_secs),
35 );
36 interval.tick().await; // consume immediate first tick
37 loop {
38 tokio::select! {
39 _ = cancel.cancelled() => break,
40 _ = interval.tick() => {}
41 }
42 for database in &backup_config.databases {
43 let result = backup::check_backup(
44 &name,
45 &backup_config.directory,
46 database,
47 backup_config.max_age_hours,
48 );
49 info!(
50 "{}: backup {} — {} (age: {}h)",
51 name,
52 database,
53 result.status,
54 result.age_hours.unwrap_or(-1),
55 );
56
57 // Check for status transitions before storing
58 let previous = db::get_latest_backup_check(&pool, &name, database)
59 .await
60 .ok()
61 .flatten();
62
63 if let Err(e) = db::insert_backup_check(&pool, &result).await {
64 tracing::error!("{name}: failed to store backup check for {database}: {e}");
65 }
66
67 // Fire alerts on status transitions
68 if let Some(ref alerter) = alerter {
69 let prev_status = previous.as_ref().map(|p| p.status.as_str());
70 let was_ok = prev_status.is_none_or(|s| s == "ok");
71 let now_ok = result.status == "ok";
72
73 if was_ok && !now_ok {
74 // Transitioned to a bad state
75 alerter.send_backup_stale_alert(
76 &name,
77 &label,
78 database,
79 &result.status,
80 result.age_hours,
81 ).await;
82 } else if !was_ok && now_ok {
83 // Recovered
84 alerter.send_backup_recovery(
85 &name,
86 &label,
87 database,
88 ).await;
89 }
90 }
91 }
92 }
93 }));
94 }
95 }
96
97 handles
98 }
99