Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nix-config

Reproducible system configuration for macOS and Linux using Nix flakes, nix-darwin, system-manager, and home-manager.

Built on flake-parts with easy-hosts for auto-discovered host management. Tasks are managed with mise.

Platforms

PlatformSystem ConfigUser ConfigPackage Manager
macOS (Apple Silicon)nix-darwinhome-managerHomebrew + Nix
macOS (Intel)nix-darwinhome-managerHomebrew + Nix
Ubuntu Linux (headless)system-managerhome-managerNix

Quick Start

Bootstrap a fresh machine with a single command:

sh -c 'curl -sSfL https://raw.githubusercontent.com/RobertDeRose/nix-config/main/bootstrap.sh | bash -s -- <hostname>'

Or from a local clone:

./bootstrap.sh <hostname>

See Bootstrapping for details.

Key Design Decisions

  • No flake.nix editing to add hosts – drop a directory into hosts/ and it’s auto-discovered.
  • Shared modules, per-host overrides – global config in modules/ and home/, host-specific additions in hosts/<arch>-darwin/<hostname>/ or systems/<arch>-linux/<hostname>/.
  • Declarative Homebrew – casks, brews, and Mac App Store apps are managed via Nix and cleaned up automatically.
  • Consistent Ayu Mirage theme – terminal, editor, multiplexer, and prompt all share the same color palette.

Bootstrapping

The bootstrap.sh script handles everything needed to set up a fresh machine from zero to a fully configured system.

One-Liner

sh -c 'curl -sSfL https://raw.githubusercontent.com/RobertDeRose/nix-config/main/bootstrap.sh | bash -s -- <hostname>'

What It Does

macOS

  1. Installs Xcode Command Line Tools (if missing)
  2. Clones this repo to ~/workspace/personal/nix-config (if not already inside it)
  3. Creates hosts/<arch>-darwin/<hostname>/ from the darwin template (if it doesn’t exist)
  4. Installs Homebrew (if missing)
  5. Installs Nix via the Determinate Systems installer (if missing)
  6. Runs darwin-rebuild switch to build and activate the full configuration

Linux (Ubuntu headless)

  1. Clones this repo (if not already inside it)
  2. Creates systems/<arch>-linux/<hostname>/ from the linux template
  3. Installs Nix (if missing)
  4. Builds and activates system-manager configuration
  5. Builds and activates home-manager configuration

After Bootstrap

Once the initial bootstrap completes, day-to-day changes are applied with:

mise run nix:switch

CI Mode

The bootstrap script supports a ci-bootstrap argument for testing in CI without actually activating:

./bootstrap.sh ci-bootstrap

This runs the full pipeline but evaluates configurations without building, verifying the flake is valid on a fresh machine.

Architecture Overview

This repo manages two distinct platform configurations through a unified Nix flake. The architecture splits into three layers:

  1. System configuration – OS-level settings, packages, services, and daemons.
  2. User configuration – dotfiles, shell, editor, and CLI tools via home-manager.
  3. Host discovery – automatic detection of host directories without manual flake registration.

Platform Split

macOS uses nix-darwin for system config. Home-manager runs as a nix-darwin module, so a single darwin-rebuild switch applies both system and user changes.

Linux (Ubuntu headless servers) uses system-manager for system config and home-manager as a standalone tool. These are applied separately because system-manager is not NixOS – it manages config files and services on an existing distro.

Module Layers

flake.nix
├── modules/common/       Shared Nix settings, fonts, overlays
├── modules/darwin/        macOS system defaults, Homebrew, iTerm2
├── modules/linux/         system-manager SSH, users, packages
├── home/common/           Cross-platform: shell, git, editors, tools
├── home/darwin.nix        macOS-specific entry point + SSH agent
├── home/linux.nix         Linux-specific entry point
├── hosts/<arch>-darwin/   Per-host Darwin overrides
└── systems/<arch>-linux/  Per-host Linux overrides

See the sub-pages for details on each layer.

Module Composition

The configuration is composed from layered Nix modules. Each layer adds to the previous one, and Nix’s module system merges lists and attribute sets automatically.

System Modules (macOS)

