Multi-platform nix configuration for macOS (nix-darwin) and Linux (NixOS) systems.
- NB2123 (macOS, aarch64-darwin): Work MacBook — corporate gateway, private.nix
- makima (macOS, aarch64-darwin): Personal MacBook
- wintermute (Linux, x86_64-linux): Gaming and homelab desktop setup (not yet declared)
Each host has its own rebuild recipe. See the host's README for the exact command and any per-host setup — NB2123.
General shape:
# macOS
sudo nix run nix-darwin/master#darwin-rebuild -- switch --flake .#<hostname>
# Linux
sudo nixos-rebuild switch --flake .#<hostname>config/ # Static configuration files
├── nvim/ # Neovim configuration
└── sketchybar/ # SketchyBar Lua configuration
hosts/ # Host-specific configurations
├── NB2123/ # macOS work laptop (see hosts/NB2123/README.md)
├── makima/ # macOS personal laptop
└── wintermute/ # Linux desktop (planned)
modules/ # Reusable modules
├── darwin/ # macOS system-level modules (nix-darwin)
│ └── minimal.nix # Shared macOS setup, apps
└── home/ # User-level home-manager modules
├── default.nix # Shared aggregator (platform-generic)
├── theme.nix / fonts.nix / shell.nix
├── development/ # Cross-platform dev: direnv, ghostty, nvim, git, rtk, zed, claude-code, codex, dev-shells/
└── darwin/ # macOS-only bundle (imports shared + darwin-only extras)
├── default.nix # Aggregator + LaunchServices registration
├── development.nix # lazydocker, xcbuild
├── internet/helium.nix
├── security/keepass.nix
└── window-manager/ # AeroSpace, SketchyBar, JankyBorders
Hosts on macOS import a single path: ../../modules/home/darwin. A non-darwin host would import ../../modules/home directly (shared modules only). Each darwin-only module carries a lib.mkIf pkgs.stdenv.hostPlatform.isDarwin guard in its config block for defense in depth.
Opt-in per host (all three modules default to disabled).
Tiling window manager with i3-like keybindings. Workspace management integrated with SketchyBar.
Status bar replacement. Modular Lua-based configuration, dynamic color generation from theme.nix, workspace indicators bound to AeroSpace, and system-monitor items (battery, network, RAM, volume).
Window borders that follow the color theme.
The macOS configuration uses nix-homebrew with automatic cask detection.
- Custom packages in
pkgs/useutils.darwin.mkBrewCaskto create Homebrew cask markers - Modules add packages to
environment.systemPackagesas usual (no Homebrew-specific code needed) modules/darwin/homebrew.nixdetects packages withpassthru.brewCaskand adds them tohomebrew.casks- Casks install via Homebrew on system rebuild (source unified in
pkgs/, no sha256 tracking)
No node-version-manager is installed. Each project gets a flake-based sidecar shell delivered via home-manager and activated by direnv on cd.
modules/home/development/dev-shells/corp-project.nixis the reusable per-project module.default.nixiterates overprivate.projectsand instantiates it once per entry — adding a project is aprivate.nixedit, not a dotfiles change- Each activation materializes real (non-symlink)
flake.nix,.envrc,.emdash.json, andenv.shunder~/.local/share/dev-shells/<projectId>/, then copies.envrc+.emdash.jsoninto~/git/<projectId>/as real files (emdash'spreservePatternsdoesn't follow symlinks into worktrees) env.shis written by the activation via unquoted heredoc (chmod 0600).project.envvalues that reference$CORP_*variables are expanded from whateverkeepassSecretsExtracthas exported — same channel asregistries.nix.envrcand.emdash.jsonare hidden by the global gitignore (modules/home/development/git.nix+~/.config/git/ignore)project.mcpServersis a data-driven attrset consumed by both agents. Each project gets its own<projectHome>/<agent>/home, and direnv exports the agent's home env var so terminals AND Emdash worktrees (shellSetupsourcesenv.shdirectly) resolve to it automatically. The merge shape differs by agent:- Codex —
<projectHome>/codex/config.tomlis rendered from the codex.nix module (host-globals + this project'smcpServers) into a store path, then envsubst runs against a$CORP_*allowlist at activation to bake$CORP_XXXrefs into literals.CODEX_HOMEpoints terminals + worktrees at this home;<projectHome>/codex/auth.jsonis symlinked back to~/.codex/auth.jsonso a singlecodex logincovers every project - Claude —
<projectHome>/claude/settings.jsonis a straight snapshot of~/.claude/settings.json(produced afterclaudeCodeSettings/claudeCodeCorpSecrets/rtkInitrun, so env/plugins/RTK hook/JWT inherit).<projectHome>/claude/.claude.jsonis the merge target:project.mcpServersis jq-merged into.mcpServers, withauthorization_env_varand$CORP_*env refs resolved to literals from the activation env.plugins/skills/CLAUDE.md/RTK.mdare symlinked from~/.claude/so upstream edits propagate;projects/(sessions) and caches are per-project (Claude creates them on first launch).CLAUDE_CONFIG_DIRpoints terminals + worktrees at this home
- Codex —
Why home.activation instead of xdg.configFile? home-manager writes xdg.configFile entries as symlinks into the nix store, but nix flake doesn't resolve a symlinked flake.nix inside an otherwise-real source directory — it generates a nested store path that doesn't exist. The activation script is the workaround.
Sidecars inherit inputs.nixpkgs.rev and pkgs.stdenv.hostPlatform.system from the parent flake, so the same module works unchanged on darwin and (future) NixOS hosts.
- Add an entry to
projects.<name>in~/.config/dotfiles/private.nix— seehosts/NB2123/private.example.nixfor the required fields (projectId,adoOrganization,packages,env). - Rebuild — sidecar files are materialized and copied into
~/git/<projectId>/automatically.
modules/home/development/git.nix provides:
- Global gitignore (
programs.git.ignores) —.envrc,.direnv/,.emdash.jsonso per-project tooling files never appear ingit statusof repos you don't own - git-credential-manager — browser-based OAuth for Azure DevOps and GitHub HTTPS clones, with tokens cached in the macOS keychain (or Secret Service on Linux)
- Identity via
includes— anygithub.com/brutcha/*remote picks up the brutcha identity automatically
Host-specific extras layer on top per host, e.g. the corp CA bundle path and Azure DevOps useHttpPath scoping in hosts/NB2123/home.nix. Per-project identity overrides are wired by corp-project.nix: for each entry in private.projects it appends a programs.git.includes block with an includeIf matching the checkout path, so any commit from ~/git/<projectId>/ picks up private.user.{name,email} automatically.
nix flake update # bump everything
nix flake update nixpkgs # bump just nixpkgs
./scripts/check-cache.sh # verify aarch64-darwin cache before rebuildingnixpkgs-unstable advances after Hydra's channel-tested job set passes, but heavy darwin builds (mono, dotnet-sdk, electron, …) aren't always in that set, so cache.nixos.org can miss aarch64-darwin binaries. check-cache.sh probes a watchlist (top of the file) of known-heavy packages and reports cached / not-cached. If a watched package is missing, either roll back flake.lock, override it in pkgs/<name>/default.nix (brew marker via utils.darwin.mkBrewCask, or a custom .dmg/AppImage fetch), or accept a one-time source build.
Packages using utils.darwin.mkBrewCask are updated by Homebrew on darwin-rebuild switch — no manual intervention.
For packages not using Homebrew:
nix run nixpkgs#nix-update -- <package-name> --version=<NEW_VERSION>nix flake checknix search nixpkgs <package-name>