Nix-darwin flake that manages macOS system configuration, Homebrew apps, and user environment via Home Manager. Supports multiple machines from a single repo.
- macOS on Apple Silicon
- Determinate Nix installer
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- installOpen a new terminal after installation.
git clone git@github.com:sgpbyrne/workstation-setup.git ~/dev/workstation-setup
cd ~/dev/workstation-setup
cp hosts-config.nix.template hosts-config.nixEdit hosts-config.nix with your values:
hostname-- runscutil --get LocalHostNameto find itusername-- your macOS usernamegitName/gitEmail-- your git identityclusters-- your AKS cluster details (see template for format)
Track the file for Nix flakes (required), then hide local changes from git:
git add hosts-config.nix
git update-index --skip-worktree hosts-config.nixIf your network uses a TLS-intercepting proxy, Nix fails with SSL errors. There are three distinct knobs, and all three must be set before the first build:
- The Nix daemon's own downloads (substituters) use
ssl-cert-fileinnix.conf. - In-build fetchers (
fetchgit/fetchurl) readNIX_SSL_CERT_FILEfrom the daemon's environment — a separate knob fromssl-cert-file. - The macOS build sandbox blocks builds from reading the cert file at all.
The key insight: the proxy presents host certs signed by a single corporate root CA (here, untrust.loc). Trust that root once and every proxied host verifies. Trusting a per-host leaf (e.g. just cache.nixos.org) only fixes that one host and silently breaks any build that fetches from elsewhere.
a) Save the proxy ROOT CA to /etc/nix/proxy-cert.pem:
This is the file the activation script consumes on every rebuild. Grab the proxy's CA chain (everything past the per-host leaf) from any HTTPS host:
echo | /usr/bin/openssl s_client -showcerts -connect cache.nixos.org:443 </dev/null 2>/dev/null \
| awk '/-----BEGIN CERTIFICATE-----/{n++} n>1' \
| sudo tee /etc/nix/proxy-cert.pem >/dev/null
# (optional) also make it visible to other system tools
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /etc/nix/proxy-cert.pemb) Point the daemon at a proxy-inclusive bundle (for in-build fetches):
# Build a stable bundle: standard CAs + the proxy root
cat /etc/nix/macos-keychain.crt /etc/nix/proxy-cert.pem | sudo tee /etc/nix/ca-bundle-proxy.crt >/dev/null
# Set the daemon's NIX_SSL_CERT_FILE via its launchd plist (SIP blocks `launchctl setenv`)
PLIST=/Library/LaunchDaemons/systems.determinate.nix-daemon.plist
sudo /usr/libexec/PlistBuddy -c "Add :EnvironmentVariables dict" "$PLIST" 2>/dev/null
sudo /usr/libexec/PlistBuddy -c "Add :EnvironmentVariables:NIX_SSL_CERT_FILE string /etc/nix/ca-bundle-proxy.crt" "$PLIST" \
|| sudo /usr/libexec/PlistBuddy -c "Set :EnvironmentVariables:NIX_SSL_CERT_FILE /etc/nix/ca-bundle-proxy.crt" "$PLIST"c) Disable the macOS sandbox and reload the daemon:
echo 'sandbox = false' | sudo tee -a /etc/nix/nix.custom.conf
sudo launchctl bootout system "$PLIST" 2>/dev/null; sudo launchctl bootstrap system "$PLIST"Verify a build now sees the cert (should print the path, not nothing):
nix-build --no-out-link --expr 'derivation {
name = "ssl-probe"; system = builtins.currentSystem; builder = "/bin/sh";
args = ["-c" "export -p; exit 1"];
impureEnvVars = ["NIX_SSL_CERT_FILE"];
outputHashMode = "flat"; outputHashAlgo = "sha256";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
}' 2>&1 | grep NIX_SSL_CERT_FILEThese steps are only needed once before the first build. After that, the activation script (hosts/common/default.nix) reads /etc/nix/proxy-cert.pem and re-asserts both the bundle and the daemon plist env on every rebuild — surviving Determinate regenerating its own cert bundle.
darwin-rebuild doesn't exist yet, so bootstrap with:
nix run nix-darwin -- switch --flake ~/dev/workstation-setup#personal-macReplace personal-mac with your config name (the key in hosts-config.nix).
Open a new terminal after this completes to pick up the new shell config.
kube-initThis logs into Azure (if needed) and fetches credentials for all configured AKS clusters. Use kubectx to switch between them.
rebuildThis alias runs darwin-rebuild switch and reloads the shell. It's available after the first build.
git-syncThis handles the skip-worktree dance for hosts-config.nix automatically -- stashes your local values, pulls, pops the stash, and re-applies skip-worktree.
nix flake updateUpdates all flake inputs (nixpkgs, home-manager, nix-darwin, etc.) to latest. Review with git diff flake.lock before rebuilding.
Add --show-trace for full error traces:
sudo darwin-rebuild switch --flake ~/dev/workstation-setup#personal-mac --show-traceCommon issues:
- "Path not tracked by Git" - new files must be
git added before Nix can see them. Nix flakes only see files tracked by git. - "attribute not found" - usually a typo in a module argument or a missing
specialArgsvalue. Check that the function signature in the failing.nixfile matches what's passed inflake.nix. - Homebrew zap removing apps -
onActivation.cleanup = "zap"removes anything not declared. If an app disappears after rebuild, add it to the appropriatehomebrew.nix. - SSL cert errors in Nix - on a corporate network, run the cert fix from step 3 above.
nix flake update/substituters usessl-cert-file; in-build fetches (a build cloning a git repo) instead needNIX_SSL_CERT_FILEset on the daemon's environment andsandbox = false. To see what a build actually gets, use thessl-probesnippet in step 3. - SSL errors after a Determinate update - a daemon update can rewrite the launchd plist and drop the
NIX_SSL_CERT_FILEenv. Runrebuild(the activation script re-asserts it in the plist), then reload the daemon or reboot for it to take effect. A plain reboot alone is fine — the plist env and/etc/nix/ca-bundle-proxy.crtboth persist.
- Create a host directory with
default.nixandhomebrew.nix:
mkdir hosts/<name># hosts/<name>/default.nix
{ ... }:
{
imports = [
../common
./homebrew.nix
];
}# hosts/<name>/homebrew.nix
{ ... }:
{
homebrew = {
casks = [];
brews = [];
};
}-
Add the host to
hosts-config.nix.template(with placeholders) and your localhosts-config.nix(with real values). -
Add the configuration to
flake.nix:
darwinConfigurations."<name>" = mkDarwin "<name>" hosts.<name>;- Stage and build:
git add hosts/<name> hosts-config.nix.template flake.nix
nix run nix-darwin -- switch --flake ~/dev/workstation-setup#<name>To all machines - add to hosts/common/homebrew.nix
To one machine - add to that host's homebrew.nix
CLI tools via Nix - add to the relevant file under home/programs/ (cli-tools.nix, devops.nix, etc.)