modules/darwin/system.nix      macOS defaults (Dock, Finder, keyboard, trackpad)
modules/darwin/apps.nix        Homebrew casks/brews/masApps + nix system packages
modules/darwin/iterm2.nix      Declarative iTerm2 plist management
modules/common/nix-core.nix    Nix daemon, caches, GC, experimental features
modules/common/fonts.nix       Nerd Fonts, Font Awesome, Material Design Icons
modules/common/overlays.nix    Temporary nixpkgs patches

These are imported by flake.nix for every Darwin host. A host’s default.nix can set any of the same options to add packages, change defaults, or enable features like the linux-builder.

System Modules (Linux)

modules/linux/system.nix       system-manager: SSH hardening, users, packages, locale
modules/common/nix-core.nix    Shared Nix daemon settings
modules/common/fonts.nix       Shared fonts

Home-Manager Modules

home/common/default.nix        Aggregator -- imports all shared modules below
home/common/core.nix           Cross-platform CLI tools and repo helper scripts
home/common/shell.nix          zsh + starship prompt + aliases
home/common/git.nix            git, gh, lazygit, SSH signing, difftastic
home/common/direnv.nix         direnv + nix-direnv + mise integration
home/common/helix.nix          Helix editor + LSPs
home/common/zellij.nix         Zellij multiplexer
home/common/htop.nix           htop layout
home/common/opencode.nix       OpenCode AI agent
home/common/ghostty.nix        Ghostty terminal (macOS only, imported by home/darwin.nix)
home/common/zed.nix            Zed editor config (macOS only, imported by home/darwin.nix)

home/darwin.nix and home/linux.nix are the platform entry points. They import common/ plus platform-specific modules.

Merge Behavior

Nix module options like homebrew.brews, environment.systemPackages, home.packages, and home.shellAliases are list or attrset types that merge across modules. This means a host’s default.nix or home.nix can simply set:

homebrew.brews = [ "mosquitto" ];

and it will be added to the global list – no need to modify apps.nix.

Host Discovery

Hosts are auto-discovered from the filesystem. No manual registration in flake.nix is needed.

macOS Hosts (easy-hosts)

easy-hosts scans hosts/<arch>-darwin/<hostname>/ directories and generates darwinConfigurations entries automatically.

hosts/
├── aarch64-darwin/
│   └── USMBDEROSER/        ← auto-discovered as darwinConfigurations.USMBDEROSER
│       ├── default.nix     ← system config (required)
│       ├── user.nix        ← username + email (required)
│       └── home.nix        ← per-host HM overrides (optional)
└── x86_64-darwin/
    └── <hostname>/
        ├── default.nix
        ├── user.nix
        └── home.nix

The default.nix must import user.nix and wire up _module.args and home-manager.extraSpecialArgs so the username is available throughout the config. See Adding a Host for the full walkthrough.

Linux Hosts (custom discovery)

Linux hosts use a similar directory convention under systems/:

systems/
├── x86_64-linux/
│   └── <hostname>/
│       ├── system.nix      ← system-manager config
│       ├── user.nix        ← username + email
│       └── home.nix        ← per-host HM overrides (optional)
└── aarch64-linux/
    └── <hostname>/
        └── ...

easy-hosts assumes all *-linux directories are NixOS, but these are Ubuntu machines using system-manager. So flake.nix contains custom logic to scan systems/ and generate systemConfigs + homeConfigurations.

Why Two Directories?

The split between hosts/ (Darwin) and systems/ (Linux) exists because easy-hosts would try to create NixOS configurations for Linux directories under hosts/. Keeping them separate avoids this conflict.

Branch Strategy

Host directories are typically developed on feature branches named host/<hostname> and merged to main once tested. This keeps main clean while allowing per-machine iteration.

Nix Toolchain

Lix vs CppNix

This repo uses Lix (a Nix fork) on most platforms for its improved error messages and performance. The exception is x86_64-darwin (Intel Macs), where Lix dropped support – those machines use the upstream CppNix package instead.

The selection logic lives in modules/common/nix-core.nix:

nix.package =
  if pkgs.stdenv.hostPlatform.system == "x86_64-darwin"
  then pkgs.nix
  else pkgs.lix;

Experimental Features

The flake enables nix-command and flakes experimental features globally. These are required for nix build, nix develop, and flake-based workflows.

Binary Caches

Three substituters are configured to speed up builds:

CachePurpose
cache.nixos.orgOfficial NixOS cache
nix-community.cachix.orgCommunity packages (home-manager, etc.)
cache.numtide.comnumtide packages (system-manager)

