nandi/atop-analyzepublic Fork 0
de739e7
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 task shortcuts and simplify Grafana image setup

nandithebull committed 2026-09-21T19:56:23-07:00 Browse files
de739e7 parent: 9864974
modified README.md +14 -2
@@ -127,8 +127,8 @@ systemctl list-timers atop-modal-export.timer
127127 To test it without waiting until midnight, run:
128128
129129 ```sh
130-run0 systemctl start atop-modal-export.service
131-journalctl -u atop-modal-export.service --no-pager -n 50
130+just fire
131+just logs
132132 ```
133133
134134 If an export fails, `atop-modal-export-failure.service` writes a high-priority
@@ -139,6 +139,18 @@ Inspect failures with:
139139 journalctl -u atop-modal-export.service --no-pager -n 100
140140 ```
141141
142+#### Task shortcuts
143+
144+The repository `justfile` keeps the routine commands in one place:
145+
146+```sh
147+just install # install/update units and enable the timer
148+just status # timer state and next run
149+just fire # run yesterday's scheduled export now
150+just test-today # manually export today's available samples
151+just logs # recent exporter output
152+```
153+
142154 ### Grafana dashboard
143155
144156 `modal_grafana.py` adds a browser-facing Grafana service with a provisioned
@@ -127,8 +127,8 @@ systemctl list-timers atop-modal-export.timer
127 To test it without waiting until midnight, run:127 To test it without waiting until midnight, run:
128 128
129 ```sh129 ```sh
130-run0 systemctl start atop-modal-export.service130+just fire
131-journalctl -u atop-modal-export.service --no-pager -n 50131+just logs
132 ```132 ```
133 133
134 If an export fails, `atop-modal-export-failure.service` writes a high-priority134 If an export fails, `atop-modal-export-failure.service` writes a high-priority
@@ -139,6 +139,18 @@ Inspect failures with:
139 journalctl -u atop-modal-export.service --no-pager -n 100139 journalctl -u atop-modal-export.service --no-pager -n 100
140 ```140 ```
141 141
142+#### Task shortcuts
143+
144+The repository `justfile` keeps the routine commands in one place:
145+
146+```sh
147+just install # install/update units and enable the timer
148+just status # timer state and next run
149+just fire # run yesterday's scheduled export now
150+just test-today # manually export today's available samples
151+just logs # recent exporter output
152+```
153+
142 ### Grafana dashboard154 ### Grafana dashboard
143 155
144 `modal_grafana.py` adds a browser-facing Grafana service with a provisioned156 `modal_grafana.py` adds a browser-facing Grafana service with a provisioned
added justfile +28 -0
new file mode 100644
@@ -0,0 +1,28 @@
1+set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
2+
3+default:
4+ @just --list
5+
6+# Install or update all systemd units and enable the daily export timer.
7+install:
8+ run0 python3 systemd/install.py --enable
9+
10+# Show whether the systemd timer is enabled and when it will next run.
11+status:
12+ run0 python3 systemd/install.py --status
13+
14+# Fire the same service used by the daily timer (exports yesterday's log).
15+fire:
16+ run0 python3 systemd/install.py --fire
17+
18+# Export today's currently available atop history without waiting for midnight.
19+test-today:
20+ modal run modal_atop_analyze.py --date "$$(date +%Y%m%d)" --initialize
21+
22+# Inspect the most recent scheduled-export output.
23+logs:
24+ journalctl -u atop-modal-export.service --no-pager -n 100
25+
26+# Show the Modal app's local-entrypoint arguments.
27+help:
28+ modal run modal_atop_analyze.py --help
new file mode 100644
@@ -0,0 +1,28 @@
1+set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
2+
3+default:
4+ @just --list
5+
6+# Install or update all systemd units and enable the daily export timer.
7+install:
8+ run0 python3 systemd/install.py --enable
9+
10+# Show whether the systemd timer is enabled and when it will next run.
11+status:
12+ run0 python3 systemd/install.py --status
13+
14+# Fire the same service used by the daily timer (exports yesterday's log).
15+fire:
16+ run0 python3 systemd/install.py --fire
17+
18+# Export today's currently available atop history without waiting for midnight.
19+test-today:
20+ modal run modal_atop_analyze.py --date "$$(date +%Y%m%d)" --initialize
21+
22+# Inspect the most recent scheduled-export output.
23+logs:
24+ journalctl -u atop-modal-export.service --no-pager -n 100
25+
26+# Show the Modal app's local-entrypoint arguments.
27+help:
28+ modal run modal_atop_analyze.py --help
modified modal_grafana.py +1 -7
@@ -25,13 +25,7 @@ grafana_secret = modal.Secret.from_name(
2525 ],
2626 )
2727 image = (
28- modal.Image.from_registry(
29- "grafana/grafana:11.6.0",
30- add_python="3.12",
31- # Grafana's registry image runs as a non-root user. Modal injects and
32- # probes its Python runtime while preparing the Function image.
33- setup_dockerfile_commands=["USER root"],
34- )
28+ modal.Image.from_registry("grafana/grafana:11.6.0", add_python="3.12")
3529 .run_commands("grafana cli plugins install grafana-clickhouse-datasource")
3630 .add_local_dir("grafana/provisioning", "/etc/grafana/provisioning")
3731 )
@@ -25,13 +25,7 @@ grafana_secret = modal.Secret.from_name(
25 ],25 ],
26 )26 )
27 image = (27 image = (
28- modal.Image.from_registry(28+ modal.Image.from_registry("grafana/grafana:11.6.0", add_python="3.12")
29- "grafana/grafana:11.6.0",
30- add_python="3.12",
31- # Grafana's registry image runs as a non-root user. Modal injects and
32- # probes its Python runtime while preparing the Function image.
33- setup_dockerfile_commands=["USER root"],
34- )
35 .run_commands("grafana cli plugins install grafana-clickhouse-datasource")29 .run_commands("grafana cli plugins install grafana-clickhouse-datasource")
36 .add_local_dir("grafana/provisioning", "/etc/grafana/provisioning")30 .add_local_dir("grafana/provisioning", "/etc/grafana/provisioning")
37 )31 )
modified systemd/install.py +21 -10
@@ -20,26 +20,37 @@ SYSTEMD_DIR = Path("/etc/systemd/system")
2020
2121 def main() -> int:
2222 parser = argparse.ArgumentParser(description="Install atop Modal-export systemd units.")
23+ parser.add_argument("--install", action="store_true", help="copy unit files and reload systemd")
2324 parser.add_argument("--enable", action="store_true", help="enable and start the timer")
25+ parser.add_argument("--fire", action="store_true", help="start the scheduled export now")
26+ parser.add_argument("--status", action="store_true", help="show timer status and schedule")
2427 args = parser.parse_args()
2528
2629 if os.geteuid() != 0:
2730 parser.error("run this installer once with: run0 python3 systemd/install.py --enable")
2831
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)
32+ if not (args.install or args.enable or args.fire or args.status):
33+ parser.error("choose at least one task")
34+
35+ if args.install or args.enable:
36+ source_dir = Path(__file__).resolve().parent
37+ for name in UNITS:
38+ source = source_dir / name
39+ if not source.is_file():
40+ raise FileNotFoundError(f"missing unit file: {source}")
41+ destination = SYSTEMD_DIR / name
42+ shutil.copyfile(source, destination)
43+ destination.chmod(0o644)
44+ subprocess.run(["systemctl", "daemon-reload"], check=True)
3945 if args.enable:
4046 subprocess.run(
4147 ["systemctl", "enable", "--now", "atop-modal-export.timer"], check=True
4248 )
49+ if args.fire:
50+ subprocess.run(["systemctl", "start", "atop-modal-export.service"], check=True)
51+ if args.status:
52+ subprocess.run(["systemctl", "status", "atop-modal-export.timer", "--no-pager"], check=True)
53+ subprocess.run(["systemctl", "list-timers", "atop-modal-export.timer", "--no-pager"], check=True)
4354 return 0
4455
4556
@@ -20,26 +20,37 @@ SYSTEMD_DIR = Path("/etc/systemd/system")
20 20
21 def main() -> int:21 def main() -> int:
22 parser = argparse.ArgumentParser(description="Install atop Modal-export systemd units.")22 parser = argparse.ArgumentParser(description="Install atop Modal-export systemd units.")
23+ parser.add_argument("--install", action="store_true", help="copy unit files and reload systemd")
23 parser.add_argument("--enable", action="store_true", help="enable and start the timer")24 parser.add_argument("--enable", action="store_true", help="enable and start the timer")
25+ parser.add_argument("--fire", action="store_true", help="start the scheduled export now")
26+ parser.add_argument("--status", action="store_true", help="show timer status and schedule")
24 args = parser.parse_args()27 args = parser.parse_args()
25 28
26 if os.geteuid() != 0:29 if os.geteuid() != 0:
27 parser.error("run this installer once with: run0 python3 systemd/install.py --enable")30 parser.error("run this installer once with: run0 python3 systemd/install.py --enable")
28 31
29- source_dir = Path(__file__).resolve().parent32+ if not (args.install or args.enable or args.fire or args.status):
30- for name in UNITS:33+ parser.error("choose at least one task")
31- source = source_dir / name34+
32- if not source.is_file():35+ if args.install or args.enable:
33- raise FileNotFoundError(f"missing unit file: {source}")36+ source_dir = Path(__file__).resolve().parent
34- destination = SYSTEMD_DIR / name37+ for name in UNITS:
35- shutil.copyfile(source, destination)38+ source = source_dir / name
36- destination.chmod(0o644)39+ if not source.is_file():
37-40+ raise FileNotFoundError(f"missing unit file: {source}")
38- subprocess.run(["systemctl", "daemon-reload"], check=True)41+ destination = SYSTEMD_DIR / name
42+ shutil.copyfile(source, destination)
43+ destination.chmod(0o644)
44+ subprocess.run(["systemctl", "daemon-reload"], check=True)
39 if args.enable:45 if args.enable:
40 subprocess.run(46 subprocess.run(
41 ["systemctl", "enable", "--now", "atop-modal-export.timer"], check=True47 ["systemctl", "enable", "--now", "atop-modal-export.timer"], check=True
42 )48 )
49+ if args.fire:
50+ subprocess.run(["systemctl", "start", "atop-modal-export.service"], check=True)
51+ if args.status:
52+ subprocess.run(["systemctl", "status", "atop-modal-export.timer", "--no-pager"], check=True)
53+ subprocess.run(["systemctl", "list-timers", "atop-modal-export.timer", "--no-pager"], check=True)
43 return 054 return 0
44 55
45 56