Skip to main content

max / makenotwork

3.6 KB · 104 lines History Blame Raw
1 //! Local systemd daemon-health task. Probes the configured units on the host
2 //! PoM runs on, applies the liveness / crash-loop / failed-unit rules, and fires
3 //! alerts on operational → degraded / down transitions.
4
5 use tokio::task::JoinHandle;
6 use tracing::{info, warn};
7
8 use pom::alerts::Alerter;
9 use pom::checks::systemd;
10 use pom::config::Config;
11 use pom::db;
12
13 use super::{CheckInterval, configured_targets};
14
15 pub(crate) fn spawn_systemd_tasks(
16 config: &Config,
17 pool: &sqlx::SqlitePool,
18 cancel: &tokio_util::sync::CancellationToken,
19 alerter: Option<&Alerter>,
20 ) -> Vec<JoinHandle<()>> {
21 let mut handles = Vec::new();
22
23 for (name, target_config) in configured_targets(config) {
24 let Some(sd_config) = target_config.systemd else {
25 continue;
26 };
27
28 let name = name.clone();
29 let label = target_config.label.clone();
30 let alerter = alerter.cloned();
31 let pool = pool.clone();
32 let cancel = cancel.clone();
33
34 info!(
35 "{name}: systemd check every {}s ({} units, failed-sweep={})",
36 sd_config.interval_secs,
37 sd_config.units.len(),
38 sd_config.check_failed,
39 );
40
41 handles.push(tokio::spawn(async move {
42 let mut ticks = CheckInterval::new(sd_config.interval_secs, cancel);
43
44 // Seed previous status from the ledger so a restart while already
45 // degraded does not re-fire: if the latest systemd alert is the
46 // failure (not the recovery) one, start from that non-ok status.
47 let mut previous_status: Option<String> =
48 db::get_latest_alert_matching(&pool, &format!("systemd:{name}"), "systemd_%")
49 .await
50 .ok()
51 .flatten()
52 .filter(|a| a.alert_type == "systemd_failure")
53 .and_then(|a| a.to_status);
54
55 while ticks.next().await {
56 let result = systemd::check_systemd(
57 &name,
58 &sd_config.units,
59 sd_config.check_failed,
60 sd_config.restart_threshold,
61 )
62 .await;
63
64 if result.issues.is_empty() && result.error.is_none() {
65 info!(
66 "{name}: daemons operational ({} units watched)",
67 result.units.len()
68 );
69 } else {
70 warn!(
71 target = %name,
72 status = %result.status,
73 issues = ?result.issues,
74 "daemons non-operational"
75 );
76 }
77
78 if let Err(e) = db::insert_systemd_check(&pool, &result).await {
79 tracing::error!("{name}: failed to store systemd check: {e}");
80 }
81
82 if let Some(ref alerter) = alerter {
83 let prev_ok = previous_status
84 .as_deref()
85 .is_none_or(|s| s == "operational");
86 let now_ok = result.status == "operational";
87
88 if prev_ok && !now_ok {
89 alerter
90 .send_systemd_alert(&name, &label, &result.status, &result.issues)
91 .await;
92 } else if !prev_ok && now_ok {
93 alerter.send_systemd_recovery(&name, &label).await;
94 }
95 }
96
97 previous_status = Some(result.status);
98 }
99 }));
100 }
101
102 handles
103 }
104