Garbage Collection

Automatic weekly GC deletes store paths older than 7 days:

nix.gc = {
  automatic = true;
  options = "--delete-older-than 7d";
};

Manual cleanup is available via mise run nix:clean [window] (prune old system generations for this config) and mise run nix:gc (aggressive store-wide garbage collection).

Per-Host Overrides

Every host can customize the global configuration without editing shared modules. Nix’s module system merges lists and attribute sets, so per-host files simply add to what’s already defined.

What Can Be Overridden

System-level (in default.nix or system.nix)

# Add Homebrew packages (macOS)
homebrew.brews = [ "mosquitto" ];
homebrew.casks = [ "slack" ];
homebrew.masApps = { "Xcode" = 497799835; };

# Add nix system packages
environment.systemPackages = with pkgs; [ terraform ];

# Change macOS defaults
system.defaults.dock.autohide = false;

# Enable the Apple container Linux builder
services.container-builder = {
  enable = true;
  cpus = 4;
  memory = "8G";
  maxJobs = 4;
  bridge.enable = true;
};

User-level (in home.nix)

{ pkgs, ... }:
{
  # Add user packages
  home.packages = with pkgs; [ spotify-player ];

  # Add shell aliases
  home.shellAliases = {
    work = "cd ~/workspace/work";
    personal = "cd ~/workspace/personal";
  };

  # Create files in $HOME
  home.file."workspace/personal/.gitconfig".text = ''
    [user]
      email = personal@example.com
  '';
}

How It Works

The host’s default.nix is imported as an additional module alongside the global modules. Nix merges all option definitions:

  • Lists (homebrew.brews, home.packages): concatenated
  • Attribute sets (home.shellAliases): merged (host values override on conflict)
  • Scalars (system.defaults.dock.autohide): last definition wins (use lib.mkForce if the global module also sets it)

File Structure

hosts/aarch64-darwin/MYMACHINE/
├── default.nix     # System overrides (required)
├── user.nix        # Username, name, email, GitHub username (required)
└── home.nix        # Home-manager overrides (optional)

The home.nix is conditionally imported – if the file doesn’t exist, it’s skipped. No boilerplate needed.

Adding a New Host

Adding a host requires no flake.nix editing. Drop a directory into the right place and it’s auto-discovered.

Quick Way (mise task)

From any machine with mise installed:

mise run add-host <hostname> [os] [arch]
  • os accepts darwin or linux (defaults to current machine)
  • arch accepts aarch64 or x86_64 (defaults to current machine)

This creates the host directory from the appropriate template, generates user.nix from git config, creates a feature branch host/<hostname>, and commits.

Manual Way

macOS Host

