--- 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. [Noctalia](noctalia-shell.md) reads `~/.local/share/noctalia/wallpapers/`, which is a set of symlinks into that directory, so the files stay on disk and untracking them broke nothing on this box. Restore on a fresh box: every filename is `wallhaven_.` and the ID is the whole address — `curl -L -o "https://wallhaven.cc/w/"` puts the library back, then `deploy.sh` relinks it. Losing the set 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/` 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.