A nix-based macOS setup for my machines.
This repo uses:
- nix-darwin for macOS system configuration
- nix-homebrew for Homebrew installation
- Home Manager for symlinking file-based dotfiles from this repo into
$HOME
For a fresh Mac, use this to prepare the machine, install Nix, clone this repo, and run the first nix-darwin provision.
The bootstrap script installs upstream Nix, and this repo is set up so nix-darwin manages Nix itself.
The installer path follows the official macOS instructions from nix.dev.
curl -fsSL
https://raw.githubusercontent.com/drinkataco/nix-mac/main/bootstrap.sh \
| bashTo target a specific flake host during bootstrap, set BUILD_HOST before
running the script:
BUILD_HOST=watts bash <(
curl -fsSL https://raw.githubusercontent.com/drinkataco/nix-mac/main/bootstrap.sh
)The bootstrap flow does this:
- install Xcode Command Line Tools if needed
- clone or update this repo into
~/projects/nix-mac - run
scripts/install.sh- install nix - run
scripts/provision.sh- provision machine
Useful variants:
bash bootstrap.sh --no-provision
bash bootstrap.sh --no-install
bash scripts/install.sh
bash scripts/provision.sh --hostname watts
bash scripts/provision.sh --hostname work
bash scripts/force-remove.shOnce the repo is on the machine, the normal update flow is:
make updateThis does:
sudo darwin-rebuild switch --flake '.#watts'
If darwin-rebuild is not on your PATH, the fallback is:
sudo nix run 'nix-darwin/master#darwin-rebuild' -- switch --flake '.#watts'For another configured host, pass its flake output:
sudo darwin-rebuild switch --flake '.#work'
make update HOST=workThere is also a local helper script updatessy.
Use that when you want one guided upgrade pass across the machine - it:
- Updates packages
make update- updatingflake.lockandnixpkgsbrew --upgrade- upgrading homebrew packagespnpm ...- upgrades any packages installed by pnpmuv ...- upgrades any packages installed by uv
- Rebuild the current host with
make update
Use updatessy --help to see its flags and how to use it
If a machine already has a different Nix installation and I want to reset it back to the repo's expected upstream Nix install, run:
bash scripts/force-remove.shhosts/: host-specific configmodules/darwin/: macOS system configmodules/home/: Home Manager bits for user-level file linking and hooksdotfiles/: file-based dotfiles and config
Configured hosts live under hosts/ and are wired into darwinConfigurations
in flake.nix.
watts: userosh, Apple Silicon (aarch64-darwin)work: userosh, Apple Silicon (aarch64-darwin)
Dotfiles live in this repo as normal files and are symlinked into $HOME by Home Manager.
The important part of this setup is that most interactive tooling still uses its
native config files under dotfiles/, rather than being rewritten
into Nix:
- shell:
dotfiles/.zshrcanddotfiles/.zsh/ - terminal:
dotfiles/.config/alacritty/ - tmux:
dotfiles/.tmux.confanddotfiles/.tmux/ - Neovim:
dotfiles/.config/nvim/
Nix and Home Manager handle package installation, system settings, file linking,
and a few small activation hooks. The source of truth for day-to-day editor,
shell, and terminal behavior is still the file tree in dotfiles/.
The main interactive setup on these machines is:
- shell and CLI: macOS
/bin/zshwith Zim, fzf, and Oh My Zsh git aliases - tmux: persistent sessions, TPM-managed plugins, clipboard integration, and navigation with Neovim
- Neovim: LSP, Treesitter, completion, formatting, Git workflow, and file-based config in this repo
Core platform pieces behind the setup:
Useful docs in this repo:
- shell and CLI notes: docs/cli.md
- tmux notes: docs/tmux.md
- Neovim notes: docs/nvim.md
- Git notes: docs/git.md
If you only need one place to understand the core editing workflow, start with
docs/nvim.md. That is the densest part of the setup and the
one most likely to need reference later.
A new macbook, a brand new slate right?
It felt a good time to start a fresh installation that transcends just using my previous dotfiles repository. How can I try and automate simple configuration of my mac, such as how the dock and finder behaves?
The goal is pretty simple:
- use Nix where it improves reproducibility
- keep dotfiles as normal files
- avoid rewriting file-based config into Nix unless there is a clear benefit
- Try and use AI, but with a benefit of driving learning, and steering it to use sensible defaults and to understand each change. AI fatigue is real, and natural language and prompt engineering can be very tiring.