Three findings from verifying the #41 merge against the live box with `deploy.sh --check`. One commit because they share the deploy tier, its documentation, and the first test file that tier ever had. **1. Generated palette output was tracked, and sync would have restored it over a live theme.** `dotfiles/zz-blankpad/niri/.config/niri/noctalia.kdl` (`b3d3df4`, 2026-10-18) sat byte-identical to the live file — harmless while the palette held, and `--check` was already proposing `ADOPT … noctalia.kdl`. The moment the theme changed, the next sync would have quarantined the live file and reinstated 2026-10-18's colours. `deploy.sh` now refuses the class outright (`SKIP … (generated theme output)`), `.gitignore` carries the matching patterns so it cannot be re-added, and the file is untracked. **2. A `zz` override older than the file it overrides.** `dotfiles/zz-blankpad/niri/…/ layout.kdl` held focus ring `width 2` / `#e01b24` (`7b7dbf5`, 2026-10-17) while base absorbed `b3d3df4` (2026-10-18): `width 4`, `#ff8c00`. Stow order makes `zz-*` win, and a cross-package owner change relinks *silently* — no quarantine, no warning. Live was linked to base, the newer intent, so `--check` printed `RELINK … layout.kdl` every run and one unattended `deploy.sh` would have reverted the amber focus ring. The duplicate is deleted; base owns the file. **3. Two topologies I had asserted and not measured.** The media law and the `.gitignore` comment both said `~/.local/share/noctalia/wallpapers` symlinks into the repo, so deleting the tracked copies would blank the desktop. Measured: it is a real directory of real files that Noctalia owns; the repo held a duplicate, and the #41 merge deleted that duplicate without the desktop flickering. The correct general law replaces the false one — untracking a path deletes the file in every other checkout, `git rm --cached` on the branch notwithstanding — and the restore procedure now points at the app's directory, which `deploy.sh` does not manage. The guard is `tests/deploy.bats`, black-box over exit status, stdout, the symlink shape under `$HOME`, and the quarantine directory. Before the change: test 1 and test 2 fail, test 2 because the live palette really is quarantined and reverted. After: 4/4, including the control that an ordinary identical config is still adopted — an exclusion nobody can turn off is just a broken tool. Verification: `bats tests/` 66/66, `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0 issues, and live `deploy.sh --check` on blankpad reports `SKIP … (generated theme output)` with `actions=0` instead of `ADOPT … noctalia.kdl`. The real `deploy.sh` run that clears the remaining `RELINK` lines is deliberately left until this is in main — running it from a worktree would repoint live symlinks into a checkout the heartbeat reaps. Refs #42
128 lines
7.4 KiB
Markdown
128 lines
7.4 KiB
Markdown
---
|
|
type: reference
|
|
title: Dotfiles deployment tier
|
|
description: How dotfiles/ packages become live configuration — stow-equivalent symlinks, host override packages, and the two laws about theme media and systemd enable symlinks.
|
|
tags:
|
|
- dotfiles
|
|
- deployment
|
|
- systemd
|
|
- noctalia
|
|
timestamp: 2026-10-20
|
|
verified: 2026-10-20
|
|
---
|
|
|
|
# Dotfiles deployment tier
|
|
|
|
`dotfiles/` holds home-relative trees and `scripts/deploy.sh` is a stow-equivalent: the
|
|
repo checkout **is** the deployment, and deploying replaces live files with symlinks into
|
|
the repo, so `git pull` + `deploy.sh` is the whole propagation story. One directory per
|
|
tool (`niri`, `ghostty`, `gtk-3.0`, `systemd-user`, …); a package named `zz-$(hostname)`
|
|
is stowed last and its files replace symlinks coming from base packages.
|
|
|
|
| Mode | Effect |
|
|
|---|---|
|
|
| `deploy.sh --check` | dry run — adopts identicals, reports what would be linked, lists divergents, writes nothing. Run this first, always |
|
|
| `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.
|
|
|
|
**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
|
|
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).
|
|
|
|
## Law: media the theme pipeline consumes is content, not config
|
|
|
|
Wallpapers live at `dotfiles/noctalia/.local/share/noctalia/wallpapers/` and are
|
|
gitignored. They were tracked until 2026-10-20 — 23 files, 80 MiB — which meant every
|
|
clone of a *configuration* repo carried a media library no diff can review, and the
|
|
payload only ever grows because deleting a tracked blob from the tree does not delete it
|
|
from history. The topology is not what one would guess: [Noctalia](noctalia-shell.md)
|
|
keeps its own library at `~/.local/share/noctalia/wallpapers/` as a **real directory of
|
|
real files**, so the tracked tree was a duplicate of it and never its source — measured
|
|
2026-10-20, when the merge of #41 deleted the repo copies and the desktop did not
|
|
flicker. Two consequences to know before untracking media: the copy that survives belongs
|
|
to the application, not to the repo; and untracking deletes the file in every other
|
|
checkout, `git rm --cached` on the branch notwithstanding.
|
|
|
|
Restore on a fresh box: the library lives in `~/.local/share/noctalia/wallpapers/`, a path
|
|
`deploy.sh` does not manage. Every filename is `wallhaven_<id>.<ext>` and the ID is the
|
|
whole address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` into that directory puts
|
|
the set back. Losing it is an acceptable outcome; 80 MB per clone forever is not.
|
|
|
|
## Law: never `systemctl --user enable` a unit from a worktree
|
|
|
|
`enable` writes `~/.config/systemd/user/default.target.wants/<unit>` as a symlink to the
|
|
unit path it was handed, and that path is absolute. The heartbeat reaps `wt/*` worktrees
|
|
older than seven days ([live-sync](live-sync.md)), so a unit enabled from a worktree turns
|
|
into a dangling symlink while `systemctl --user is-enabled` still answers `enabled` — the
|
|
unit is dead and the status lies about it. Found on 2026-10-20:
|
|
`blankpad-desk-autoswitch.service` pointed into the reaped `wt/de-redesign-20261009`, so
|
|
desk auto-switching had been silently off since the reap.
|
|
|
|
Check the link target before the unit file:
|
|
|
|
```bash
|
|
ls -l ~/.config/systemd/user/default.target.wants/
|
|
# every target must resolve; a dangling one is a reaped worktree, not a config bug
|
|
```
|
|
|
|
Fix is to relink into the deployment checkout (`~/.omp/dotfiles/…`), `systemctl --user
|
|
daemon-reload`, then confirm with `is-active` — `is-enabled` proves nothing.
|
|
|
|
## Law: a generated file must never be a required include
|
|
|
|
`~/.config/niri/config.kdl` ends with `include "noctalia.kdl"` — no `optional=true`.
|
|
That file is noctalia's palette output (`#ff8c00`, `#f43841`, one re-stamp
|
|
`2026-10-18 17:36:27`), so it is machine state and correctly untracked. But on a fresh
|
|
box `deploy.sh` links `config.kdl` first, and niri then refuses to start until noctalia
|
|
has run once and written the file — the compositor is bricked by the order of operations,
|
|
not by a bad config. Close it at the include, which survives someone skipping the theme
|
|
pipeline: mark generated includes `optional=true`, as the `display.kdl` and
|
|
`switch-events.kdl` lines in the same file already are.
|
|
|
|
## Desk choice is live state, never config
|
|
|
|
`~/.config/niri/cfg/display.kdl` is a relative symlink to whichever profile is active
|
|
(`desk-1-home.kdl`, `desk-2-portable.kdl`), written by `desk.sh`. Both profiles are
|
|
tracked in `dotfiles/zz-blankpad/`; the pointer is not, because which desk the box sits on
|
|
is not a fact about the repo. A fresh box therefore starts with no output configuration —
|
|
`config.kdl` includes `display.kdl` with `optional=true`, so niri comes up usable but with
|
|
default monitors until `desk.sh` is run once.
|
|
|
|
## Law: generated theme output is never deployed
|
|
|
|
noctalia rewrites palette output in place — `noctalia.*`, `themes/noctalia*`,
|
|
`colors/noctalia*`, `noctalia-*` (the classes the [census](/inventory/home-config-census.md)
|
|
proves by same-millisecond mtime). A tracked copy of one is a stale snapshot of a colour
|
|
scheme, and the failure is quiet: `dotfiles/zz-blankpad/niri/.config/niri/noctalia.kdl`
|
|
(commit `b3d3df4`, 2026-10-18) sat byte-identical to the live file for two days, harmless,
|
|
while `--check` cheerfully proposed `ADOPT … noctalia.kdl`. The moment the palette changed,
|
|
the next sync would have quarantined the live file and restored 2026-10-18's colours —
|
|
a tool silently reverting a user-visible theme choice.
|
|
|
|
`deploy.sh` now refuses the class (`SKIP … (generated theme output)`), `.gitignore` carries
|
|
the matching patterns so it cannot be re-added, and `tests/deploy.bats` pins both
|
|
directions: generated output is never adopted, and an ordinary identical config still is.
|
|
Untracked in #43, Refs #42.
|
|
|
|
## Law: a `zz` override older than the file it overrides is a time bomb
|
|
|
|
Stow order means `zz-$HOST` wins, and when both packages carry the same path, sync relinks
|
|
it *silently* — the script's own comment: "managed file changing owners between packages:
|
|
silent relink, nothing to quarantine". `dotfiles/zz-blankpad/niri/.config/niri/cfg/layout.kdl`
|
|
held focus ring `width 2` / `active-color "#e01b24"` from `7b7dbf5` (2026-10-17) while the
|
|
base package absorbed `b3d3df4` (2026-10-18): `width 4`, `"#ff8c00"`. Live was linked to
|
|
base, the newer intent, so `--check` printed `RELINK .config/niri/cfg/layout.kdl` on every
|
|
run and one unattended `deploy.sh` would have reverted the amber focus ring with no
|
|
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.
|