This guide explains how to customize and extend the nix-darwin configuration for your needs.
Architecture: This repository uses the dendritic pattern with flake-parts. Each feature is a flake-parts module that can write to both the darwin (system) and home-manager (user) sides. See ADR-007.
Tip: Run
just --listfrom the repository root to see all available commands.
- Quick Commands
- Which File Do I Edit?
- Common Tasks
- Configuration Layers
- Testing Changes
- Troubleshooting
Common tasks using the justfile at the repository root:
# From ~/Developer/dotfiles
just fmt # Format all Nix files
just lint # Lint Nix files
just check # Run all checks (format, lint, deadcode, flake validation)
just build # Build without applying
just switch # Apply configuration
just update # Update flake inputs
just gc # Garbage collect (keeps last 7 days)| I want to... | Edit this file |
|---|---|
| Add a CLI tool (nix package) | modules/features/home-core.nix → home.packages |
| Add a GUI app (Homebrew cask) for all machines | modules/features/homebrew.nix → casks (standalone apps) |
| Add a GUI app with HM config | Co-locate cask in the feature module's darwin side |
| Add a GUI app for personal machine only | modules/hosts.nix → personal host homebrew.casks |
| Add a GUI app for work machine only | modules/hosts.nix → work host homebrew.casks |
| Change macOS Dock settings | modules/features/dock.nix |
| Change macOS Finder settings | modules/features/finder.nix |
| Change macOS trackpad settings | modules/features/trackpad.nix |
| Change menu bar clock settings | modules/features/menuextra-clock.nix |
| Change firewall/Touch ID settings | modules/features/security.nix |
| Configure a program (git, bat, etc.) | modules/features/<name>.nix (auto-discovered) |
| Change git identity | modules/features/git/default.nix (directory-based includes) |
| Add a new machine | modules/hosts.nix + modules/options.nix |
| Add config files (non-Nix) | Co-locate in modules/features/<name>/ |
| Add a fish abbreviation | modules/features/fish/_abbreviations.nix |
| Add a fish alias | modules/features/fish/_aliases.nix |
| Add a fish function | modules/features/fish/_functions.nix |
Packages installed via Nix go in modules/features/home-core.nix:
# modules/features/home-core.nix
home.packages = with pkgs; [
# ... existing packages ...
# Add your new package here
ripgrep # Fast grep alternative
htop # Interactive process viewer
];Finding package names:
# Search for packages
nix search nixpkgs <package-name>
# Example
nix search nixpkgs ripgrepGUI applications are installed via Homebrew casks.
For standalone apps (no HM config needed) - edit modules/features/homebrew.nix:
# modules/features/homebrew.nix
homebrew.casks = [
# ... existing casks ...
"spotify"
];For apps with HM configuration - co-locate the cask in the feature module:
# modules/features/myapp.nix
_: {
# Darwin side: install via Homebrew
flake.modules.darwin.myapp = _: {
homebrew.casks = ["myapp"];
};
# HM side: configure
flake.modules.homeManager.myapp = { config, ... }: {
xdg.configFile."myapp/config.json".source = ./config.json;
};
}For a specific machine only - edit modules/hosts.nix:
# modules/hosts.nix
dotfiles.hosts.personal.homebrew.casks = [
"transmission" # Personal only
];Finding cask names:
brew search <app-name>macOS settings are split into focused modules in modules/features/:
Dock settings (modules/features/dock.nix):
_: {
flake.modules.darwin.dock = _: {
system.defaults.dock = {
autohide = true;
tilesize = 48;
orientation = "left";
};
};
}Reference: See nix-darwin options for all available settings.
Programs in modules/features/ are auto-discovered via import-tree — just create the file and it's included!
Simple HM-only module (single file):
# modules/features/tmux.nix
_: {
flake.modules.homeManager.tmux = { pkgs, ... }: {
programs.tmux = {
enable = true;
terminal = "screen-256color";
};
};
}Cross-cutting module (darwin + HM):
# modules/features/myshell.nix
_: {
flake.modules.darwin.myshell = { pkgs, ... }: {
environment.shells = [ pkgs.myshell ];
};
flake.modules.homeManager.myshell = { pkgs, ... }: {
programs.myshell.enable = true;
};
}Complex module (directory with co-located config):
# modules/features/myapp/default.nix
_: {
flake.modules.homeManager.myapp = _: {
xdg.configFile."myapp/settings.json".source = ./settings.json;
};
}Drafting a module (excluded from auto-discovery):
Prefix with _ to exclude from auto-discovery while working on it:
_tmux.nix # Ignored by import-tree
_neovim/ # Ignored by import-tree- Add host data in
modules/hosts.nix:
# modules/hosts.nix
_: {
dotfiles.hosts.new-laptop = {
hostname = "New-Laptop"; # Run: scutil --get LocalHostName
homebrew.casks = []; # Machine-specific casks
};
}-
Update
justfilewith the new hostname if needed. -
Apply on the new machine:
sudo darwin-rebuild switch --flake ~/Developer/dotfiles#New-LaptopGit identity is handled via conditional includes in modules/features/git/default.nix based on directory paths:
includes = [
{
condition = "gitdir:~/Developer/work/";
contents.user = {
email = "giovanni@company.com";
signingkey = config.dotfiles.sshKeys.git-signing;
};
}
];The signing key is sourced from the shared dotfiles.sshKeys option (set by the 1password module).
Fish is configured in modules/features/fish/:
Add an abbreviation: modules/features/fish/_abbreviations.nix
Add an alias: modules/features/fish/_aliases.nix
Add a function: modules/features/fish/_functions.nix
Co-locate config files with their module:
# Create program directory
mkdir -p modules/features/myapp
# Add default.nix that references the config
# Add the config file alongside it
modules/features/myapp/
default.nix # Nix module
config.json # Co-located configConfiguration flows through the dendritic architecture:
┌─────────────────────────────────────────────────┐
│ Flake-parts options (modules/options.nix) │ Typed options
│ - dotfiles.user, dotfiles.hosts │
├─────────────────────────────────────────────────┤
│ Host Assembly (modules/host-assembly.nix) │ Orchestration
│ - Builds darwinConfigurations per host │
│ - Merges all feature modules │
├─────────────────────────────────────────────────┤
│ Feature Modules (modules/features/*.nix) │ Cross-cutting
│ - Each writes to darwin AND/OR HM sides │
│ - Co-locates installation + configuration │
├─────────────────────────────────────────────────┤
│ Shared HM Options (hm-options.nix) │ Data bridges
│ - hostname, sshKeys, onePasswordAgentSock │
└─────────────────────────────────────────────────┘
just buildjust checkjust switchNix flakes only see files tracked by git. Add new files:
git add <new-file>Usually means a typo in an attribute name or missing import. Check:
- Spelling of attribute names
- All required arguments are passed to functions
- New files are
git add-ed
# List previous generations
just generations
# Rollback to previous
just rollback# Search nix-darwin options
man darwin-configuration.nix
# Search home-manager options
man home-configuration.nix
# Or online:
# https://nix-darwin.github.io/nix-darwin/manual/index.html
# https://nix-community.github.io/home-manager/options.xhtmljust update # Update all
just update-inputs nixpkgs # Update specific input
just switch # Rebuild to applyjust gc # Remove generations older than 7 days
just gc-all # Remove all old generations except currentEnsure 1Password CLI is installed and you're signed in:
op signinThe shell plugins (gh, awscli2) will prompt for biometric auth on first use.