Declarative Nix configuration for macOS (Apple Silicon supported), using nix-darwin, home-manager, and sops for secrets and dotfiles.
In this section:
- Minimal command sequence to bootstrap a new machine
- Install Xcode CLI tools and Nix
- Ensure hostname matches
flake.nix - Set up secrets (SSH + age + sops)
- Build and switch to your configuration using
nixorrebuild
If you just want to get a machine up and running, follow these steps in order:
# 1. Install Xcode command line tools
xcode-select --install
# 2. Install Nix (Determinate Systems installer)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# 3. Open a new terminal so `nix` is on your PATH
# 4. Make sure your hostname matches the one used in flake.nix
# 5. Set up secrets (SSH + age + sops)
# - Generate SSH key (if needed)
# - Generate age key
# - Create and encrypt secrets.yaml
# 6. Build and switch to your configuration
nix --extra-experimental-features 'nix-command flakes' build ".#darwinConfigurations.$(hostname).system"
# or simply use the rebuild helper once configured:
# rebuild- ⚡ Quick Start
- ✅ Installation (macOS)
- 🔐 Secrets Setup (sops + age)
- 🏗️ Build
- 🔁 Rebuild
- ⚙️ Post-Rebuild Configuration & Tweaks
- 💡 Tips
- 📦 Project Templates & direnv
In this section:
- Install Xcode command line tools
- Install Nix using Determinate Systems installer
- Ensure
nixis on yourPATH - Align your macOS hostname with
flake.nix
This configuration supports Apple Silicon Macs.
xcode-select --installThanks to the installer by Determinate Systems!
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- installAfter installation, open a new terminal session to make the nix executable available in your $PATH.
⚠️ IMPORTANT
The installer will ask if you want to install Determinate Nix. Answer No.
The hostname should match the one set in flake.nix.
You can either:
- Change your macOS hostname to match the one in
flake.nix, or - Change the hostname in
flake.nixto match your machine’s actual hostname.
In this section:
- Generate an SSH key (if needed)
- Set up age keys for
sops - Create and encrypt
secrets.yaml - Fix SSH host key errors
- Add SSH keys to GitHub
- Configure
.sops.yamlrules
ssh-keygen -t ed25519 -C "utopiaeh01@gmail.com"mkdir -p ~/.config/sops/agenix run nixpkgs#ssh-to-age -- -private-key -i ~/.ssh/id_ed25519 > ~/.config/sops/age/keys.txtnix shell nixpkgs#age -c age-keygen -y ~/.config/sops/age/keys.txtCopy the example:
cp secrets/flow48/secrets_example.yaml secrets/flow48/secrets.yamlThen replace the placeholders with your own keys.
Example: to show your private key from ~/.ssh:
bat --plain ~/.ssh/id_ed25519sops -e secrets/flow48/secrets.yaml > secrets/flow48/secrets.enc.yaml
⚠️ IMPORTANT
After encrypting, removesecrets.yamlto avoid accidentally committing it to Git.
If you see errors like:
Cannot read ssh key '/etc/ssh/ssh_host_rsa_key': no such file or directory
Cannot read ssh key '/etc/ssh/ssh_host_ed25519_key': no such file or directory
Run:
sudo ssh-keygen -ATo display your public key from ~/.ssh:
bat --plain ~/.ssh/id_ed25519.pubAdd that key to GitHub under Settings → SSH and GPG keys.
Create or update .sops.yaml to include a rule with your age public key so that future sops edits use it automatically.
In this section:
- Build your
nix-darwinconfiguration usingnix - Understand the
darwinConfigurations.<<hostname/profile>>.systemtarget
You can build the darwin system directly with nix (what your rebuild alias wraps).
By default, it should use your hostname. Replace <<hostname/profile>> with your hostname:
nix --extra-experimental-features 'nix-command flakes' build ".#darwinConfigurations.<<hostname/profile>>.system"In this section:
- Use the
rebuildalias for day-to-day updates - Target specific profiles (e.g.
mac-pro)
Use the rebuild alias (defined below in 💡 Tips).
- By default, it uses your current
hostname - You can also pass a specific profile:
rebuild mac-proIn this section:
- Configure Dock layout
- Use the pre-configured iTerm2 setup
- Configure CleanShot X with LuLu
- Handle global Node.js packages
- Manually configure a few GUI apps
Check the custom-dock file in hosts/darwin — it defines the default Dock apps.
iTerm2 is pre-configured with:
- custom theme
- Starship prompt
- keybindings
If you prefer macOS Terminal, set the font manually to:
MesloLGLNF
- Should be activated with your license.
- After activation, launch the LuLu app and block CleanShot’s network access to prevent license checks (useful if reusing a license across machines).
⚠️ IMPORTANT
This also disables CleanShot’s cloud functionality.
While nodejs and tools like @aws-amplify/cli can be installed declaratively via Home Manager, global NPM packages can’t be uninstalled via Nix.
npm uninstall -g @aws-amplify/clirm -rf ~/.npm-global/lib/node_modules/@aws-amplify
rm ~/.npm-global/bin/amplifySome apps are installed but require manual configuration after first launch:
- MiddleClick
- HiddenBar
- AltTab
- BetterDisplay
Raycast settings must be imported manually from:
data/raycast/*
In this section:
- Define a
rebuildhelper function - Update flake inputs and rebuild
- Clean the Nix store and remove old generations
You can define a rebuild alias in your shell config (data/mac-dot-zshrc) like this:
rebuild() {
local host="${1:-$(hostname)}"
if [[ $# -gt 0 ]]; then
shift
fi
sudo darwin-rebuild switch --flake ".#${host}" "$@"
}To update your dependencies and rebuild your system:
nix flake update
rebuildTo clean the Nix store and remove old generations:
cleanupThis repo also exposes flake templates to quickly bootstrap new projects that integrate nicely with your global Zsh + Home Manager setup and direnv.
These are already enabled in this config:
-
Nix with flakes and
nix-command -
direnv+nix-direnvvia Home Manager:programs.direnv = { enable = true; nix-direnv.enable = true; };
With this setup, direnv will automatically load your project’s Nix dev environment into your existing Zsh shell (so you keep autosuggestions, syntax highlighting, aliases, etc.).
The flake in this repo exposes templates under templates:
-
node-lts
Path:./templates/note-lts
Description: Node.js project starter (flake devShell +.envrcfor direnv) -
esp32-rust
Path:./templates/esp32-rust-project
Description: ESP32‑S3 Rust project starter (devShell +.envrcfor direnv)
The default template is node.
To create a new Node‑based project that uses the Node devShell and direnv:
mkdir -p ~/Developer/my-node-app
cd ~/Developer/my-node-app
# Initialize from this repo's Node template
nix flake init -t "path:/Users/utopiaeh/nix-config#node"
# Trust direnv for this directory
direnv allowThis will create:
-
flake.nix– with a devShell that includes:nodejs_20pnpmyarntypescript
-
.envrc– containing:use flake
After direnv allow, whenever you cd into this project:
direnvwill call Nix to load the devShell environment,- Your existing Home Manager Zsh session stays active,
- You keep autosuggestions + syntax highlighting from
programs.zshinhome-manager/profiles/base.nix.
To create a new ESP32‑S3 Rust project:
mkdir -p ~/Developer/mcu/my-esp32-project
cd ~/Developer/mcu/my-esp32-project
# Initialize from this repo's ESP32 Rust template
nix flake init -t "path:/Users/utopiaeh/nix-config#esp32-rust"
# Trust direnv for this directory
direnv allowThis will create:
-
flake.nix– with a devShell that includes:rust-analyzerespflashldproxyesp-generate- sensible defaults:
CARGO_BUILD_TARGET = "xtensa-esp32s3-none-elf"RUST_BACKTRACE = 1
-
.envrc– containing:use flake
Again, entering this directory will automatically load the ESP32 dev environment via direnv while preserving your global Zsh configuration from Home Manager.
For any project created from these templates:
cdinto the project directory.- On the first time:
direnv allow. - Afterwards, simply
cdin/out:- Zsh (with Home Manager config) stays the same.
- The project’s flake devShell is layered on top by
direnv.
You rarely need to run nix develop manually. If you edit flake.nix in a project, just run:
direnv reloadto pick up the changes in your current shell.