- Shell 50.5%
- Nix 28.7%
- Python 18.3%
- Vim Script 2.5%
| AppSettings | ||
| modules | ||
| .editorconfig | ||
| .envrc | ||
| .gitignore | ||
| .gitmessage | ||
| bootstrap.sh | ||
| CLAUDE.md | ||
| flake.lock | ||
| flake.nix | ||
| install.sh | ||
| open-apps.sh | ||
| README.md | ||
| registry.json | ||
| setup-keychain.sh | ||
| sync-to-bitwarden.sh | ||
| test-bw-keychain.sh | ||
Nix Configuration for macOS
Personal nix-darwin + home-manager flake for macOS (Apple Silicon). One script, ~15 minutes, fully configured machine.
New Machine Setup
Prerequisites
- macOS on Apple Silicon
- Internet connection
Quick Start
# On a fresh machine with nothing installed:
curl -sSL https://setup.cnr.ad | sh
# Or if you've already cloned this repo:
./bootstrap.sh
The setup.cnr.ad URL serves install.sh, a tiny POSIX sh wrapper that
downloads the full bootstrap.sh to a temp file and runs it with bash.
This avoids stdin/pipe conflicts with interactive installers.
The bootstrap script handles everything:
| Step | What it does | Time |
|---|---|---|
| 1 | Set computer name | ~10 sec |
| 2 | Install Rosetta 2 | ~1 min |
| 3 | Install Xcode CLT | ~2 min |
| 4 | Install Nix (Determinate Systems) | ~2 min |
| 5 | Install Homebrew | ~1 min |
| 6 | Clone config repos (nix + nvim) via HTTPS | ~10 sec |
| 7 | nix-darwin switch (packages, configs, system prefs) |
~5 min |
| 8 | Create data directories (Mail, calendars, contacts) | instant |
| 9 | Print keychain setup instructions (secrets added separately) | instant |
Post-Bootstrap
After the bootstrap script finishes:
# 1. Add secrets to macOS Keychain (REQUIRED before email/calendar/IRC work)
./setup-keychain.sh --personal --bitwarden # recommended: auto-pull from Bitwarden
./setup-keychain.sh --personal # alternative: interactive prompts
./setup-keychain.sh --personal --pass # alternative: read from pass
# 2. Initial data sync (requires keychain secrets from step 1)
mbsync -a && notmuch new # email
vdirsyncer discover && vdirsyncer sync # calendars + contacts
# 3. Sign into GUI apps
# - Bitwarden
# - Docker Desktop
# - Raycast (license key)
# - Ollama
Note: neomutt, mbsync, vdirsyncer, and senpai will all fail if you skip step 1. Run
security find-generic-password -s gmail-imap -wto check if secrets are present.
What's Managed
nix-darwin (system level)
| Category | Details |
|---|---|
| System preferences | Dark mode, caps-lock-to-escape, dock (auto-hide, left), menu bar hidden, Touch ID sudo |
| Homebrew casks | Ghostty, Bitwarden, Docker Desktop, Rectangle, Raycast, Ollama, etc. |
| Homebrew brews | newsboat, minikube, temporal, trippy, java |
| Services | Tailscale |
| Shell | Zsh as default |
home-manager (user level)
| Category | What's deployed |
|---|---|
| Packages | 100+ nix packages (neovim, go, rust, k8s tools, etc.) |
| Shell | Zsh + oh-my-zsh (11 plugins), aliases, shortcuts |
| Prompt | Starship with Kanagawa theme + jj integration |
| Terminal | Tmux (C-a prefix, 6 plugins, Kanagawa theme, quick-launch bindings) |
| Git | programs.git with GPG signing, conventional commit template |
| VCS | programs.jujutsu (jj) |
| neomutt + mbsync + notmuch (Kanagawa colorscheme) | |
| Calendar | khal (3 calendars) + vdirsyncer (Google + RustiCal CalDAV) |
| Contacts | khard + vdirsyncer CardDAV |
| Todos | todoman (CalDAV-backed) |
| RSS | newsboat (630+ YouTube feeds with duration filtering, podcasts, blogs) |
| Music | mpd + ncmpcpp (vim bindings, wave visualizer) |
| Files | vifm (vim bindings, iceberg colorscheme) |
| Monitor | btop (vim keys, braille graphs) |
| IRC | senpai + glirc |
| Window mgmt | Rectangle (keyboard shortcuts config) |
| Security | GPG agent with SSH, YubiKey agent, pass, browserpass |
| AI/Coding | opencode, gh-dash |
| Scripts | Custom scripts (mail sync, calendar import, contact save, etc.) |
| Agents | 5 launchd agents (mail-sync, khal-notify, vdirsyncer, podcast-cleanup, yt-duration-cache) |
Not managed by Nix
| Item | Reason |
|---|---|
| Neovim config | Separate git repo (github.com/conrad760/nvim) — cloned by bootstrap |
| Keychain secrets | Added by setup-keychain.sh — 7 entries for email, calendar, IRC, GitHub |
| Mail data | ~/Mail/ — synced by mbsync after bootstrap |
| Calendar data | ~/khal/ — synced by vdirsyncer after bootstrap |
| Contact data | ~/contacts/ — synced by vdirsyncer after bootstrap |
| Password store | ~/.password-store/ — clone from your git remote |
| GPG keys | On YubiKey — no file management needed |
Project Structure
.
├── flake.nix # Main flake (darwinConfigurations."shadow")
├── flake.lock # Locked dependency versions
├── install.sh # curl setup.cnr.ad | sh (POSIX sh wrapper)
├── bootstrap.sh # Fresh machine setup script (called by install.sh)
├── setup-keychain.sh # Populate macOS Keychain secrets
├── AppSettings/
│ └── rectangle/RectangleConfig.json # Rectangle window manager config
├── modules/
│ ├── darwin/
│ │ ├── default.nix # Imports system, environment, homebrew
│ │ └── settings/
│ │ ├── system.nix # macOS system preferences
│ │ ├── environment.nix # Shell, env vars
│ │ └── homebrew.nix # Declarative Homebrew casks + brews
│ └── home-manager/
│ ├── default.nix # Packages and config deployments
│ ├── launchd.nix # launchd agents + activation hooks
│ ├── settings/
│ │ ├── zsh.nix # Zsh + oh-my-zsh + plugins
│ │ ├── tmux.nix # Tmux + plugins
│ │ └── git.nix # programs.git + programs.jujutsu
│ └── files/
│ ├── bin/ # Scripts -> ~/.local/bin/
│ └── config/ # Config files deployed to ~/.config/
│ ├── neomutt/ # neomuttrc, mailcap, colors/
│ ├── newsboat/ # config, base-urls, youtube-urls, podcast-urls
│ ├── vifm/ # vifmrc, shortcuts, colors/
│ ├── ncmpcpp/ # config, bindings
│ ├── btop/ # btop.conf
│ ├── senpai/ # senpai.scfg
│ ├── glirc/ # config
│ ├── opencode/ # opencode.json
│ ├── git/ # gitcommit_template.txt
│ ├── mbsyncrc # -> ~/.mbsyncrc
│ ├── vdirsyncer-config # -> ~/.config/vdirsyncer/config
│ ├── khard.conf # -> ~/.config/khard/khard.conf
│ ├── todoman-config.py # -> ~/.config/todoman/config.py
│ ├── gh-dash.yml # -> ~/.config/gh-dash/config.yml
│ └── tmux-status.py # Custom tmux status bar
├── .editorconfig
├── .envrc # direnv: `use flake`
└── .gitmessage # Conventional commit template for this repo
Day-to-Day Usage
Rebuild after config changes
nixswitch # alias for: darwin-rebuild switch --flake ~/.config/nix#shadow
Update all flake inputs + rebuild
nixup # alias for: nix flake update + nixswitch
Add a nix package
- Add to
home.packagesinmodules/home-manager/default.nix - Run
nixswitch
Add a Homebrew cask/brew
- Add to
modules/darwin/settings/homebrew.nix - Run
nixswitch
Edit a deployed config
- Edit the source file under
modules/home-manager/files/config/ - Run
nixswitch(deploys the updated file)
For configs with path templating (khal, mpd, notmuch), edit the text = ''...'' block in default.nix.
Roll back
darwin-rebuild --rollback
Search for packages
nix search nixpkgs <package-name>
Secrets
Configs use security find-generic-password to read secrets from macOS Keychain at runtime. No secrets are stored in this repository.
| Keychain service | Used by | Purpose |
|---|---|---|
gmail-imap |
mbsync | Gmail IMAP app password |
gmail-smtp |
neomutt | Gmail SMTP app password |
vdirsyncer-google-url |
vdirsyncer | Google Calendar private .ics URL |
rustical-username |
vdirsyncer | RustiCal CalDAV username |
rustical-token |
vdirsyncer | RustiCal CalDAV token |
senpai |
senpai | IRC password |
gh-token |
gh CLI | GitHub personal access token |
To add or update secrets:
# Add new secret
security add-generic-password -a <account> -s <service> -w <password>
# Update existing secret
security add-generic-password -a <account> -s <service> -w <password> -U
# Delete a secret (needed to re-run setup-keychain.sh for that entry, since it skips existing)
security delete-generic-password -a <account> -s <service>
# Verify a secret
security find-generic-password -s <service> -w
Or use ./setup-keychain.sh to add all secrets interactively.
setup-keychain.shskips entries that already exist. To re-pull a secret from Bitwarden, delete it first withsecurity delete-generic-password, then re-run the script.
Git Workflow
This repo uses conventional commits:
<type>(<scope>): <description>
Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert
Examples:
feat(home-manager): add ncmpcpp config deployment
fix(darwin): correct homebrew cask list
docs: update README with bootstrap instructions
Troubleshooting
Build fails
nix flake check # validate flake
nix-instantiate --parse <file.nix> # check nix syntax
rm -rf result* && nixswitch # clean rebuild
nix flake check runs Nix formatting checks, shell script lint/format checks, and a flake eval for darwinConfigurations.shadow.
home-manager file conflict
If darwin-rebuild fails with "file already exists":
# Back up the conflicting file and retry
mv ~/.config/<path> ~/.config/<path>.bak
nixswitch
This happens when a file exists at a path home-manager wants to manage. The first rebuild after adding new file deployments may hit this.
neomutt / mbsync / vdirsyncer / senpai not working
Most likely cause: missing keychain secrets. Check with:
security find-generic-password -s gmail-imap -w # should print your app password
security find-generic-password -s gmail-smtp -w
If you get SecKeychainSearchCopyNext: The specified item could not be found, run:
./setup-keychain.sh --personal --bitwarden
Then sync mail with mbsync gmail before opening neomutt.
Homebrew failures
brew doctor # diagnose brew issues
brew update # update brew itself
nixswitch # retry
Rollback to previous generation
darwin-rebuild --rollback # go back one generation
# or list and switch to a specific generation:
nix-env --list-generations -p /nix/var/nix/profiles/system
nix-env --switch-generation <N> -p /nix/var/nix/profiles/system
Notes
files/config/tmux-status.pycontains a hardcoded email for filtering declined CalDAV events. Update theUSER_EMAILline if needed.- Newsboat youtube-urls and podcast-urls are deployed via home-manager. To add/remove feeds, edit the files in
modules/home-manager/files/config/newsboat/and runnixswitch.