mkdir -p hosts/aarch64-darwin/<hostname>
cp templates/darwin/* hosts/aarch64-darwin/<hostname>/

Edit user.nix with the machine’s identity fields:

{
  username = "jdoe";
  fullname = "Jane Doe";
  useremail = "jdoe@example.com";
  githubUsername = "jdoe";
}

Linux Host

mkdir -p systems/x86_64-linux/<hostname>
cp templates/linux/* systems/x86_64-linux/<hostname>/

Edit user.nix the same way.

Testing

After creating the host directory:

# macOS -- build without activating
nix build --accept-flake-config .#darwinConfigurations.<hostname>.system

# Linux system-manager
nix build --accept-flake-config .#systemConfigs.<hostname>

# Linux home-manager
nix build --accept-flake-config .#homeConfigurations.<hostname>.activationPackage

Activating

On the target machine:

# macOS
sudo darwin-rebuild switch --flake .

# Linux
sudo system-manager switch --flake .
home-manager switch --flake .#<hostname>

Or use the mise tasks which handle platform detection:

mise run nix:switch

Shell & Prompt

The shell configuration lives in home/common/shell.nix and provides a consistent experience across macOS and Linux.

Zsh

Zsh is the default shell with these plugins:

  • zsh-autosuggestions – fish-like history suggestions
  • zsh-syntax-highlighting – command syntax coloring

It also restores a few interactive behaviors that changed after the move to Home Manager-managed zsh:

  • Delete performs forward delete instead of inserting ~
  • Up and Down search history by the current typed prefix
  • Ctrl-W uses bash-style word boundaries
  • ~/.zshrc.local is sourced if present for machine-local shell experiments

Starship Prompt

The prompt uses Starship with an elaborate Ayu Mirage-themed configuration using Nerd Font “pill” segments:

┌  macOS  ~/workspace/personal/nix-config   main   nix   1.2s
└

Each segment is color-coded and separated by Nerd Font glyphs. The prompt shows:

SegmentWhat it displays
OS iconmacOS/Linux logo
DirectoryCurrent path (truncated to 3 levels)
Git branchBranch name + status
LanguagesNode, Python, Rust, Go, Java versions (when detected)
DurationCommand execution time (if > 2 seconds)
ShellCurrent shell name
TimeHH:MM format

Aliases

Global aliases defined in shell.nix:

AliasCommandNotes
ls, l, ll, laeza variantsWith icons and git status
cat, lessbatSyntax-highlighted pager
ocopencodeAI coding agent
gst, gd, ga, gcgit shortcutsCommon git operations
dockercontainermacOS only (Apple container runtime)
up Ncd ../../../...Navigate up N directories

Per-host aliases go in hosts/<arch>-darwin/<hostname>/home.nix or systems/<arch>-linux/<hostname>/home.nix – see Per-Host Overrides.

CLI Helpers

Repo-managed helper scripts installed from files/scripts/:

ScriptPurpose
rundRun a disposable Ubuntu container with the current directory mounted in

Packaged Git workflow tools installed via Home Manager:

ToolPurpose
wtWorktrunk worktree management with shell integration
git trimTrim tracking branches merged or gone upstream
git townHigher-level branch workflow and sync helpers

Git worktree management is now provided by Worktrunk via the wt command and its shell integration configured in home/common/default.nix.

Platform-Aware Behavior

  • macOS adds /opt/homebrew/bin and /opt/homebrew/sbin to $PATH
  • The docker alias only applies on Darwin (maps to Apple’s container runtime)
  • Esc Esc toggles a leading sudo like the oh-my-zsh sudo plugin
  • mise activate zsh is loaded when mise is available
  • Ghostty shell integration is sourced manually (workaround for cmux)

Theme Consistency

The Ayu Mirage color palette is used across all configured tools for a consistent visual experience.

Where Ayu Mirage Is Applied

ToolConfig FileTheme Setting
Ghosttyhome/common/ghostty.nixtheme = "Ayu Mirage"
Helixhome/common/helix.nixtheme = "ayu_mirage"
Zedhome/common/zed.nixtheme.dark = "Ayu Mirage"
Zellijhome/common/zellij.nixCustom Ayu Mirage theme block
Starshiphome/common/shell.nixAyu Mirage hex colors in each segment
batvia shell aliasInherits terminal colors

Fonts

Font configuration lives in modules/common/fonts.nix:

  • DejaVu SansM Nerd Font – Ghostty terminal
  • MesloLGS Nerd Font – iTerm2, Zed editor
  • Fira Code Nerd Font – available as alternative
  • Symbols Only Nerd Font – fallback for Nerd Font glyphs

Changing the Theme

To switch to a different color scheme:

  1. Update home/common/ghostty.nix – change theme
  2. Update home/common/helix.nix – change theme
  3. Update home/common/zed.nix – change the theme names in userSettings.theme
  4. Update home/common/zellij.nix – replace the color hex values in the theme block
  5. Update home/common/shell.nix – replace the Starship segment colors (search for hex color codes like #1F2430)

All five files need to change for full consistency. The Starship prompt is the most involved since each segment has individual foreground and background colors.

Day-to-Day Commands

All commands are run via mise tasks defined in mise.toml.

Common Commands

# Apply config on the current machine (auto-detects hostname + platform)
mise run nix:switch

# Debug a failing build (show trace)
mise run nix:debug

# Update all flake inputs
mise run nix:up

# Update a single input
mise run nix:up nixpkgs

# Clean up nix-config history
mise run nix:clean              # default retention: 7d
mise run nix:clean 30d          # keep 30 days of nix-config generations
mise run nix:gc                 # aggressive: free unused store paths across the machine

# Format all .nix files
mise run nix:fmt

# Open a nix repl with the flake loaded
mise run nix:repl

Listing All Tasks

mise tasks

This shows every available task grouped by category. Hidden tasks (internal building blocks) are not shown by default.

Platform Detection

mise run nix:switch and mise run nix:debug automatically detect:

  • Hostname from the system
  • Platform (darwin vs linux)
  • Architecture (aarch64 vs x86_64)

They then run the appropriate build command (darwin-rebuild switch on macOS, system-manager switch + home-manager switch on Linux).

System History

# Show recent system generations
mise run nix:history

This shows the system profile generations for the current machine.

Use mise run nix:clean when you want to trim old nix-config generations without collecting unrelated store paths from other projects.

Remote Deployment

Linux hosts can be configured remotely from a macOS machine using the nix:deploy task.

How It Works

The deploy task:

  1. Builds the system-manager and home-manager configurations locally (or on the configured Linux builder if cross-compiling)
  2. Copies the built closures to the remote host via SSH
  3. Activates system-manager and home-manager on the remote host

Usage

mise run nix:deploy <user@host-or-ssh-alias> [config-name]

If the config name is omitted, the task resolves it from hostname -s on the remote machine.

Prerequisites

  • The remote host must have Nix installed
  • SSH access with key-based authentication
  • The local machine must be able to build aarch64-linux or x86_64-linux derivations (via the configured Linux builder or another remote builder)

SSH Key Setup

This repo uses the Bitwarden SSH agent on macOS for key management. The SSH agent configuration is in home/darwin/ssh.nix and uses the Bitwarden Desktop app’s sandboxed socket.

For remote hosts, SSH authorized keys are fetched from GitHub (github.com/<username>.keys) with a caching mechanism configured in modules/linux/system.nix.

Linux Builder

This repo uses nix-hex-box to provide the aarch64-linux builder used by the Darwin hosts for Linux derivations.

The integration here is intentionally high-level. The implementation details, runtime model, recovery flow, generated helper files, and operational guidance all live in the nix-hex-box project documentation.

Project docs:

How This Repo Uses It

The Darwin hosts import the nix-hex-box module and enable services.container-builder in host-specific configuration.

Typical settings used in this repo:

services.container-builder = {
  enable = true;
  cpus = 4;
  memory = "8G";
  maxJobs = 4;
  bridge.enable = true;
};

At a high level, this gives the host:

  • an Apple Container based aarch64-linux builder
  • a container-builder SSH endpoint used by nix.buildMachines
  • helper state and logs under ~/.local/state/hb
  • on-demand builder startup for user access
  • a compatible bridge path for the root nix-daemon

For runtime details, verification steps, recovery behavior, and option reference, use the upstream project docs instead of this repo chapter.

Apple Container Testing

Apple Container provides a way to test Linux configurations from macOS before deploying to real servers.

Tasks

# Run the Linux bootstrap flow in a clean Apple container
mise run test:bootstrap

How It Works

The bootstrap test recreates a systemd-capable Ubuntu Apple container, configures a disposable tester account, and runs the pushed branch through the real bootstrap entrypoint:

curl -sSfL https://raw.githubusercontent.com/<owner>/<repo>/<branch>/bootstrap.sh | bash -s -- <test-hostname>

Uncommitted changes and local commits that have not been pushed to origin are warned about and are not exercised by test:bootstrap.

When to Use

  • Testing changes to modules/linux/system.nix before deploying to production
  • Verifying the bootstrap script works on a clean Ubuntu installation

Task Reference

All tasks are defined in mise.toml and are safest to run as mise run <task>.

Bootstrap & Host Management

TaskDescription
nix:initFull bootstrap pipeline (install nix, add host, activate)
add-hostCreate a new host directory from template
install-nixInstall Nix via nix-installer (hidden)
github-authAuthenticate with GitHub CLI (hidden)
activateBuild and activate the current machine’s config (hidden)

Day-to-Day

TaskDescription
nix:switchApply config on the current machine
nix:debugApply config with --show-trace for debugging
nix:dry-runDry-run a build target or the current host config
nix:check-cacheCheck whether a store path exists in configured substituters
nix:deployBuild locally and deploy system config to a remote Linux host

Maintenance

TaskDescription
nix:upUpdate a single flake input, or all inputs if none specified
nix:historyList system profile generations via nix-env
nix:replOpen a nix repl with nixpkgs
nix:cleanRemove system generations older than a retention window
nix:gcGarbage-collect unused store entries across the machine
nix:gcrootList auto GC roots
nix:fmtFormat all .nix files with the configured formatter
nix:trustAdd current user to Nix trusted-users
nix:uninstallFully uninstall Nix from the system

iTerm2

TaskDescription
iterm:exportRe-export iTerm2 preferences plist

Apple Container Tests (macOS only)

TaskDescription
test:bootstrapRun Linux bootstrap validation in an Apple container

Documentation

TaskDescription
docs:buildBuild the mdBook documentation site
docs:serveServe docs locally with hot-reload

Hidden helper tasks also exist for bootstrap flow: install-nix, github-auth, add-host, and activate.

Module Reference

Quick reference for what each module provides.

System Modules

modules/common/nix-core.nix

Nix daemon settings shared across platforms. Configures experimental features (nix-command, flakes), binary caches, trusted users, and weekly garbage collection. Selects Lix or CppNix based on platform.

modules/common/fonts.nix

Font packages installed system-wide: Nerd Fonts (DejaVu Sans Mono, Fira Code, Meslo LG, Symbols Only), Font Awesome, and Material Design Icons.

modules/common/overlays.nix

Temporary nixpkgs overrides. Currently:

  • Disables direnv tests (fail under macOS SIP sandbox)
  • Replaces mas with version 6.0.1 (required by Homebrew, not yet in nixpkgs)

modules/darwin/system.nix

macOS system defaults: Dock (autohide, persistent apps, hot corners), Finder (Posix path in title, list view, show extensions), Trackpad (tap-to-click, three-finger drag), Keyboard (Caps Lock remapped to Escape), security (TouchID + WatchID for sudo), timezone.

modules/darwin/apps.nix

Declarative package management: nix system packages (bat, eza, git, fastfetch, devenv), Homebrew taps, brews, casks, and Mac App Store apps. Uses zap cleanup to remove anything not listed.

modules/darwin/iterm2.nix

Copies the exported iTerm2 plist to a nix-managed location on activation. The plist is stored as a binary file in config/iterm2/.

modules/linux/system.nix

system-manager config for headless Ubuntu: hostname, timezone, locale, SSH hardening (password auth disabled, root login disabled), GitHub-based SSH authorized keys with caching, system packages, and user creation with zsh shell.

Home-Manager Modules

home/common/core.nix

Cross-platform CLI tools including btop, jq, openspec, pstree, ripgrep, tmux, yazi, yq, git-town, git-trim, and repo helper scripts (rund). Sets Helix as the default editor outside Zed-integrated terminals.

home/common/shell.nix

Zsh with autosuggestions and syntax highlighting. Starship prompt with Ayu Mirage colors and Nerd Font pill segments. Navigation aliases, git shortcuts, double-Escape sudo toggling, mise activate zsh, and platform-aware PATH setup.

home/common/git.nix

Git config with SSH commit signing (ed25519), difftastic as diff tool, merge style zdiff3, pull rebase, and conditional includes for per-workspace identity. GitHub CLI with credential helper. Lazygit.

home/common/direnv.nix

direnv + nix-direnv for automatic Nix shell activation. Custom mise integration script that sources mise without pulling the package into the Nix closure.

home/common/helix.nix

Helix editor: Ayu Mirage theme, relative line numbers, rulers at 100/120 chars. Keybindings matching VSCode conventions. Language servers: harper-ls (grammar), marksman, markdown-oxide, rumdl.

home/common/zellij.nix

Zellij terminal multiplexer with Ayu Mirage theme, compact layout. Platform-aware clipboard: pbcopy on macOS, OSC52 escape sequence on Linux.

home/common/ghostty.nix

Ghostty terminal (macOS only): Ayu Mirage theme, DejaVu SansM Nerd Font size 16, quick terminal panel (top, 35% height). Package set to null (installed via Homebrew).

home/common/zed.nix

Zed editor config (macOS only): package managed outside Nix, theme set to Ayu Mirage, VSCode keymap with Helix mode, Copilot edit predictions, and custom keybindings matching Helix.

home/common/opencode.nix

OpenCode AI coding agent. Uses the opencode flake input, currently backed by Numtide’s llm-agents.nix, so the package can come from cache.numtide.com without rebuilding the upstream Bun workspace locally.

home/darwin/ssh.nix

macOS SSH client using Bitwarden Desktop as the SSH agent. Configures IdentityAgent to the Bitwarden sandbox socket path.

Project Structure

.
├── flake.nix                  # Entry point — flake-parts + easy-hosts
├── flake.lock                 # Pinned input revisions
├── mise.toml                  # Task runner (30+ tasks)
├── bootstrap.sh               # One-liner bootstrap (curl | bash)
├── hk.pkl                     # Pre-commit hook config (Pkl language)
│
├── hosts/                     # macOS hosts (auto-discovered by easy-hosts)
│   ├── aarch64-darwin/
│   │   └── <hostname>/
│   │       ├── default.nix    # System config (required)
│   │       ├── user.nix       # Username, name, email, GitHub username
│   │       └── home.nix       # Per-host HM overrides (optional)
│   └── x86_64-darwin/
│       └── <hostname>/...
│
├── systems/                   # Linux hosts (custom discovery in flake.nix)
│   ├── x86_64-linux/
│   │   └── <hostname>/
│   │       ├── system.nix     # system-manager config
│   │       ├── user.nix       # Username, name, email, GitHub username
│   │       └── home.nix       # Per-host HM overrides (optional)
│   └── aarch64-linux/
│       └── <hostname>/...
│
├── templates/                 # Host templates (copied by add-host task)
│   ├── darwin/
│   │   ├── default.nix
│   │   └── home.nix
│   └── linux/
│       ├── system.nix
│       └── home.nix
│
├── modules/                   # System-level configuration modules
│   ├── common/
│   │   ├── nix-core.nix       # Nix daemon, caches, GC, Lix/CppNix
│   │   ├── fonts.nix          # Shared fonts
│   │   └── overlays.nix       # Temporary nixpkgs patches
│   ├── darwin/
│   │   ├── system.nix         # macOS defaults (Dock, Finder, trackpad...)
│   │   ├── apps.nix           # Homebrew + nix system packages
│   │   └── iterm2.nix         # iTerm2 plist management
│   └── linux/
│       └── system.nix         # system-manager: SSH, users, packages
│
├── home/                      # Home-manager (user-level) configuration
│   ├── darwin.nix             # macOS entry point
│   ├── linux.nix              # Linux entry point
│   ├── common/
│   │   ├── default.nix        # Aggregator: imports all shared modules
│   │   ├── core.nix           # CLI tools
│   │   ├── shell.nix          # zsh + starship
│   │   ├── git.nix            # git, gh, lazygit
│   │   ├── direnv.nix         # direnv + nix-direnv + mise
│   │   ├── helix.nix          # Helix editor
│   │   ├── zellij.nix         # Zellij multiplexer
│   │   ├── ghostty.nix        # Ghostty terminal (macOS)
│   │   ├── zed.nix            # Zed editor (macOS)
│   │   ├── htop.nix           # htop config
│   │   └── opencode.nix       # OpenCode AI agent
│   └── darwin/
│       └── ssh.nix            # Bitwarden Desktop SSH agent config
│
├── config/
│   └── iterm2/
│       └── com.googlecode.iterm2.plist  # Exported iTerm2 preferences
│
├── files/
│   ├── scripts/
│   │   └── rund               # Run in disposable Ubuntu container
│   └── workflows/             # macOS Finder Quick Actions
│
├── docs/                      # mdBook documentation (this site)
│   ├── book.toml
│   ├── theme/
│   └── src/
│
└── .github/workflows/
    ├── ci.yml                 # Validate: flake check + build configs
    ├── hk.yml                 # Lint: hk checks on PRs
    └── docs.yml               # Deploy: mdBook to GitHub Pages

Git Hooks

This repo uses hk for pre-commit and lint checks. Configuration lives in hk.pkl (Pkl language).

Setup

Hooks auto-install via the mise postinstall hook. You can also install manually:

hk install --mise

Running Checks

# Run all checks
hk check -a

# Fix auto-fixable issues
hk fix -a

Configured Checks

Formatting

CheckFilesWhat it does
nixfmt**/*.nixFormat Nix files
shfmt**/*.shFormat shell scripts
pkl_format**/*.pklFormat Pkl files
tombi_format**/*.tomlFormat TOML files
actionlint.github/workflows/*.ymlLint GitHub Actions
shellcheck**/*.shLint shell scripts
mise-fmtmise.tomlFormat the mise config
misemise.tomlValidate mise task definitions

