One flake. macOS and any Linux distro, from the same module set.
# macOS
darwin-rebuild switch --flake . # hostname auto-selects
# Linux — any distro, including boxes you don't own
home-manager switch --flake .#linux-genericNew here? Read docs/METHODOLOGY.md — how the store, symlink farms, PATH, and generations actually work, and how the dotfiles ride on them (generated-vs-live, the zsh startup order, edit workflows).
The previous setup ran three package managers with overlapping responsibility —
Homebrew, afx, and mise — and 15 tools were declared in
two of them at once, with ~/bin silently shadowing $HOMEBREW_PREFIX/bin via PATH order.
Consolidating onto afx alone was impossible: tmux publishes source tarballs only, and
eza ships no darwin release assets. nixpkgs builds both. That is the entire argument for
this rewrite — it is the only option that removes the overlap and delivers a terminal that
installs on Linux.
The old system is preserved on the protecht branch and still works.
flake.nix inputs: nixpkgs, home-manager, nix-darwin, sops-nix
hosts/teakbookM5DJ/ nix-darwin: macOS defaults, Homebrew casks, fonts
hosts/linux-generic/ standalone home-manager overrides
home/
default.nix shared, imported by BOTH platforms
packages.nix the portable tool layer — replaces afx AND Brewfile.core
zsh.nix prompt-critical; see below
git.nix tmux.nix starship.nix neovim.nix ghostty.nix catalyst.nix claude.nix
darwin.nix linux.nix platform-conditional
dotfiles/ raw payload, STORE mode (read-only, rebuild-per-tweak)
dotfiles/ raw payload, LIVE mode (symlinked, editable without a rebuild)
Two directories named dotfiles, deliberately: home/dotfiles/ is the
store set, repo-root dotfiles/ is the live set. Same kind of content —
real config files with real extensions — the location encodes the mode.
A file moves between them only when its mode changes (see the decider below).
home-manager normally symlinks config read-only into /nix/store. That is correct for
reproducibility and miserable for iteration. So:
| Path | Mode | Why |
|---|---|---|
~/Library/…/Code/User/settings.json |
live (mkOutOfStoreSymlink) |
VS Code writes to it at runtime |
~/.claude/{settings.json, CLAUDE.md, agents/, commands/} |
live (mkOutOfStoreSymlink) |
Claude Code writes via /config, /memory, /agents |
~/.claude/hooks/ |
live (deliberate exception) | app never writes them, but hook scripts iterate like prompts, not like nix config |
| everything else | pure store | reproducible, read-only |
The dotfiles/ folder is exactly the set of files that need symlinks — and
the ability decider is: does the application itself write to the file?
VS Code updates settings.json from its own UI, and you want those edits
persisted back into this repo — so it lives in dotfiles/ behind a live
symlink. If only you (or nix) ever write a file, it's static config and
belongs in a home/ module, not here. One deliberate exception:
dotfiles/claude/hooks/ — nothing but the operator writes those scripts, but
they get tuned as often as the prompt they sit next to, so they ride the live
link rather than paying rebuild-per-tweak.
Shell config and nvim used to be live too; both melted into pure store
(2026-09): nvim's plugins now come from nixpkgs (no lazy-lock.json to write),
and the shell's command library lives in home/zsh/*.zsh under the curation
contract described in those files. Editing either is now rebuild-per-tweak.
compinitmust run before anycompdef(home/zsh/functions.zsh uses one). home-manager emits compinit ahead ofinitContent, which is why all shell payload is sourced from there.- afx was load-bearing beyond packages — its
localpackage sourced~/.zsh/[0-9]*.zsh. The first migration re-implemented that loop insidehome/zsh.nix; the second melt (2026-09) deleted the loop and the~/.zshdirectory entirely — HM'sinitContentis the only init mechanism now. A vendored 2015_gitcompletion had been silently shadowing zsh's modern one the whole time; the vendoredCompletion/dir is gone with it. tmux.confhardcoded/bin/zsh, which under Nix is the wrong zsh. Now${pkgs.zsh}/bin/zsh.- Three competing fzf configs collapsed into
programs.fzf. .curlrchad-k, disabling TLS verification for every curl on the machine. Removed..gitconfigcarried a plaintext PAT and an employer email globally. Credentials are gone; identity is nowincludeIf-scoped by directory.
nix-darwin does not install Homebrew — it drives brew bundle for GUI apps. Casks are
declared in hosts/<hostname> with cleanup = "zap", so unlisted apps are removed. little-snitch
installs a kernel extension and can never be a Nix package.
Nerd fonts, 1password-cli, ngrok and session-manager-plugin moved to nixpkgs and are no
longer casks.
The dev-secrets layer is declarative — op CLI from home/packages.nix
(nixpkgs _1password-cli, cross-platform) and the use_onepassword direnv
function from home/zsh.nix. Only authentication is manual, once per machine:
macOS
just switch— installsopand the 1Password app (cask,hosts/<hostname>).- Open 1Password, sign in to the account.
- Settings → Developer → Integrate with 1Password CLI (biometric auth for
op). - Verify:
op vault list→ Touch ID prompt → vault list.
Linux
just switch— sameopfrom the shared package list.- Desktop flow (optional): install the 1Password app from the vendor's
deb/rpm/flatpak — nixpkgs'
_1password-guineeds NixOS's polkit module for CLI integration, which generic distros can't provide — then enable Settings → Developer → CLI integration, same as macOS. - Headless flow:
op account add --address <acct>.1password.com --email <email>theneval $(op signin)per session; or exportOP_SERVICE_ACCOUNT_TOKENfor fully non-interactive use (CI, servers). - Verify:
op vault list.
Repos consume secrets by committing an .env.tpl of op:// references and
calling use_onepassword from .envrc — entering the repo materializes them
as env vars (ESO-for-dev; naming: op://dev/<repo-name>/<kebab-field>).
Skeleton written; not yet validated — Nix is not installed on this machine, so nothing here has been evaluated. Gates, in order:
nix flake checkdarwin-rebuild build --flake . # hostname auto-selects— compiles, changes nothing- Docker: provision
.#linux-genericin a clean Debian container; assert a silent interactive shell darwin-rebuild switch
- afx is fully retired: its aliases live in
home/zsh.nix, its fzf helper functions inhome/zsh/fzf-git.zsh, and its 81 packages inhome/packages.nix. The.config/afxdirectory is deleted; theprotechtbranch keeps the original YAMLs if archaeology is needed. - The two babarot gists supply
gcp-contextandkube-context, called from the tmuxstatus-left. Not yet packaged. sops-nixis wired as a flake input but no secrets are declared yet.