Personal dotfiles, managed in three layers:
- Nix (
nix/) — system configuration and packages, pinned byflake.lockand applied with a rebuild. Fully reproducible. - Stow (
stow/) — dotfiles symlinked into$HOMEwith GNU Stow. Changes take effect immediately, no rebuild needed. - Homebrew (macOS) — GUI apps and Mac App Store apps. Nix declares which packages and a rebuild installs or removes to match, but versions are unpinned and upgraded manually.
Note
- This requires having Nix installed. See the Nix README.
- The
flake.nixis designed to set up the machine based on its hostname.
# Rebuild system + packages + dotfiles (reproducible, uses flake.lock)
sudo darwin-rebuild switch --flake ~/.dotfiles#"$(hostname -s)" # macOS
sudo nixos-rebuild switch --flake ~/.dotfiles#"$(hostname -s)" # NixOS
# Update ALL flake inputs, then rebuild
nix flake update
# Update only the unstable-pinned inputs, then rebuild
nix flake update nixpkgs-unstable nix-darwin home-manager-unstable llm-agents
# Clean up old Nix generations, keeping the last 5 days for rollback safety
sudo nix-collect-garbage --delete-older-than 5dNote
On macOS, home-manager's per-user activation can silently fail to apply (a
known upstream launchctl asuser flakiness). The rebuild self-heals this: a
guard in nix/shared/system/darwin.nix verifies the activation landed and
retries it, failing loudly otherwise. See CLAUDE.md for manual
verification and recovery.
Dotfiles are managed with GNU Stow, not Nix.
Note
Rebuilds run stow too. On NixOS only when the home-manager generation
changed, so after editing just stow/, run dotfiles-stow.
- Edit files in
stow/and rundotfiles-stow - Changes are immediately active (no rebuild needed)
Stow forbids slashes in package names, so each level is its own invocation:
| Package | Applies to |
|---|---|
stow/shared/ |
every machine |
stow/platform/{Darwin,Linux}/ |
matching uname -s |
stow/kaizen/ |
the niri + Quickshell hosts (renoir, wily) |
stow/host/<hostname>/ |
that machine only; optional |
A file in a later package must not target a path an earlier one already supplies; GNU Stow reports that as a conflict rather than treating it as an override, so per-host drift means keeping that file out of the shared package entirely.
--adopt absorbs any real file that has replaced a managed symlink into the
repo instead of aborting; review the result with git diff before committing.
The shell entrypoint is stow/shared/.zshrc, which sources
stow/shared/.zshrc_user. The user file loads the shell configuration chain:
shell/exports.sh— PATH (includingshell/bin/utils), globals, env varsshell/aliases.sh— shell aliasesshell/sourcing.sh— tool initialization, plugins, completions
See Project config for details on shell initialization, direnv, and per-project tooling.
nix/shared/system/darwin.nix declares the Homebrew taps,
brews, casks and Mac App Store apps, and a rebuild installs or removes packages
to match that set (cleanup = "zap", so anything undeclared is uninstalled).
Versions are not pinned — bump them deliberately:
brew update && brew upgrade # add --greedy to also bump self-updating casksPinning Homebrew versions is possible via nix-homebrew with locked taps, but it buys little here: casks that self-update ignore the pin, vendors delete old cask artifacts, and Mac App Store apps cannot be pinned at all.
LLM agent CLIs (claude-code, opencode,...) are the exception: they are plain Nix
packages from the llm-agents flake input, upgraded via
nix flake update llm-agents + rebuild.
- Neovim ⌨️
- My Neovim config - uses
vim.pack - Minimalistic config
- My Neovim config - uses
- Workflows 🌊
- Fonts
- Host-specific documentation
- rpi5-homelab - requires custom installation procedure
