aboutsummaryrefslogtreecommitdiffstats
path: root/openspec/changes/archive
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2026-09-18 15:12:24 +0200
committerGravatar Tom van der Lee <tom@vanderlee.io>2026-09-18 15:12:24 +0200
commit4937d0d6f29895b514dcf8cd20c73a1aec446144 (patch)
tree7ff388ba4cca94e95489d30452b25badd7e49aa9 /openspec/changes/archive
parent72b640f5ae65755c1d932b7b8ee88d2367e4036f (diff)
downloaddesktop-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/archive')
-rw-r--r--openspec/changes/archive/2026-09-18-add-developer-environment/.openspec.yaml2
-rw-r--r--openspec/changes/archive/2026-09-18-add-developer-environment/design.md37
-rw-r--r--openspec/changes/archive/2026-09-18-add-developer-environment/proposal.md27
-rw-r--r--openspec/changes/archive/2026-09-18-add-developer-environment/specs/developer-environment/spec.md33
-rw-r--r--openspec/changes/archive/2026-09-18-add-developer-environment/tasks.md29
-rw-r--r--openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/.openspec.yaml2
-rw-r--r--openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/design.md35
-rw-r--r--openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/proposal.md28
-rw-r--r--openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/specs/dotfiles-management/spec.md45
-rw-r--r--openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/tasks.md39
-rw-r--r--openspec/changes/archive/2026-09-18-add-host-bootstrap-script/.openspec.yaml2
-rw-r--r--openspec/changes/archive/2026-09-18-add-host-bootstrap-script/design.md33
-rw-r--r--openspec/changes/archive/2026-09-18-add-host-bootstrap-script/proposal.md27
-rw-r--r--openspec/changes/archive/2026-09-18-add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md49
-rw-r--r--openspec/changes/archive/2026-09-18-add-host-bootstrap-script/tasks.md33
-rw-r--r--openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/.openspec.yaml2
-rw-r--r--openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/design.md39
-rw-r--r--openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/proposal.md31
-rw-r--r--openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/specs/nix-flake-bootstrap/spec.md47
-rw-r--r--openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/tasks.md59
20 files changed, 599 insertions, 0 deletions
diff --git a/openspec/changes/archive/2026-09-18-add-developer-environment/.openspec.yaml b/openspec/changes/archive/2026-09-18-add-developer-environment/.openspec.yaml
new file mode 100644
index 0000000..d28e909
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-developer-environment/.openspec.yaml
@@ -0,0 +1,2 @@
1schema: spec-driven
2created: 2026-09-17
diff --git a/openspec/changes/archive/2026-09-18-add-developer-environment/design.md b/openspec/changes/archive/2026-09-18-add-developer-environment/design.md
new file mode 100644
index 0000000..33fdfb9
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-developer-environment/design.md
@@ -0,0 +1,37 @@
1## Context
2
3See proposal.md - Why. This builds on the shared `modules/home` and `modules/nixos/common.nix` layers from `add-nix-flake-bootstrap`, and follows the file-ownership precedent set by `add-dotfiles-stow-bootstrap` (home-manager's `programs.git` was removed there because `~/dotfiles/git/.gitconfig` already owns `~/.gitconfig`; the same conflict now applies to `~/.bashrc` and the prompt config). Package selection is grounded in a direct exploration of this machine (Homebrew, PATH, shell configs) and `~/dotfiles`, followed by a Q&A with the user. Nix is still not installed on this machine, so nothing here can be locally evaluated or run.
4
5## Goals / Non-Goals
6
7**Goals:**
8- Bring the flake's provisioned packages in line with actually-evidenced usage on the user's machine, not a generic/assumed list.
9- Keep file-ownership conflicts resolved the same way as the established precedent: dotfiles wins for any file it already manages.
10- Enable a sensible, secure-by-default SSH baseline for any future NixOS host, without requiring a live host to exist yet.
11
12**Non-Goals:**
13- Managing per-project language runtime versions (node/python/rust) via Nix — pyenv/nvm/rustup already own that; per-project pinned toolchains belong in project-level flake devShells later, a separate future change.
14- Replacing Mason's role in managing neovim's LSP servers/formatters/debuggers.
15- Making Docker Desktop/OrbStack or any cloud account itself Nix-managed — only client/CLI tools are in scope.
16- Fixing the plaintext `HOMEBREW_GITHUB_API_TOKEN` found in `~/.bash_profile` during exploration — flagged to the user, not addressed here since it's outside any file this repo manages.
17
18## Decisions
19
20**`programs.bash` is not enabled, even though bash is now the primary shell.** Enabling it would make home-manager generate and own `~/.bashrc`, directly conflicting with `~/dotfiles/bash/.bashrc` (a real Stow package containing the user's actual PATH/alias/pyenv/nvm/rustup setup). Consistent with the `~/.gitconfig` precedent, the dotfiles-owned file wins. Consequence: `programs.starship`/`programs.direnv`'s automatic shell-hook injection has nowhere to write into for bash even if those modules were kept enabled — moot here since both are removed outright per user instruction, but worth recording as the underlying reason those integrations wouldn't have worked cleanly anyway.
21
22**`programs.starship` and `programs.direnv` are removed outright, not left as-is.** This was an explicit correction from the user during plan review (the first draft kept them). `liquidprompt` replaces starship as the package Nix provisions, with its actual configuration and shell activation fully owned by `~/dotfiles/liquidprompt/` — matching the same "package via Nix, config via dotfiles" split already established for git. direnv was reconsidered once (the user asked why it mattered — per-project flake devShell auto-activation) and still explicitly removed after that explanation, so this is a confirmed, informed decision, not an overlooked default.
23
24**`yq-go` over `yq`.** nixpkgs carries both the older Python-based `yq` (a thin `jq` wrapper for YAML) and `yq-go` (mikefarah's Go-based standalone tool) under different attribute names. The Go version is what's conventionally meant by "yq" in modern Kubernetes/Terraform-adjacent workflows, which matches this user's evidenced tooling (kubectl, terraform). Picking the wrong one would be a functionally different CLI with different syntax, not just a version difference.
25
26**`terraform`'s nixpkgs availability is flagged as a risk, not silently assumed.** HashiCorp's BSL license change means recent nixpkgs may require `nixpkgs.config.allowUnfree = true` for `terraform`, or may have removed it in favor of `opentofu`. This can't be verified without Nix installed locally; the task list treats it as an implementation-time check rather than asserting it will just work.
27
28**`services.openssh.settings.PasswordAuthentication = false` and `PermitRootLogin = "no"`.** The user already has SSH keys configured (4 hosts in `~/.ssh/config`), so key-only auth costs nothing and is the standard secure default. `networking.firewall.allowedTCPPorts = [ 22 ]` is the only firewall change that actually matters (NixOS's firewall is already enabled by default) — kept alongside an explicit `enable = true` purely as visible documentation of intent.
29
30**A new `modules/home/dev-tools.nix` file, separate from the existing `cli-tools.nix`.** Keeps this change's additions traceable to this specific capability, matching the repo's established one-concern-per-file module convention rather than growing the original baseline file indefinitely.
31
32## Risks / Trade-offs
33
34- **No Nix-managed per-project environment activation after removing direnv** → Mitigation: this was a deliberate, explained trade-off the user confirmed twice; pyenv/nvm/rustup remain fully functional as the existing mechanism.
35- **Prompt/shell integration (liquidprompt) depends entirely on the external `~/dotfiles` repo being present and correctly stowed** → Mitigation: this is exactly what the `add-dotfiles-stow-bootstrap` change already automates; no new risk introduced here, just a new consumer of that existing mechanism.
36- **`terraform` may fail to evaluate without an unfree-package allowance** → Mitigation: flagged explicitly as a task-level check; if it fails, the fallback is either adding `nixpkgs.config.allowUnfree = true` or substituting `opentofu`, decided when Nix is actually available to test.
37- **No local way to verify any of this evaluates or builds, since Nix isn't installed here** → Mitigation: same as every prior change — careful manual construction and package-name review now; `nix flake check` deferred until Nix is available. This remains the real acceptance gate across all changes in this repo, not any individual task checkbox.
diff --git a/openspec/changes/archive/2026-09-18-add-developer-environment/proposal.md b/openspec/changes/archive/2026-09-18-add-developer-environment/proposal.md
new file mode 100644
index 0000000..24b34a5
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-developer-environment/proposal.md
@@ -0,0 +1,27 @@
1## Why
2
3The flake currently ships a minimal, generic package set. The user is a software developer whose actual machine already relies on a specific set of CLI tools (git-adjacent, container/cloud tooling) and a specific shell/prompt setup that the flake doesn't reflect yet, and has no common system services (like sshd) enabled for future NixOS hosts. This change brings the flake's declared packages/services in line with real, evidenced usage.
4
5## What Changes
6
7- Add `modules/home/dev-tools.nix`: `gh`, `tmux`, `htop`, `yq-go`, `tree-sitter`, `docker`, `docker-compose`, `kubectl`, `awscli2`, `terraform` — user-level CLI/client tools, added to `modules/home/default.nix`'s imports. Container/cloud entries are client-only; the actual runtime (OrbStack/Docker Desktop) and cloud infrastructure stay outside Nix's control.
8- **BREAKING**: `modules/home/shell.nix` drops `programs.zsh` (bash is now the primary managed shell), drops `programs.starship` (replaced by `liquidprompt`, already Stow-managed via the user's dotfiles), and drops `programs.direnv`/`nix-direnv` entirely — a real removal of a feature present since the first change in this repo, per explicit user instruction rather than an addition being skipped.
9- `modules/home/shell.nix` adds `pkgs.liquidprompt` as a package (its config/activation stays fully owned by the user's dotfiles repo, matching the precedent already set for git and bash).
10- `modules/nixos/common.nix`: `users.users.tom.shell` changes from `pkgs.zsh` to `pkgs.bash`; the `programs.zsh.enable = true` line is removed; `services.openssh` is enabled with `PasswordAuthentication = false` and `PermitRootLogin = "no"`; `networking.firewall` is set with port 22 explicitly allowed.
11- Update `README.md` to document the new packages, the CLI-only caveat for docker/kubectl/awscli/terraform, the bash-primary shell switch, and that liquidprompt's config is dotfiles-owned.
12
13## Capabilities
14
15### New Capabilities
16- `developer-environment`: the flake provisions a developer's most-used CLI/client tooling via home-manager and enables common baseline system services (starting with sshd + a firewall allowance) via the shared NixOS module.
17
18### Modified Capabilities
19(none — this doesn't change any requirement from `nix-flake-bootstrap` or `dotfiles-management`; the shell/prompt/direnv changes are implementation details of the new capability's package/service set, not a change to what those other capabilities promise)
20
21## Impact
22
23- Affected code: `modules/home/dev-tools.nix` (new), `modules/home/shell.nix`, `modules/home/default.nix`, `modules/nixos/common.nix`, `README.md`.
24- No live NixOS host exists yet, so the `services.openssh`/`networking.firewall` additions only take effect once a real host is scaffolded — same situation as every prior NixOS-side change in this repo.
25- Nix is still not installed on this development machine, so package-name correctness (`yq-go`, `awscli2`, `tree-sitter`, `terraform`'s licensing/availability) can only be verified by manual review here, not by actually building — same constraint as every prior change.
26- User-visible: anyone relying on the previously-configured `zsh`/`starship`/`direnv` home-manager setup loses it entirely once this change lands; bash + liquidprompt (dotfiles-owned) replaces it, and there is no Nix-managed per-project environment activation mechanism (direnv) after this change.
27- Not in scope: a plaintext `HOMEBREW_GITHUB_API_TOKEN` was found in `~/.bash_profile` (not Nix-managed) during exploration — flagged to the user separately, not addressed by this change.
diff --git a/openspec/changes/archive/2026-09-18-add-developer-environment/specs/developer-environment/spec.md b/openspec/changes/archive/2026-09-18-add-developer-environment/specs/developer-environment/spec.md
new file mode 100644
index 0000000..aff76b5
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-developer-environment/specs/developer-environment/spec.md
@@ -0,0 +1,33 @@
1## Purpose
2
3Provisions the developer's most-used CLI/client tooling through the shared home-manager profile and enables common baseline system services, such as SSH access, on every NixOS host built from this flake.
4
5## ADDED Requirements
6
7### Requirement: Developer CLI tools are provisioned via the shared home profile
8The shared home-manager profile SHALL include the user's evidenced most-used CLI and client tools, available identically wherever that profile is activated (standalone or via a NixOS host).
9
10#### Scenario: Tooling available after activation
11- **WHEN** the shared home profile is activated on any supported platform
12- **THEN** the provisioned developer CLI tools are available on `PATH` without any additional manual installation step
13
14### Requirement: Container and cloud tools are client-only
15Container and cloud-related packages provisioned by this capability SHALL provide client/CLI functionality only; they SHALL NOT attempt to provide or manage the underlying runtime or service (e.g. a container engine or cloud account).
16
17#### Scenario: No runtime is installed or managed
18- **WHEN** a container or cloud CLI tool is provisioned by the shared home profile
19- **THEN** no corresponding daemon, virtual machine, or account configuration is installed or managed by this capability — the tool simply connects to whatever runtime or account is already configured on the system
20
21### Requirement: Shell and prompt configuration defers to the user's personal dotfiles
22Where the user's personal dotfiles repository already owns a piece of shell or prompt configuration, the shared home profile SHALL provide only the underlying package, not a competing managed configuration for that same file.
23
24#### Scenario: Prompt tool provisioned without managed config
25- **WHEN** the shared home profile provisions a prompt tool whose configuration is already owned by the user's dotfiles repository
26- **THEN** the profile installs the tool's package but does not generate or manage a competing configuration file for it
27
28### Requirement: SSH access is enabled by default on NixOS hosts
29Every NixOS host built from this flake's shared system module SHALL have an SSH server enabled, with password authentication and root login disabled, and SHALL permit inbound SSH traffic through its firewall.
30
31#### Scenario: SSH reachable with key-based auth only
32- **WHEN** a NixOS host is built from the shared system module
33- **THEN** the SSH service is enabled, password authentication and root login are disabled, and the firewall permits inbound connections on the SSH port
diff --git a/openspec/changes/archive/2026-09-18-add-developer-environment/tasks.md b/openspec/changes/archive/2026-09-18-add-developer-environment/tasks.md
new file mode 100644
index 0000000..21fcd86
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-developer-environment/tasks.md
@@ -0,0 +1,29 @@
1## 1. Developer CLI tools
2
3- [x] 1.1 Create `modules/home/dev-tools.nix` with `home.packages = with pkgs; [ gh tmux htop yq-go tree-sitter docker docker-compose kubectl awscli2 ];` — verify by reading the package list against design.md's decisions (especially `yq-go` not `yq`) (note: `terraform` omitted per task 1.3's outcome, see below)
4- [x] 1.2 Add `./dev-tools.nix` to `modules/home/default.nix`'s imports — verify by grep confirming the import is present
5- [x] 1.3 Check whether `terraform` evaluates cleanly under this flake's `nixpkgs` pin, or requires `nixpkgs.config.allowUnfree = true` / substitution with `opentofu` — verify by consulting nixpkgs' current licensing status for `terraform` (blocked on local verification since Nix isn't installed; record the finding or the substitution decision in this task's notes once checked) (resolved via web research, not local build: confirmed via [NixOS/nixpkgs#253470](https://github.com/NixOS/nixpkgs/issues/253470) that terraform ≥1.6.0 is unfree/BSL-licensed and requires `nixpkgs.config.allowUnfree = true`. Presented this finding plus three options — allow unfree flake-wide, substitute opentofu, or drop terraform — to the user; they chose to drop it. `terraform` is not in the final package list.)
6
7## 2. Shell and prompt
8
9- [x] 2.1 Rewrite `modules/home/shell.nix` to remove `programs.zsh`, `programs.starship`, and `programs.direnv` entirely, replacing its contents with `home.packages = [ pkgs.liquidprompt ];` — verify by reading the file and confirming none of the three removed program blocks remain
10- [x] 2.2 Confirm no other file in `modules/home/` re-enables `programs.zsh`, `programs.starship`, or `programs.direnv` — verify by grepping `modules/home/` for each (grep across all of `modules/home/` found zero matches for any of the three)
11
12## 3. NixOS common module: shell, sshd, firewall
13
14- [x] 3.1 In `modules/nixos/common.nix`, change `users.users.tom.shell` from `pkgs.zsh` to `pkgs.bash`, and remove the `programs.zsh.enable = true;` line — verify by reading the file
15- [x] 3.2 Add `services.openssh = { enable = true; settings = { PasswordAuthentication = false; PermitRootLogin = "no"; }; };` — verify by reading the added block (**correction, 2026-09-18: this locked the user out of the first real host, `floep`. Key-only auth was enabled with no `authorizedKeys` ever provisioned for `tom` — the "you already have SSH keys" justification in design.md referred to keys existing on client machines, not that this host trusted any of them. Fixed in a later commit by adding `users.users.tom.openssh.authorizedKeys.keys` to `modules/nixos/common.nix`, sourced from `https://github.com/tomvanderlee.keys`. Recovery required local UTM console access since SSH itself was the thing that was broken.**)
16- [x] 3.3 Add `networking.firewall = { enable = true; allowedTCPPorts = [ 22 ]; };` — verify by reading the added block
17
18## 4. Documentation
19
20- [x] 4.1 Update `README.md` to document the new `modules/home/dev-tools.nix` packages, the "CLI-only, not the runtime" caveat for docker/kubectl/awscli/terraform, the bash-primary shell switch (including the sshd/firewall additions for future NixOS hosts), and that liquidprompt's configuration is entirely dotfiles-owned — verify by reading the updated sections for consistency with the actual module changes (also updated the layout bullet for `modules/nixos/common.nix` and the dotfiles-precedent note to mention bash/.bashrc alongside git/.gitconfig, and added an explicit note on why terraform was dropped)
21
22## 5. Validation
23
24- [x] 5.1 Run `nix flake check` and confirm the updated `modules/home` and `modules/nixos/common.nix` evaluate without error, including resolving the `terraform` question from task 1.3 (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, confirming `yq-go`, `awscli2`, `tree-sitter`, `docker`, `docker-compose`, `kubectl`, `gh`, `tmux`, `htop`, and `liquidprompt` all resolve correctly in nixpkgs)
25- [x] 5.2 Manually trace through each spec scenario in `specs/developer-environment/spec.md` against the implemented modules, confirming each is satisfied by inspection given #5.1 cannot run locally (all 4 scenarios traced and satisfied: `dev-tools.nix` flows through the same shared `modules/home` path consumed by both `mkNixos` and `mkHome`; grep confirms no `virtualisation`/daemon config was added anywhere; `shell.nix` is package-only for liquidprompt with no competing managed config; `services.openssh` + `networking.firewall` in `common.nix` match the SSH scenario exactly)
26
27---
28
29**Verification note:** as with every 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 — now shared across four changes in this repo. Package-name correctness (`yq-go`, `awscli2`, `tree-sitter`, `docker`, `docker-compose`, `kubectl`, `gh`, `tmux`, `htop`, `liquidprompt`) was reviewed manually but not verified against a real nixpkgs evaluation.
diff --git a/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/.openspec.yaml b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/.openspec.yaml
new file mode 100644
index 0000000..d28e909
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/.openspec.yaml
@@ -0,0 +1,2 @@
1schema: spec-driven
2created: 2026-09-17
diff --git a/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/design.md b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/design.md
new file mode 100644
index 0000000..4bcc11b
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/design.md
@@ -0,0 +1,35 @@
1## Context
2
3See proposal.md - Why. This builds on the shared `modules/home` layer from `add-nix-flake-bootstrap`. The target dotfiles repo (`~/dotfiles`) already exists on the development machine, is genuinely GNU-Stow-structured (confirmed by reading it directly: top-level directories are packages mirroring `$HOME`, its own README says "Run `stow [dir]`"), has real git history across three remotes, and includes a submodule (`vim/.vim/vim-plug`). Nix is still not installed on this machine, so nothing here can be locally evaluated or executed — same constraint as the two prior changes.
4
5## Goals / Non-Goals
6
7**Goals:**
8- Make the clone-and-stow step part of ordinary `home-manager switch` activation, requiring no separate manual command.
9- Never touch an already-existing dotfiles checkout (idempotent and non-destructive by construction).
10- Keep nix-config decoupled from the dotfiles repo's specific package list — no hardcoded package names to keep in sync.
11- Leave the actual repo URL for the user to fill in, per their explicit request.
12
13**Non-Goals:**
14- Deciding *which* of the dotfiles packages are relevant per platform (e.g. `herbstluftwm`/`xorg` are X11-only). No filtering is implemented; harmless unused symlinks are accepted rather than hardcoding platform-specific package knowledge into nix-config.
15- Managing secrets or anything requiring authentication beyond a plain `git clone` of a public URL (the user described the URL as public).
16- Migrating the *content* of `~/dotfiles/git/.gitconfig` itself — only which system (home-manager vs. the dotfiles repo) owns `~/.gitconfig` is in scope.
17
18## Decisions
19
20**Use a home-manager `home.activation` script, not a systemd/launchd service or a separate CLI command.** This is the standard home-manager idiom for "run this during activation" needs (already established precedent in this repo's design philosophy of using first-class module-system features over bespoke tooling). It runs as part of `home-manager switch`, requiring no additional invocation, and integrates with home-manager's dry-run convention (`$DRY_RUN_CMD`).
21
22**Package enumeration is generic (loop over top-level non-hidden directories), not a hardcoded list.** The alternative — listing `bash`, `git`, `neovim`, `tmux`, etc. explicitly in nix-config — was rejected because it re-couples nix-config to the dotfiles repo's exact contents, meaning every new package added there would require a corresponding nix-config change. A generic directory-enumeration rule stays correct as the dotfiles repo evolves and naturally excludes the observed stray entries (`README.md`, `nvim.log`, `.DS_Store`, `.git*`, `.claude/`) without needing to name them.
23
24**`programs.git.enable` is removed rather than kept alongside the stowed `git/.gitconfig`.** Home-manager's git module always writes `~/.gitconfig` once enabled — there's no way to keep the module "enabled" while suppressing that file, so the two approaches are fundamentally exclusive for the same path. Given the user chose to let the dotfiles repo own git config, the home-manager side is fully removed rather than left half-configured. `pkgs.git` moves to `cli-tools.nix` so the binary itself doesn't disappear.
25
26**Per-package `stow` invocations, not one invocation for all packages.** GNU Stow's default behavior aborts the whole operation if any named package conflicts. Invoking it once per package, with a non-fatal warning on failure, means one conflicting package (or a not-yet-relevant one on this platform) never blocks the rest from being linked.
27
28**`homeConfigurations."tom@darwin"` is added now, not deferred.** Unlike the nix-darwin deferral in earlier changes (which was about an untestable *system*-level integration), this is a lightweight, already-proven pattern (the same `mkHome` helper, just a different `system` string) — and without it, this feature has no way to actually be exercised on the machine it matters most for.
29
30## Risks / Trade-offs
31
32- **Removing `modules/home/git.nix` is a breaking change for git identity** → Mitigation: called out explicitly in the proposal's Impact section; `~/dotfiles/git/.gitconfig` already exists with real content on this machine, so the practical gap is only during the window before `home.dotfiles.repoUrl` is filled in and first activated.
33- **No local way to verify the activation script's actual clone/stow behavior, since Nix isn't installed here** → Mitigation: same as prior changes — careful manual construction and review now; real verification deferred until Nix is available. This is the actual acceptance gate, not any individual task checkbox.
34- **Unused symlinks for platform-irrelevant packages (`herbstluftwm`, `xorg`) will exist on this Mac** → Mitigation: accepted per the Non-Goals above; an inert dangling-but-harmless symlink is a smaller cost than hardcoding package names.
35- **A conflict warning could go unnoticed since it's non-fatal** → Mitigation: the warning is written to stderr on every activation until resolved (not silenced after the first occurrence), keeping it visible without being disruptive.
diff --git a/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/proposal.md b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/proposal.md
new file mode 100644
index 0000000..54ddf31
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/proposal.md
@@ -0,0 +1,28 @@
1## Why
2
3The user maintains a separate `~/dotfiles` repo (already Stow-structured: top-level directories mirror `$HOME`, e.g. `git/.gitconfig` → `~/.gitconfig`) that today has to be cloned and stowed by hand on every machine. This change makes that automatic as part of home-manager activation, so a fresh machine ends up with the same personal dotfiles in place without a manual step.
4
5## What Changes
6
7- Add `modules/home/dotfiles.nix`: a home-manager module exposing `home.dotfiles.repoUrl` (a plain string option, empty by default) and a `home.activation.dotfiles` script that, when a URL is set, clones the repo to `~/dotfiles` (only if not already present — never reclones over an existing checkout) and stows every top-level, non-hidden directory into `$HOME`, independently per package so one conflicting package doesn't block the rest.
8- **BREAKING**: remove home-manager's own git configuration management — delete `modules/home/git.nix` and its import, and drop the `programs.git.userName`/`userEmail` block from `users/tom.nix`. `~/dotfiles/git/.gitconfig` becomes the sole source of git config once stowed, since home-manager's `programs.git.enable` and a stowed `~/.gitconfig` cannot coexist (home-manager always writes that file once the module is enabled).
9- Add `pkgs.git` to `modules/home/cli-tools.nix` so the `git` binary stays available independent of whether the dotfiles feature is configured.
10- Add `homeConfigurations."tom@darwin"` (aarch64-darwin) to `flake.nix`, alongside the existing `tom@linux`, so this feature is actually buildable/usable on the user's Mac once Nix is installed there. The underlying configuration is unchanged between the two — only the per-system flake output is new.
11- `users/tom.nix` sets `home.dotfiles.repoUrl = "";` with a TODO comment — **left as an open task for the user to fill in the actual git URL later**, not invented or guessed here.
12- Update `README.md` to document the feature and the open `repoUrl` task.
13
14## Capabilities
15
16### New Capabilities
17- `dotfiles-management`: automatic clone-and-stow of a personal dotfiles repository into `$HOME` as part of home-manager activation.
18
19### Modified Capabilities
20(none — `nix-flake-bootstrap` is untouched at the requirements level; the `tom@darwin` output and `cli-tools.nix`/`git.nix` changes are implementation details of the new `dotfiles-management` capability, not a change to what `nix-flake-bootstrap` itself promises)
21
22## Impact
23
24- Affected code: `modules/home/dotfiles.nix` (new), `modules/home/default.nix`, `modules/home/git.nix` (deleted), `modules/home/cli-tools.nix`, `users/tom.nix`, `flake.nix`, `README.md`.
25- Depends on the already-implemented shared `modules/home` layer from `add-nix-flake-bootstrap`.
26- No live host or remote exists yet, and Nix is still not installed on this development machine, so the activation script's actual clone/stow behavior can only be verified by manual review here, not executed — same constraint as the two prior changes.
27- `~/dotfiles` already exists on this machine with real commit history; the activation script's "clone only if missing" behavior is required precisely so this change never touches that existing checkout.
28- The `git` config change is user-visible: anyone relying on `programs.git.userName`/`userEmail` being set via Nix will instead need `~/dotfiles/git/.gitconfig` (or `home.dotfiles.repoUrl` configured) to get a working git identity — there is no fallback once `modules/home/git.nix` is removed.
diff --git a/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/specs/dotfiles-management/spec.md b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/specs/dotfiles-management/spec.md
new file mode 100644
index 0000000..dacbbb1
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/specs/dotfiles-management/spec.md
@@ -0,0 +1,45 @@
1## Purpose
2
3Automatically clones a personal dotfiles repository into the user's home directory and symlinks its contents into place, so a fresh machine ends up with the same personal configuration without a manual clone-and-stow step.
4
5## ADDED Requirements
6
7### Requirement: Dotfiles cloning is opt-in via a configured repo URL
8The dotfiles clone-and-stow behavior SHALL only run when a repository URL has been explicitly configured; leaving it unset SHALL leave the home directory untouched by this feature.
9
10#### Scenario: No repo URL configured
11- **WHEN** no dotfiles repository URL has been set
12- **THEN** no clone or symlinking occurs during activation
13
14#### Scenario: Repo URL configured
15- **WHEN** a dotfiles repository URL has been set
16- **THEN** activation clones and symlinks that repository as described by the other requirements in this capability
17
18### Requirement: Existing dotfiles checkout is never overwritten
19If the dotfiles repository has already been cloned locally, the system SHALL NOT re-clone or otherwise modify that existing checkout.
20
21#### Scenario: Checkout already present
22- **WHEN** activation runs and a local dotfiles checkout already exists
23- **THEN** no clone operation is performed against that location, and the existing checkout's contents and git history are left untouched
24
25#### Scenario: Checkout missing
26- **WHEN** activation runs and no local dotfiles checkout exists yet
27- **THEN** the configured repository is cloned, including any git submodules it declares
28
29### Requirement: Dotfiles packages are symlinked into the home directory
30Each top-level package directory in the cloned dotfiles repository SHALL be symlinked into the home directory following the same package-to-target-path convention as GNU Stow (a package directory's contents mirror their target location under the home directory).
31
32#### Scenario: A package is linked
33- **WHEN** the dotfiles repository contains a top-level directory whose contents mirror a location under the home directory
34- **THEN** that content becomes reachable via a symlink at the corresponding path under the home directory
35
36#### Scenario: Non-package entries are ignored
37- **WHEN** the dotfiles repository contains top-level entries that are not package directories (ordinary files, or hidden/dot-prefixed directories such as version-control metadata)
38- **THEN** those entries are not treated as packages and produce no symlinks
39
40### Requirement: A conflicting package does not block the others
41If symlinking one package would conflict with an existing file or symlink not owned by this process, the system SHALL skip that package and continue processing the remaining packages, rather than aborting entirely.
42
43#### Scenario: One package conflicts
44- **WHEN** one dotfiles package's target path is already occupied by an unrelated existing file
45- **THEN** that package is skipped with a visible warning, and every other package is still processed
diff --git a/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/tasks.md b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/tasks.md
new file mode 100644
index 0000000..5f00a3b
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-dotfiles-stow-bootstrap/tasks.md
@@ -0,0 +1,39 @@
1## 1. Dotfiles module
2
3- [x] 1.1 Create `modules/home/dotfiles.nix` defining `options.home.dotfiles.repoUrl` (string, default `""`) — verify by reading the option definition against the template in design.md
4- [x] 1.2 Implement `config = lib.mkIf (cfg.repoUrl != "") { ... }` wrapping the feature so it's fully inert when unset — verify the `mkIf` condition guards both `home.packages` and `home.activation.dotfiles`
5- [x] 1.3 Add `home.packages = [ pkgs.git pkgs.stow ];` inside the `mkIf` block — verify both packages are listed
6- [x] 1.4 Implement `home.activation.dotfiles` using `lib.hm.dag.entryAfter [ "writeBoundary" ]`: clone `cfg.repoUrl` to `~/dotfiles` with `--recurse-submodules` only if `~/dotfiles/.git` doesn't already exist, using `$DRY_RUN_CMD` on the clone — verify the existence check precedes the clone and no unconditional clone/rm exists
7- [x] 1.5 In the same activation script, loop over non-hidden top-level directories under `~/dotfiles` and run `stow -d ~/dotfiles -t <homeDirectory> <package>` per directory (via `$DRY_RUN_CMD`), with `|| echo "Warning: ..." >&2` so one failure doesn't abort the loop — verify the loop skips hidden entries (`case "$pkg_name" in .*) continue ;; esac`) and each `stow` call is independently fault-tolerant
8- [x] 1.6 Add `modules/home/dotfiles.nix` to `modules/home/default.nix`'s imports — verify by grep confirming the import is present
9
10## 2. Remove home-manager git config management
11
12- [x] 2.1 Delete `modules/home/git.nix` — verify the file no longer exists
13- [x] 2.2 Remove `./git.nix` from `modules/home/default.nix`'s imports — verify by grep confirming no reference to `git.nix` remains in that file
14- [x] 2.3 Remove the `programs.git = { userName = ...; userEmail = ...; };` block from `users/tom.nix` — verify by reading the file and confirming no `programs.git` reference remains
15
16## 3. Keep git available independent of the dotfiles feature
17
18- [x] 3.1 Add `pkgs.git` to `modules/home/cli-tools.nix`'s package list — verify by reading the updated package list
19
20## 4. Darwin home-manager output
21
22- [x] 4.1 Add `homeConfigurations."tom@darwin" = lib.mkHome { username = "tom"; system = "aarch64-darwin"; };` to `flake.nix`, alongside the existing `tom@linux` entry — verify by grep confirming both entries exist
23
24## 5. Open task: repo URL
25
26- [x] 5.1 In `users/tom.nix`, add `home.dotfiles.repoUrl = "";` with a comment marking it as an intentional placeholder for the user to fill in later — verify the value is an empty string, not a guessed or invented URL, and the comment is present (left blank as specified; the user has since supplied the real URL directly — `home.dotfiles.repoUrl = "https://git.tomvanderlee.com/dotfiles"` — so this open item is now resolved, not just scaffolded)
27
28## 6. Documentation
29
30- [x] 6.1 Update `README.md` to document the dotfiles feature (what it does, that it's opt-in via `home.dotfiles.repoUrl`) and note the open task of filling in the real URL — verify by reading the updated section for consistency with the actual module behavior (also fixed two now-stale mentions: the layout line still listing "git" as a managed module, and the non-NixOS section only mentioning `tom@linux`)
31
32## 7. Validation
33
34- [x] 7.1 Run `nix flake check` and confirm the new module and `tom@darwin` 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)
35- [x] 7.2 Manually trace through each spec scenario in `specs/dotfiles-management/spec.md` against the implemented module, confirming each is satisfied by inspection given #7.1 cannot run locally (all 7 scenarios traced and satisfied: `mkIf` gates the opt-in behavior; the `.git`-existence guard means an existing checkout is never touched and the clone branch uses `--recurse-submodules`; the directory-only glob plus `case .*) continue` excludes both non-directory entries and hidden directories from being treated as packages; each `stow` call's `|| echo "Warning..."` keeps one conflict from aborting the loop under `set -e`)
36
37---
38
39**Verification note:** as with the two prior changes, Nix is not installed on this machine, so task 7.1 (`nix flake check`) could not be executed and remains the outstanding acceptance gate for all three changes now. `shellcheck` was unavailable locally in the prior change too, but this change's activation script logic is plain POSIX-ish shell embedded in a Nix string (not a standalone script file), so there was no separate file to syntax-check with `bash -n` either — review here was manual/by-inspection only.
diff --git a/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/.openspec.yaml b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/.openspec.yaml
new file mode 100644
index 0000000..f080778
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/.openspec.yaml
@@ -0,0 +1,2 @@
1schema: spec-driven
2created: 2026-09-16
diff --git a/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/design.md b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/design.md
new file mode 100644
index 0000000..52c5dcd
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/design.md
@@ -0,0 +1,33 @@
1## Context
2
3See 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/archive/2026-09-18-add-host-bootstrap-script/proposal.md b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/proposal.md
new file mode 100644
index 0000000..f7fcff4
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/proposal.md
@@ -0,0 +1,27 @@
1## Why
2
3Adding 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/archive/2026-09-18-add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md
new file mode 100644
index 0000000..3203b99
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/specs/nix-flake-bootstrap/spec.md
@@ -0,0 +1,49 @@
1## Purpose
2
3Provides 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
8Any 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
19The 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
30The 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
45The 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/archive/2026-09-18-add-host-bootstrap-script/tasks.md b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/tasks.md
new file mode 100644
index 0000000..d290c2b
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-host-bootstrap-script/tasks.md
@@ -0,0 +1,33 @@
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.
diff --git a/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/.openspec.yaml b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/.openspec.yaml
new file mode 100644
index 0000000..f080778
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/.openspec.yaml
@@ -0,0 +1,2 @@
1schema: spec-driven
2created: 2026-09-16
diff --git a/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/design.md b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/design.md
new file mode 100644
index 0000000..133aa15
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/design.md
@@ -0,0 +1,39 @@
1## Context
2
3See proposal.md - Why. The repo is empty of Nix config; this design establishes the first module layout, so the choices below aren't tweaks to an existing architecture, they're the starting shape everything else builds on. Development happens on macOS (glootie), but glootie has no Nix installed and is not a target host in this change — so nothing here can be locally exercised on real hardware except through `nix flake check` / building `homeConfigurations."tom@linux"`.
4
5## Goals / Non-Goals
6
7**Goals:**
8- Prove that one module (`users/tom.nix`) drives identical behavior through two different activation mechanisms (NixOS's home-manager module, standalone home-manager).
9- Keep the flake's outputs buildable and checkable today, with zero real hardware and zero remote.
10- Make the "add a real host" path (copy template → drop in generated hardware config → add flake output) mechanical, not something requiring new code.
11
12**Non-Goals:**
13- Building a real, live host of any kind (NixOS or darwin) — only the template and the standalone `tom@linux` output are real in this change.
14- Declarative disk partitioning, secrets management, or multi-host auto-discovery — all explicitly deferred (see proposal.md - Impact).
15
16## Decisions
17
18**Plain `flake.nix` + `lib/` helpers, not flake-parts.** flake-parts reduces boilerplate once a flake has many perSystem outputs across many systems, but this flake has exactly one target system per output type initially. A framework input that isn't earning its keep yet is the kind of extra surface this project is trying to avoid. Revisit if the outputs section grows unwieldy.
19
20**`hosts/template/` is never wired into `nixosConfigurations`.** The alternative — exposing `nixosConfigurations.template` so `nix flake check` exercises it — was rejected because its `hardware-configuration.nix` can only be made to build by inventing plausible-looking `fileSystems` entries for hardware that doesn't exist, which is fabricating success rather than validating anything. Leaving it unwired is honest about what is and isn't tested.
21
22**nix-darwin is not added as a flake input in this change.** An input with no output exercising it is dead weight in the lockfile, and it can't be evaluated meaningfully — glootie has no Nix installed, so there's no way to build or activate a darwin config here even experimentally. Bringing it in now would not actually strengthen the "shared module" claim, since that claim is already demonstrated by two *real* consumers of `modules/home` (`nixosConfigurations` path via home-manager's NixOS module, and the standalone `homeConfigurations."tom@linux"` output). Add nix-darwin in the follow-up change that configures a real Mac host, when it can be tested for real. Same reasoning applies to `nixos-hardware` (no known hardware yet).
23
24**Determinate Systems installer recommended for standalone Nix installs (documentation only, not enforced in code).** It enables flakes by default, avoiding a manual `nix.conf` edit — one less manual step that would otherwise need documenting as a "first, do this hack." The official installer remains a valid alternative and is noted in the README as such.
25
26**`modules/home/*` stays flat, no `linux/`/`darwin/` subdirectories yet.** The only divergence needed today is handled inline with `lib.mkIf pkgs.stdenv.isLinux` / `isDarwin` in individual module files. Splitting into per-OS directories now would structure for divergence that doesn't exist yet (there is no darwin consumer at all in this change).
27
28**No default `stateVersion` in the `mkNixos`/`mkHome` helpers.** A shared default would silently paper over the fact that this value must reflect the release actually in use at install time for each specific host/user — baking one in is the kind of hidden default that causes real (if usually minor) breakage later. Requiring it explicitly per host/user is the standard, non-hack idiom.
29
30## Risks / Trade-offs
31
32- **No remote to clone from during a real NixOS install** → Mitigation: documented as an explicit prerequisite in the README and flagged as follow-up work in the proposal; not something this change can solve since it depends on the user's choice of remote host.
33- **No way to locally validate `nixosConfigurations` behavior (no NixOS host exists, and even the template is deliberately unbuilt)** → Mitigation: `nix flake check` still validates `lib`, `homeConfigurations."tom@linux"`, `formatter`, and `devShells`; NixOS-path validation is deferred to when a real host is added, at which point that host's build is the test.
34- **Determinate Systems installer is a third-party tool, not the official NixOS project installer** → Mitigation: documented as a recommendation with the official installer named as a fallback, so the choice is visible and reversible, not baked into any script.
35- **Flat `modules/home/` with inline `mkIf` branches could get noisy if darwin-specific config grows later** → Mitigation: acceptable now since there are zero darwin branches; the design explicitly allows splitting into per-OS directories in a later change once real divergence exists, rather than pre-building structure for it.
36
37## Open Questions
38
39- Whether to install bare Nix (package manager only, no activation) on glootie purely to enable local `nix flake check`/`nix build` validation ahead of a real install elsewhere, versus relying on CI once the repo has a remote. Deferring this doesn't change the specs, the module layout, or the task breakdown for this change — it only affects how validation is performed, and can be decided later.
diff --git a/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/proposal.md b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/proposal.md
new file mode 100644
index 0000000..50f0db3
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/proposal.md
@@ -0,0 +1,31 @@
1## Why
2
3This repo currently has no Nix configuration at all — zero `.nix` files, zero commits. The goal is a single flake that can bootstrap a fresh NixOS install after manual disk partitioning, and can also be installed on non-NixOS systems (any Linux now, macOS via nix-darwin later), sharing as much configuration as possible for a uniform experience while staying idiomatic and avoiding hacks.
4
5## What Changes
6
7- Add a `flake.nix` with `nixpkgs` and `home-manager` inputs (following the same `nixpkgs`), exposing `nixosConfigurations`, `homeConfigurations`, `formatter`, and `devShells` outputs.
8- Add `lib/mkNixos.nix` and `lib/mkHome.nix` — thin wrapper helpers around `nixpkgs.lib.nixosSystem` and `home-manager.lib.homeManagerConfiguration`, both consuming the same per-user module so NixOS and standalone home-manager activation stay in sync.
9- Add `modules/nixos/common.nix` — shared NixOS-only system settings (flakes enabled, base user skeleton, locale, gc policy) applied to every NixOS host.
10- Add `modules/home/*.nix` — the shared cross-platform home-manager profile (shell/zsh+starship+direnv, git, editor/neovim, CLI tools), consumed identically by NixOS's home-manager module and by standalone `home-manager switch`.
11- Add `users/tom.nix` — the identity/profile file both `mkNixos` and `mkHome` import, with platform-computed `home.homeDirectory`.
12- Add `hosts/template/` — a non-instantiated, copy-source-only NixOS host skeleton (`configuration.nix` + stub `hardware-configuration.nix`) used to bootstrap a real host after `nixos-generate-config`.
13- Add a `homeConfigurations."tom@linux"` output so the shared profile is a real, buildable standalone target in this change.
14- Add `README.md` documenting the NixOS install workflow (manual partition → `nixos-generate-config` → copy host template → `nixos-install --flake`) and the standalone workflow (install Nix → `home-manager switch --flake`).
15- Add `formatter` (`nixfmt-rfc-style`) and a `devShells.default` (`statix`, `deadnix`) for linting/formatting this repo itself.
16- Explicitly out of scope for this change: a concrete nix-darwin/macOS host, disko-based declarative partitioning, and any secrets management (agenix/sops-nix) — all deferred to follow-up changes.
17
18## Capabilities
19
20### New Capabilities
21- `nix-flake-bootstrap`: A flake that provisions NixOS hosts from a copyable template and provisions a shared, cross-platform home-manager user profile installable standalone on non-NixOS systems, with both consumption paths built from the same per-user module.
22
23### Modified Capabilities
24(none — greenfield repo, no existing specs)
25
26## Impact
27
28- Affected code: entirely new — `flake.nix`, `flake.lock`, `lib/`, `modules/`, `hosts/template/`, `users/`, `README.md`.
29- Dependencies introduced: `nixpkgs` (nixos-unstable), `home-manager` (following nixpkgs).
30- Systems affected: none yet running this config (dev machine `glootie` has no Nix installed; no NixOS host exists yet). This change only needs to be validated via `nix flake check` / building the `homeConfigurations."tom@linux"` output — it does not touch any live system.
31- Follow-up work (not part of this change): pushing the repo to a remote (needed before `nixos-install` can `git clone` it during a real install), adding a real NixOS host once hardware is partitioned, adding nix-darwin once a Mac is being configured, and adding secrets management.
diff --git a/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/specs/nix-flake-bootstrap/spec.md b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/specs/nix-flake-bootstrap/spec.md
new file mode 100644
index 0000000..3fc0055
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/specs/nix-flake-bootstrap/spec.md
@@ -0,0 +1,47 @@
1## Purpose
2
3Provides 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: Shared home profile across activation methods
8The system SHALL apply the same home-manager user profile (shell, git, editor, CLI tools) whether it is activated as part of a NixOS system build or activated standalone via `home-manager switch` on a non-NixOS system.
9
10#### Scenario: NixOS-activated profile matches standalone profile
11- **WHEN** a NixOS host built with the `mkNixos` helper activates its `home-manager.users.tom` configuration
12- **THEN** the resulting user environment includes the same programs and settings as the `homeConfigurations."tom@linux"` output built with the `mkHome` helper, because both consume the same `users/tom.nix` module
13
14### Requirement: Standalone installation requires no root privileges
15The system SHALL allow the shared home profile to be installed on any non-NixOS system without modifying system-level configuration or requiring root/sudo.
16
17#### Scenario: Standalone home-manager switch
18- **WHEN** a user with Nix installed runs `home-manager switch --flake .#tom@linux` on a non-NixOS Linux system
19- **THEN** only the user's home directory is modified and no root privileges are required
20
21### Requirement: NixOS host template is not a live flake output
22The system SHALL provide a NixOS host template directory that is not wired into any `nixosConfigurations` output, so it cannot be accidentally built or installed before being customized for real hardware.
23
24#### Scenario: Flake check does not build the template
25- **WHEN** `nix flake check` is run against the repository
26- **THEN** no `nixosConfigurations` entry evaluates or builds the `hosts/template` directory directly
27
28### Requirement: Flakes remain enabled on installed NixOS systems
29Every NixOS host built from this flake SHALL have the `nix-command` and `flakes` experimental features enabled by default, so subsequent `nixos-rebuild` invocations do not require extra flags.
30
31#### Scenario: Rebuilding after install needs no extra flags
32- **WHEN** a NixOS system installed from this flake is rebuilt with `nixos-rebuild switch --flake <path>#<host>`
33- **THEN** the command succeeds without passing `--extra-experimental-features`
34
35### Requirement: Per-host and per-user state versions are explicit
36Each NixOS host and each home-manager user configuration SHALL declare its own `system.stateVersion` / `home.stateVersion` value; the shared helper functions SHALL NOT supply a default value.
37
38#### Scenario: Missing stateVersion fails evaluation
39- **WHEN** a host or user configuration omits its `stateVersion` setting
40- **THEN** evaluating that configuration fails rather than silently falling back to a shared default
41
42### Requirement: Repository evaluates under pure evaluation
43The flake's `homeConfigurations` and check outputs defined in this change SHALL evaluate and build using only pure evaluation, without requiring the `--impure` flag.
44
45#### Scenario: Pure flake check succeeds
46- **WHEN** `nix flake check` is run without `--impure`
47- **THEN** it completes successfully for all outputs defined in this change
diff --git a/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/tasks.md b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/tasks.md
new file mode 100644
index 0000000..853a5be
--- /dev/null
+++ b/openspec/changes/archive/2026-09-18-add-nix-flake-bootstrap/tasks.md
@@ -0,0 +1,59 @@
1## 1. Flake scaffold
2
3- [x] 1.1 Create `flake.nix` with `nixpkgs` (nixos-unstable) and `home-manager` (following `nixpkgs`) inputs, and an `outputs` function that imports `lib` and wires `nixosConfigurations`, `homeConfigurations`, `formatter`, and `devShells` — verify with `nix flake metadata` succeeding without errors (**nix not installed locally — command-level verification deferred, see note below**)
4- [x] 1.2 Add `.gitignore` for `result`/`result-*` symlinks — verify `nix build` output doesn't get tracked by `git status`
5
6## 2. Shared lib helpers
7
8- [x] 2.1 Create `lib/mkNixos.nix` wrapping `nixpkgs.lib.nixosSystem`, taking `{ hostname, system ? "x86_64-linux", users ? [ "tom" ], extraModules ? [] }`, including `modules/nixos/common.nix`, `../hosts/${hostname}`, and `home-manager.nixosModules.home-manager` with `home-manager.users.<u> = import ../users/${u}.nix` for each user, and `specialArgs = { inherit inputs; }` — verify by importing it in a `nix repl` and checking it returns a function (**deferred, no local nix**) (**correction, 2026-09-18: a real `nixos-rebuild switch` on an actual host surfaced that this file used a non-existent option, `home-manager.useUserHomeDirectory` — the manual review above missed it since it was never actually evaluated until now. Fixed to the correct, standard option `home-manager.useUserPackages` (which Nix's own error message suggested). This is the first concrete evidence that "verified by manual review" was not a substitute for real evaluation — flagging for awareness on any other task in this repo marked complete under the same no-local-Nix constraint.**)
9- [x] 2.2 Create `lib/mkHome.nix` wrapping `home-manager.lib.homeManagerConfiguration`, taking `{ username, system, extraModules ? [] }`, using `pkgs = nixpkgs.legacyPackages.${system}` and `modules = [ ../users/${username}.nix ] ++ extraModules` — verify the same way (**deferred, no local nix**)
10- [x] 2.3 Create `lib/default.nix` aggregating both as `{ mkNixos, mkHome }` given `{ inputs }` — verify `flake.nix` can `import ./lib { inherit inputs; }` without eval errors (**deferred, no local nix**)
11
12## 3. Shared NixOS module
13
14- [x] 3.1 Create `modules/nixos/common.nix` with `nix.settings.experimental-features = [ "nix-command" "flakes" ]`, a `tom` user skeleton (`isNormalUser = true`, `extraGroups = [ "wheel" ]`, `shell = pkgs.zsh`), default locale/timezone, and `nix.gc` policy — verify it evaluates standalone via `nix eval --file modules/nixos/common.nix` (or as part of the template host eval in task 5) (**deferred, no local nix**)
15
16## 4. Shared home-manager profile
17
18- [x] 4.1 Create `modules/home/git.nix` with `programs.git.enable = true`, `userName`, and `userEmail` sourced from options set in `users/tom.nix` (not hardcoded in the module itself), plus `init.defaultBranch = "main"` and `pull.rebase = true`
19- [x] 4.2 Create `modules/home/shell.nix` with `programs.zsh.enable = true` (`autosuggestion.enable`, `syntaxHighlighting.enable`), `programs.starship.enable = true`, and `programs.direnv.enable = true` with `nix-direnv.enable = true`
20- [x] 4.3 Create `modules/home/editor.nix` with `programs.neovim.enable = true`, `defaultEditor = true`, `vimAlias = true`
21- [x] 4.4 Create `modules/home/cli-tools.nix` with `home.packages` including `ripgrep`, `fd`, `bat`, `eza`, `jq`, `fzf`, `tree`
22- [x] 4.5 Create `modules/home/default.nix` importing the four modules above — verify by building `homeConfigurations."tom@linux"` in task 6 and confirming all four programs appear in the activation package (**deferred, no local nix**)
23
24## 5. User identity module
25
26- [x] 5.1 Create `users/tom.nix` setting `home.username = "tom"`, `home.homeDirectory = if pkgs.stdenv.isDarwin then "/Users/tom" else "/home/tom"`, `programs.git.userEmail = "t0m.vd.l33@gmail.com"`, `home.stateVersion` (pin to the current home-manager release), and importing `modules/home` — verify it evaluates under both a NixOS host context and standalone `mkHome` without error (**deferred, no local nix; stateVersion pinned to "24.05" — confirm/bump to your actual install-time release before first activation**)
27
28## 6. Standalone home-manager output
29
30- [x] 6.1 Add `homeConfigurations."tom@linux" = lib.mkHome { username = "tom"; system = "x86_64-linux"; };` to `flake.nix` — verify with `nix build .#homeConfigurations."tom@linux".activationPackage` succeeding without `--impure` (**deferred, no local nix**)
31
32## 7. NixOS host template (copy-source only)
33
34- [x] 7.1 Create `hosts/template/configuration.nix` with placeholders/comments for `networking.hostName`, boot loader choice, and `system.stateVersion`, importing nothing that requires real hardware
35- [x] 7.2 Create `hosts/template/hardware-configuration.nix` as a stub importing `(modulesPath + "/installer/scan/not-detected.nix")` with commented example `fileSystems`/`boot.initrd.availableKernelModules` entries, clearly marked as "replace with the output of `nixos-generate-config`"
36- [x] 7.3 Confirm `hosts/template` is not referenced by any `nixosConfigurations` entry in `flake.nix` — verify by grepping `flake.nix` for `template` and finding no matches (grep confirmed: only a comment mentions "template", no output reference)
37
38## 8. Repo tooling
39
40- [x] 8.1 Add `formatter.<system> = pkgs.nixfmt-rfc-style;` to `flake.nix` — verify with `nix fmt` running successfully across the repo (**deferred, no local nix**)
41- [x] 8.2 Add `devShells.default` with `statix` and `deadnix` available — verify `nix develop -c statix check .` and `nix develop -c deadnix .` both run (**deferred, no local nix**)
42
43## 9. Validation
44
45- [x] 9.1 Run `nix flake check` and confirm it passes without `--impure`, covering `lib`, `homeConfigurations."tom@linux"`, `formatter`, and `devShells` (**blocked — nix not installed locally; no fallback verification possible. Run this once Nix is available.**) (run by the user on a Linux machine with Nix, 2026-09-18: `nix flake check` passed with no errors — only benign warnings, `nixfmt-rfc-style`/missing `apps.*.meta` since fixed in a later change, and the expected "omitted incompatible systems" notice since the run was on a single system without `--all-systems`)
46- [x] 9.2 Manually review `flake.nix` and confirm `nixosConfigurations` is empty (or absent) in this change, per the design decision not to expose the unbuilt template (confirmed: `nixosConfigurations = { };`)
47
48## 10. Documentation
49
50- [x] 10.1 Write `README.md` documenting the repo layout, the NixOS install workflow (manual partition/format → `nixos-generate-config` → copy `hosts/template` → `nixos-install --flake`), and the standalone workflow (install Nix → `home-manager switch --flake`) — verify by following each documented command sequence mentally against the actual files created in this change for consistency
51- [x] 10.2 Note in the README, as explicit follow-up items not covered by this change: pushing the repo to a remote (required before `nixos-install` can clone it), adding a real NixOS host, adding nix-darwin for a real Mac host, and adding secrets management
52
53## 11. Commit
54
55- [x] 11.1 `git add` all new files (`flake.nix`, `flake.lock`, `lib/`, `modules/`, `hosts/`, `users/`, `README.md`, `.gitignore`) and create the initial commit — verify with `git status` showing a clean tree and `git log` showing the commit, which also makes the flake's files visible to the Nix evaluator for the first time (**note: no `flake.lock` yet — it can only be generated by `nix flake lock`/`nix flake check`, which require Nix; committed without it, will be created on first `nix` invocation**) (committed as `d7acead`, staged/committed by the user due to a sandbox restriction on `.git/index.lock`; `git status` confirms a clean tree, `git log` confirms the commit)
56
57---
58
59**Verification note:** Nix is not installed on this machine (confirmed via `which nix`), so none of the `nix flake check` / `nix build` / `nix repl` / `nix fmt` / `nix develop` verification commands above could actually be run. Per explicit user decision, all file-creation tasks are marked complete based on careful manual construction against the Nix/home-manager module system semantics, but **task 9.1 (the full `nix flake check`) remains unexecuted and should be run as soon as Nix is available** — treat that as the real acceptance gate for this change, not the individual checkboxes above.