Nix-managed: nix-darwin for macOS system state, home-manager for every
account, NixOS for Linux VMs. One flake, pinned by flake.lock.
hosts/— the root of the tree, one file per machine: its system modules, Homebrew casks, every account (home module list), its VMs.lib/— turns the host tree into flake outputs:mk-darwin.nix(system),mk-home.nix(standalone home-manager per account),apps.nix(bootstrap/switch).modules/— nix-darwin and home-manager modules; each account picks its own list inhosts/.config/— raw dotfiles the modules deliver.vms/— Lima guests: NixOS config +lima.yaml.
Outputs mirror the tree: darwinConfigurations.<host>,
homeConfigurations."<host>-<user>" and ."<vm>",
nixosConfigurations.<vm>, plus a check per system and home.
make # = nix run .#switch
make check # = nix flake checkswitch derives the host from scutil --get LocalHostName and converges
everything in order: the nix-darwin system, each account's home (the
invoking user directly, other accounts via sudo -u), then each VM
(started via Lima, rebuilt from the read-only /mnt/dotfiles mount).
make bootstrap HOST=<host> # one of hosts/A pre-Nix shim only: starts the Xcode Command Line Tools installer if
missing, installs Determinate Nix, then delegates to
nix run .#bootstrap -- <host>, which installs Homebrew and runs the same
convergence as switch. Open a new terminal afterwards; from then on plain
make.
The Determinate installer manages the daemon and /etc/nix/nix.conf;
nix-darwin runs with nix.enable = false so the two never fight. Extra
daemon settings go through determinateNix.customSettings, which lands in
nix.custom.conf.
nixos-unstable for all outputs, home-manager/nix-darwin follow
nixpkgs — one nixpkgs in the closure, and the shared modules evaluate
identically on host and VM. Determinism comes from flake.lock; upgrading
is a deliberate nix flake update.
Config lives inline in modules/ — as program options or Nix strings — and
is delivered through the store: read-only, rollback-able, matching the
flake revision. config/ holds only the out-of-store exceptions, where
the app owns the file — it rewrites it, edits it from its UI, or needs a
live edit loop (nvim, Zed, agent settings).
Claude and pi settings.json are tracked but locally marked
skip-worktree because the agents rewrite them during normal use. To
commit a settings change: clear the bit, commit, restore it.
vim.pack + nvim-pack-lock.json already pin plugins and work anywhere
with git + neovim. Nix's job for nvim is the binary and the config tree.
Projects declare their Ruby in their own flake.nix + .envrc; the
shared module only enables direnv + nix-direnv.
Casks and MAS apps are declared per host in hosts/; CLI tooling is
Nix except a couple of host-only tools. cleanup = "zap" makes the
declared lists the single source of truth; auto-update/upgrade off so a
switch is fast. nix-darwin never installs Homebrew — bootstrap and switch
do when absent.
Nothing credential- or host-bound crosses into modules shared with VMs. SSH keys and config stay on the host; the VM uses its own. Commit identity (name, email) does cross, deliberately: work done in a VM should be attributable.
Nightfox (Duskfox dark, Dawnfox light) across terminals, editors, CLI tools, and agent UI. Match new tool config to https://github.com/EdenEast/nightfox.nvim when it supports theming.