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. - Package tools — software installed by its own package manager (Homebrew
on macOS,
uvfor Python,denofor npm). Nix declares which packages and installs missing ones on rebuild, 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 dotfiles
# 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
The darwin-rebuild and nixos-rebuild commands will run stow as well.
- Edit files in
stow/directory and run stow - Changes are immediately active (no rebuild needed)
# Apply dotfiles (no Nix rebuild needed)
cd ~/.dotfiles/stow
stow --target="$HOME" --restow --no-folding --adopt shared "$(uname -s)"--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.
Note
I'm not happy with how this is designed, see dotfiles#202.
CLI tools that come from language ecosystems rather than nixpkgs are declared
in Nix (see nix/shared/home/package-tools.nix) but installed by their native
package manager. A rebuild installs anything missing; upgrades are manual:
uv tool upgrade --all # Python tools (uv)
npm-tools-upgrade # npm tools (deno)Homebrew works the same way: nix/shared/system/darwin.nix declares the 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
