Files
nixos-config/.sisyphus/plans/dendritic-migration.md
2026-03-05 15:41:41 +00:00

1795 lines
77 KiB
Markdown

# Dendritic Migration: Rewrite NixOS Config with den Framework
## TL;DR
> **Quick Summary**: Complete rewrite of the NixOS/darwin configuration from host-centric architecture to feature/aspect-centric using the den framework (vic/den). Replaces specialArgs pass-through, unifies home-manager integration paths, replaces colmena with deploy-rs, and adopts auto-importing via import-tree.
>
> **Deliverables**:
> - New `flake.nix` using den + flake-parts + import-tree + flake-file
> - All 38+ profiles converted to den aspects in `modules/`
> - All 4 hosts (chidi, jason, michael, tahani) building successfully
> - deploy-rs replacing colmena for NixOS deployment
> - Zero specialArgs usage — inputs flow via flake-parts module args
> - Overlays preserved and migrated to den module structure
> - SOPS secrets preserved exactly
>
> **Estimated Effort**: Large
> **Parallel Execution**: YES - 5 waves
> **Critical Path**: Task 1 → Task 2 → Task 4 → Tasks 8-11 → Task 20 → Final
---
## Context
### Original Request
Migrate NixOS configuration to the dendritic pattern using the den framework. Organize config by feature instead of by tool (nixos vs home-manager). Make every file a flake-parts module. Eliminate specialArgs pass-through hell.
### Interview Summary
**Key Discussions**:
- **Framework choice**: User chose den (vic/den) over pure dendritic pattern — wants full aspect/context/batteries system
- **Migration strategy**: Clean rewrite, not incremental — fresh start with new structure
- **Deploy**: Replace colmena with deploy-rs for NixOS server deployment
- **Input management**: Use flake-file (vic/flake-file) to auto-manage flake.nix inputs from modules
- **Custom modules**: Absorb my.gitea and my.pgbackrest into den aspects (no separate nixosModules export)
- **Host roles**: chidi = work laptop (Slack, tuist.dev email), jason = personal laptop
- **Feature granularity**: Trusted to planner — will group by concern where natural
**Research Findings**:
- den framework provides: aspects, contexts, batteries (define-user, primary-user, user-shell, inputs')
- import-tree auto-imports all .nix files, ignores `/_` prefixed paths
- flake-file auto-manages flake.nix from module-level declarations
- deploy-rs uses `deploy.nodes.<name>` flake output — limited darwin support, use only for NixOS
- Real-world den migration examples reviewed (hyperparabolic/nix-config, dendrix community)
### Metis Review
**Identified Gaps (addressed)**:
- `local.dock.*` is a third custom module namespace that was initially missed — will be migrated
- `profiles/wallpaper.nix`, `profiles/packages.nix`, `profiles/open-project.nix` are pure functions, NOT modules — must go under `_`-prefixed paths to avoid import-tree failures
- Himalaya cross-dependency (tahani reads HM config from NixOS scope) — redesign using overlay package directly
- `zellij.nix` uses `osConfig.networking.hostName == "tahani"` hostname comparison — replace with per-host aspect override
- `pgbackrest` module is exported but never imported internally — absorb into aspects per user's decision
- `apps/` directory contains bash scripts — will be rewritten to Nushell as part of this migration
- Platform-conditional code (`stdenv.isDarwin`) in HM profiles is correct — keep, don't split into per-class
- Published flake outputs need explicit preservation decisions
---
## Work Objectives
### Core Objective
Rewrite the entire NixOS/darwin configuration to use the den framework's aspect-oriented architecture, eliminating specialArgs pass-through and organizing all configuration by feature/concern instead of by host.
### Concrete Deliverables
- New `flake.nix` with den + import-tree + flake-aspects + flake-file
- `modules/` directory with all features as den aspects (auto-imported)
- `_lib/` directory for non-module utility functions
- All 4 hosts building identically to current config
- deploy-rs configuration for michael and tahani
- No residual `hosts/`, `profiles/`, old `modules/` structure
### Definition of Done
- [ ] `nix build ".#darwinConfigurations.chidi.system"` succeeds
- [ ] `nix build ".#darwinConfigurations.jason.system"` succeeds
- [ ] `nix build ".#nixosConfigurations.michael.config.system.build.toplevel"` succeeds
- [ ] `nix build ".#nixosConfigurations.tahani.config.system.build.toplevel"` succeeds
- [ ] `nix flake check` passes
- [ ] `alejandra --check .` passes
- [ ] `nix eval ".#deploy.nodes.michael"` returns valid deploy-rs node
- [ ] `nix eval ".#deploy.nodes.tahani"` returns valid deploy-rs node
- [ ] Zero uses of `specialArgs` or `extraSpecialArgs` in codebase
### Must Have
- Exact behavioral equivalence — same services, packages, config files on all hosts
- SOPS secret paths preserved exactly (darwin: age keyfile, NixOS: ssh host key)
- Per-host git email overrides preserved (chidi→tuist.dev, jason/tahani→schmatzler.com)
- All current overlays functional
- deploy-rs for NixOS hosts (michael, tahani)
- Darwin deployment stays as local `darwin-rebuild switch` (deploy-rs has limited darwin support)
- All stateVersion values unchanged (darwin: 6, nixos: "25.11", homeManager: "25.11")
- All perSystem apps (build, apply, build-switch, rollback) preserved or equivalently replaced
### Must NOT Have (Guardrails)
- MUST NOT add new packages, services, or features not in current config
- MUST NOT split simple single-concern profiles into multiple aspects (atuin.nix → one aspect)
- MUST NOT add abstractions for "future use" (multi-user support, dynamic host detection, etc.)
- MUST NOT rewrite app scripts in any language other than Nushell (per AGENTS.md scripting policy)
- MUST NOT change any stateVersion values
- MUST NOT re-encrypt or restructure SOPS secrets — paths and key assignments stay identical
- MUST NOT change any service configuration values (ports, paths, domains, credentials)
- MUST NOT add den batteries beyond what's needed — each must map to a current requirement
- MUST NOT create abstractions over overlays (current pattern is clear)
- MUST NOT add excessive comments, JSDoc, or documentation to nix files
- MUST NOT use hostname string comparisons in shared aspects (no `osConfig.networking.hostName == "tahani"`)
- MUST NOT read HM config values from NixOS scope (the himalaya anti-pattern)
---
## Verification Strategy
> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. No exceptions.
### Test Decision
- **Infrastructure exists**: NO (this is a NixOS config, not a software project)
- **Automated tests**: None (verification is via `nix build` / `nix eval` / `nix flake check`)
- **Framework**: N/A
### QA Policy
Every task MUST verify its changes compile via `nix eval` or `nix build --dry-run`.
Formatting MUST pass `alejandra --check .` after every file change.
---
## Execution Strategy
### Parallel Execution Waves
```
Wave 1 (Foundation — flake bootstrap + utilities):
├── Task 1: New flake.nix with den + import-tree + flake-file [deep]
├── Task 2: den bootstrap module (hosts, users, defaults, base) [deep]
├── Task 3: Utility functions under _lib/ [quick]
├── Task 4: Overlays module [quick]
├── Task 5: SOPS secrets aspects (all 4 hosts) [quick]
└── Task 6: Deploy-rs module [quick]
Wave 2 (Core shared aspects — no host dependencies):
├── Task 7: Core system aspect (nix settings, shells) [quick]
├── Task 8: Darwin system aspect (system defaults, dock, homebrew, nix-homebrew) [unspecified-high]
├── Task 9: NixOS system aspect (boot, sudo, systemd, users) [unspecified-high]
├── Task 10: User aspect (cschmatzler — define-user, primary-user, shell) [quick]
└── Task 11: perSystem apps module [quick]
Wave 3 (Home-manager aspects — bulk migration, MAX PARALLEL):
├── Task 12: Shell aspects (nushell, bash, zsh, starship) [quick]
├── Task 13: Dev tools aspects (git, jujutsu, lazygit, jjui, direnv, mise) [quick]
├── Task 14: Editor aspects (neovim/nixvim) [unspecified-high]
├── Task 15: Terminal aspects (ghostty, zellij, yazi, bat, fzf, ripgrep, zoxide) [quick]
├── Task 16: Communication aspects (himalaya, mbsync, ssh) [unspecified-high]
├── Task 17: Desktop aspects (aerospace, wallpaper, home base) [quick]
├── Task 18: AI tools aspects (opencode, claude-code) [quick]
└── Task 19: Miscellaneous aspects (atuin, zk, open-project) [quick]
Wave 4 (Host-specific + services + scripts):
├── Task 20: Server aspects — michael (gitea + litestream + restic) [deep]
├── Task 21: Server aspects — tahani (adguard, paperless, docker, inbox-triage) [deep]
├── Task 22: Host aspects — chidi (work-specific) [quick]
├── Task 23: Host aspects — jason (personal-specific) [quick]
├── Task 24: Network aspects (openssh, fail2ban, tailscale, postgresql) [quick]
├── Task 25: Packages aspect (system packages list) [quick]
└── Task 29: Rewrite apps/ scripts from bash to Nushell [quick]
Wave 5 (Cleanup + verification):
├── Task 26: Remove old structure (hosts/, profiles/, old modules/) [quick]
├── Task 27: Full build verification all 4 hosts [deep]
└── Task 28: Formatting pass + final cleanup [quick]
Wave FINAL (After ALL tasks — independent review, 4 parallel):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
Critical Path: Task 1 → Task 2 → Task 7 → Tasks 8-9 → Task 10 → Tasks 12-19 → Tasks 20-21 + Task 29 → Task 27 → F1-F4
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 8 (Wave 3)
```
### Dependency Matrix
- **1**: — — 2, 3, 4, 5, 6, 11, W1
- **2**: 1 — 7, 8, 9, 10, W1
- **3**: 1 — 4, 14, 16, 20, 21, W1
- **4**: 1, 3 — 7, 8, 9, W1
- **5**: 1, 2 — 20, 21, 22, 23, W1
- **6**: 1, 2 — 27, W1
- **7**: 2, 4 — 12-19, W2
- **8**: 2, 4 — 17, 22, 23, W2
- **9**: 2, 4 — 20, 21, 24, W2
- **10**: 2 — 12-19, W2
- **11**: 1 — 27, W2
- **12-19**: 7, 10 — 20-25, W3
- **20**: 5, 9, 14, 16 — 27, W4
- **21**: 5, 9, 16, 18 — 27, W4
- **22**: 5, 8, 17 — 27, W4
- **23**: 5, 8, 17 — 27, W4
- **24**: 9 — 27, W4
- **25**: 7 — 27, W4
- **29**: 11 — 27, W4
- **26**: 27 — F1-F4, W5
- **27**: 20-25, 29 — 26, 28, W5
- **28**: 27 — F1-F4, W5
### Agent Dispatch Summary
- **W1**: **6** — T1 → `deep`, T2 → `deep`, T3 → `quick`, T4 → `quick`, T5 → `quick`, T6 → `quick`
- **W2**: **5** — T7 → `quick`, T8 → `unspecified-high`, T9 → `unspecified-high`, T10 → `quick`, T11 → `quick`
- **W3**: **8** — T12-T13 → `quick`, T14 → `unspecified-high`, T15 → `quick`, T16 → `unspecified-high`, T17-T19 → `quick`
- **W4**: **7** — T20-T21 → `deep`, T22-T25 → `quick`, T29 → `quick`
- **W5**: **3** — T26 → `quick`, T27 → `deep`, T28 → `quick`
- **FINAL**: **4** — F1 → `oracle`, F2 → `unspecified-high`, F3 → `unspecified-high`, F4 → `deep`
---
## TODOs
- [x] 1. New flake.nix with den + import-tree + flake-file
**What to do**:
- Move existing `modules/gitea.nix` and `modules/pgbackrest.nix` to `modules/_legacy/` (underscore prefix = ignored by import-tree). These will be absorbed into den aspects in Tasks 20/21 and deleted in Task 26
- Create new `flake.nix` that uses `flake-parts.lib.mkFlake` with `inputs.import-tree ./modules`
- Add all required inputs: preserve existing (nixpkgs, flake-parts, sops-nix, home-manager, darwin, nix-homebrew, homebrew-core, homebrew-cask, nixvim, zjstatus, llm-agents, disko, jj-ryu, jj-starship, himalaya, naersk, tuicr) AND add new required inputs (den, import-tree, flake-aspects, flake-file, deploy-rs)
- Preserve all existing `follows` declarations and `flake = false` attributes
- The `outputs` should be minimal: `inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules)`
- Remove the old `outputs` block entirely (specialArgs, genAttrs, inline configs — all gone)
- Create a minimal placeholder module in `modules/` (e.g. empty flake-parts module) so import-tree has something to import
- Ensure `nix flake show` doesn't error
**Must NOT do**:
- Do not include ANY configuration logic in flake.nix — everything goes in modules/
- Do not change any existing input URLs or versions
- Do not add inputs beyond the required new ones (den, import-tree, flake-aspects, flake-file, deploy-rs)
**Recommended Agent Profile**:
- **Category**: `deep`
- Reason: This is the highest-risk task — if the flake bootstrap is wrong, nothing works. Needs careful attention to input declarations, follows chains, and flake-file compatibility.
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 1 (start first)
- **Blocks**: All other tasks
- **Blocked By**: None
**References**:
- `flake.nix:1-168` — Current flake with all inputs and follows declarations to preserve
- `templates/default/flake.nix` in vic/den repo — Reference flake.nix structure using import-tree
- den migration guide: https://den.oeiuwq.com/guides/migrate/
- import-tree README: https://github.com/vic/import-tree — Auto-import pattern
- flake-file: https://github.com/vic/flake-file — For auto-managing inputs from modules
**Acceptance Criteria**:
- [ ] `flake.nix` has all current inputs preserved with correct follows
- [ ] `flake.nix` has new required inputs: den, import-tree, flake-aspects, flake-file, deploy-rs
- [ ] `flake.nix` outputs use `import-tree ./modules`
- [ ] `modules/_legacy/gitea.nix` and `modules/_legacy/pgbackrest.nix` exist (moved from modules/)
- [ ] `nix flake show` doesn't error (with a minimal placeholder module)
**QA Scenarios**:
```
Scenario: Flake evaluates without errors
Tool: Bash
Preconditions: New flake.nix written, modules/ directory exists with at least one .nix file
Steps:
1. Run `nix flake show --json 2>&1`
2. Assert exit code 0
3. Run `nix flake check --no-build 2>&1`
Expected Result: Both commands exit 0 without evaluation errors
Evidence: .sisyphus/evidence/task-1-flake-eval.txt
Scenario: All inputs preserved
Tool: Bash
Preconditions: New flake.nix written
Steps:
1. Run `nix flake metadata --json | nu -c '$in | from json | get locks.nodes | columns | sort'`
2. Compare against expected list: [darwin, disko, deploy-rs, den, flake-aspects, flake-file, flake-parts, himalaya, home-manager, homebrew-cask, homebrew-core, import-tree, jj-ryu, jj-starship, llm-agents, naersk, nix-homebrew, nixpkgs, nixvim, sops-nix, tuicr, zjstatus]
Expected Result: All inputs present in lock file
Evidence: .sisyphus/evidence/task-1-inputs.txt
```
**Commit**: YES (group with 2, 3)
- Message: `feat(den): bootstrap flake with den + import-tree + flake-file`
- Files: `flake.nix`, `modules/`
- Pre-commit: `alejandra --check .`
- [x] 2. Den bootstrap module — hosts, users, defaults, base
**What to do**:
- Create `modules/den.nix` — import den flakeModule, declare all 4 hosts and their users:
```nix
den.hosts.aarch64-darwin.chidi.users.cschmatzler = {};
den.hosts.aarch64-darwin.jason.users.cschmatzler = {};
den.hosts.x86_64-linux.michael.users.cschmatzler = {};
den.hosts.x86_64-linux.tahani.users.cschmatzler = {};
```
- Set `den.base.user.classes = lib.mkDefault ["homeManager"];`
- Create `modules/defaults.nix` — define `den.default` with:
- `includes = [den.provides.define-user den.provides.inputs']`
- State versions: `nixos.system.stateVersion = "25.11"`, `homeManager.home.stateVersion = "25.11"`
- Darwin state version handled per-class
- Wire flake-file module import if needed
- Create `modules/flake-parts.nix` to import `inputs.flake-parts.flakeModules.modules`
**Must NOT do**:
- Do not add multi-user abstractions
- Do not add batteries not needed
- Do not change stateVersion values
**Recommended Agent Profile**:
- **Category**: `deep`
- Reason: Core den bootstrap — needs understanding of den's host/user/aspect model and correct battery usage
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 1 (after Task 1)
- **Blocks**: Tasks 7-10, all Wave 2+
- **Blocked By**: Task 1
**References**:
- `lib/constants.nix:1-14` — Current constants (user name, SSH keys, stateVersions)
- den docs: https://den.oeiuwq.com/guides/declare-hosts/ — Host/user declaration
- den docs: https://den.oeiuwq.com/guides/batteries/ — Available batteries
- `templates/example/modules/den.nix` in vic/den — Example host declarations
- `templates/minimal/modules/den.nix` in vic/den — Minimal den setup with aspects
**Acceptance Criteria**:
- [ ] `nix eval ".#darwinConfigurations" --json` shows chidi and jason
- [ ] `nix eval ".#nixosConfigurations" --json` shows michael and tahani
- [ ] den.default includes define-user and inputs' batteries
**QA Scenarios**:
```
Scenario: All 4 host configurations exist
Tool: Bash
Preconditions: modules/den.nix and modules/defaults.nix created
Steps:
1. Run `nix eval ".#darwinConfigurations" --json 2>&1 | nu -c '$in | from json | columns | sort'`
2. Assert output contains "chidi" and "jason"
3. Run `nix eval ".#nixosConfigurations" --json 2>&1 | nu -c '$in | from json | columns | sort'`
4. Assert output contains "michael" and "tahani"
Expected Result: All 4 hosts registered
Evidence: .sisyphus/evidence/task-2-hosts.txt
```
**Commit**: YES (group with 1, 3)
- Message: `feat(den): bootstrap flake with den + import-tree + flake-file`
- Files: `modules/den.nix`, `modules/defaults.nix`, `modules/flake-parts.nix`
- Pre-commit: `alejandra --check .`
- [x] 3. Utility functions under _lib/
**What to do**:
- Create `modules/_lib/` directory (underscore prefix = ignored by import-tree)
- Move `lib/build-rust-package.nix` → `modules/_lib/build-rust-package.nix` (preserve content exactly)
- Convert `profiles/wallpaper.nix` → `modules/_lib/wallpaper.nix` (it's a pure function `{pkgs}: ...`, not a module)
- Convert `profiles/open-project.nix` → `modules/_lib/open-project.nix` (pure function)
- Move `lib/constants.nix` → `modules/_lib/constants.nix` (preserve content exactly — values used by den.base and aspects)
- NOTE: `profiles/packages.nix` is also a function (`callPackage`-compatible) — convert to `modules/_lib/packages.nix`
**Must NOT do**:
- Do not modify function signatures or return values
- Do not place these under non-underscore paths (import-tree would fail)
**Recommended Agent Profile**:
- **Category**: `quick`
- Reason: Simple file moves/copies with path updates
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 4, 5, 6)
- **Blocks**: Tasks that reference these utilities (overlays, packages, wallpaper aspects)
- **Blocked By**: Task 1
**References**:
- `lib/constants.nix:1-14` — Constants to move
- `lib/build-rust-package.nix` — Rust package builder overlay helper
- `profiles/wallpaper.nix` — Pure function returning derivation
- `profiles/open-project.nix` — Pure function returning derivation
- `profiles/packages.nix` — callPackage-compatible function returning package list
- import-tree docs: `/_` prefix convention for ignoring paths
**Acceptance Criteria**:
- [ ] `modules/_lib/` directory exists with all 5 files
- [ ] No function files exist under non-underscore paths in modules/
- [ ] `alejandra --check modules/_lib/` passes
**QA Scenarios**:
```
Scenario: Utility files exist and are ignored by import-tree
Tool: Bash
Preconditions: _lib/ directory created with all files
Steps:
1. Verify `modules/_lib/constants.nix` exists
2. Verify `modules/_lib/build-rust-package.nix` exists
3. Verify `modules/_lib/wallpaper.nix` exists
4. Verify `modules/_lib/open-project.nix` exists
5. Verify `modules/_lib/packages.nix` exists
6. Run `nix flake check --no-build` to confirm import-tree ignores _lib/
Expected Result: All files present, flake evaluates without trying to import them as modules
Evidence: .sisyphus/evidence/task-3-lib-files.txt
```
**Commit**: YES (group with 1, 2)
- Message: `feat(den): bootstrap flake with den + import-tree + flake-file`
- Files: `modules/_lib/*`
- [x] 4. Overlays module
**What to do**:
- Create `modules/overlays.nix` — a flake-parts module that defines all overlays
- Port the current overlay pattern: each overlay takes `inputs` from flake-parts module args and produces a nixpkgs overlay
- Register overlays via `nixpkgs.overlays` in `den.default` or via `flake.overlays`
- Port these overlays: himalaya, jj-ryu (uses _lib/build-rust-package.nix), jj-starship (passthrough), tuicr, zjstatus
- The current dynamic loader pattern (`overlays/default.nix`) is no longer needed — each overlay is defined inline or imported from `_lib/`
- Ensure overlays are applied to all hosts via `den.default.nixos` and `den.default.darwin` or equivalent
**Must NOT do**:
- Do not create abstractions over the overlay pattern
- Do not change what packages the overlays produce
- Do not publish `flake.overlays` output (user chose to absorb into aspects — if external consumers need it, reconsider)
**Recommended Agent Profile**:
- **Category**: `quick`
- Reason: Translating existing overlays into a flake-parts module is straightforward
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 3, 5, 6)
- **Blocks**: Tasks 7-9 (overlays must be registered before host configs use overlayed packages)
- **Blocked By**: Tasks 1, 3
**References**:
- `overlays/default.nix:1-18` — Current dynamic overlay loader
- `overlays/himalaya.nix:1-3` — Overlay pattern: `{inputs}: final: prev: { himalaya = inputs.himalaya.packages...; }`
- `overlays/jj-ryu.nix` — Uses `_lib/build-rust-package.nix` helper with naersk
- `overlays/jj-starship.nix` — Passes through upstream overlay
- `overlays/zjstatus.nix` — Package from input
- `overlays/tuicr.nix` — Package from input
**Acceptance Criteria**:
- [ ] `modules/overlays.nix` defines all 5 overlays
- [ ] Overlays are applied to nixpkgs for all hosts
- [ ] `nix eval ".#nixosConfigurations.tahani.config.nixpkgs.overlays" --json` shows overlays present
**QA Scenarios**:
```
Scenario: Overlayed packages are available
Tool: Bash
Preconditions: overlays.nix created and hosts building
Steps:
1. Run `nix eval ".#nixosConfigurations.tahani.pkgs.himalaya" --json 2>&1`
2. Assert it evaluates (doesn't error with "himalaya not found")
Expected Result: himalaya package resolves from overlay
Evidence: .sisyphus/evidence/task-4-overlays.txt
```
**Commit**: NO (groups with Wave 1)
- [x] 5. SOPS secrets aspects (all 4 hosts)
**What to do**:
- Create `modules/secrets.nix` — a flake-parts module handling SOPS for all hosts
- Use den aspects to define per-host secrets:
- `den.aspects.chidi`: darwin SOPS — `sops.age.keyFile = "/Users/cschmatzler/.config/sops/age/keys.txt"`, disable ssh/gnupg paths
- `den.aspects.jason`: same darwin SOPS pattern
- `den.aspects.michael`: NixOS SOPS — `sops.age.sshKeyPaths`, define secrets (michael-gitea-litestream, michael-gitea-restic-password, michael-gitea-restic-env) with exact same sopsFile paths, owners, groups
- `den.aspects.tahani`: NixOS SOPS — define secrets (tahani-paperless-password, tahani-email-password) with exact same paths and owners
- Import sops-nix modules per-class: `den.default.nixos` imports `inputs.sops-nix.nixosModules.sops`, `den.default.darwin` imports `inputs.sops-nix.darwinModules.sops`
- Use flake-file to declare sops-nix input dependency from this module
**Must NOT do**:
- Do not change any sopsFile paths, owners, groups, or formats
- Do not re-encrypt secrets
- Do not modify the `secrets/` directory structure
**Recommended Agent Profile**:
- **Category**: `quick`
- Reason: Direct translation of existing secrets config into den aspects
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 3, 4, 6)
- **Blocks**: Tasks 20-23 (host aspects need secrets available)
- **Blocked By**: Tasks 1, 2
**References**:
- `hosts/chidi/secrets.nix:1-5` — Darwin SOPS pattern
- `hosts/jason/secrets.nix` — Same as chidi
- `hosts/michael/secrets.nix:1-22` — NixOS SOPS with gitea secrets (owners, groups, sopsFile paths)
- `hosts/tahani/secrets.nix:1-13` — NixOS SOPS with paperless + email secrets
- `profiles/nixos.nix:57` — `sops.age.sshKeyPaths` for NixOS
- `secrets/` directory — Encrypted secret files (DO NOT MODIFY)
**Acceptance Criteria**:
- [ ] `nix eval ".#nixosConfigurations.michael.config.sops.secrets" --json | nu -c '$in | from json | columns'` contains michael-gitea-litestream, michael-gitea-restic-password, michael-gitea-restic-env
- [ ] `nix eval ".#nixosConfigurations.tahani.config.sops.secrets" --json | nu -c '$in | from json | columns'` contains tahani-paperless-password, tahani-email-password
**QA Scenarios**:
```
Scenario: SOPS secrets paths preserved exactly
Tool: Bash
Preconditions: secrets.nix module created
Steps:
1. Run `nix eval ".#nixosConfigurations.michael.config.sops.secrets" --json 2>&1`
2. Assert keys include michael-gitea-litestream, michael-gitea-restic-password, michael-gitea-restic-env
3. Run `nix eval ".#nixosConfigurations.tahani.config.sops.secrets" --json 2>&1`
4. Assert keys include tahani-paperless-password, tahani-email-password
Expected Result: All secrets defined with correct paths
Evidence: .sisyphus/evidence/task-5-sops.txt
Scenario: Darwin SOPS uses age keyfile, not SSH
Tool: Bash
Preconditions: secrets.nix module created
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.sops.age.keyFile" 2>&1`
2. Assert output is "/Users/cschmatzler/.config/sops/age/keys.txt"
Expected Result: Darwin hosts use age keyfile path
Evidence: .sisyphus/evidence/task-5-sops-darwin.txt
```
**Commit**: NO (groups with Wave 1)
- [x] 6. Deploy-rs module
**What to do**:
- Create `modules/deploy.nix` — a flake-parts module that configures deploy-rs
- Add `deploy-rs` as a flake input (replacing colmena)
- Define `flake.deploy.nodes.michael` and `flake.deploy.nodes.tahani` with:
- `hostname = "<hostname>"`
- `profiles.system.user = "root"`
- `profiles.system.path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.<hostname>`
- `sshUser = "cschmatzler"`
- Add deploy-rs checks to `flake.checks` via `deploy-rs.lib.x86_64-linux.deployChecks self.deploy`
- Only NixOS hosts — darwin stays local `darwin-rebuild switch`
- Use flake-file to declare deploy-rs input from this module
**Must NOT do**:
- Do not add darwin deploy-rs nodes (limited support)
- Do not remove the perSystem apps that handle darwin apply
**Recommended Agent Profile**:
- **Category**: `quick`
- Reason: Straightforward deploy-rs configuration with two nodes
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 3, 4, 5)
- **Blocks**: Task 27 (verification needs deploy-rs nodes)
- **Blocked By**: Tasks 1, 2
**References**:
- `flake.nix:110-130` — Current colmena config to replace
- deploy-rs docs: https://github.com/serokell/deploy-rs — Node config format
- Current deployment users: `user = "cschmatzler"` for colmena targetUser
**Acceptance Criteria**:
- [ ] `nix eval ".#deploy.nodes.michael.hostname"` returns "michael"
- [ ] `nix eval ".#deploy.nodes.tahani.hostname"` returns "tahani"
- [ ] deploy-rs checks registered in flake checks
**QA Scenarios**:
```
Scenario: deploy-rs nodes exist for NixOS hosts only
Tool: Bash
Preconditions: deploy.nix module created
Steps:
1. Run `nix eval ".#deploy.nodes" --json 2>&1 | nu -c '$in | from json | columns | sort'`
2. Assert output is ["michael", "tahani"]
3. Assert no darwin hosts in deploy nodes
Expected Result: Exactly 2 deploy nodes for NixOS hosts
Evidence: .sisyphus/evidence/task-6-deploy.txt
```
**Commit**: NO (groups with Wave 1)
- [x] 7. Core system aspect (nix settings, shells)
**What to do**:
- Create `modules/core.nix` — den aspect for core nix/system settings shared across all hosts
- Port `profiles/core.nix` content into `den.aspects.core` with appropriate per-class configs:
- `os` class (or both `nixos` and `darwin`): nix settings (experimental-features, auto-optimise-store), fish shell enable, environment.systemPackages (common tools)
- Include this aspect in `den.default.includes` so all hosts get it
- Remove the `user` and `constants` args dependency — use den context instead
**Must NOT do**:
- Do not change nix settings values
- Do not add packages not in current core.nix
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 2 (with Tasks 8-11)
- **Blocks**: Tasks 12-19 (Wave 3 aspects may depend on core being applied)
- **Blocked By**: Tasks 2, 4
**References**:
- `profiles/core.nix` — Current core profile with nix settings, fish, systemPackages
**Acceptance Criteria**:
- [ ] `den.default.includes` contains core aspect
- [ ] nix experimental-features setting preserved
**QA Scenarios**:
```
Scenario: Core nix settings applied to all hosts
Tool: Bash
Steps:
1. Run `nix eval ".#nixosConfigurations.michael.config.nix.settings.experimental-features" --json 2>&1`
2. Assert contains "nix-command" and "flakes"
Expected Result: Nix settings from core.nix applied
Evidence: .sisyphus/evidence/task-7-core.txt
```
**Commit**: NO (groups with Wave 2)
- [x] 8. Darwin system aspect (system defaults, dock, homebrew, nix-homebrew)
**What to do**:
- Create `modules/darwin.nix` — den aspect for darwin-specific system configuration
- Port `profiles/darwin.nix` into `den.aspects.darwin-system`:
- `darwin` class: system.defaults (NSGlobalDomain, dock, finder, trackpad, screencapture, screensaver, loginwindow, spaces, WindowManager, menuExtraClock, CustomUserPreferences), nix GC settings, nix trusted-users
- User creation for darwin (users.users.${user}) — or rely on den.provides.define-user
- `home-manager.useGlobalPkgs = true`
- Port `profiles/dock.nix` — the `local.dock.*` custom option module. Absorb into the darwin aspect or create `modules/dock.nix`
- Port `profiles/homebrew.nix` into `modules/homebrew.nix` — nix-homebrew configuration with taps, casks, etc.
- Include darwin-system aspect in darwin host aspects (chidi, jason)
- Wire nix-homebrew module import via `den.aspects.darwin-system.darwin`
- State version: `system.stateVersion = 6` (from constants.stateVersions.darwin)
**Must NOT do**:
- Do not change any system.defaults values
- Do not change dock entries
- Do not change homebrew taps or cask lists
**Recommended Agent Profile**:
- **Category**: `unspecified-high`
- Reason: Multiple complex darwin profiles need merging into aspects with correct module imports
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 2 (with Tasks 7, 9-11)
- **Blocks**: Tasks 17, 22, 23 (desktop and host-specific darwin)
- **Blocked By**: Tasks 2, 4
**References**:
- `profiles/darwin.nix:1-125` — Full darwin system config (system defaults, users, nix settings)
- `profiles/dock.nix` — Custom `local.dock.*` option module with entries
- `profiles/homebrew.nix` — Homebrew config with taps, brews, casks
- `flake.nix:77-94` — Current darwin configuration wiring (nix-homebrew module, homebrew taps)
**Acceptance Criteria**:
- [ ] All system.defaults values preserved
- [ ] nix-homebrew configured with same taps
- [ ] dock entries preserved
- [ ] `nix eval ".#darwinConfigurations.chidi.config.system.defaults.NSGlobalDomain.AppleInterfaceStyle"` returns null
**QA Scenarios**:
```
Scenario: Darwin system defaults applied
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.system.defaults.dock.autohide" 2>&1`
2. Assert output is "true"
Expected Result: Dock autohide preserved from darwin.nix
Evidence: .sisyphus/evidence/task-8-darwin.txt
```
**Commit**: NO (groups with Wave 2)
- [x] 9. NixOS system aspect (boot, sudo, systemd, users)
**What to do**:
- Create `modules/nixos-system.nix` — den aspect for NixOS-specific system configuration
- Port `profiles/nixos.nix` into `den.aspects.nixos-system`:
- `nixos` class: security.sudo, boot (systemd-boot, EFI, kernel modules, latest kernel), nix settings (trusted-users, gc, nixPath), time.timeZone, user creation with groups
- `home-manager.useGlobalPkgs = true` and `home-manager.sharedModules` — but this should now be handled by den's HM integration battery (no manual _module.args)
- Root user SSH authorized keys from constants
- State version: `system.stateVersion = "25.11"`
- `sops.age.sshKeyPaths` for NixOS hosts
**Must NOT do**:
- Do not change sudo rules
- Do not change boot loader settings
- Do not change user groups
**Recommended Agent Profile**:
- **Category**: `unspecified-high`
- Reason: NixOS system profile has multiple interconnected settings that need careful migration
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 2 (with Tasks 7, 8, 10, 11)
- **Blocks**: Tasks 20, 21, 24 (server and network aspects)
- **Blocked By**: Tasks 2, 4
**References**:
- `profiles/nixos.nix:1-79` — Full NixOS system config (sudo, boot, nix, users, HM wiring)
- `lib/constants.nix:4-7` — SSH keys for root and user
**Acceptance Criteria**:
- [ ] sudo rules preserved exactly
- [ ] boot settings (systemd-boot, kernel) preserved
- [ ] No manual `_module.args` or `sharedModules` for HM — den handles it
- [ ] Root SSH authorized keys set
**QA Scenarios**:
```
Scenario: NixOS boot config preserved
Tool: Bash
Steps:
1. Run `nix eval ".#nixosConfigurations.michael.config.boot.loader.systemd-boot.enable" 2>&1`
2. Assert output is "true"
Expected Result: Boot settings from nixos.nix preserved
Evidence: .sisyphus/evidence/task-9-nixos.txt
```
**Commit**: NO (groups with Wave 2)
- [x] 10. User aspect (cschmatzler — primary user)
**What to do**:
- Create `modules/user.nix` — den aspect for the cschmatzler user
- Define `den.aspects.cschmatzler`:
- `includes`: `den.provides.primary-user`, `(den.provides.user-shell "nushell")`, relevant shared aspects
- `homeManager`: basic home config from `profiles/home.nix` (programs.home-manager.enable, home.packages via callPackage _lib/packages.nix, home.activation for wallpaper on darwin)
- SSH authorized keys from constants
- The user aspect is the central hub that `includes` all feature aspects the user wants
- Per-host email overrides will be in host aspects (Tasks 22-23), not here
- Default email: `homeManager.programs.git.settings.user.email` left for host-specific override
**Must NOT do**:
- Do not add features not in current user config
- Do not set git email here (it's per-host)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 2 (with Tasks 7-9, 11)
- **Blocks**: Tasks 12-19 (HM aspects)
- **Blocked By**: Task 2
**References**:
- `profiles/home.nix:1-24` — Home base config (home-manager enable, packages, wallpaper activation)
- `lib/constants.nix:2` — User name "cschmatzler"
- `lib/constants.nix:4-7` — SSH authorized keys
- den batteries docs: https://den.oeiuwq.com/guides/batteries/ — primary-user, user-shell
**Acceptance Criteria**:
- [ ] `den.aspects.cschmatzler` defined with primary-user and user-shell batteries
- [ ] Home packages list from packages.nix applied
**QA Scenarios**:
```
Scenario: User aspect creates correct system user
Tool: Bash
Steps:
1. Run `nix eval ".#nixosConfigurations.michael.config.users.users.cschmatzler.isNormalUser" 2>&1`
2. Assert output is "true"
Expected Result: User created with isNormalUser
Evidence: .sisyphus/evidence/task-10-user.txt
```
**Commit**: NO (groups with Wave 2)
- [x] 11. perSystem apps module
**What to do**:
- Create `modules/apps.nix` — a flake-parts module that defines perSystem apps
- Port the current `perSystem` block from flake.nix that creates apps: build, apply, build-switch, rollback
- These apps reference `apps/${system}/${name}` shell scripts — keep this mechanism
- Ensure apps are available for both x86_64-linux and aarch64-darwin
**Must NOT do**:
- Do not change app behavior (the actual script rewrite to Nushell is Task 29)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 2 (with Tasks 7-10)
- **Blocks**: Task 27 (verification)
- **Blocked By**: Task 1
**References**:
- `flake.nix:143-165` — Current perSystem apps block
- `apps/` directory — Shell scripts for each platform
**Acceptance Criteria**:
- [ ] `nix eval ".#apps.aarch64-darwin" --json | nu -c '$in | from json | columns'` includes build, apply
- [ ] `nix eval ".#apps.x86_64-linux" --json | nu -c '$in | from json | columns'` includes build, apply
**QA Scenarios**:
```
Scenario: perSystem apps accessible
Tool: Bash
Steps:
1. Run `nix eval ".#apps.aarch64-darwin" --json 2>&1 | nu -c '$in | from json | columns | sort'`
2. Assert contains apply, build, build-switch, rollback
Expected Result: All 4 apps registered
Evidence: .sisyphus/evidence/task-11-apps.txt
```
**Commit**: NO (groups with Wave 2)
- [ ] 12. Shell aspects (nushell, bash, zsh, starship)
**What to do**:
- Create `modules/shell.nix` — den aspect grouping shell-related HM config
- Port into `den.aspects.shell.homeManager`: nushell config (`profiles/nushell.nix` — programs.nushell with aliases, env, config, platform-conditional PATH), bash config (`profiles/bash.nix`), zsh config (`profiles/zsh.nix`), starship config (`profiles/starship.nix` — programs.starship with settings)
- Keep `stdenv.isDarwin` platform checks in HM config — these are correct in HM context
- Include shell aspect in user aspect (Task 10's `den.aspects.cschmatzler.includes`)
**Must NOT do**:
- Do not change shell aliases, env vars, or config values
- Do not split into 4 separate aspects (these are naturally grouped)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 13-19)
- **Blocks**: Tasks 20-25
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/nushell.nix` — Nushell config with platform conditionals
- `profiles/bash.nix` — Bash config
- `profiles/zsh.nix` — Zsh config
- `profiles/starship.nix` — Starship prompt config
**Acceptance Criteria**:
- [ ] All shell programs configured in HM
- [ ] Platform-conditional nushell PATH preserved
**QA Scenarios**:
```
Scenario: Shell programs enabled in HM
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.nushell.enable" 2>&1`
2. Assert true
Expected Result: Nushell enabled
Evidence: .sisyphus/evidence/task-12-shell.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 13. Dev tools aspects (git, jujutsu, lazygit, jjui, direnv, mise)
**What to do**:
- Create `modules/dev-tools.nix` — den aspect grouping developer tools HM config
- Port into `den.aspects.dev-tools.homeManager`: git config (`profiles/git.nix` — programs.git with aliases, settings, ignores, diff tools), jujutsu config (`profiles/jujutsu.nix`), lazygit (`profiles/lazygit.nix`), jjui (`profiles/jjui.nix`), direnv (`profiles/direnv.nix`), mise (`profiles/mise.nix`)
- NOTE: Do NOT set `programs.git.settings.user.email` here — that's per-host
- Include dev-tools aspect in user aspect
**Must NOT do**:
- Do not change git aliases or settings
- Do not set git email (per-host override)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12, 14-19)
- **Blocks**: Tasks 20-25
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/git.nix` — Git config with delta, aliases, ignores
- `profiles/jujutsu.nix` — Jujutsu VCS config
- `profiles/lazygit.nix` — Lazygit TUI
- `profiles/jjui.nix` — jj TUI
- `profiles/direnv.nix` — Direnv with nix-direnv
- `profiles/mise.nix` — Mise version manager
**Acceptance Criteria**:
- [ ] All dev tools configured
- [ ] Git email NOT set in this aspect
**QA Scenarios**:
```
Scenario: Git configured without email override
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.git.enable" 2>&1`
2. Assert true
Expected Result: Git enabled in HM
Evidence: .sisyphus/evidence/task-13-devtools.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 14. Editor aspects (neovim/nixvim)
**What to do**:
- Create `modules/neovim/` directory with den aspect for neovim/nixvim
- Port the entire `profiles/neovim/` directory (16+ files) into den aspect structure
- The main module (`profiles/neovim/default.nix`) imports all plugin configs — replicate this structure
- Import `inputs.nixvim.homeModules.nixvim` in the HM class config
- Use flake-file to declare nixvim input dependency from this module
- The neovim sub-files can be imported via the module's own imports (NOT via import-tree — they're HM modules, not flake-parts modules). Place them under `modules/neovim/_plugins/` or similar to avoid import-tree scanning them, OR use den aspect's `homeManager.imports`
**Must NOT do**:
- Do not change any neovim plugin configurations
- Do not restructure plugin files unnecessarily
**Recommended Agent Profile**:
- **Category**: `unspecified-high`
- Reason: neovim config is 16+ files with complex imports — needs careful migration of the import chain
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12-13, 15-19)
- **Blocks**: Tasks 20-23
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/neovim/default.nix` — Main neovim module (imports all plugins)
- `profiles/neovim/` — 16+ plugin config files
- nixvim input: `inputs.nixvim.homeModules.nixvim`
**Acceptance Criteria**:
- [ ] All neovim plugins configured
- [ ] nixvim HM module imported
**QA Scenarios**:
```
Scenario: Nixvim enabled in home-manager
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.nixvim.enable" 2>&1`
2. Assert true (or check that nixvim module is imported)
Expected Result: Nixvim working
Evidence: .sisyphus/evidence/task-14-neovim.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 15. Terminal aspects (ghostty, zellij, yazi, bat, fzf, ripgrep, zoxide)
**What to do**:
- Create `modules/terminal.nix` — den aspect for terminal tools HM config
- Port: ghostty (`profiles/ghostty.nix`), bat (`profiles/bat.nix`), fzf (`profiles/fzf.nix`), ripgrep (`profiles/ripgrep.nix`), zoxide (`profiles/zoxide.nix`), yazi (`profiles/yazi.nix`)
- Create `modules/zellij.nix` — separate aspect for zellij because it needs per-host behavior
- Port `profiles/zellij.nix` but REMOVE the `osConfig.networking.hostName == "tahani"` check
- Instead, the tahani-specific zellij auto-start behavior goes in Task 21 (tahani host aspect)
**Must NOT do**:
- Do not use hostname string comparisons
- Do not change tool configurations
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12-14, 16-19)
- **Blocks**: Tasks 20-25
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/zellij.nix` — Zellij with host-specific autostart check (line 20: `osConfig.networking.hostName == "tahani"`)
- `profiles/ghostty.nix`, `profiles/bat.nix`, `profiles/fzf.nix`, `profiles/ripgrep.nix`, `profiles/zoxide.nix`, `profiles/yazi.nix`
**Acceptance Criteria**:
- [ ] All terminal tools configured
- [ ] No hostname string comparisons in shared aspects
- [ ] Zellij base config without host-specific auto-start
**QA Scenarios**:
```
Scenario: Terminal tools enabled
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.bat.enable" 2>&1`
2. Assert true
Expected Result: bat enabled in HM
Evidence: .sisyphus/evidence/task-15-terminal.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 16. Communication aspects (himalaya, mbsync, ssh)
**What to do**:
- Create `modules/email.nix` — den aspect for email (himalaya + mbsync)
- Port `profiles/himalaya.nix` — the himalaya HM module that creates a wrapper script (`writeShellApplication` around himalaya binary with IMAP password from sops)
- Port `profiles/mbsync.nix` — mbsync HM config
- CRITICAL: The himalaya wrapper package is currently accessed from NixOS scope in tahani (cross-module dependency). In den, instead create the wrapper within the `homeManager` class AND also make it available to the NixOS `nixos` class via the overlay package (himalaya overlay already exists). The inbox-triage systemd service in Task 21 should use `pkgs.himalaya` (from overlay) directly, not reach into HM config.
- Create `modules/ssh-client.nix` — den aspect for SSH client config
- Port `profiles/ssh.nix` — SSH client HM config with platform-conditional paths
**Must NOT do**:
- Do not create cross-module dependencies (HM → NixOS reads)
- Do not change himalaya or mbsync settings
**Recommended Agent Profile**:
- **Category**: `unspecified-high`
- Reason: Himalaya cross-dependency redesign is architecturally critical
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12-15, 17-19)
- **Blocks**: Tasks 20, 21
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/himalaya.nix` — Himalaya HM module with writeShellApplication wrapper
- `profiles/mbsync.nix` — mbsync config
- `profiles/ssh.nix` — SSH client config with platform paths
- `hosts/tahani/default.nix:9` — The cross-module dependency to eliminate: `himalaya = config.home-manager.users.${user}.programs.himalaya.package`
- `overlays/himalaya.nix:1-3` — Himalaya overlay (packages from input)
**Acceptance Criteria**:
- [ ] Himalaya configured in HM
- [ ] No cross-module HM→NixOS reads
- [ ] SSH client configured with correct platform paths
**QA Scenarios**:
```
Scenario: Himalaya configured without cross-module dependency
Tool: Bash
Steps:
1. Grep new modules/ for "config.home-manager.users"
2. Assert zero matches (no HM config reads from NixOS scope)
Expected Result: No cross-module dependency
Evidence: .sisyphus/evidence/task-16-email.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 17. Desktop aspects (aerospace, home base)
**What to do**:
- Create `modules/desktop.nix` — den aspect for desktop/GUI HM config
- Port `profiles/aerospace.nix` — AeroSpace tiling WM config (darwin-only HM module)
- The wallpaper activation from `profiles/home.nix` is already in _lib/wallpaper.nix — wire it via the user aspect's darwin HM config
- Include fonts.fontconfig.enable for darwin hosts (currently in chidi/jason host configs)
**Must NOT do**:
- Do not change aerospace settings
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12-16, 18-19)
- **Blocks**: Tasks 22, 23
- **Blocked By**: Tasks 7, 8, 10
**References**:
- `profiles/aerospace.nix` — AeroSpace WM config
- `hosts/chidi/default.nix:50` — `fonts.fontconfig.enable = true`
**Acceptance Criteria**:
- [ ] AeroSpace configured in darwin HM
- [ ] fonts.fontconfig.enable set for darwin hosts
**QA Scenarios**:
```
Scenario: AeroSpace configured for darwin hosts
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.aerospace.enable" 2>&1` (or check relevant config)
Expected Result: AeroSpace enabled
Evidence: .sisyphus/evidence/task-17-desktop.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 18. AI tools aspects (opencode, claude-code)
**What to do**:
- Create `modules/ai-tools.nix` — den aspect for AI coding tools
- Port `profiles/opencode.nix` — references `inputs.llm-agents.packages...opencode`
- Port `profiles/claude-code.nix` — references `inputs.llm-agents.packages...claude-code`
- These need `inputs` access — use den's `inputs'` battery or access `inputs` from flake-parts module args
- Use flake-file to declare llm-agents input dependency
- NOTE: The opencode config may include `profiles/opencode/` directory content
**Must NOT do**:
- Do not change opencode or claude-code settings
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12-17, 19)
- **Blocks**: Task 21 (tahani uses opencode in systemd service)
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/opencode.nix` — OpenCode HM config using inputs.llm-agents
- `profiles/claude-code.nix` — Claude Code HM config using inputs.llm-agents
- `profiles/opencode/` — OpenCode config directory (if exists)
**Acceptance Criteria**:
- [ ] Both AI tools configured in HM
- [ ] inputs.llm-agents accessed correctly (not via specialArgs)
**QA Scenarios**:
```
Scenario: AI tools packages available
Tool: Bash
Steps:
1. Verify opencode and claude-code are in home packages (nix eval)
Expected Result: Both tools in user's home packages
Evidence: .sisyphus/evidence/task-18-ai.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 19. Miscellaneous aspects (atuin, zk)
**What to do**:
- Create `modules/atuin.nix` — den aspect for atuin (shell history sync)
- Port `profiles/atuin.nix` into `den.aspects.atuin.homeManager`
- Create `modules/zk.nix` — den aspect for zk (zettelkasten)
- Port `profiles/zk.nix` into `den.aspects.zk.homeManager`
- Include both in user aspect
**Must NOT do**:
- Do not change settings
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 3 (with Tasks 12-18)
- **Blocks**: Tasks 20-25
- **Blocked By**: Tasks 7, 10
**References**:
- `profiles/atuin.nix` — Atuin config
- `profiles/zk.nix` — Zk zettelkasten config
**Acceptance Criteria**:
- [ ] Atuin and zk configured in HM
**QA Scenarios**:
```
Scenario: Atuin enabled
Tool: Bash
Steps:
1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.atuin.enable" 2>&1`
2. Assert true
Expected Result: Atuin enabled
Evidence: .sisyphus/evidence/task-19-misc.txt
```
**Commit**: NO (groups with Wave 3)
- [ ] 20. Server aspects — michael (gitea + litestream + restic)
**What to do**:
- Create `modules/michael.nix` — den aspect for michael host
- Port michael-specific config into `den.aspects.michael`:
- `nixos` class: import disko module (`inputs.disko.nixosModules.disko`), import disk-config.nix and hardware-configuration.nix (place originals under `modules/_hosts/michael/` with `_` prefix to avoid import-tree)
- `nixos` class: gitea service config — absorb the entire `modules/gitea.nix` custom module into this aspect. The `my.gitea` options become direct service configuration (services.gitea, services.litestream, services.restic). Use SOPS secrets from Task 5.
- `nixos` class: `modulesPath` imports (installer/scan/not-detected.nix, profiles/qemu-guest.nix)
- `nixos` class: `networking.hostName = "michael"`
- Include: nixos-system, core, openssh, fail2ban, tailscale aspects
- HM: minimal imports — only nushell, home base, ssh, nixvim
- Git email override: `homeManager.programs.git.settings.user.email` is NOT set (michael has no email override in current config)
- Wire disko input via flake-file
**Must NOT do**:
- Do not change gitea service configuration values
- Do not change disk-config or hardware-configuration
- Do not create abstractions over gitea's backup system
**Recommended Agent Profile**:
- **Category**: `deep`
- Reason: Complex server with custom gitea module absorption, disko, hardware config, and multiple service interactions
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 21-25)
- **Blocks**: Task 27
- **Blocked By**: Tasks 5, 9, 14, 16
**References**:
- `hosts/michael/default.nix:1-48` — Full michael host config
- `hosts/michael/disk-config.nix` — Disko partition config
- `hosts/michael/hardware-configuration.nix` — Hardware config
- `modules/gitea.nix` — Custom my.gitea module (litestream, restic, s3) to absorb
- `hosts/michael/secrets.nix:1-22` — SOPS secrets (already in Task 5)
**Acceptance Criteria**:
- [ ] `nix build ".#nixosConfigurations.michael.config.system.build.toplevel"` succeeds
- [ ] Gitea service configured with litestream and restic
- [ ] Disko disk-config preserved
**QA Scenarios**:
```
Scenario: Michael host builds successfully
Tool: Bash
Steps:
1. Run `nix build ".#nixosConfigurations.michael.config.system.build.toplevel" --dry-run 2>&1`
2. Assert exit code 0
Expected Result: Michael builds without errors
Evidence: .sisyphus/evidence/task-20-michael.txt
Scenario: Gitea service configured
Tool: Bash
Steps:
1. Run `nix eval ".#nixosConfigurations.michael.config.services.gitea.enable" 2>&1`
2. Assert true
Expected Result: Gitea enabled
Evidence: .sisyphus/evidence/task-20-gitea.txt
```
**Commit**: NO (groups with Wave 4)
- [ ] 21. Server aspects — tahani (adguard, paperless, docker, inbox-triage)
**What to do**:
- Create `modules/tahani.nix` — den aspect for tahani host
- Port tahani-specific config into `den.aspects.tahani`:
- `nixos` class: import tahani-specific files (adguardhome.nix, cache.nix, networking.nix, paperless.nix — place under `modules/_hosts/tahani/`)
- `nixos` class: `networking.hostName = "tahani"`, `virtualisation.docker.enable`, docker group for user
- `nixos` class: swap device config
- `nixos` class: **Inbox-triage systemd service** — REDESIGNED to avoid cross-module dependency:
- Use `pkgs.himalaya` (from overlay, NOT from HM config) for the himalaya binary
- Use `inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.opencode` for opencode binary
- Define systemd service and timer exactly as current
- Include: nixos-system, core, openssh, tailscale aspects
- HM: all the profiles that tahani currently imports (most shared aspects + himalaya, mbsync)
- HM: git email override: `homeManager.programs.git.settings.user.email = "christoph@schmatzler.com"`
- HM: zellij auto-start override (the tahani-specific behavior from zellij.nix)
**Must NOT do**:
- Do not read HM config from NixOS scope (no `config.home-manager.users...`)
- Do not change service configs (adguard, paperless, docker)
**Recommended Agent Profile**:
- **Category**: `deep`
- Reason: Most complex host — has the himalaya cross-dependency redesign, multiple services, and the most HM profiles
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 20, 22-25)
- **Blocks**: Task 27
- **Blocked By**: Tasks 5, 9, 16, 18
**References**:
- `hosts/tahani/default.nix:1-94` — Full tahani config with cross-module dependency
- `hosts/tahani/adguardhome.nix` — AdGuard Home service config
- `hosts/tahani/cache.nix` — Cache config
- `hosts/tahani/networking.nix` — Network config
- `hosts/tahani/paperless.nix` — Paperless-NGX service config
- `profiles/zellij.nix:20` — The hostname check to replace with per-host override
- `overlays/himalaya.nix` — Himalaya available as `pkgs.himalaya` via overlay
**Acceptance Criteria**:
- [ ] `nix build ".#nixosConfigurations.tahani.config.system.build.toplevel"` succeeds
- [ ] Inbox-triage systemd service uses `pkgs.himalaya` (overlay), NOT HM config read
- [ ] Zero instances of `config.home-manager.users` in any module
- [ ] Zellij auto-start is tahani-specific (not hostname comparison)
**QA Scenarios**:
```
Scenario: Tahani builds without cross-module dependency
Tool: Bash
Steps:
1. Run `nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" --dry-run 2>&1`
2. Assert exit code 0
3. Grep modules/ for "config.home-manager.users"
4. Assert zero matches
Expected Result: Tahani builds, no HM→NixOS cross-reads
Evidence: .sisyphus/evidence/task-21-tahani.txt
Scenario: Docker enabled on tahani
Tool: Bash
Steps:
1. Run `nix eval ".#nixosConfigurations.tahani.config.virtualisation.docker.enable" 2>&1`
2. Assert true
Expected Result: Docker enabled
Evidence: .sisyphus/evidence/task-21-docker.txt
```
**Commit**: NO (groups with Wave 4)
- [ ] 22. Host aspects — chidi (work-specific)
**What to do**:
- Create `modules/chidi.nix` — den aspect for chidi (work laptop)
- Define `den.aspects.chidi`:
- Include: darwin-system, core, tailscale, all shared user aspects
- `darwin` class: `environment.systemPackages = [pkgs.slack]` (work-specific)
- `darwin` class: `networking.hostName = "chidi"`, `networking.computerName = "chidi"`
- `homeManager` class: `programs.git.settings.user.email = "christoph@tuist.dev"` (work email)
- `homeManager` class: `fonts.fontconfig.enable = true`
**Must NOT do**:
- Do not add work-specific packages beyond Slack
- Do not change git email
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 20-21, 23-25)
- **Blocks**: Task 27
- **Blocked By**: Tasks 5, 8, 17
**References**:
- `hosts/chidi/default.nix:1-57` — Current chidi config
- `hosts/chidi/secrets.nix:1-5` — Already handled in Task 5
**Acceptance Criteria**:
- [ ] `nix build ".#darwinConfigurations.chidi.system"` succeeds
- [ ] Slack in systemPackages
- [ ] Git email = "christoph@tuist.dev"
**QA Scenarios**:
```
Scenario: Chidi builds with work email
Tool: Bash
Steps:
1. Run `nix build ".#darwinConfigurations.chidi.system" --dry-run 2>&1`
2. Assert exit code 0
3. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.git.settings.user.email" 2>&1`
4. Assert output is "christoph@tuist.dev"
Expected Result: Chidi builds, work email set
Evidence: .sisyphus/evidence/task-22-chidi.txt
```
**Commit**: NO (groups with Wave 4)
- [ ] 23. Host aspects — jason (personal-specific)
**What to do**:
- Create `modules/jason.nix` — den aspect for jason (personal laptop)
- Define `den.aspects.jason`:
- Include: darwin-system, core, tailscale, all shared user aspects
- `darwin` class: `networking.hostName = "jason"`, `networking.computerName = "jason"`
- `homeManager` class: `programs.git.settings.user.email = "christoph@schmatzler.com"` (personal email)
- `homeManager` class: `fonts.fontconfig.enable = true`
**Must NOT do**:
- Do not add packages not in current jason config
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 20-22, 24-25)
- **Blocks**: Task 27
- **Blocked By**: Tasks 5, 8, 17
**References**:
- `hosts/jason/default.nix:1-52` — Current jason config
**Acceptance Criteria**:
- [ ] `nix build ".#darwinConfigurations.jason.system"` succeeds
- [ ] Git email = "christoph@schmatzler.com"
**QA Scenarios**:
```
Scenario: Jason builds with personal email
Tool: Bash
Steps:
1. Run `nix build ".#darwinConfigurations.jason.system" --dry-run 2>&1`
2. Assert exit code 0
Expected Result: Jason builds successfully
Evidence: .sisyphus/evidence/task-23-jason.txt
```
**Commit**: NO (groups with Wave 4)
- [ ] 24. Network aspects (openssh, fail2ban, tailscale, postgresql)
**What to do**:
- Create `modules/network.nix` — den aspect for network services
- Port `profiles/openssh.nix` → `den.aspects.openssh.nixos` (SSH server config)
- Port `profiles/fail2ban.nix` → `den.aspects.fail2ban.nixos`
- Port `profiles/tailscale.nix` → `den.aspects.tailscale` with per-class configs (nixos + darwin support, uses `lib.optionalAttrs pkgs.stdenv.isLinux` currently — convert to per-class)
- Port `profiles/postgresql.nix` → `den.aspects.postgresql.nixos` (if used by any host)
- Include these in appropriate host aspects
**Must NOT do**:
- Do not change SSH, fail2ban, or tailscale settings
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 20-23, 25)
- **Blocks**: Task 27
- **Blocked By**: Task 9
**References**:
- `profiles/openssh.nix` — OpenSSH server config
- `profiles/fail2ban.nix` — Fail2ban config
- `profiles/tailscale.nix` — Tailscale with platform conditionals
- `profiles/postgresql.nix` — PostgreSQL config
**Acceptance Criteria**:
- [ ] All network services configured
- [ ] Tailscale works on both darwin and nixos
**QA Scenarios**:
```
Scenario: Tailscale enabled on all hosts
Tool: Bash
Steps:
1. Run `nix eval ".#nixosConfigurations.michael.config.services.tailscale.enable" 2>&1`
2. Assert true
Expected Result: Tailscale enabled
Evidence: .sisyphus/evidence/task-24-network.txt
```
**Commit**: NO (groups with Wave 4)
- [ ] 25. Packages aspect (system packages list)
**What to do**:
- Ensure the home.packages list from `_lib/packages.nix` is wired into the user aspect
- The `callPackage` pattern for packages.nix should be replicated — import `_lib/packages.nix` and pass required args
- Ensure platform-conditional packages (`lib.optionals stdenv.isDarwin/isLinux`) are preserved
- Remove colmena from the package list, add deploy-rs CLI if needed
**Must NOT do**:
- Do not add packages not in current packages.nix (except deploy-rs CLI)
- Do not remove colmena replacement from packages without confirming
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 20-24)
- **Blocks**: Task 27
- **Blocked By**: Task 7
**References**:
- `profiles/packages.nix` — Full package list (moved to _lib/packages.nix in Task 3)
- `profiles/home.nix:13` — `home.packages = pkgs.callPackage ./packages.nix {inherit inputs;};`
**Acceptance Criteria**:
- [ ] All packages from current list present
- [ ] Platform-conditional packages preserved
**QA Scenarios**:
```
Scenario: Home packages include expected tools
Tool: Bash
Steps:
1. Verify packages.nix is loaded and packages are in home.packages
Expected Result: Packages available
Evidence: .sisyphus/evidence/task-25-packages.txt
```
**Commit**: NO (groups with Wave 4)
- [ ] 26. Remove old structure
**What to do**:
- Delete `hosts/` directory entirely
- Delete `profiles/` directory entirely
- Delete `modules/_legacy/` directory (old NixOS modules moved here by Task 1, now fully absorbed into den aspects)
- Delete `overlays/` directory entirely (now in modules/overlays.nix)
- Delete `lib/` directory entirely (now in modules/_lib/)
- Keep `secrets/` directory (encrypted files, unchanged)
- Keep `apps/` directory (rewritten to Nushell by Task 29)
- Keep `.sops.yaml` (unchanged)
- Verify no broken references remain
**Must NOT do**:
- Do not delete `secrets/`, `apps/`, `.sops.yaml`, or `alejandra.toml`
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: [`git-master`]
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 5 (after Task 27 confirms all builds pass)
- **Blocks**: F1-F4
- **Blocked By**: Task 27
**References**:
- All old directories to remove: hosts/, profiles/, overlays/, lib/
**Acceptance Criteria**:
- [ ] Old directories removed
- [ ] `nix flake check` still passes after removal
- [ ] No broken file references
**QA Scenarios**:
```
Scenario: Old structure removed, builds still pass
Tool: Bash
Steps:
1. Verify hosts/ directory doesn't exist
2. Verify profiles/ directory doesn't exist
3. Run `nix flake check --no-build 2>&1`
4. Assert exit code 0
Expected Result: Clean structure, still evaluates
Evidence: .sisyphus/evidence/task-26-cleanup.txt
```
**Commit**: YES
- Message: `chore: remove old host-centric structure`
- Files: deleted directories
- Pre-commit: `nix flake check --no-build && alejandra --check .`
- [ ] 27. Full build verification all 4 hosts
**What to do**:
- Build ALL 4 host configurations (or dry-run if cross-platform):
- `nix build ".#darwinConfigurations.chidi.system" --dry-run`
- `nix build ".#darwinConfigurations.jason.system" --dry-run`
- `nix build ".#nixosConfigurations.michael.config.system.build.toplevel" --dry-run`
- `nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" --dry-run`
- Run `nix flake check`
- Run `alejandra --check .`
- Verify deploy-rs nodes: `nix eval ".#deploy.nodes" --json`
- Verify zero specialArgs: grep for specialArgs in all .nix files
- Verify no hostname comparisons in shared aspects
- Verify no HM→NixOS cross-reads
**Must NOT do**:
- Do not skip any host build
**Recommended Agent Profile**:
- **Category**: `deep`
- Reason: Comprehensive verification requiring multiple build commands and assertions
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 5 (after all implementation)
- **Blocks**: Tasks 26, 28, F1-F4
- **Blocked By**: Tasks 20-25
**References**:
- Definition of Done section of this plan
- All acceptance criteria from all tasks
**Acceptance Criteria**:
- [ ] All 4 hosts build (or dry-run) successfully
- [ ] `nix flake check` passes
- [ ] `alejandra --check .` passes
- [ ] deploy-rs nodes exist for michael and tahani
- [ ] Zero specialArgs usage in codebase
- [ ] Zero hostname string comparisons in shared modules
**QA Scenarios**:
```
Scenario: Complete build verification
Tool: Bash
Steps:
1. Build all 4 hosts (dry-run)
2. Run nix flake check
3. Run alejandra --check .
4. Eval deploy-rs nodes
5. Grep for specialArgs — assert 0 matches
6. Grep for "networking.hostName ==" in shared modules — assert 0 matches
Expected Result: All checks pass
Evidence: .sisyphus/evidence/task-27-verification.txt
```
**Commit**: NO
- [ ] 28. Formatting pass + final cleanup
**What to do**:
- Run `alejandra .` to format all files
- Remove any TODO comments or placeholder code
- Verify `modules/` directory structure is clean
- Ensure `.sops.yaml` still references correct secret file paths
- Final `nix flake check`
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 5 (after Task 27)
- **Blocks**: F1-F4
- **Blocked By**: Task 27
**References**:
- `alejandra.toml` — Formatter config (tabs for indentation)
**Acceptance Criteria**:
- [ ] `alejandra --check .` passes
- [ ] No TODO/FIXME/HACK comments
**QA Scenarios**:
```
Scenario: All files formatted
Tool: Bash
Steps:
1. Run `alejandra --check . 2>&1`
2. Assert exit code 0
Expected Result: All files pass formatter
Evidence: .sisyphus/evidence/task-28-format.txt
```
**Commit**: YES
- Message: `feat: rewrite config with den framework`
- Files: all modules/*, flake.nix, flake.lock
- Pre-commit: `nix flake check && alejandra --check .`
- [ ] 29. Rewrite apps/ scripts from bash to Nushell
**What to do**:
- Rewrite `apps/common.sh` → `apps/common.nu` — convert colored output helper functions (`print_info`, `print_success`, `print_error`, `print_warning`) to Nushell using `ansi` commands
- Rewrite all 4 darwin scripts (`apps/aarch64-darwin/{build,apply,build-switch,rollback}`) from bash to Nushell:
- `build`: hostname detection via `scutil --get LocalHostName` (fallback `hostname -s`), `nix build` darwin config, unlink result
- `apply`: hostname detection, `nix run nix-darwin -- switch`
- `build-switch`: hostname detection, `nix build` then `sudo darwin-rebuild switch`, unlink result
- `rollback`: list generations via `darwin-rebuild --list-generations`, prompt for generation number via `input`, switch to it
- Rewrite all 4 linux scripts (`apps/x86_64-linux/{build,apply,build-switch,rollback}`) from bash to Nushell:
- `build`: hostname via `hostname`, `nix build` nixos config, unlink result
- `apply`: hostname, sudo-aware `nixos-rebuild switch`
- `build-switch`: hostname, `nix build` then sudo-aware `nixos-rebuild switch`
- `rollback`: list generations via sudo-aware `nix-env --profile ... --list-generations`, prompt for number via `input`, sudo-aware switch-generation + switch-to-configuration
- All scripts get `#!/usr/bin/env nu` shebang
- Delete `apps/common.sh` after `apps/common.nu` is created
- Use `use ../common.nu *` (or equivalent) to import shared helpers in each script
- Preserve exact same behavior — same commands, same output messages, same error handling
- Handle sudo checks in linux scripts: use `(id -u)` or `$env.EUID` equivalent in Nushell
**Must NOT do**:
- Do not change any nix build/switch/rebuild commands — only the shell scripting around them changes
- Do not add new functionality beyond what exists in the bash scripts
- Do not change the file names (the perSystem apps module references them by path)
**Recommended Agent Profile**:
- **Category**: `quick`
- Reason: Straightforward 1:1 rewrite of 9 small scripts (~186 lines total) from bash to Nushell. No architectural decisions needed.
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 4 (with Tasks 20-25)
- **Blocks**: Task 27 (build verification should confirm apps still work)
- **Blocked By**: Task 11 (perSystem apps module must be in place first)
**References**:
**Pattern References**:
- `apps/common.sh` — Current colored output helpers (23 lines) — rewrite to Nushell `ansi` equivalents
- `apps/aarch64-darwin/build` — Darwin build script (16 lines) — template for all darwin scripts
- `apps/x86_64-linux/build` — Linux build script (16 lines) — template for all linux scripts
- `apps/x86_64-linux/rollback` — Most complex script (30 lines, sudo checks, interactive input) — key test case
**API/Type References**:
- `modules/apps.nix` (created by Task 11) — perSystem apps module that references these scripts by path
**External References**:
- Nushell documentation: https://www.nushell.sh/book/ — Language reference for bash→nu translation
- Nushell `ansi` command: for colored output (replaces ANSI escape codes)
- Nushell `input` command: for interactive prompts (replaces bash `read -r`)
**Acceptance Criteria**:
- [ ] All 9 scripts rewritten with `#!/usr/bin/env nu` shebang
- [ ] `apps/common.nu` exists with colored output helpers
- [ ] `apps/common.sh` deleted
- [ ] `nix run ".#build" -- --help 2>&1` doesn't error (script is parseable by nu)
- [ ] `nix run ".#apply" -- --help 2>&1` doesn't error
- [ ] No bash files remain in `apps/` directory
**QA Scenarios**:
```
Scenario: All apps/ scripts are valid Nushell
Tool: Bash
Preconditions: Task 11 (perSystem apps module) complete, all scripts rewritten
Steps:
1. Run `find apps/ -type f -name "*.sh" 2>&1` — assert no .sh files remain
2. Run `head -1 apps/aarch64-darwin/build` — assert contains "#!/usr/bin/env nu"
3. Run `head -1 apps/x86_64-linux/build` — assert contains "#!/usr/bin/env nu"
4. Run `nu -c 'source apps/common.nu; print_info "test"' 2>&1` — assert exit code 0 and output contains "[INFO]"
5. Run `nu -c 'source apps/common.nu; print_error "test"' 2>&1` — assert exit code 0 and output contains "[ERROR]"
Expected Result: All scripts are Nushell, common.nu functions work
Failure Indicators: Any .sh file found, shebang mismatch, nu parse errors
Evidence: .sisyphus/evidence/task-29-nushell-scripts.txt
Scenario: perSystem apps still reference correct scripts
Tool: Bash
Preconditions: Apps module (Task 11) and scripts (Task 29) both complete
Steps:
1. Run `nix eval ".#apps.x86_64-linux" --json 2>&1 | nu -c '$in | from json | columns | sort'`
2. Assert output contains: apply, build, build-switch, rollback
3. Run `nix eval ".#apps.aarch64-darwin" --json 2>&1 | nu -c '$in | from json | columns | sort'`
4. Assert output contains: apply, build, build-switch, rollback
Expected Result: All 4 apps registered on both platforms
Failure Indicators: Missing app entries, nix eval errors
Evidence: .sisyphus/evidence/task-29-apps-registered.txt
```
**Commit**: YES
- Message: `refactor: rewrite app scripts from bash to nushell`
- Files: `apps/common.nu`, `apps/aarch64-darwin/*`, `apps/x86_64-linux/*`
- Pre-commit: `nu -c 'source apps/common.nu'`
---
## Final Verification Wave (MANDATORY — after ALL implementation tasks)
> 4 review agents run in PARALLEL. ALL must APPROVE. Rejection → fix → re-run.
- [ ] F1. **Plan Compliance Audit** — `oracle`
Read the plan end-to-end. For each "Must Have": verify implementation exists (nix eval, read file). For each "Must NOT Have": search codebase for forbidden patterns (specialArgs, hostname comparisons, HM→NixOS cross-reads). Check all 4 hosts build. Compare deliverables against plan.
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
- [ ] F2. **Code Quality Review** — `unspecified-high`
Run `alejandra --check .` + `nix flake check`. Review all new modules for: dead code, unused imports, inconsistent patterns, hardcoded values that should be options. Check den API usage is idiomatic. Verify import-tree conventions (no function files in import paths).
Output: `Format [PASS/FAIL] | Check [PASS/FAIL] | Files [N clean/N issues] | VERDICT`
- [ ] F3. **Real Manual QA** — `unspecified-high`
Start from clean state. Build all 4 hosts. Verify deploy-rs nodes exist via nix eval. Verify SOPS secrets preserved via nix eval. Verify git email overrides. Verify overlays produce correct packages. Verify no specialArgs usage anywhere. Save evidence to `.sisyphus/evidence/final-qa/`.
Output: `Builds [4/4 pass] | Deploy [2/2] | Secrets [N/N] | VERDICT`
- [ ] F4. **Scope Fidelity Check** — `deep`
For each task: read "What to do", verify actual implementation matches 1:1. Check "Must NOT Have" compliance — no new packages, no new services, no abstractions. Verify every current profile has a corresponding den aspect. Flag any behavioral differences.
Output: `Tasks [N/N compliant] | Scope [CLEAN/N issues] | VERDICT`
---
## Commit Strategy
Since this is a clean rewrite, the work should be committed as a small series of logical commits:
- **1**: `feat: rewrite config with den framework` — All new modules, flake.nix
- **2**: `refactor: rewrite app scripts from bash to nushell` — apps/ directory
- **3**: `chore: remove old host-centric structure` — Delete hosts/, profiles/, modules/_legacy/
- Pre-commit: `alejandra --check . && nix flake check`
---
## Success Criteria
### Verification Commands
```bash
nix build ".#darwinConfigurations.chidi.system" # Expected: builds successfully
nix build ".#darwinConfigurations.jason.system" # Expected: builds successfully
nix build ".#nixosConfigurations.michael.config.system.build.toplevel" # Expected: builds successfully
nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" # Expected: builds successfully
nix flake check # Expected: passes
alejandra --check . # Expected: passes
nix eval ".#deploy.nodes.michael" --json # Expected: valid deploy-rs node
nix eval ".#deploy.nodes.tahani" --json # Expected: valid deploy-rs node
```
### Final Checklist
- [ ] All "Must Have" present
- [ ] All "Must NOT Have" absent
- [ ] All 4 hosts build
- [ ] Zero specialArgs usage
- [ ] SOPS paths identical to current
- [ ] deploy-rs configured for NixOS hosts
- [ ] All overlays functional
- [ ] Formatting passes