Security & Hygiene

CheckWhat it does
detect-private-keyCatch accidentally committed private keys
check-merge-conflictDetect merge conflict markers
check-added-large-filesPrevent large file commits
check-byte-order-markerDetect UTF-8 BOM
check-case-conflictDetect case-only filename conflicts
check-symlinksValidate symlink targets exist

Whitespace

CheckWhat it does
trailing-whitespaceRemove trailing whitespace
mixed-line-endingEnforce consistent line endings
newlinesEnsure files end with newline

CI Pipeline

Two GitHub Actions workflows validate changes.

Validate (ci.yml)

Runs on pushes to main and pull requests.

Jobs

Build matrix (ubuntu-latest + macos-latest):

StepmacOSLinux
Install NixYesYes
Flake checkYesYes
Build Darwin configsYes
Build Linux system configsYes
Build Linux HM configsYes

Bootstrap test (separate job):

  • Runs ./bootstrap.sh ci-bootstrap on a fresh runner
  • Verifies the bootstrap pipeline reaches the handoff to the real tasks
  • Evaluates the target configurations without building or activating them (CI mode)

What It Catches

  • Nix evaluation errors (syntax, missing options, type mismatches)
  • Build failures in any host configuration
  • Bootstrap script regressions

Lint (hk.yml)

