System Configuration¶
A machine is not converged when its packages are installed. It still has to hold
the group memberships those packages assume, enable the units they ship, log
itself in on TTY1, point /etc/zshenv at the XDG config, run zsh as its login
shell, and — on a Mac — hold its defaults keys where this repo put them.
install/system.yml declares all of it. Four kinds share one mechanism and live
in src/dotfiles/providers/sysconfig.py. Preference keys are their own kind, in
providers/macdefaults.py. What is left shares no mechanism at all and is
providers/steps.py. Root is src/dotfiles/privilege.py and nowhere else. Each
of those docstrings is the account of how its own rows converge. This page holds
what no one of them can state.
Configuration is a second file because payload is a different question¶
Every section of install/packages.yml names bytes: something that arrives from
somewhere, carries a version, and can fall behind. A group membership has none of
those properties. One file for both would make packages the noun a reader
reaches for to ask whether this account is in the docker group, and the system
resource exists so that it is not. The header of install/system.yml makes the
argument at the point where a row gets added.
An entry narrows itself, and one narrowing forces a second pass¶
A package section is one question asked of every machine. These rows are not:
three of them can turn on three unrelated conditions, so the condition sits on
the entry instead. SystemConfig in src/dotfiles/catalog.py carries the fields
and refuses a key that is not one of them.
requires_package is the only narrowing that depends on the rest of the plan:
what a machine installs is settled before what a machine configures can be.
src/dotfiles/registry.py orders the providers so that dependency is satisfied
by position, which is what keeps it from being a special case in the resolver.
Every read is unprivileged, and a new row has to keep it that way¶
check never escalates. That is what lets it run from a plain shell, on a timer
with nobody at the keyboard, and inside a container that has no passwordless
sudo. It holds only because every observation was chosen for a read that needs no
root, so a new row is not free to observe itself however it likes. A row that
cannot be seen unprivileged reports UNKNOWN with its reason instead of
guessing.
src/dotfiles/privilege.py holds the acquisition model and the design it
reversed.
steps is the name for no shared mechanism¶
Rows with nothing in common except needing to be reconciled. Each is a pair of
functions in providers/steps.py, and tests/resources/test_steps.py asserts
that the declared set and the implemented set match in both directions. These
are worth knowing about from here.
The scheduled check is a steps row. providers/steps.py hands it to
providers/schedule.py, which installs the timer and argues why a machine opts
in rather than getting one by default. What each check leaves for a reader who
is not watching is src/dotfiles/status.py.
Its state depends on the manifest it is measured for. A manifest's
check_schedule overrides the trust domain's [schedule] enabled for that
machine. A machine that declines still plans the row, so a timer installed
earlier is found and removed. registry.SystemConfigProvider.states is how the
manifest reaches the row.
The /etc/hosts sudo grant is a steps row, though it writes one file. A
managed_files row observes by reading the file, and a sudoers drop-in cannot be
read unprivileged. On Arch, /etc/sudoers.d is 0750, so the row would report
missing forever and ask for a password on every apply. A managed file is also
installed unchecked, and one unparseable drop-in stops sudo running at all. The
step reads the grant's effect from sudo -n -l instead, through
privilege.passwordless. It installs nothing that visudo has not parsed first.
install/wsl/docker-repo.sh is deliberately not a row. Nothing in the
install path runs it. WSL borrows its engine from Docker Desktop, and
docs/configuration/docker.md presents that script as the manual escape hatch
for a machine that cannot have Docker Desktop. Declaring it would make the
installer run it, and that page promises the reverse.
A browser's extensions are configuration where the browser has a policy¶
Safari's extensions are App Store apps and Zen's come from addons.mozilla.org, so
both are declared in packages.yml. Vivaldi's are neither. Chromium installs
whatever an enterprise policy file names, which makes the file the declared state
and the extensions a consequence of it. So the set is a managed_files row and
not a vivaldi_extensions section with a provider behind it: writing one file is
the whole of the work, and a section would be a mechanism built for three
strings.
It takes two rows, because Linux and macOS disagree about what a policy is
rather than about where one lives. install/system.yml carries both, with the
path that took reading the binary to find and the reason a Mac cannot say this
with a preference key.
The trade is that packages list cannot see the three extensions, and that one
list is spelled twice in two syntaxes. That is what a section and a provider
would earn themselves against. Three strings do not.