Declarative personal setup for Apple Silicon macOS using nix-darwin, Home Manager, nix-homebrew, and Homebrew casks. A standalone Linux Home Manager configuration is also included.
Nix packages and configuration are pinned by flake.lock. Homebrew casks
are intentionally used for self-updating GUI applications, so their exact
versions are not pinned. Secrets and private machine data never belong in
this repository.
During Setup Assistant, create the first macOS account with the short name
gwku and enable FileVault. Store its recovery key somewhere secure and
outside this repository. Then run:
xcode-select --install
git clone https://github.com/gwku/dotfiles.git ~/development/dotfiles
cd ~/development/dotfiles
./install.sh gkmpAfter the installer succeeds, continue with the complete
POST-INSTALL.md checklist. It covers the first restart,
Mac App Store and Bitwarden sign-in, SSH, privacy approvals, developer
credentials, SDKs, application data, code-managed macOS policy, and final
verification.
The installer:
- installs Determinate Nix when necessary;
- verifies that the configured macOS account exists;
- uses the nix-darwin revision pinned by this flake;
- activates nix-darwin and Home Manager.
The installer front-loads macOS administrator authentication and keeps that authorization alive during the build. If Xcode command-line tools are missing, it starts Apple's installer and asks you to rerun after it finishes. If the Bitwarden CLI is already configured but locked, its unlock also happens before the long switch starts. Once preflight is complete, the Nix/Homebrew activation itself is non-interactive.
End-to-end bootstrap is not completely unattended: Mac App Store applications require an Apple ID sign-in and the separate command below, and a clean machine still needs one-time Bitwarden desktop/CLI login and SSH-agent setup after activation. FileVault enrollment belongs in Setup Assistant because it creates credential-bound recovery material; the runtime test verifies that it remains enabled.
After signing into the Mac App Store, restore the declared App Store applications:
./scripts/install-mas-apps.shThis installs Amphetamine, HP Smart, Keynote, Microsoft Word, Numbers, Pages, Todoist, and WireGuard. It is deliberately separate from activation so a clean VM build never needs Apple ID credentials.
This repository standardises on Determinate Nix. Its official nix-darwin module prevents nix-darwin from trying to replace the Determinate-managed daemon and declares the additional Nix settings used by this setup.
The Linux target manages the user environment with standalone Home Manager; it is not a complete NixOS system configuration. On an x86-64 Linux machine, install Nix if the machine does not already provide it, then run:
curl --proto '=https' --tlsv1.2 -sSf -L \
https://install.determinate.systems/nix | sh -s -- install
git clone https://github.com/gwku/dotfiles.git ~/development/dotfiles
cd ~/development/dotfiles
nix run path:.#home-manager -- switch --flake path:.#gwku@workstation
./scripts/smoke-test.shOn NixOS, use the existing system Nix installation and start with the clone. The same Home Manager target can be imported into a future NixOS system configuration if system-level NixOS management is added later.
These commands are not a daily routine. Normal computer use requires no Nix maintenance. Run them only after changing or pulling this repository, when deliberately updating pinned dependencies, or when troubleshooting.
# Test changes without activating them
./scripts/check.sh gkmp
# Apply repository changes to this Mac
./scripts/switch.sh gkmp
# Periodically update pinned Nix inputs, test, and then apply
nix flake update
./scripts/check.sh gkmp
./scripts/switch.sh gkmp
# Troubleshooting: inspect or roll back to an older Mac generation
darwin-rebuild --list-generations
sudo darwin-rebuild --switch-generation <generation>Fish provides a few optional abbreviations:
drsapplies this Mac's configuration (macOS only);hmsapplies the standalone Home Manager configuration (Linux only);nfuupdates the repository's pinned Nix inputs;nfcchecks the repository's flake.
The abbreviations use the repository at ~/development/dotfiles, so they
work from any directory. Updating inputs can change many package versions;
review flake.lock, run the checks, and switch only when you intend to
upgrade.
The Homebrew declaration is exhaustive: activation runs an explicit,
non-zapping brew bundle cleanup --force, so formulae and casks removed
from the declaration are also removed from the machine while their user
data is preserved. Close Homebrew-managed applications before switching.
On an existing Mac, first complete a successful switch. Then remove the privileged leftovers that Homebrew cannot own:
./scripts/finish-current-mac-cleanup.shThe script moves obsolete Docker launch daemons and privileged helpers to the Trash, asks macOS to garbage-collect extensions, and tells you if a reboot is needed. It never deletes the archive directly.
Run the non-activating checks first:
nix flake check path:. --all-systems --no-build
nix build path:.#darwinConfigurations.gkmp.systemFor a clean-machine test on Apple Silicon:
./scripts/test-vm.sh --switch --stop-afterThe VM test installs Determinate Nix, builds the full configuration,
performs a real switch, and runs the runtime smoke tests. It is still
important to test a fresh VM rather than relying only on a previously
mutated dotfiles-test VM.
After activation, run:
./scripts/smoke-test.shBitwarden is the source of truth for private SSH keys and private host metadata. Home Manager:
- points OpenSSH at the Bitwarden desktop SSH-agent socket;
- installs
bw-ssh-sync; - includes the generated
~/.ssh/config.bitwarden; - synchronizes metadata in the switch preflight, prompting before the build when the CLI is logged in but locked.
Private keys are never written by Nix or bw-ssh-sync. The sync command
writes only public-key selector files under ~/.ssh/bitwarden/ and Host
blocks generated from Bitwarden custom fields.
- Open the Bitwarden desktop application, sign in, enable its SSH agent, and allow Bitwarden to remain open in the background.
- Log the CLI into the same account once with
bw login. - Run
bw-ssh-sync. If the CLI vault is locked, it prompts to unlock it for that command. - Verify the agent and a configured host:
ssh-add -L
ssh -G github.comEvery native Bitwarden SSH item is offered by the desktop agent automatically; there is no hardcoded key list in this repository. Host aliases require metadata because a key alone cannot reveal its hostname, username, or port.
Add a Text custom field named ssh_config to any SSH item that needs one
or more host aliases. Use {{identity_file}} where the generated public
selector belongs:
Host example
HostName example.internal
User deploy
Port 22
IdentityFile {{identity_file}}
IdentitiesOnly yesRun bw-ssh-sync after adding or changing an item. The command discovers
all SSH items dynamically, validates each public-key fingerprint,
regenerates the config atomically, and removes stale generated selector
files. Custom fields such as source_path, public_key_comment, and
host_aliases are retained as provenance but are not required at
runtime.
scripts/switch.sh prompts for bw unlock before building when the CLI is
logged in but locked. Home Manager activation always uses the
non-interactive mode, preserving the last generated configuration when the
CLI is locked or has never logged in. The repository contains neither
secret blobs nor vault credentials.
Nix does not reproduce credentials, application databases, or cloud
accounts. Follow the ordered POST-INSTALL.md checklist
after a clean bootstrap. UNMANAGED.md explains why that
state remains external. Important examples include:
- enabling Bitwarden's desktop SSH agent and running
bw-ssh-sync; - AWS, Kubernetes, GitHub CLI, GPG, and Stripe authentication;
- Android SDK platforms, system images, and accepted licences;
- application sign-ins and synced IDE/editor settings;
- network/peripheral enrollment and a credential-bound Time Machine destination.
Public Cursor extensions, Shopify CLI 4.5.2, and Google Lighthouse 13.4.1
are pinned by the flake. Cursor's bundled anysphere.* extensions continue
to ship with Cursor itself.
Home Manager uses the backup suffix .hm-backup when taking over an
existing file. Inspect those backups after the first successful switch
and remove them only after confirming the managed replacement works.
gkmp— Apple Silicon MacBookworkstation— x86-64 Linux placeholder