Runs on pull requests (opened, synchronized, reopened) and manual dispatch.

  • Installs mise (which installs hk and all linting tools)
  • Runs hk check -a for all configured checks
  • Catches formatting, security, and hygiene issues

Docs (docs.yml)

Runs on pushes to main and manual dispatch.

  • Builds the mdBook documentation site
  • Deploys to GitHub Pages

See Git Hooks for the local equivalent of the lint checks.

Troubleshooting

Common Issues

darwin-rebuild switch fails on first run

On a freshly bootstrapped machine, the first darwin-rebuild switch may fail with a conflict about /etc/nix/nix.conf. This happens because the Nix installer created the file and nix-darwin wants to manage it.

Fix: Back up and remove the conflicting file, then retry:

sudo mv /etc/nix/nix.conf /etc/nix/nix.conf.bak
sudo darwin-rebuild switch --flake .

iTerm2 preferences not applying

iTerm2 preferences are deployed from an exported binary plist in config/iterm2/. If you make changes in iTerm2’s UI, they won’t persist across rebuilds unless you re-export:

mise iterm:export

This copies the live preferences back to the repo. Commit the updated plist.

Font names don’t match after update

Nerd Fonts v3 changed font naming conventions. If your terminal or editor shows a fallback font:

Old name (v2)New name (v3)
MesloLGS NFMesloLGS Nerd Font
MesloLGS-NF-RegularMesloLGSNF-Regular

