|
|
|
@@ -158,6 +158,27 @@ fresh box — where everything reports `disabled` — still gets its units up. B
|
|
|
|
|
are pinned in `tests/deploy.bats` over a stub `systemctl`, because the real one on this box
|
|
|
|
|
is exactly what must not be invoked from a test. Refs #44.
|
|
|
|
|
|
|
|
|
|
## Law: expand systemd specifiers before calling a unit foreign
|
|
|
|
|
|
|
|
|
|
`ExecStart=%h/.omp/scripts/live-sync.sh` is valid systemd and meaningless to a shell. The
|
|
|
|
|
resolution check handed that literal string to `command -v`, which fails everywhere, so the
|
|
|
|
|
deployment concluded the unit belonged to another machine and skipped it — silently, through
|
|
|
|
|
the same code path that is supposed to keep a *foreign* unit inert. The unit it kept skipping
|
|
|
|
|
was this repo's own heartbeat, and `%h` is precisely where this checkout lives, so the false
|
|
|
|
|
verdict was the same on every box that deployed it. Measured on blankpad: the first real
|
|
|
|
|
`deploy.sh` run printed `SKIP live-sync.service (ExecStart '%h/.omp/scripts/live-sync.sh'
|
|
|
|
|
absent on this host)` while `/home/blank/.omp/scripts/live-sync.sh` sits right there.
|
|
|
|
|
|
|
|
|
|
Expand the specifiers a user unit can carry — `%h`, `%u`, `%U` — before resolving, and let an
|
|
|
|
|
unknown specifier fail rather than guess at it. The probe needs no privileges and writes
|
|
|
|
|
nothing:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
bin=$(sed -n 's/^ExecStart=\([^ ]*\).*/\1/p' \
|
|
|
|
|
~/.omp/dotfiles/systemd-user/.config/systemd/user/live-sync.service | head -1)
|
|
|
|
|
command -v "${bin//%h/$HOME}" # resolves → the unit is this box's; the SKIP was the lie
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Law: the dconf dump holds preferences, not per-box state
|
|
|
|
|
|
|
|
|
|
`dconf load /` writes every key in `dotfiles/dconf/dump.txt`, so the dump carries whatever
|
|
|
|
@@ -184,3 +205,51 @@ it in the other. Re-dump on the box whose settings are canonical (`dconf dump /
|
|
|
|
|
dotfiles/dconf/dump.txt`), then delete the state sections before committing — the census's
|
|
|
|
|
*Never track: app state* class applies inside a tracked file, not just to whole directories.
|
|
|
|
|
Refs #44.
|
|
|
|
|
|
|
|
|
|
## Law: a watcher must restart on a clean exit too
|
|
|
|
|
|
|
|
|
|
`swayidle-lock.service` was `enabled`, present in `default.target.wants/`, and **dead for eight
|
|
|
|
|
days**: `Result=success`, `NRestarts=0`, gone since 2026-10-12 16:22 after 2d 21h of uptime.
|
|
|
|
|
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. The trigger is
|
|
|
|
|
attributable to eight seconds: **`niri` and `noctalia` both started `Mon Oct 12 16:22:31 2026`**
|
|
|
|
|
(`ps -o lstart=`) while swayidle's last state change was `16:22:23` — the compositor session
|
|
|
|
|
restarted, the watcher went with it, and because it was wanted by `default.target` rather than tied
|
|
|
|
|
to the session target, nothing brought it back. The user journal keeps no entries for that window,
|
|
|
|
|
so the attribution rests on process ages rather than logs, which is its own lesson: diagnose a
|
|
|
|
|
lapse within the retention you actually have.
|
|
|
|
|
|
|
|
|
|
**The severity claim in this page's first draft was wrong, and is corrected here.** It said closing
|
|
|
|
|
the lid suspended the machine unlocked, because the `before-sleep` hook lived in the dead unit. But
|
|
|
|
|
`systemd-inhibit --list` shows `noctalia … sleep "Lock before sleep" delay` — the shell holds its
|
|
|
|
|
own sleep inhibitor and locks on suspend independently of swayidle, so the suspend path was covered
|
|
|
|
|
the whole time. What was actually dead for eight days is the **idle** lock: five minutes of
|
|
|
|
|
inactivity locked nothing, and `after-resume` never fired. `pgrep -c -u blankpad
|
|
|
|
|
'swayidle|swaylock|hyprlock|gtklock'` = 0 remains true; it simply did not mean *no lock path at
|
|
|
|
|
all*, because a locker can be a hook inside a running shell rather than a name on the process list.
|
|
|
|
|
Read the inhibitor table, not only `pgrep`.
|
|
|
|
|
|
|
|
|
|
Three settings, each with a job: `Restart=always`, because a supervisor must not assume it can
|
|
|
|
|
only die badly; `StartLimitIntervalSec=0`, because a fast loop must never wedge it into `failed` —
|
|
|
|
|
a lock that keeps trying beats one that stopped trying; and `PartOf=graphical-session.target`,
|
|
|
|
|
because the unit is wanted by `default.target`, so the session coming back did not bring *it*
|
|
|
|
|
back.
|
|
|
|
|
|
|
|
|
|
Verify by behaviour rather than by reading the file — the point is surviving the event that killed
|
|
|
|
|
it in October:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
systemctl --user start swayidle-lock.service
|
|
|
|
|
kill -TERM "$(systemctl --user show swayidle-lock.service -p MainPID --value)"
|
|
|
|
|
sleep 8 && systemctl --user show swayidle-lock.service -p NRestarts -p MainPID -p ActiveState
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
An idle inhibitor also disarms the timeout outright, which surprises people: while an `omp` agent
|
|
|
|
|
session is running, swayidle logs `Not enabling timeouts: idle inhibitor found` and arms no idle
|
|
|
|
|
timeout at all — `systemd-inhibit --list` shows `omp … idle "omp agent session" block`. A box with
|
|
|
|
|
an agent working therefore does not lock on inactivity, by harness design, while `before-sleep` and
|
|
|
|
|
`after-resume` keep working. Anyone asking "why did the screen never lock" should read the inhibitor
|
|
|
|
|
table before suspecting the unit.
|
|
|
|
|
|
|
|
|
|
The general shape: `deploy.sh` only ever *enables*. Nothing on any box re-checks that a unit the
|
|
|
|
|
system depends on is still running, so a security posture can lapse silently and stay lapsed for a
|
|
|
|
|
week. A liveness check for this tier belongs in the heartbeat, not in a human's memory. Refs #47.
|