nandi/atop-analyzepublic Fork 0
c8c2d90
Commits
Clone
git clone https://git.rickub.com/nandi/atop-analyze.git
git clone ssh://git@rickub.com/nandi/atop-analyze.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Add systemd export failure notifications

nandithebull committed 2026-09-21T19:47:31-07:00 Browse files
c8c2d90 parent: a275b50
modified README.md +9 -4
@@ -120,10 +120,7 @@ was off at the scheduled time (`Persistent=true`). They are configured for this
120120 host's `nandi` user and Modal CLI location:
121121
122122 ```sh
123-run0 install -m 0644 systemd/atop-modal-export.service /etc/systemd/system/
124-run0 install -m 0644 systemd/atop-modal-export.timer /etc/systemd/system/
125-run0 systemctl daemon-reload
126-run0 systemctl enable --now atop-modal-export.timer
123+run0 python3 systemd/install.py --enable
127124 systemctl list-timers atop-modal-export.timer
128125 ```
129126
@@ -134,6 +131,14 @@ run0 systemctl start atop-modal-export.service
134131 journalctl -u atop-modal-export.service --no-pager -n 50
135132 ```
136133
134+If an export fails, `atop-modal-export-failure.service` writes a high-priority
135+journal record and displays a short `wall` message to active local terminals.
136+Inspect failures with:
137+
138+```sh
139+journalctl -u atop-modal-export.service --no-pager -n 100
140+```
141+
137142 ### Grafana dashboard
138143
139144 `modal_grafana.py` adds a browser-facing Grafana service with a provisioned
@@ -120,10 +120,7 @@ was off at the scheduled time (`Persistent=true`). They are configured for this
120 host's `nandi` user and Modal CLI location:120 host's `nandi` user and Modal CLI location:
121 121
122 ```sh122 ```sh
123-run0 install -m 0644 systemd/atop-modal-export.service /etc/systemd/system/123+run0 python3 systemd/install.py --enable
124-run0 install -m 0644 systemd/atop-modal-export.timer /etc/systemd/system/
125-run0 systemctl daemon-reload
126-run0 systemctl enable --now atop-modal-export.timer
127 systemctl list-timers atop-modal-export.timer124 systemctl list-timers atop-modal-export.timer
128 ```125 ```
129 126
@@ -134,6 +131,14 @@ run0 systemctl start atop-modal-export.service
134 journalctl -u atop-modal-export.service --no-pager -n 50131 journalctl -u atop-modal-export.service --no-pager -n 50
135 ```132 ```
136 133
134+If an export fails, `atop-modal-export-failure.service` writes a high-priority
135+journal record and displays a short `wall` message to active local terminals.
136+Inspect failures with:
137+
138+```sh
139+journalctl -u atop-modal-export.service --no-pager -n 100
140+```
141+
137 ### Grafana dashboard142 ### Grafana dashboard
138 143
139 `modal_grafana.py` adds a browser-facing Grafana service with a provisioned144 `modal_grafana.py` adds a browser-facing Grafana service with a provisioned
added systemd/atop-modal-export-failure.service +9 -0
new file mode 100644
@@ -0,0 +1,9 @@
1+[Unit]
2+Description=Report a failed atop Modal export
3+
4+[Service]
5+Type=oneshot
6+# Keep a durable, high-priority record for journalctl and show a short alert to
7+# users currently logged into a local terminal. No data or credentials are sent.
8+ExecStart=/usr/bin/logger -p daemon.err -t atop-modal-export "atop-modal-export.service failed; inspect with: journalctl -u atop-modal-export.service --no-pager -n 100"
9+ExecStart=/usr/bin/wall "atop Modal export failed. Inspect: journalctl -u atop-modal-export.service --no-pager -n 100"
new file mode 100644
@@ -0,0 +1,9 @@
1+[Unit]
2+Description=Report a failed atop Modal export
3+
4+[Service]
5+Type=oneshot
6+# Keep a durable, high-priority record for journalctl and show a short alert to
7+# users currently logged into a local terminal. No data or credentials are sent.
8+ExecStart=/usr/bin/logger -p daemon.err -t atop-modal-export "atop-modal-export.service failed; inspect with: journalctl -u atop-modal-export.service --no-pager -n 100"
9+ExecStart=/usr/bin/wall "atop Modal export failed. Inspect: journalctl -u atop-modal-export.service --no-pager -n 100"
modified systemd/atop-modal-export.service +1 -0
@@ -2,6 +2,7 @@
22 Description=Export completed atop history to Modal ClickHouse
33 Wants=network-online.target
44 After=network-online.target atop.service
5+OnFailure=atop-modal-export-failure.service
56
67 [Service]
78 Type=oneshot
@@ -2,6 +2,7 @@
2 Description=Export completed atop history to Modal ClickHouse2 Description=Export completed atop history to Modal ClickHouse
3 Wants=network-online.target3 Wants=network-online.target
4 After=network-online.target atop.service4 After=network-online.target atop.service
5+OnFailure=atop-modal-export-failure.service
5 6
6 [Service]7 [Service]
7 Type=oneshot8 Type=oneshot
added systemd/install.py +47 -0
new file mode 100644
@@ -0,0 +1,47 @@
1+#!/usr/bin/env python3
2+"""Install atop Modal-export systemd units. Run only through run0."""
3+
4+from __future__ import annotations
5+
6+import argparse
7+import os
8+import shutil
9+import subprocess
10+import sys
11+from pathlib import Path
12+
13+UNITS = (
14+ "atop-modal-export.service",
15+ "atop-modal-export.timer",
16+ "atop-modal-export-failure.service",
17+)
18+SYSTEMD_DIR = Path("/etc/systemd/system")
19+
20+
21+def main() -> int:
22+ parser = argparse.ArgumentParser(description="Install atop Modal-export systemd units.")
23+ parser.add_argument("--enable", action="store_true", help="enable and start the timer")
24+ args = parser.parse_args()
25+
26+ if os.geteuid() != 0:
27+ parser.error("run this installer once with: run0 python3 systemd/install.py --enable")
28+
29+ source_dir = Path(__file__).resolve().parent
30+ for name in UNITS:
31+ source = source_dir / name
32+ if not source.is_file():
33+ raise FileNotFoundError(f"missing unit file: {source}")
34+ destination = SYSTEMD_DIR / name
35+ shutil.copyfile(source, destination)
36+ destination.chmod(0o644)
37+
38+ subprocess.run(["systemctl", "daemon-reload"], check=True)
39+ if args.enable:
40+ subprocess.run(
41+ ["systemctl", "enable", "--now", "atop-modal-export.timer"], check=True
42+ )
43+ return 0
44+
45+
46+if __name__ == "__main__":
47+ sys.exit(main())
new file mode 100644
@@ -0,0 +1,47 @@
1+#!/usr/bin/env python3
2+"""Install atop Modal-export systemd units. Run only through run0."""
3+
4+from __future__ import annotations
5+
6+import argparse
7+import os
8+import shutil
9+import subprocess
10+import sys
11+from pathlib import Path
12+
13+UNITS = (
14+ "atop-modal-export.service",
15+ "atop-modal-export.timer",
16+ "atop-modal-export-failure.service",
17+)
18+SYSTEMD_DIR = Path("/etc/systemd/system")
19+
20+
21+def main() -> int:
22+ parser = argparse.ArgumentParser(description="Install atop Modal-export systemd units.")
23+ parser.add_argument("--enable", action="store_true", help="enable and start the timer")
24+ args = parser.parse_args()
25+
26+ if os.geteuid() != 0:
27+ parser.error("run this installer once with: run0 python3 systemd/install.py --enable")
28+
29+ source_dir = Path(__file__).resolve().parent
30+ for name in UNITS:
31+ source = source_dir / name
32+ if not source.is_file():
33+ raise FileNotFoundError(f"missing unit file: {source}")
34+ destination = SYSTEMD_DIR / name
35+ shutil.copyfile(source, destination)
36+ destination.chmod(0o644)
37+
38+ subprocess.run(["systemctl", "daemon-reload"], check=True)
39+ if args.enable:
40+ subprocess.run(
41+ ["systemctl", "enable", "--now", "atop-modal-export.timer"], check=True
42+ )
43+ return 0
44+
45+
46+if __name__ == "__main__":
47+ sys.exit(main())