Check the font name in your app’s config and update it to the v3 name.

Linux Builder won’t start

The builder is managed through the hb helper and a user bridge launch agent, not a single system launchd service. Start with the current builder summary:

hb status

If the builder is unhealthy or the Apple container runtime is wedged, run the recovery path first:

hb repair

If it still will not come up:

  1. Check if the bridge agent is registered:
    launchctl print gui/$(id -u)/org.nixos.hexbox-bridge | head -20
    
  2. If the bridge agent shows “Could not find service”, the launch agent is not loaded. Run mise run nix:switch or sudo darwin-rebuild switch --flake .#$(hostname -s).
  3. If the bridge agent is loaded but readiness still fails, inspect the HexBox logs:
    hb logs readiness
    hb logs bridge
    hb logs boot
    hb inspect
    

nix build doesn’t use the Linux builder

Nix prefers binary cache substitution over remote building. If the package is already cached, the builder is never contacted. Force remote building with:

nix build <derivation> --max-jobs 0

Bitwarden SSH agent not working

The SSH agent config expects Bitwarden Desktop (Mac App Store version) to be running. The socket path is:

~/Library/Containers/com.bitwarden.desktop/Data/.bitwarden-ssh-agent.sock

If using the non-App Store version, the path will be different.

mas (Mac App Store CLI) errors

The repo uses a patched mas 6.0.1 via an overlay in modules/common/overlays.nix. If you see errors about Mac App Store apps failing to install, check that the overlay is still working and that you’re signed into the App Store.

Sudo prompts during darwin-rebuild switch

darwin-rebuild switch requires root. Since the 25.05 nix-darwin release, activation must be run as root:

sudo darwin-rebuild switch --flake .

The mise run nix:switch task handles this automatically.