From dotfiles to declarative desktops

A Linux workstation evolved from shell scripts to NixOS, with agentic tooling and defence-in-depth security.

Architecture diagram: declarative workstations and secure AI
  1. The declarative NixOS stack
    How my dotfiles went from symlink scripts to one Nix flake that rebuilds a Linux workstation: system, shell, editor, devshells and secrets.
  2. The agentic development stack
    Wiring up OpenCode, Claude Code, MCP servers, agent skills and a research, plan, implement agent pipeline with Nix, so it rebuilds on any machine.
  3. Security posture: defence in depth on a declarative desktop
    Eight security layers on a NixOS desktop, from sops-nix secrets and DNS-over-TLS to opensnitch and encrypted restic backups, with gaps marked.
  4. Agent security: a design for running untrusted code
    A design, not yet built, for unattended coding agents: a gVisor sandbox with no secrets and a host broker that holds credentials and logs every call.
  5. Future roadmap: what's next
    The February 2026 plan for the dotfiles: a gVisor agent runner, Firecracker microVMs, skill cleanup, and a home server, with a September status update.
  6. From NixOS to macOS: porting a declarative workstation across platforms
    How the NixOS and Hyprland config from Part 1 moved to a MacBook with most of the home-manager modules unchanged, and the five bugs that cost me evenings.
  7. Auditing a declarative desktop: what to look for and what I found
    A layer-by-layer security audit of my nix-darwin setup: what to check, the firewall and secrets gaps I found, and a ten-line checklist to reuse.
  8. New machine, one hour: a fully automated macOS bootstrap
    One script takes a bare MacBook to the full nix-darwin setup: ten idempotent stages, a data restore script, and a Lima VM that isolates the OpenCode agent.
  9. The production pass: shared config, security scripts, and a three-tier agent design
    Two sessions after the macOS bootstrap: one shared file for SSM tunnels that fixed a port collision, Touch ID in tmux, and a design for three OpenCode agents.
  10. Runtime state is not configuration: what a Nix bootstrap cannot restore
    OpenCode plugins, agent session history, and the Zen browser profile live outside Nix, plus the small fixes from the first day of using the macOS setup.
  11. One flake, two people: moving per-user config into one file each
    How 17 commits of fork drift collapsed into one nix/personal file per person, and the audit process that keeps a shared nix-darwin flake shared.
  12. The case of the generic 'sh'
    Every launchd agent on my Mac showed up as 'sh' in Activity Monitor. A small Rust wrapper built by Nix restored their names and kept the /nix/store wait.
  13. 66 loose directories and how I made them Nix's problem
    An audit found 202 agent skill entries and only 4 managed by Nix. Here is how I moved 66 loose directories into the flake and fixed the skill enumerator.
  14. Four small fixes from July and August
    A meeting overlay that fixes multi-monitor coordinate math, herdr next to tmux, an AeroSpace config bump that changes a default, and a local model cull.
  15. Reaching an agent session from anywhere, and not losing it afterward
    Tailscale plus a disposable Lima VM for remote agent sessions, a nightly session backup gated by a secret scan, and the pipefail bug that nearly broke it.
  16. Automating PR review: stale reviews, a new engine, and a PATH bug
    GitHub hides a review once new commits land. I replaced my hand-rolled OpenCode reviewer with ocr, handled stale reviews, and fixed an exit 127 bug.
  17. Bonsai 2: a 2-bit local model that fits in 10 GB of RAM
    A 2-bit ternary model runs 2.5x faster than a 4-bit rival on my M5 and needs a custom FastAPI server. It now serves three agent roles and Perplexica.
  18. What changed in the dotfiles since Part 17
    A command guard that stuck, a semantic-search layer, a nix cleanup that split up two oversized modules, an agent-version migration that's still just a plan, and a workaround for a Bedrock bug that kills long agent sessions.
  19. Blocking destructive commands before an agent runs them
    How a hook denylist and a local classifier screen coding agent shell commands, what each one missed when I tested them, and why I removed one.
  20. The agent VM I actually run, and the NixOS jail I designed
    What the Lima agent VM mounts, how secrets reach it over stdin, why its no-sudo claim only holds for a NixOS image I never booted, and what it still does not isolate.
  21. Giving an agent VM Bedrock access without long-lived keys
    How an assumed AWS role and a 50 minute launchd job give an agent VM one-hour Bedrock credentials, and the gap where the VM copy goes stale.
  22. Moving my agent sessions from tmux to herdr
    Why I moved OpenCode sessions into herdr, how the plugin, the nix config and the prefix key fit together, and what still lives in tmux.
  23. Pinning the agent skill supply chain
    What a skill can do on my machine, how my catalog installs them, which installs are pinned, and why my pin script no longer pins anything.
  24. What I dropped and why
    Eight agent sandbox and tooling designs I wrote about as if they existed, checked against the repo, with the reason for each cut where one was recorded.
  25. A git policy for agents belongs in tested config
    I thought a restricted git MCP server protected main. Reading it showed it never did, so I moved the rule into a tested, declarative dcg pack.
  26. Constraining the tool and the session, not the prompt
    A web fetch sanitiser plus a taint rule that blocks side effects after hostile content arrives, what I measured and rejected, and the gaps that remain.