diff options
| author | 2026-09-18 15:12:24 +0200 | |
|---|---|---|
| committer | 2026-09-18 15:12:24 +0200 | |
| commit | 4937d0d6f29895b514dcf8cd20c73a1aec446144 (patch) | |
| tree | 7ff388ba4cca94e95489d30452b25badd7e49aa9 /openspec/changes/add-host-bootstrap-script | |
| parent | 72b640f5ae65755c1d932b7b8ee88d2367e4036f (diff) | |
| download | desktop-flake-4937d0d6f29895b514dcf8cd20c73a1aec446144.tar.gz desktop-flake-4937d0d6f29895b514dcf8cd20c73a1aec446144.tar.bz2 desktop-flake-4937d0d6f29895b514dcf8cd20c73a1aec446144.zip | |
Archive four completed changes, sync main specs
add-nix-flake-bootstrap, add-host-bootstrap-script,
add-dotfiles-stow-bootstrap, and add-developer-environment all had
every task complete (verified via a real nix flake check and a real
NixOS host build). Archived in dependency order so the two changes
sharing the nix-flake-bootstrap capability merged additively. Main
specs now exist for the first time: nix-flake-bootstrap (10
requirements), dotfiles-management (4), developer-environment (4).
add-gnome-desktop remains active (not yet implemented).
Diffstat (limited to 'openspec/changes/add-host-bootstrap-script')
5 files changed, 0 insertions, 144 deletions
diff --git a/openspec/changes/add-host-bootstrap-script/.openspec.yaml b/openspec/changes/add-host-bootstrap-script/.openspec.yaml deleted file mode 100644 index f080778..0000000 --- a/openspec/changes/add-host-bootstrap-script/.openspec.yaml +++ /dev/null | |||
| @@ -1,2 +0,0 @@ | |||
| 1 | schema: spec-driven | ||
| 2 | created: 2026-09-16 | ||
diff --git a/openspec/changes/add-host-bootstrap-script/design.md b/openspec/changes/add-host-bootstrap-script/design.md deleted file mode 100644 index 52c5dcd..0000000 --- a/openspec/changes/add-host-bootstrap-script/design.md +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | ## Context | ||
| 2 | |||
| 3 | See proposal.md - Why. The `add-nix-flake-bootstrap` change already put `hosts/template/`, `lib/mkNixos.nix`, and a hardcoded empty `nixosConfigurations = { };` in place; this change replaces the "hand-copy the template, hand-edit `flake.nix`" workflow with a script and a directory-driven flake output. Nix is still not installed on this development machine, so nothing here can be locally evaluated or run — same constraint as the prior change. | ||
| 4 | |||
| 5 | ## Goals / Non-Goals | ||
| 6 | |||
| 7 | **Goals:** | ||
| 8 | - Make `flake.nix` require zero edits when a new NixOS host is added — dropping a correctly-shaped directory under `hosts/` is sufficient. | ||
| 9 | - Make it structurally impossible for the script to default to reading the invoking environment's own `/etc/nixos` — the root must always be named explicitly by the caller. | ||
| 10 | - Keep the script a plain, auditable shell script wrapped as a flake app, not a new categorically different tool. | ||
| 11 | |||
| 12 | **Non-Goals:** | ||
| 13 | - Driving `nixos-install`/`nixos-rebuild` itself (confirmed: scaffold only). | ||
| 14 | - Any scaffolding for non-NixOS/standalone hosts (confirmed: `homeConfigurations."tom@linux"` stays generic). | ||
| 15 | - Mountpoint auto-detection or any other heuristic for guessing the root path (confirmed: always an explicit flag). | ||
| 16 | |||
| 17 | ## Decisions | ||
| 18 | |||
| 19 | **`nixosConfigurations` becomes directory-driven (`builtins.readDir ./hosts` + per-host `system` file) instead of a hand-maintained attrset.** The alternative — having the script parse and rewrite `flake.nix` text to insert a new `nixosConfigurations.<name> = lib.mkNixos {...};` line — was rejected as fragile: it requires the script to locate the right insertion point, preserve formatting the `nixfmt` formatter would otherwise enforce, and handle the case of the attrset already containing entries. Reading the `hosts/` directory structure is declarative and needs no generated/edited Nix source at all, which is both simpler and more idiomatic. The cost is one small new per-host file (`system`, a bare string, no trailing newline) that didn't exist before; `hosts/template/system` gets one too so the template stays a complete, consistent copy-source, though it's still excluded from discovery by name exactly as the template's other files already are. | ||
| 20 | |||
| 21 | **Root path is a required flag, not detected.** Considered auto-detecting via `mountpoint -q /mnt` (the most common real-world case). Rejected per explicit user decision: an explicit `--root` on every invocation is a stronger, simpler guarantee against the exact failure mode being guarded against (reading the live ISO's own `/etc/nixos`) than any heuristic, at the cost of one extra flag to type. | ||
| 22 | |||
| 23 | **Hardware config is read from `<root>/etc/nixos/hardware-configuration.nix`, generating it via `nixos-generate-config --root <root>` if absent and the command is available.** This mirrors the existing manual workflow's own use of `nixos-generate-config --root /mnt` (see the prior change's README), just invoked by the script instead of by hand. `nixos-generate-config` is not added as a nixpkgs runtime dependency of the wrapped app — it's inherently an ambient NixOS system tool tied to whichever NixOS the script is currently running under (live ISO or an installed system), not a standalone package to vendor; the script fails clearly if it's both missing and no pre-existing hardware config is found at the root. | ||
| 24 | |||
| 25 | **Script stays a plain file (`scripts/bootstrap-host.sh`) wrapped by `pkgs.writeShellApplication`, not inlined into `flake.nix`.** Keeps the logic readable and diffable as ordinary shell, while `writeShellApplication` gives shellcheck validation and a clean `PATH` when exposed as `apps.<system>.bootstrap-host` — avoids both "raw unvalidated bash" and "logic buried inside deeply-nested Nix string interpolation." | ||
| 26 | |||
| 27 | **`--state-version` has no default, matching the existing project-wide rule** (from the prior change's design) that `stateVersion` must never be silently defaulted by tooling, since it must reflect the actual release in use at install time for that specific host. | ||
| 28 | |||
| 29 | ## Risks / Trade-offs | ||
| 30 | |||
| 31 | - **Adding a `hosts/<name>/system` file is a new convention with no precedent elsewhere in the repo** → Mitigation: it's the smallest possible mechanism (one line, no Nix syntax) and is documented in the updated README alongside the rest of the host-creation flow. | ||
| 32 | - **No local way to test that `builtins.readDir`-based discovery actually evaluates correctly, or that the script's shell logic is fully correct, since Nix isn't installed here** → Mitigation: same as the prior change — careful manual construction plus shellcheck-level self-review now, full `nix flake check` / actually running the script deferred until Nix is available (real install or CI). This is the real acceptance gate, not any individual task checkbox. | ||
| 33 | - **This change's spec delta targets the same `nix-flake-bootstrap` capability path as the still-unarchived `add-nix-flake-bootstrap` change** → Mitigation: uses `ADDED Requirements` only (no `MODIFIED`), so the two changes' deltas are purely additive and don't conflict regardless of archive order. | ||
diff --git a/openspec/changes/add-host-bootstrap-script/proposal.md b/openspec/changes/add-host-bootstrap-script/proposal.md deleted file mode 100644 index f7fcff4..0000000 --- a/openspec/changes/add-host-bootstrap-script/proposal.md +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | ## Why | ||
| 2 | |||
| 3 | Adding a real host today is fully manual: copy `hosts/template`, hand-edit `CHANGEME` placeholders, copy `hardware-configuration.nix` from wherever the caller happens to point, and hand-append a `nixosConfigurations.<host>` stanza into `flake.nix`. This is error-prone (nothing stops copying `hardware-configuration.nix` from the wrong place, e.g. a NixOS live ISO's own `/etc/nixos` instead of a freshly mounted install target) and requires editing generated Nix source by hand for every new machine. | ||
| 4 | |||
| 5 | ## What Changes | ||
| 6 | |||
| 7 | - Switch `flake.nix`'s `nixosConfigurations` from a hardcoded empty attrset to auto-discovery: any directory under `hosts/` (other than `hosts/template`) that contains a `system` file becomes a real `nixosConfigurations.<name>` output automatically, with `system` read from that file. **BREAKING**: adding a host no longer requires editing `flake.nix`; the previously-documented manual stanza-append step is removed from the workflow. | ||
| 8 | - Add `hosts/template/system` (a placeholder system-string file) so the template stays a complete copy-source consistent with real hosts, while remaining excluded from discovery by name. | ||
| 9 | - Add `scripts/bootstrap-host.sh`, exposed as `apps.<system>.bootstrap-host` in `flake.nix` (via `pkgs.writeShellApplication`), that scaffolds a new host: copies `hosts/template`, fills in hostname/state-version, writes the `system` file, and copies `hardware-configuration.nix` from an explicitly-specified root directory. | ||
| 10 | - The script always requires an explicit `--root` flag for where to source `hardware-configuration.nix` from (`<root>/etc/nixos/hardware-configuration.nix`) — there is no default and no environment auto-detection, so a run from a NixOS live/installer environment can never accidentally read the live ISO's own `/etc/nixos` instead of the mounted install target. | ||
| 11 | - Update `README.md`'s NixOS install workflow to use the script instead of the manual copy/edit/flake.nix-edit sequence. | ||
| 12 | - Non-NixOS (standalone home-manager) hosts are explicitly out of scope for this script: `homeConfigurations."tom@linux"` stays generic, and the existing clone + `home-manager switch` workflow already covers "getting the right files onto a non-NixOS machine" — nothing new is scaffolded there. | ||
| 13 | |||
| 14 | ## Capabilities | ||
| 15 | |||
| 16 | ### New Capabilities | ||
| 17 | - `nix-flake-bootstrap`: extends the existing flake-bootstrap capability (from the still-in-flight `add-nix-flake-bootstrap` change, not yet archived into `openspec/specs/`) with scripted host creation and directory-driven `nixosConfigurations` discovery. Reusing the same capability path rather than introducing a second one, since this is additional behavior of the same "provision hosts from this flake" capability, not a separate concern. | ||
| 18 | |||
| 19 | ### Modified Capabilities | ||
| 20 | (none — `nix-flake-bootstrap` does not yet exist under `openspec/specs/`, since the change that introduces it hasn't been archived. This change's delta uses ADDED requirements only, extending rather than altering what that change already proposed.) | ||
| 21 | |||
| 22 | ## Impact | ||
| 23 | |||
| 24 | - Affected code: `flake.nix` (nixosConfigurations discovery + new `apps` output), `scripts/bootstrap-host.sh` (new), `hosts/template/system` (new), `README.md` (install workflow section). | ||
| 25 | - Depends on the `add-nix-flake-bootstrap` change's files already present in the repo (`hosts/template/`, `lib/mkNixos.nix`) — this change builds directly on that structure. | ||
| 26 | - No live NixOS host or remote exists yet, so the script's actual `nixos-generate-config`/mount-based behavior can only be verified by careful manual review and (eventually) a real install or CI, not locally — Nix is still not installed on this development machine. | ||
| 27 | - No system currently depends on the old "hand-edit `flake.nix`" workflow (no real host has been created yet), so the breaking change has no live migration cost. | ||
diff --git a/openspec/changes/add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md b/openspec/changes/add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md deleted file mode 100644 index 3203b99..0000000 --- a/openspec/changes/add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md +++ /dev/null | |||
| @@ -1,49 +0,0 @@ | |||
| 1 | ## Purpose | ||
| 2 | |||
| 3 | Provides a single Nix flake that provisions NixOS hosts from a copyable template and provisions a shared, cross-platform home-manager user profile that installs standalone on non-NixOS systems, keeping both paths built from the same per-user module. | ||
| 4 | |||
| 5 | ## ADDED Requirements | ||
| 6 | |||
| 7 | ### Requirement: Host directories are auto-discovered as flake outputs | ||
| 8 | Any directory under `hosts/` other than `hosts/template` that contains a valid host definition SHALL automatically become a `nixosConfigurations.<name>` output, without requiring any edit to `flake.nix`. | ||
| 9 | |||
| 10 | #### Scenario: New host directory becomes buildable without touching flake.nix | ||
| 11 | - **WHEN** a new directory `hosts/<name>/` is created containing `configuration.nix`, `hardware-configuration.nix`, and a `system` file, and `flake.nix` itself is left unmodified | ||
| 12 | - **THEN** `nixosConfigurations.<name>` evaluates successfully as part of the flake's outputs | ||
| 13 | |||
| 14 | #### Scenario: Template remains excluded from discovery | ||
| 15 | - **WHEN** the flake's outputs are evaluated | ||
| 16 | - **THEN** `hosts/template` does not appear as a `nixosConfigurations` entry, regardless of its contents | ||
| 17 | |||
| 18 | ### Requirement: Host creation is scripted | ||
| 19 | The system SHALL provide a script that scaffolds a new NixOS host directory from the template, given a hostname, a target root path, and a state version. | ||
| 20 | |||
| 21 | #### Scenario: Scaffolding a new host | ||
| 22 | - **WHEN** the host creation script is run with a hostname, a `--root` path, and a `--state-version` | ||
| 23 | - **THEN** a `hosts/<hostname>/` directory is created containing a `configuration.nix` with the hostname and state version filled in, a `system` file, and a `hardware-configuration.nix` copied from the given root | ||
| 24 | |||
| 25 | #### Scenario: Refusing to overwrite an existing host | ||
| 26 | - **WHEN** the host creation script is run with a hostname that already has a `hosts/<hostname>/` directory, without an explicit override flag | ||
| 27 | - **THEN** the script fails without modifying the existing directory | ||
| 28 | |||
| 29 | ### Requirement: Hardware configuration source is always explicit | ||
| 30 | The host creation script SHALL require an explicit root path for locating `hardware-configuration.nix` and SHALL NOT read from the invoking system's own `/etc/nixos` unless that path is explicitly given as the root. | ||
| 31 | |||
| 32 | #### Scenario: No root provided | ||
| 33 | - **WHEN** the host creation script is run without a root path specified | ||
| 34 | - **THEN** the script fails with an error rather than falling back to any default location | ||
| 35 | |||
| 36 | #### Scenario: Live installer environment targets the mounted install, not the live system | ||
| 37 | - **WHEN** the host creation script is run from a NixOS live/installer environment with `--root /mnt` pointing at a freshly mounted install target | ||
| 38 | - **THEN** the resulting `hardware-configuration.nix` is copied from `/mnt/etc/nixos/hardware-configuration.nix`, and the live environment's own `/etc/nixos/hardware-configuration.nix` is never read | ||
| 39 | |||
| 40 | #### Scenario: Hardware configuration cannot be found or generated | ||
| 41 | - **WHEN** the host creation script is run with a `--root` path that has no `hardware-configuration.nix` and no `nixos-generate-config` command is available to create one | ||
| 42 | - **THEN** the script fails with a clear error instead of proceeding with a missing or stub file | ||
| 43 | |||
| 44 | ### Requirement: Install target matches the created host name | ||
| 45 | The host attribute produced for a newly scaffolded host SHALL exactly match the hostname given to the creation script, so the host can be installed or rebuilt via `<flake>#<hostname>`. | ||
| 46 | |||
| 47 | #### Scenario: Installing a newly scaffolded host | ||
| 48 | - **WHEN** a host is scaffolded with a given hostname | ||
| 49 | - **THEN** `nixos-install --flake <flake-path>#<hostname>` and `nixos-rebuild switch --flake <flake-path>#<hostname>` both resolve to the configuration that was just scaffolded | ||
diff --git a/openspec/changes/add-host-bootstrap-script/tasks.md b/openspec/changes/add-host-bootstrap-script/tasks.md deleted file mode 100644 index d290c2b..0000000 --- a/openspec/changes/add-host-bootstrap-script/tasks.md +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | ## 1. Directory-driven host discovery | ||
| 2 | |||
| 3 | - [x] 1.1 Add `hosts/template/system` containing a placeholder system string (e.g. `x86_64-linux`, no trailing newline) so the template stays a complete copy-source — verify the file exists and contains no trailing newline (`wc -c` matches the string length) (verified: 12 bytes, matches `x86_64-linux` exactly) | ||
| 4 | - [x] 1.2 In `flake.nix`, replace the hardcoded `nixosConfigurations = { };` with discovery logic that reads `./hosts`, filters out non-directories and `template`, and for each remaining directory builds `lib.mkNixos { hostname = <name>; system = builtins.readFile (./hosts + "/${name}/system"); }` — verify by grep confirming `nixosConfigurations` no longer contains a literal `{ };` and instead derives from `builtins.readDir` (verified via grep) | ||
| 5 | - [x] 1.3 Confirm `hosts/template` is still excluded from discovery by name — verify by re-reading the filter logic and confirming `template` remains an explicit exclusion even though it now has a `system` file too (confirmed: `name != "template"` filter present) | ||
| 6 | |||
| 7 | ## 2. Bootstrap script | ||
| 8 | |||
| 9 | - [x] 2.1 Create `scripts/bootstrap-host.sh` accepting a positional `<hostname>` and flags `--root <path>` (required), `--state-version <version>` (required), `--system <system>` (optional, default `x86_64-linux`), `--force` (optional) — verify with `bash -n scripts/bootstrap-host.sh` (syntax check) and a manual read-through against the interface documented in design.md (`bash -n` passed; shellcheck not available locally to double-check, see note at end of file) | ||
| 10 | - [x] 2.2 Implement argument validation: fail with a clear error if `--root` or `--state-version` is missing, or if `hosts/<hostname>` already exists without `--force` — verify by tracing each failure path in the script against the corresponding spec scenario (all three checks present: missing `--root`, missing `--state-version`, existing `hosts/<hostname>` without `--force`) | ||
| 11 | - [x] 2.3 Implement the scaffold step: `cp -r hosts/template hosts/<hostname>`, then replace the `CHANGEME` placeholders for `networking.hostName` and `system.stateVersion` in the copied `configuration.nix` — verify by reading the sed/replacement logic against the exact placeholder text in `hosts/template/configuration.nix` (sed patterns match the literal `"CHANGEME"` text in both fields) | ||
| 12 | - [x] 2.4 Write `hosts/<hostname>/system` from the `--system` value (or default), with no trailing newline, using `printf '%s'` — verify by reading the write logic (`printf '%s' "$system" >"$host_dir/system"`) | ||
| 13 | - [x] 2.5 Implement hardware-configuration resolution: check for `<root>/etc/nixos/hardware-configuration.nix`; if absent, run `nixos-generate-config --root <root>` only if that command is on `PATH`; if still absent, fail with a clear error — verify the logic never references `/etc/nixos` directly without the `<root>` prefix (grep the script for any bare `/etc/nixos` reference and confirm none exist) (grep confirms every `/etc/nixos` occurrence is prefixed with `$root/`) | ||
| 14 | - [x] 2.6 Copy the resolved hardware config over `hosts/<hostname>/hardware-configuration.nix`, replacing the template stub — verify by reading the copy logic (`cp "$hw_config_src" "$host_dir/hardware-configuration.nix"`) | ||
| 15 | - [x] 2.7 Print next-step instructions on success: review the boot loader stanza, `git add hosts/<hostname>`, and the exact `nixos-install --root <root> --flake .#<hostname>` / `sudo nixos-rebuild switch --flake .#<hostname>` commands — verify the printed text uses `<flake>#<hostname>` syntax matching the spec requirement (final heredoc prints both commands using `"$repo_root#$hostname"`) | ||
| 16 | |||
| 17 | ## 3. Flake app wiring | ||
| 18 | |||
| 19 | - [x] 3.1 In `flake.nix`, add `apps.<system>.bootstrap-host` (using the existing `forAllSystems`) built with `pkgs.writeShellApplication` wrapping `scripts/bootstrap-host.sh` — verify by grep confirming the `apps` output references `scripts/bootstrap-host.sh` and is defined for the same systems as `formatter`/`devShells` (grep confirmed; also added `runtimeInputs = [ pkgs.coreutils pkgs.gnused ]` since `writeShellApplication` scopes `PATH`, needed beyond what the task literally specified — noting this as a necessary correctness detail, not scope creep: without it `cp`/`sed`/`rm` inside the wrapped app would not be found) | ||
| 20 | |||
| 21 | ## 4. Documentation | ||
| 22 | |||
| 23 | - [x] 4.1 Rewrite the "Installing NixOS on a new machine" section of `README.md` to use `nix run .#bootstrap-host -- <hostname> --root /mnt --state-version <ver>` in place of the old manual copy/edit/flake.nix-edit steps, keeping the surrounding partition/mount/`nixos-generate-config`/`nixos-install` steps that remain manual — verify by reading the updated section end-to-end for consistency with the script's actual behavior (read through end-to-end; the manual `nixos-generate-config` step was folded into the script's own resolution logic per design.md rather than kept as a separate manual step, since the script now does that itself) | ||
| 24 | - [x] 4.2 Note in the README that non-NixOS/standalone hosts are unaffected by this script and continue to use the existing clone + `home-manager switch` workflow — verify the note is present and doesn't contradict the "Installing on a non-NixOS system" section (note added at the top of that section; contents otherwise unchanged) | ||
| 25 | |||
| 26 | ## 5. Validation | ||
| 27 | |||
| 28 | - [x] 5.1 Run `nix flake check` and confirm the auto-discovery logic and new `apps` output evaluate without error (blocked — Nix is not installed on this machine; run this once Nix is available, e.g. via a real install or CI — this is the real acceptance gate, not the individual checkboxes above) (run by the user on a Linux machine with Nix, 2026-09-18: passed with no errors, only a benign `apps.aarch64-linux.bootstrap-host lacks attribute 'meta'` warning, since fixed by adding `meta.description` to the `writeShellApplication` in `flake.nix`) | ||
| 29 | - [x] 5.2 Manually trace through each spec scenario in `specs/nix-flake-bootstrap/spec.md` against the implemented script and flake logic, confirming each is satisfied by inspection given #5.1 cannot run locally (all 8 scenarios traced and satisfied: discovery + template exclusion in `flake.nix`; scaffold, overwrite-refusal, no-root, missing-hardware-config failure modes all exit before any mutation in `scripts/bootstrap-host.sh`; live-installer scenario and install-target-matching confirmed via grep and directory-name-to-attribute-name correspondence) | ||
| 30 | |||
| 31 | --- | ||
| 32 | |||
| 33 | **Verification note:** as with the prior change, Nix is not installed on this machine, so task 5.1 (`nix flake check`) could not be executed and remains the outstanding acceptance gate. `shellcheck` was also unavailable locally to double-check `scripts/bootstrap-host.sh` beyond `bash -n` and manual review. One implementation detail beyond the literal task wording: `pkgs.writeShellApplication` scopes `PATH` to `runtimeInputs`, so `runtimeInputs = [ pkgs.coreutils pkgs.gnused ]` was added to the `bootstrap-host` app in `flake.nix` (task 3.1) — without it `cp`/`sed`/`rm` inside the wrapped script would not resolve at runtime. `nixos-generate-config` is deliberately left out of `runtimeInputs` per design.md's decision (relies on it being ambient on the invoking NixOS system); `writeShellApplication` prepends `runtimeInputs` to the inherited `$PATH` rather than replacing it, so this should still resolve when present — unverified locally, same blocker as 5.1. | ||
