modified README.md +9 -4
| @@ -120,10 +120,7 @@ was off at the scheduled time (`Persistent=true`). They are configured for this |
| 120 | 120 | host's `nandi` user and Modal CLI location: |
| 121 | 121 | |
| 122 | 122 | ```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 |
| 127 | 124 | systemctl list-timers atop-modal-export.timer |
| 128 | 125 | ``` |
| 129 | 126 | |
| @@ -134,6 +131,14 @@ run0 systemctl start atop-modal-export.service |
| 134 | 131 | 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 | 142 | ### Grafana dashboard |
| 138 | 143 | |
| 139 | 144 | `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 | ```sh | 122 | ```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.timer | 124 | 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 50 | 131 | 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 dashboard | 142 | ### Grafana dashboard |
| 138 | | 143 | |
| 139 | `modal_grafana.py` adds a browser-facing Grafana service with a provisioned | 144 | `modal_grafana.py` adds a browser-facing Grafana service with a provisioned |
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()) |