Personal macOS (Apple Silicon) dotfiles, built on nix-darwin + home-manager. Everything is declarative in nix/. The darwinConfiguration is named MacBook-Pro-de-Hugo and the repo is expected to live at ~/workspace/kocal/dotfiles.
Clone the repo:
mkdir -p ~/workspace/kocal && cd $_
git clone https://github.com/Kocal/dotfiles.git dotfiles && cd dotfiles
git remote set-url origin git@github.com:Kocal/dotfiles.gitInstall the Xcode command-line tools and Nix:
xcode-select --install
curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install | shBootstrap nix-darwin (first run only):
sudo nix run nix-darwin/master#darwin-rebuild --extra-experimental-features "nix-command flakes" -- switch --flake "$PWD/nix"All subsequent rebuilds:
sudo darwin-rebuild switch --flake "$PWD/nix"nix/
flake.nix flake inputs, system packages, homebrew casks
home.nix home-manager entrypoint, imports all home/ modules
home/
options.nix dotfiles.dir option (defaults to ~/workspace/kocal/dotfiles)
git.nix programs.git: SSH signing via 1Password's op-ssh-sign
zsh.nix zsh + Starship, aliases, shell functions
vim.nix programs.vim: plugins + vimrc
node.nix fnm (Node version manager)
php.nix PHP 8.1-8.5 + composer + symfony-cli
docker.nix ~/.docker/config.json symlinked out-of-store to nix/home/docker/
claude.nix ~/.claude/* symlinked out-of-store to nix/home/claude/
ghostty.nix Ghostty config (app itself is a Homebrew cask)
CLI tools and GUI apps go into environment.systemPackages in flake.nix when they're available and working in nixpkgs: things like gh, bat, ripgrep, ffmpeg, claude-code, orbstack, jetbrains-toolbox, rectangle-pro, and more. See flake.nix for the full list.
GUI apps installed this way get copied as real .app bundles into /Applications/Nix Apps, so Spotlight and Launchpad find them automatically.
Homebrew casks (homebrew.casks in flake.nix) are for apps that can't come from Nix: not in nixpkgs, darwin build broken (GTK/Qt deps, appstream/libadwaita), or strict signing and location requirements (1Password, Cloudflare WARP). Current casks include Ghostty, 1Password, Inkscape, Pinta, Affinity, mGBA, and a few others; see flake.nix.
- git (
home/git.nix): commits and tags signed via 1Password'sop-ssh-sign. Machine-local overrides (different email, signing key, etc.) go in~/.gitconfig.local, included but not managed by Nix. - zsh (
home/zsh.nix): native completion, autosuggestions, syntax highlighting, Starship prompt. Aliases and shell functions are innix/home/zsh/functions.zsh. Loads~/.zshrc.localif it exists. - vim (
home/vim.nix): plugins (vim-sensible, vim-obsession, vim-airline, vim-solarized8) via Nix; config fromnix/home/vim/vimrc.vim. - node (
home/node.nix): fnm as the version manager. Nix installs fnm itself; you still need to runfnm install --lts(or a specific version) after first setup. - php (
home/php.nix): PHP 8.1-8.5, each with opcache, xdebug, apcu, blackfire probe, xsl, redis, amqp, and imagick. Default unversionedphpis 8.4. Versioned binaries (php8.1...php8.5) are on PATH so the Symfony CLI picks the right one via.php-version. PHP 8.1 comes from thephpsinput (EOL, dropped from nixpkgs). - claude (
home/claude.nix):~/.claude/settings.json,~/.claude/CLAUDE.md,~/.claude/agents/,~/.claude/skills/, and~/.claude/agent-memory/are all out-of-store symlinks pointing back intonix/home/claude/. Runtime writes by Claude land in the repo, not a read-only store path. - docker (
home/docker.nix):~/.docker/config.jsonsymlinked out-of-store tonix/home/docker/config.json, sodocker login/docker context usewrites land in the repo instead of failing against a read-only store path. SetscredsStore = osxkeychain(the helper OrbStack ships); this replaced a stray globalecr-logindefault that broke every Docker Hub pull once the Brew-installed helper disappeared in the Nix migration. - ghostty (
home/ghostty.nix): config written to~/.config/ghostty/configby home-manager. The app itself is a cask; nixpkgs ghostty is broken on darwin.
Steps Nix can't handle declaratively:
RTK: initialize the global config:
rtk init --globalBlackfire: configure the agent (credentials from your Blackfire account). Nix installs the binary but sets up no service, so start it yourself or add a launchd agent:
blackfire agent:configNode: install at least one version via fnm:
fnm install --ltsDependencies are pinned in nix/flake.lock. Updating bumps a flake input and rebuilds; there is no per-package pin, so a package's version follows whatever input it comes from. Most CLI tools and apps (including claude-code, gh, php, the GUI apps) come from nixpkgs.
Update everything:
sudo nix flake update --flake "$PWD/nix"
sudo darwin-rebuild switch --flake "$PWD/nix"Update a single input:
sudo nix flake update nixpkgs --flake "$PWD/nix" # bumps everything from nixpkgs: claude-code, gh, php, GUI apps, ...
# other inputs: home-manager, nix-darwin, phps, nix-homebrew
sudo darwin-rebuild switch --flake "$PWD/nix""Update just Claude Code" means updating the full nixpkgs input, which moves every nixpkgs package to the channel's latest at once. Commit the resulting flake.lock change to keep the machine reproducible.
Homebrew casks (1Password, Ghostty, ...) are managed by Homebrew, not the lock file. Upgrade them with brew upgrade, or set homebrew.onActivation.upgrade = true; in flake.nix to upgrade them automatically on every darwin-rebuild switch.
Roll back a bad update:
sudo darwin-rebuild --rollbackEvery darwin-rebuild switch (aliased drs) creates a new generation; old generations stay on disk as GC roots and pile up. Concretely, each rebuild that changes PHP leaves stale php-with-extensions store paths behind, so the Symfony CLI's local PHP-discovery patch lists the same PHP version several times over.
Free the store by deleting old generations:
nix-clean # alias for: sudo nix-collect-garbage -d-d deletes ALL old generations of every profile (system + user) and collects garbage; there's no rollback after that. To keep a rollback window instead, delete only generations older than a few days:
sudo nix-collect-garbage --delete-older-than 7dUnlike a rebuild, garbage collection needs no flake path: it operates on the Nix store and the profiles under /nix/var/nix/profiles, not on your config. It needs sudo because the nix-darwin system generations live under /nix/var/nix/profiles/system (root-owned); without it, only your user profile gets cleaned.
For CLI tools or Nix-packaged GUI apps, add to environment.systemPackages in nix/flake.nix, then rebuild. For casks, add the cask name to homebrew.casks; if it's from a third-party tap, add the tap to nix-homebrew.taps as well.