|
| 1 | +# ADR-0013: Per-user `nix.conf` is left to Home Manager |
| 2 | + |
| 3 | +**Date:** 2026-04-29 |
| 4 | +**Status:** Accepted |
| 5 | +**Supersedes:** — |
| 6 | +**Superseded by:** — |
| 7 | + |
| 8 | +## Context |
| 9 | + |
| 10 | +Home Manager's `switch` requires `experimental-features = |
| 11 | +nix-command flakes` to be enabled at the daemon or per-user level. |
| 12 | +Without it, `nix profile`-style invocations that HM uses internally |
| 13 | +fail with `error: experimental Nix feature 'nix-command' is disabled`. |
| 14 | + |
| 15 | +Plan §7.4 W1 listed "per-user `nix.conf` handling" alongside the |
| 16 | +`runuser` drop wrapper, but M3 only shipped runuser. The M3 |
| 17 | +retrospective marked nix.conf provisioning `[~]` and called for an |
| 18 | +ADR before any code lands. |
| 19 | + |
| 20 | +Three approaches were considered: |
| 21 | + |
| 22 | +1. **Pearlite writes `~/.config/nix/nix.conf`** from a declared |
| 23 | + block in the per-host Nickel config — e.g. |
| 24 | + `users.alice.nix.conf.experimental_features = |
| 25 | + ["nix-command", "flakes"]`. Pearlite owns the file; HM doesn't |
| 26 | + touch it. |
| 27 | +2. **Pearlite leaves `~/.config/nix/nix.conf` alone.** HM already |
| 28 | + exposes a `nix.settings` block that compiles down to a per-user |
| 29 | + nix.conf; the operator declares `experimental-features` there. |
| 30 | + Pearlite establishes the system-wide minimum (`/etc/nix/nix.conf`) |
| 31 | + during bootstrap and stays out of the per-user file's way. |
| 32 | +3. **Hybrid.** Pearlite writes a baseline `experimental-features = |
| 33 | + nix-command flakes` to per-user nix.conf if the file is missing |
| 34 | + or doesn't contain that line; otherwise leaves it alone. |
| 35 | + |
| 36 | +The hybrid option's "if missing" branch is the kind of magic that |
| 37 | +breaks operator mental model: HM's `nix.settings` block silently |
| 38 | +loses ground to a Pearlite-owned line that the operator didn't |
| 39 | +declare. Ownership questions emerge: if HM and Pearlite both want to |
| 40 | +write the same key, which wins? Three-way merges of generated config |
| 41 | +are exactly the muddle ADR-001 (Nickel for human config, TOML for |
| 42 | +machine state) was written to avoid. |
| 43 | + |
| 44 | +## Decision |
| 45 | + |
| 46 | +**Adopt option 2: Pearlite does not manage per-user nix.conf.** |
| 47 | + |
| 48 | +Concretely: |
| 49 | + |
| 50 | +1. **`pearlite bootstrap` (per ADR-0012) writes |
| 51 | + `/etc/nix/nix.conf`** with `experimental-features = nix-command |
| 52 | + flakes` and `auto-optimise-store = true` (the Determinate |
| 53 | + defaults). This is the system-wide minimum that lets HM's first |
| 54 | + `switch` succeed. The write is idempotent: if the keys are |
| 55 | + already present and match, the file isn't touched. |
| 56 | +2. **`~/.config/nix/nix.conf` is HM territory.** Operators who want |
| 57 | + per-user overrides declare them in the HM `home.nix` via the |
| 58 | + `nix.settings` Nickel block. HM compiles that into the per-user |
| 59 | + file; the file's contents are owned by HM's generation, not by |
| 60 | + Pearlite. |
| 61 | +3. **No `[[managed.nix_conf]]` block in `state.toml`.** Per-user |
| 62 | + nix.conf isn't a Pearlite-managed file. The system-wide |
| 63 | + `/etc/nix/nix.conf` is touched only by `bootstrap`, which |
| 64 | + doesn't record state (per ADR-0012 decision 4). If Pearlite later |
| 65 | + needs to verify the system-wide file has its expected contents, |
| 66 | + that's a preflight concern, not a `state.toml` row. |
| 67 | +4. **The host config's documentation** — `docs/book/host-config.md` |
| 68 | + when it lands in M6 — points operators at HM's `nix.settings` |
| 69 | + for any per-user nix configuration questions. The cross-reference |
| 70 | + is "if you want to set a nix.conf option for user X, declare it |
| 71 | + in their HM config; Pearlite does not provide a separate path." |
| 72 | + |
| 73 | +## Consequences |
| 74 | + |
| 75 | +- **No new managed file.** `state.toml`'s `[[managed.*]]` blocks |
| 76 | + stay at the M3 set: pacman, cargo, services, config_files, |
| 77 | + user_env. The config-file inventory in PRD §7.3 is unchanged. |
| 78 | +- **One write site for `/etc/nix/nix.conf`.** Inside |
| 79 | + `pearlite bootstrap`. Idempotent (read-then-write-if-different), |
| 80 | + no `[[managed.config_files]]` entry, no diff-time involvement. |
| 81 | +- **HM's `nix.settings` is the documented per-user surface.** |
| 82 | + Pearlite's docs, error hints, and bootstrap output all cross-link |
| 83 | + to the HM option. Operators who try to declare a Pearlite-side |
| 84 | + per-user nix block get an explicit error pointing at HM. |
| 85 | +- **No competing source of truth.** The "two systems writing the |
| 86 | + same file" problem (HM and Pearlite both managing |
| 87 | + `~/.config/nix/nix.conf`) is structurally eliminated. The |
| 88 | + `experimental-features` line that bootstrap establishes |
| 89 | + system-wide is sufficient for HM's first switch; per-user |
| 90 | + refinements happen exclusively through HM. |
| 91 | +- **No fleet-wide knob for per-user nix settings.** v1.0 ships |
| 92 | + without this. v1.1's fleet mode (PRD §17.1) may revisit if real |
| 93 | + operations show a need; that revision supersedes this ADR. |
| 94 | +- **Cost of holding.** Operators who prefer to manage everything |
| 95 | + through Pearlite's host file (rather than HM's `home.nix`) lose |
| 96 | + one knob. That preference is a v1.1+ topic; v1.0 commits to the |
| 97 | + Nickel-host-file + HM-home.nix split as the canonical division. |
| 98 | + |
| 99 | +## References |
| 100 | + |
| 101 | +- ADR-001 — Nickel for human config, TOML for machine state |
| 102 | + (Plan §13.1). |
| 103 | +- ADR-0012 — Nix bootstrap wiring (this ADR's bootstrap-side |
| 104 | + partner). |
| 105 | +- M3 retrospective `docs/retrospectives/M3.md` §"What didn't" |
| 106 | + and §"Actions for M4" item 3. |
| 107 | +- Plan §7.4 W1 — original "per-user nix.conf" task. |
| 108 | +- Home Manager docs — `nix.settings` option (linked from M6 book |
| 109 | + chapter when written). |
0 commit comments