deploy.sh no longer starts units a live session already runs
`sync` on a box with a live session on it was unsafe in two independent ways, found by preflighting the run rather than performing it. Refs #44. **`enable --now` would have armed a screen lock mid-session.** The unit loop started every `systemd-user/` unit whose `ExecStart` resolves. `swayidle-lock.service` on blankpad is `enabled`/`inactive` — nothing started it, no swayidle process exists, `autostart.kdl` does not launch one — so one `deploy.sh` would have begun blanking and locking the monitors while the operator was mid-sentence. `live-sync.service` is `linked`, driven by its timer, and `--now` forces a heartbeat commit at an arbitrary moment. A unit is now started only when systemd reports it as nothing-yet (`""`/`disabled`), which is precisely a fresh box; anything already installed is merely `enable`d. The state has to come from the **printed state**: `is-enabled` exits 1 for `linked` exactly as for `disabled`, so exit status cannot distinguish "installed, timer-driven" from "never installed" — the first version of this fix got that wrong and the test caught it. **`--check` output was being read as more than it can mean.** It prints `RELINK` for every path the `zz` tier carries, even when the live link already points at the override (`rules.kdl`, `keybinds.kdl`, `misc.kdl`, `autostart.kdl`, `.zshrc` all did on blankpad), because the walk visits the base package first and reports the intermediate owner change. The sentence committed in #43 — RELINK noise is "the sound of a stale override waiting" — is too strong and is corrected: the line proves a pending change only when `readlink` shows the live target is not already the `zz-$HOST` file, and `actions=0` is the honest summary. The stale-override hazard #43 removed was and is real; the signal for it is the target, not the line. **The dconf dump carried six of its 24 keys from another box**, none of them preferences: a Firefox file-chooser `last-folder-path` of `/home/dank/Downloads`, a nautilus `window-state` section, and `picture-uri`/`picture-uri-dark` pointing at `file:///home/dank/.config/background`. The last five showed no conflict because blankpad's live tree *already* held those `/home/dank` values — an earlier deploy wrote them here — and a foreign value that agrees with live is invisible to any diff while still being wrong. They are dropped; `picture-options='zoom'` stays, because it is a preference. The live wallpaper URIs are cleared on the box as part of landing this (niri draws the wallpaper, noctalia owns it, nothing reads a dead path). `tests/deploy.bats` grows by four, over a stub `systemctl` — the real one on this box is exactly what must not be invoked from a test. Red before: an already-`enabled` unit and a `linked` unit both get `--now`. Green after, including the two directions that keep the change honest: a `disabled` unit is still `enable --now`'d so a fresh box comes up, and a unit whose `ExecStart` is absent here is never touched at all. Verification: `bats tests/` 70/70, `kb-lint.sh omp-docs` 0, `kb-coverage.py` 0. The dump now reconciles against live state with **0 conflicts and no `/home/` values in 18 keys**, proven by key-by-key comparison rather than `diff` — `dconf load` writes only the keys present, so a diff overstates the damage one way and hides it the other.
This commit is contained in:
1 parent
7523ca5c19
commit
ecc089c6ef
5 files changed
+143
-20
No files matched your search
@@ -25,14 +25,16 @@ is stowed last and its files replace symlinks coming from base packages.
|
||||
| `deploy.sh` (sync) | adopt identicals, link new, quarantine divergents into `.quarantine/` and replace them |
|
||||
| `deploy.sh --adopt` | like sync, but divergents are reported and skipped rather than quarantined |
|
||||
|
||||
Two package shapes are not symlinks: `dconf/dump.txt` is loaded with `dconf load /`, and
|
||||
`systemd-user` units get `daemon-reload` plus `enable --now` only when their `ExecStart`
|
||||
binary resolves on the current machine, so a unit belonging to another box stays inert
|
||||
instead of crash-looping.
|
||||
Two package shapes are not symlinks. `dconf/dump.txt` is loaded with `dconf load /`, and
|
||||
`systemd-user` units get `daemon-reload` plus `enable`; a unit is **started** only when
|
||||
systemd reports it as nothing-yet (`""`/`disabled`), and never when its `ExecStart` binary
|
||||
is absent here, so a unit belonging to another box stays inert instead of crash-looping.
|
||||
Starting something already installed is what makes `deploy.sh` unsafe to run beside a live
|
||||
session — see *Never start a unit a session may already be running*.
|
||||
|
||||
**The tracked unit is the file, not the directory.** A live directory can be a real
|
||||
directory and still be fully tracked, because every file inside it is a symlink into the
|
||||
repo (`niri` is 9 tracked / 1 box-only; `gtk-3.0` is 3/0). Any audit that classifies by
|
||||
repo (`niri` is 8 tracked / 2 box-only; `gtk-3.0` is 3/0). Any audit that classifies by
|
||||
directory mislabels the tree — classify per file with `readlink -f`. The live census and
|
||||
its classifier command are in [home-config-census](/inventory/home-config-census.md).
|
||||
|
||||
@@ -123,5 +125,62 @@ run and one unattended `deploy.sh` would have reverted the amber focus ring with
|
||||
quarantine and no warning. The duplicate is deleted in #43.
|
||||
|
||||
Rule: a `zz` file exists only to differ. When base absorbs the change, delete the `zz` copy
|
||||
in the same commit — and never read `RELINK` lines from `--check` as cosmetic noise; they
|
||||
are the sound of a stale override waiting for someone to run the tool for real.
|
||||
in the same commit.
|
||||
|
||||
How to read `--check`, measured on blankpad: it prints `RELINK` for **every** path the `zz`
|
||||
tier carries, even when the live link already points at the override — `rules.kdl`,
|
||||
`keybinds.kdl`, `misc.kdl`, `autostart.kdl` and `.zshrc` all did — because the walk
|
||||
processes the base package first and reports the intermediate owner change. A `RELINK` line
|
||||
therefore proves a pending change only when `readlink` shows the live target is *not*
|
||||
already the `zz-$HOST` file. `actions=0` is the only honest summary.
|
||||
## Law: never start a unit a session may already be running
|
||||
|
||||
`sync` used to end with `systemctl --user enable --now` for every unit in `systemd-user/`
|
||||
whose `ExecStart` resolves. Measured on blankpad before one such run:
|
||||
|
||||
|unit|`UnitFileState`|`ActiveState`|
|
||||
|---|---|---|
|
||||
|`live-sync.service`|`linked`|`inactive`|
|
||||
|`shelly-notifications.service`|`enabled`|`active`|
|
||||
|`swayidle-lock.service`|`enabled`|`inactive`|
|
||||
|
||||
`swayidle-lock.service` was installed and **not running** — nothing started it, no swayidle
|
||||
process existed (`pgrep -c swayidle` = 0), and `autostart.kdl` does not launch one. A
|
||||
`deploy.sh` run would have armed it: monitors blanking and locking on the idle timer while
|
||||
the operator was mid-sentence. `live-sync.service` is `linked`, driven by its timer;
|
||||
`--now` forces a heartbeat commit at an arbitrary moment.
|
||||
|
||||
Two rules follow. Install state must be read from the **printed state**, not the exit
|
||||
status — `is-enabled` exits 1 for `linked` exactly as for `disabled`, so exit status cannot
|
||||
tell "installed, driven by a timer" apart from "never installed". And a unit is started only
|
||||
in the latter case: a machine with a session on it has already decided what runs, while a
|
||||
fresh box — where everything reports `disabled` — still gets its units up. Both directions
|
||||
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: the dconf dump holds preferences, not per-box state
|
||||
|
||||
`dconf load /` writes every key in `dotfiles/dconf/dump.txt`, so the dump carries whatever
|
||||
box it was taken from. Comparing key by key against live state on blankpad found **six of
|
||||
24** keys that were not this box's, and not one of them was a preference:
|
||||
|
||||
[org/gnome/portal/filechooser/firefox] last-folder-path
|
||||
repo: '/home/dank/Downloads' live: '/home/blank/Downloads'
|
||||
[org/gnome/nautilus/window-state] initial-size, initial-size-file-chooser, maximized
|
||||
[org/gnome/desktop/background] picture-uri, picture-uri-dark
|
||||
both: 'file:///home/dank/.config/background'
|
||||
|
||||
A file-chooser memory, a window geometry, a wallpaper URI: application state that arrived
|
||||
with the dotfiles import. The last five did not appear as conflicts at all, because
|
||||
blankpad's **live** tree already held the same `/home/dank` values — an earlier deploy had
|
||||
written them here. A foreign value that agrees with live is invisible to any diff and still
|
||||
wrong. All six come out in #44; the two wallpaper URIs are also cleared on this box
|
||||
(`gsettings set org.gnome.desktop.background picture-uri ''`, and its `-dark` twin), because
|
||||
niri draws the wallpaper, noctalia owns it, and nothing reads a dead path.
|
||||
|
||||
Compare **key by key**, not with a plain `diff`: `dconf load` writes only the keys present,
|
||||
so live-only keys survive and a diff overstates the damage in one direction and understates
|
||||
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.
|
||||
Reference in new issue
Block a user