fix: pos systemd units stop fast — TimeoutStopSec=5s everywhere, listener TERM traps; drop dead pos-health/pos-entertainment unit docs
This commit is contained in:
@@ -10,19 +10,19 @@
|
||||
|
||||
<!-- GEN:START docmap -->
|
||||
| ## 1. Project Overview | 28–43 |
|
||||
| ## 2. Directory Structure | 44–190 |
|
||||
| ## 3. Installation Flow | 191–243 |
|
||||
| ## 4. The `pos` CLI System | 244–313 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 314–345 |
|
||||
| ## 6. Docker Compose / ScaleTail | 346–388 |
|
||||
| ## 7. Optional Apps (`apps/`) | 389–418 |
|
||||
| ## 8. Entertainment Module | 419–432 |
|
||||
| ## 9. Systemd Services | 433–445 |
|
||||
| ## 10. Configuration Files | 446–472 |
|
||||
| ## 11. Coding Conventions | 473–505 |
|
||||
| ## 12. Development Workflow | 506–558 |
|
||||
| ## 13. Key File Quick Reference | 559–616 |
|
||||
| ## 14. Common Tasks for Agents | 617–646 |
|
||||
| ## 2. Directory Structure | 44–189 |
|
||||
| ## 3. Installation Flow | 190–241 |
|
||||
| ## 4. The `pos` CLI System | 242–311 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 312–343 |
|
||||
| ## 6. Docker Compose / ScaleTail | 344–386 |
|
||||
| ## 7. Optional Apps (`apps/`) | 387–416 |
|
||||
| ## 8. Entertainment Module | 417–430 |
|
||||
| ## 9. Systemd Services | 431–442 |
|
||||
| ## 10. Configuration Files | 443–469 |
|
||||
| ## 11. Coding Conventions | 470–502 |
|
||||
| ## 12. Development Workflow | 503–555 |
|
||||
| ## 13. Key File Quick Reference | 556–613 |
|
||||
| ## 14. Common Tasks for Agents | 614–643 |
|
||||
<!-- GEN:END docmap -->
|
||||
|
||||
## 1. Project Overview
|
||||
@@ -160,8 +160,7 @@ Linux_post_install/
|
||||
├── systemd/
|
||||
│ ├── autostart.service # Runs autostart.sh on boot
|
||||
│ ├── ssh-agent.service # System-wide SSH agent socket
|
||||
│ ├── pos-health.service # Runs the health digest as the installing user (triggered by timer)
|
||||
│ └── pos-health.timer # Daily 08:00 trigger for the health digest (enabled when Telegram is configured)
|
||||
│ └── usb-automount.service # Auto-mounts USB sticks (usb-automount feature)
|
||||
│
|
||||
├── scripts/ # Dev tooling
|
||||
│ ├── gen-docs.sh # Regenerates code-derived doc sections + completion flags
|
||||
@@ -208,8 +207,7 @@ User runs: ./install.sh [--apps|--full|--feature|--dry-run|--skip <phase>|--step
|
||||
│ └─ PATH export in ~/.bashrc
|
||||
│ └─ Bash completion for pos CLI
|
||||
│ └─ Copies systemd/*.service + systemd/*.timer → /etc/systemd/system/, enables them
|
||||
│ (autostart.service only when the `autostart` flag is set;
|
||||
│ pos-health.timer only when the user's Telegram config exists)
|
||||
│ (autostart.service and usb-automount.service only when their feature flags are set)
|
||||
│
|
||||
├─ Phase 4: ScaleTail clone
|
||||
│ └─ Shallow-clones ScaleTail templates to /usr/local/share/linux_post_install/scale-tail
|
||||
@@ -436,8 +434,7 @@ Public-API "entertainment" plugins (weather, joke, gold) that can auto-send thei
|
||||
|---------|------|---------|
|
||||
| `ssh-agent.service` | `systemd/ssh-agent.service` | System-wide SSH agent, socket at `/run/ssh-agent/socket` |
|
||||
| `autostart.service` | `systemd/autostart.service` | Runs `autostart.sh` on boot |
|
||||
| `pos-health.service` | `systemd/pos-health.service` | Runs `pos system health --send --markdown` once (oneshot) as the installing user |
|
||||
| `pos-health.timer` | `systemd/pos-health.timer` | Daily 08:00 trigger for the digest (enabled only when `~/.config/linux_post_install/telegram.env` exists) |
|
||||
| `usb-automount.service` | `systemd/usb-automount.service` | Auto-mounts USB sticks at `/media/<label>` (usb-automount feature) |
|
||||
|
||||
All `.service` files in `systemd/` are automatically copied to `/etc/systemd/system/` and enabled by `postinstall.sh` (timers too, when present).
|
||||
|
||||
@@ -566,8 +563,8 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `lib/common.sh` | 144 | Shared library (log/warn/err/run/spawn, dry-run aware, `load_system_env`) |
|
||||
| `lib/flags.sh` | 60 | Feature flag store (set/clear/is_set/value/list/status) |
|
||||
| `lib/notify.sh` | 76 | Multi-platform alerting (`notify_send`) — opt-in source, silent-fails |
|
||||
| `lib/entertainment-lib.sh` | 350 | Entertainment module lib (ENABLED parsing, scheduler sync) |
|
||||
| `lib/scheduler-lib.sh` | 822 | Scheduler lib (job parsing, notify policies, per-job user timers, legacy migrate) |
|
||||
| `lib/entertainment-lib.sh` | 354 | Entertainment module lib (ENABLED parsing, scheduler sync) |
|
||||
| `lib/scheduler-lib.sh` | 830 | Scheduler lib (job parsing, notify policies, per-job user timers, legacy migrate) |
|
||||
| `bin/flag-reader` | 58 | Inspect flags (list/status/`--raw`) |
|
||||
| `bin/flag-set` | 21 | Set a flag (optionally with a value) |
|
||||
| `bin/flag-clear` | 21 | Unset a flag |
|
||||
@@ -576,9 +573,9 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
<!-- GEN:START filetable -->
|
||||
| `bin/pos` | 292 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos-ai-gemini` | 311 | Chat with Google Gemini (ask, chat, models, sessions) |
|
||||
| `bin/pos-communication-matrix-listener` | 565 | Matrix listener: map /command → bash, run them on room messages |
|
||||
| `bin/pos-communication-matrix-listener` | 568 | Matrix listener: map /command → bash, run them on room messages |
|
||||
| `bin/pos-communication-matrix-sender` | 224 | Send messages to a Matrix room via the client-server API (send, test, login) |
|
||||
| `bin/pos-communication-telegram-listener` | 563 | Telegram bot listener: map /command → bash, run them on chat messages |
|
||||
| `bin/pos-communication-telegram-listener` | 566 | Telegram bot listener: map /command → bash, run them on chat messages |
|
||||
| `bin/pos-communication-telegram-sender` | 221 | Send Telegram messages/files/links/stickers via Bot API (send, test) |
|
||||
| `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) |
|
||||
| `bin/pos-docker-compose` | 366 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
|
||||
@@ -593,7 +590,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/pos-media-mp3` | 80 | Download audio as MP3 (yt-dlp) |
|
||||
| `bin/pos-media-mp4` | 126 | Download video as MP4 (smart/interactive format select) |
|
||||
| `bin/pos-network-checkport` | 496 | Check TCP/UDP port reachability (nmap, or bash/nc fallback) + local interface view |
|
||||
| `bin/pos-network-download` | 949 | aria2 RPC daemon + queue control (add/torrent/metalink, watch, limits) |
|
||||
| `bin/pos-network-download` | 952 | aria2 RPC daemon + queue control (add/torrent/metalink, watch, limits) |
|
||||
| `bin/pos-network-hotspot` | 93 | Wi-Fi hotspot via create_ap + wihotspot-gui |
|
||||
| `bin/pos-network-ip` | 69 | Show interfaces, routes, public IP + location |
|
||||
| `bin/pos-network-scan` | 271 | Parallel ping sweep of CIDR |
|
||||
|
||||
+11
@@ -323,6 +323,17 @@ then listing it in `NOTIFY_PLATFORM`. Platform keys map to tool names via `notif
|
||||
|
||||
Check before creating, use `>>` with grep guards, don't overwrite user configs.
|
||||
|
||||
### Systemd units
|
||||
|
||||
Every unit a tool writes (or `systemd/` ships) sets `TimeoutStopSec=5s` so a
|
||||
stuck process can't stall a reboot for the 90s systemd default. Long-polling
|
||||
daemons (listeners) also `trap TERM INT` in their loop so a stop returns in
|
||||
well under a second — the unit timeout is the backstop. Keep `KillMode=`
|
||||
explicit (`control-group`) on the daemons. A oneshot job running at shutdown is
|
||||
SIGKILLed 5s after stop begins — fine, `Persistent` timers re-run it next boot.
|
||||
Existing installs keep the old unit files until the tool rewrites them (re-run
|
||||
the `enable` path), so template changes need a regeneration step on live boxes.
|
||||
|
||||
### Managed Config Blocks
|
||||
|
||||
To let a tool own a slice of a user/system config file (e.g. Samba shares in `/etc/samba/smb.conf`) without clobbering hand edits, delimit the tool's section with start/end marker lines and rewrite only that slice:
|
||||
|
||||
+2
-2
@@ -70,8 +70,8 @@ Adding another platform = create `bin/pos-communication-<p>` implementing
|
||||
|
||||
### Scheduling
|
||||
|
||||
- **Daily health digest** (`pos system health --send` at 08:00) — `systemd/pos-health.{service,timer}`,
|
||||
enabled by postinstall once `telegram.env` exists. See [system](howto/system.md).
|
||||
- **Daily health digest** — add a `daily` schedule job `pos system health --send --markdown`
|
||||
via `pos system schedule config` (the old `pos-health.{service,timer}` units are gone). See [system](howto/system.md).
|
||||
- **Entertainment auto-triggers** — per-plugin `pos entertainment enable <plugin> <interval>`,
|
||||
uses systemd user timers (or cron fallback). See [entertainment](howto/entertainment.md).
|
||||
- **`pos system schedule` jobs** — run any command on a per-job timer and notify
|
||||
|
||||
+1
-1
@@ -217,7 +217,7 @@ The standalone `vbox` command still works and forwards to `pos docker vbox` (see
|
||||
| `pos system health [--send] [--markdown]` | `bin/pos-system-health` | Host health dashboard: disk per mount, RAM/swap, failed systemd units, backup age, fail2ban, docker containers. Exits 1 if any check FAILs | `--send`/`--markdown` are notification-only: they send the summary via `lib/notify.sh` to every platform in `NOTIFY_PLATFORM` and do NOT print the dashboard (so wrappers like the Telegram listener don't echo it back — pair with the listener's `@quiet` marker). `HEALTH_BACKUP_MAX_AGE_DAYS` (default 2) and `BACKUP_SERVICE_ROOTS` come from `~/.config/linux_post_install/system.env`; `--help` shows the effective values. Platform list from `~/.config/linux_post_install/notify.env` |
|
||||
| `pos system schedule <cmd>` | `bin/pos-system-schedule` | Scheduled jobs — run a command on a timer, notify (or stay silent): `run [name\|all]`, `list`, `config`, `enable [name\|all]`, `disable [name\|all]`, `status`, `migrate`. Each job is a file in `~/.config/linux_post_install/schedule.d/<name>.env` with `INTERVAL` (`5m…59m`, `1h…23h`, `hourly`, `daily`, `weekly`, `OnCalendar=…`), `NOTIFY` policy, optional `MSG`, `RULE` (threshold only), and `COMMAND` = the literal rest of the line (pipes/quotes/`sudo` fine). Policies: `always` (full output every run), `onchange` (send when output differs from the last run; first run always sends), `onerror` (non-zero exit or empty output), `threshold` (first numeric output vs `RULE`, alert on false→true + one recovery — the old event-trigger behavior), `never` (side-effect jobs, no notify) | One systemd **user** timer pair per job (`pos-schedule-<name>.timer` + oneshot `.service`, `Persistent=true`), reconciled on `enable`/`disable`; the legacy single `pos-event-trigger` timer is auto-removed. `migrate` converts a pre-existing `event.env` rule set into `schedule.d/rule-N.env` threshold jobs. `config` is an interactive editor (add/edit/remove/enable/disable, validates interval + threshold); alerts via `lib/notify.sh`; `--dry-run` previews runs/writes/sends; jobs are arbitrary shell commands (chmod 600, same trust model as the Telegram map); starter jobs in `config/schedule.d/` auto-installed no-clobber by postinstall |
|
||||
|
||||
`systemd/pos-health.service` + `systemd/pos-health.timer` run `pos system health --send --markdown` daily at 08:00 as the installing user. `postinstall.sh` enables the timer automatically once `~/.config/linux_post_install/telegram.env` exists — re-run postinstall after configuring a notify platform to pick it up. The service also loads `system.env` + `notify.env` via `EnvironmentFile=`.
|
||||
A scheduled job is the recommended way to run the health dashboard on a timer, e.g. a `daily` job with `COMMAND=pos system health --send` — no separate systemd unit needed (the old `pos-health.{service,timer}` units are gone; a legacy install may still have them failed/leftover — disable and remove them).
|
||||
|
||||
### ssh
|
||||
|
||||
|
||||
+16
-32
@@ -6,8 +6,8 @@ The units installed and enabled by `postinstall.sh`, plus the `pos` bash complet
|
||||
- [`autostart.service`](#autostartservice)
|
||||
- [`usb-automount.service`](#usb-automountservice)
|
||||
- [`ssh-agent.service`](#ssh-agentservice)
|
||||
- [`pos-health.service`](#pos-healthservice)
|
||||
- [Per-user units (`pos network download`)](#per-user-units-pos-network-download)
|
||||
- [Stop behavior](#stop-behavior)
|
||||
- [Feature-flag gating](#feature-flag-gating)
|
||||
- [Bash completion](#bash-completion)
|
||||
|
||||
@@ -81,32 +81,6 @@ WantedBy=multi-user.target
|
||||
|
||||
**Configuration:** socket at `/run/ssh-agent/socket` (world-readable/writable). `~/.bashrc` (set by `postinstall.sh`) exports `SSH_AUTH_SOCK` to it. Not gated on any feature flag.
|
||||
|
||||
### pos-health.service
|
||||
|
||||
**Purpose:** daily "health digest" — runs `pos system health --send --markdown` at 08:00 and sends the report to the configured notify platform(s).
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=POS Health digest (daily report via configured notify platforms)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=__POS_USER__
|
||||
EnvironmentFile=-%h/.config/linux_post_install/system.env
|
||||
EnvironmentFile=-%h/.config/linux_post_install/notify.env
|
||||
ExecStart=/usr/local/bin/pos system health --send --markdown
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 08:00:00
|
||||
Persistent=true
|
||||
```
|
||||
|
||||
The service is `Type=oneshot` and is driven **only** by its companion `pos-health.timer` (`WantedBy=timers.target`); the service itself is never enabled directly.
|
||||
|
||||
**Configuration:** `postinstall.sh` substitutes `__POS_USER__` with the installing user (`${SUDO_USER:-$USER}`) so the digest uses that user's real notify config. The `EnvironmentFile=` lines load `system.env` (health/backup settings) and `notify.env` (`NOTIFY_PLATFORM`). The timer is enabled only when a Telegram config (`~/.config/linux_post_install/telegram.env`) already exists — otherwise postinstall warns and skips; re-run postinstall after configuring a notify platform to install it.
|
||||
|
||||
---
|
||||
|
||||
## Per-user units (`pos network download`)
|
||||
@@ -134,9 +108,23 @@ enable-linger` warning so the user units survive logout.
|
||||
|
||||
---
|
||||
|
||||
## Stop behavior
|
||||
|
||||
Every pos unit — the tool-written user units (`pos-aria2`, `pos-aria2-retry`,
|
||||
`scheduler-lib.sh` job units, `entertainment-lib.sh` plugin units, the Telegram
|
||||
and Matrix listener units) and the shipped `systemd/*.service` files — sets
|
||||
`TimeoutStopSec=5s` so nothing can stall a reboot for the systemd default of
|
||||
90s. The two long-polling listener daemons also `trap TERM INT` in their poll
|
||||
loop (kills in-flight curl, exits 0), so `systemctl --user stop` returns in
|
||||
well under a second; `TimeoutStopSec` is the backstop if a process ignores
|
||||
SIGTERM. A oneshot job that happens to be running at shutdown gets SIGKILLed
|
||||
5s after stop begins — timers are `Persistent`, so the work re-runs next boot.
|
||||
|
||||
---
|
||||
|
||||
## Feature-flag gating
|
||||
|
||||
The systemd loop in `postinstall.sh` special-cases three units:
|
||||
The systemd loop in `postinstall.sh` special-cases two units:
|
||||
|
||||
```bash
|
||||
if [ "$svc_name" = "autostart.service" ] && ! flag_is_set autostart; then
|
||||
@@ -147,14 +135,10 @@ if [ "$svc_name" = "usb-automount.service" ] && ! flag_is_set usb-automount; the
|
||||
warn "usb-automount feature not installed — skipping usb-automount.service (run ./install.sh --feature)"
|
||||
continue
|
||||
fi
|
||||
if [ "$svc_name" = "pos-health.service" ]; then
|
||||
# substitute User= and enable pos-health.timer only if Telegram is configured
|
||||
fi
|
||||
```
|
||||
|
||||
- `autostart.service` is **enabled** only when the `autostart` feature flag is set (`./install.sh --feature` or `flag-set autostart`). See [SCRIPTS.md → lib/flags.sh](SCRIPTS.md#libflagssh--feature-flags).
|
||||
- `usb-automount.service` is **enabled** only when the `usb-automount` feature flag is set — same mechanism.
|
||||
- `pos-health.service` is **not** enabled at all — `postinstall.sh` enables `pos-health.timer` instead, and only when a Telegram config already exists.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+15
-8
@@ -47,19 +47,26 @@ NOTIFY_PLATFORM=telegram
|
||||
|
||||
### Daily digest (automated)
|
||||
|
||||
`systemd/pos-health.{service,timer}` run `pos system health --send --markdown`
|
||||
at 08:00 as the installing user. Enable it (re-run postinstall after Telegram
|
||||
is configured):
|
||||
Run the health report on a timer with a scheduled job (no systemd unit needed):
|
||||
|
||||
```bash
|
||||
./postinstall.sh # enables timer once telegram.env exists
|
||||
systemctl list-timers | grep pos-health
|
||||
systemctl start pos-health.service # run once now, check status
|
||||
pos system schedule config # add a job: INTERVAL=daily,
|
||||
# COMMAND=pos system health --send --markdown
|
||||
systemctl --user list-timers | grep pos-schedule
|
||||
pos system schedule run <name> # run once now
|
||||
```
|
||||
|
||||
The old `pos-health.{service,timer}` systemd units are gone — a legacy install
|
||||
may still have them failed/leftover; disable and remove them:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now pos-health.timer pos-health.service 2>/dev/null
|
||||
sudo rm -f /etc/systemd/system/pos-health.{service,timer} && sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
**Recipes:**
|
||||
- Watch the backup age without email: enable the digest; if the backup check
|
||||
turns WARN you'll see it in the morning report.
|
||||
- Watch the backup age without email: add the daily digest job; if the backup
|
||||
check turns WARN you'll see it in the morning report.
|
||||
- Exit code in a cron/scheduled check:
|
||||
`pos system health >/dev/null 2>&1 || notify_send "health FAIL"`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user