Installation
Full setup guide, from a freshly unboxed machine to a working environment. For the short version, see README.md.
macOS (nix-darwin)
1. Xcode Command Line Tools
Needed for git and the system toolchain before Nix is in place.
xcode-select --install
Sign in to the App Store now as well — mas installs the App Store apps
during the first switch and fails if no account is signed in.
2. Install Determinate Nix
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --determinate
--determinate matters: modules/darwin/default.nix sets nix.enable = false,
which hands Nix management to the Determinate installer instead of nix-darwin.
Installing plain upstream Nix here leaves the daemon unmanaged.
Open a new shell afterwards so nix is on PATH.
3. Install Homebrew
nix-darwin declares Homebrew packages but does not install Homebrew itself. The first switch fails without it.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/opt/homebrew/bin/brew shellenv)"
4. Clone the repository
The clone path is not arbitrary — the rebuild and update aliases in
modules/home/zsh.nix point at ~/ghq/simenandre.no/dotfiles, so clone it
there.
mkdir -p ~/ghq/simenandre.no
git clone https://simenandre.no/dotfiles.git ~/ghq/simenandre.no/dotfiles
cd ~/ghq/simenandre.no/dotfiles
HTTPS on purpose: SSH access depends on 1Password's SSH agent, which is not
installed yet. After the first switch, git rewrites the HTTPS remote to SSH
(url."git@simenandre.no:".insteadOf), so this remote keeps working.
5. Grant Full Disk Access to the terminal
modules/darwin/macos.nix writes preferences for three sandboxed apps — Safari,
Help Viewer, and TextEdit. Their preference files live under
~/Library/Containers/, which macOS protects with TCC, so defaults is denied
unless the terminal running the switch has Full Disk Access. The activation
script runs under set -e, so a single denial aborts the whole switch.
System Settings → Privacy & Security → Full Disk Access → + → add
/System/Applications/Utilities/Terminal.app (Ghostty is not installed yet on a
fresh machine) → quit and reopen the terminal, so the new grant applies to a
fresh process.
6. First switch (bootstrap nix-darwin)
Pick the configuration that matches the machine — each one pins its own username, so switching with the wrong attribute fails during activation:
| Attribute | Machine | User |
|---|---|---|
makkie |
MacBook Pro (sets its hostname to makkie) |
simenandre |
default |
Older MacBook Pro | cobraz |
sudo nix run nix-darwin#darwin-rebuild -- switch --flake .#makkie
This is the long one: it installs every Homebrew cask, the Mac App Store apps, all nixpkgs packages, and applies the macOS defaults. Expect Homebrew to prompt for your password, and expect to approve a few system dialogs.
Subsequent switches use the installed binary:
sudo darwin-rebuild switch --flake .#makkie
The rebuild and update aliases already carry the right attribute for the
machine they were built for, so after the first switch you can just run
rebuild.
Adding another Mac later means one entry in flake.nix:
"newhost" = mkDarwin {
username = "simenandre";
flakeAttr = "newhost";
hostName = "newhost";
};
Restart the terminal (or log out and back in) so the new zsh config, PATH, and
Ghostty configuration take effect.
7. 1Password and SSH
Git commits are signed through 1Password's SSH agent
(/Applications/1Password.app/Contents/MacOS/op-ssh-sign), so this has to be
set up before committing anything.
- Open 1Password, sign in, and unlock it.
- Settings → Developer → enable Use the SSH agent.
- Settings → Developer → enable Integrate with 1Password CLI (for
op). - Verify the signing key matches the one pinned in
modules/home/git.nix:
shell
ssh-add -l
- Confirm GitHub over SSH works:
shell
ssh -T git@github.com
8. Sign in to the rest
Each of these is a one-time manual login:
gh auth login # GitHub CLI
op signin # 1Password CLI
gcloud auth login # Google Cloud
gcloud auth application-default login
GUI apps that need a login or permission grant: Tailscale (App Store), Slack, Discord, Signal, Obsidian, Proton Mail, ProtonVPN, Spotify, Raycast (grant Accessibility), Google Drive.
9. Container runtime
Docker CLIs come from Homebrew, but the VM has to be started once:
colima start
DOCKER_HOST is already pointed at ~/.colima/default/docker.sock by
modules/home/zsh.nix.
10. Rust toolchain
rustup is installed, but ships without a toolchain:
rustup default stable
11. Tools not managed by Nix
These are installed with go install and live in ~/go/bin. Shell completions
for them are generated on every switch, and the generator quietly skips any that
are missing:
go install github.com/sokkelorg/cli/cmd/so@latest
go install github.com/simenandre/bobbin/cmd/bobbin@latest
go install github.com/simenandre/robin-cli/cmd/robin@latest
go install github.com/dinacomputer/api/cmd/dinactl@latest
go install github.com/sokkelorg/identity/cmd/identityctl@latest
12. Loose ends
mkdir -p ~/Screenshots # macos.nix points screencapture here; it is not created for you
~/.gitignore_global is referenced by core.excludesfile but is not managed by
this repo. Create it if you want it:
.DS_Store
.twm.yaml
.twm.yml
**/.claude/settings.local.json
Some macOS defaults (Dock, Finder, trackpad) only take effect after a logout or restart.
NixOS
sudo nixos-generate-config --show-hardware-config > hosts/nixbox/hardware-configuration.nix
sudo nixos-rebuild switch --flake .#nixbox
The nixbox host expects the user simenandre. Git commit signing is disabled
outside macOS, so no 1Password setup is required.
Linux (standalone home-manager)
# x86_64
home-manager switch --flake .#simenandre
# aarch64
home-manager switch --flake .#simenandre@aarch64-linux
Day-to-day
Aliases defined in modules/home/zsh.nix (macOS):
| Alias | Does |
|---|---|
rebuild |
darwin-rebuild switch against this flake |
update |
nix flake update, then rebuild |
nix-gc |
Garbage-collect store paths older than 14 days |
scripts/cleanup.sh is a separate macOS cleanup utility (caches, logs, trash).
Troubleshooting
error: Homebrew is not installed — step 3 was skipped, or brew is not on
PATH for the shell running the switch. Run eval "$(/opt/homebrew/bin/brew shellenv)".
Mac App Store apps fail to install — sign in to the App Store, then re-run
the switch. mas cannot authenticate on its own.
defaults[…] Could not write domain …/Library/Containers/…; exiting — the
terminal lacks Full Disk Access (step 5), so a sandboxed app's preferences can't
be written, and set -e aborts the switch. Grant it, then reopen the terminal
before retrying — an already-running process keeps the old denial.
If it still fails after that, the app's container does not exist yet:
ls -d ~/Library/Containers/com.apple.Safari
defaults cannot create a container the app has never made. Launch the app once
(Safari, Help Viewer, or TextEdit — whichever the error names), quit it, and
re-run. The three container-backed domains are all in modules/darwin/macos.nix
under CustomUserPreferences; deleting the offending block is the other way out
if you don't care about those settings.
home-manager refuses to overwrite an existing file — files it would clobber
are backed up with a .bak suffix (backupFileExtension = "bak"). If a switch
still fails on a collision, a stale .bak from an earlier run is usually in the
way; delete it and re-run.
A cask fails to install because the app already exists — remove the
hand-installed copy from /Applications and re-run the switch.
warning: failed to generate _<tool> during activation — that tool is not
installed (see step 10). Harmless; only its completions are missing.
A nixpkgs package suddenly fails to build — check flake.lock. Pinned
overrides live in modules/darwin/default.nix (for example WhatsApp's version
and hash).
