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
| Platform | System Config | User Config | Package Manager |
|---|---|---|---|
| macOS (Apple Silicon) | nix-darwin | home-manager | Homebrew + Nix |
| macOS (Intel) | nix-darwin | home-manager | Homebrew + Nix |
| Ubuntu Linux (headless) | system-manager | home-manager | Nix |
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/andhome/, host-specific additions inhosts/<arch>-darwin/<hostname>/orsystems/<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
- Installs Xcode Command Line Tools (if missing)
- Clones this repo to
~/workspace/personal/nix-config(if not already inside it) - Creates
hosts/<arch>-darwin/<hostname>/from the darwin template (if it doesn’t exist) - Installs Homebrew (if missing)
- Installs Nix via the Determinate Systems installer (if missing)
- Runs
darwin-rebuild switchto build and activate the full configuration
Linux (Ubuntu headless)
- Clones this repo (if not already inside it)
- Creates
systems/<arch>-linux/<hostname>/from the linux template - Installs Nix (if missing)
- Builds and activates system-manager configuration
- 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:
- System configuration – OS-level settings, packages, services, and daemons.
- User configuration – dotfiles, shell, editor, and CLI tools via home-manager.
- 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:
| Cache | Purpose |
|---|---|
cache.nixos.org | Official NixOS cache |
nix-community.cachix.org | Community packages (home-manager, etc.) |
cache.numtide.com | numtide 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 (uselib.mkForceif 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]
osacceptsdarwinorlinux(defaults to current machine)archacceptsaarch64orx86_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:
Deleteperforms forward delete instead of inserting~UpandDownsearch history by the current typed prefixCtrl-Wuses bash-style word boundaries~/.zshrc.localis 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:
| Segment | What it displays |
|---|---|
| OS icon | macOS/Linux logo |
| Directory | Current path (truncated to 3 levels) |
| Git branch | Branch name + status |
| Languages | Node, Python, Rust, Go, Java versions (when detected) |
| Duration | Command execution time (if > 2 seconds) |
| Shell | Current shell name |
| Time | HH:MM format |
Aliases
Global aliases defined in shell.nix:
| Alias | Command | Notes |
|---|---|---|
ls, l, ll, la | eza variants | With icons and git status |
cat, less | bat | Syntax-highlighted pager |
oc | opencode | AI coding agent |
gst, gd, ga, gc… | git shortcuts | Common git operations |
docker | container | macOS only (Apple container runtime) |
up N | cd ../../../... | 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/:
| Script | Purpose |
|---|---|
rund | Run a disposable Ubuntu container with the current directory mounted in |
Packaged Git workflow tools installed via Home Manager:
| Tool | Purpose |
|---|---|
wt | Worktrunk worktree management with shell integration |
git trim | Trim tracking branches merged or gone upstream |
git town | Higher-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/binand/opt/homebrew/sbinto$PATH - The
dockeralias only applies on Darwin (maps to Apple’scontainerruntime) Esc Esctoggles a leadingsudolike the oh-my-zsh sudo pluginmise activate zshis loaded whenmiseis 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
| Tool | Config File | Theme Setting |
|---|---|---|
| Ghostty | home/common/ghostty.nix | theme = "Ayu Mirage" |
| Helix | home/common/helix.nix | theme = "ayu_mirage" |
| Zed | home/common/zed.nix | theme.dark = "Ayu Mirage" |
| Zellij | home/common/zellij.nix | Custom Ayu Mirage theme block |
| Starship | home/common/shell.nix | Ayu Mirage hex colors in each segment |
| bat | via shell alias | Inherits 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:
- Update
home/common/ghostty.nix– changetheme - Update
home/common/helix.nix– changetheme - Update
home/common/zed.nix– change the theme names inuserSettings.theme - Update
home/common/zellij.nix– replace the color hex values in the theme block - 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:
- Builds the system-manager and home-manager configurations locally (or on the configured Linux builder if cross-compiling)
- Copies the built closures to the remote host via SSH
- 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-linuxorx86_64-linuxderivations (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-linuxbuilder - a
container-builderSSH endpoint used bynix.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.nixbefore 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
| Task | Description |
|---|---|
nix:init | Full bootstrap pipeline (install nix, add host, activate) |
add-host | Create a new host directory from template |
install-nix | Install Nix via nix-installer (hidden) |
github-auth | Authenticate with GitHub CLI (hidden) |
activate | Build and activate the current machine’s config (hidden) |
Day-to-Day
| Task | Description |
|---|---|
nix:switch | Apply config on the current machine |
nix:debug | Apply config with --show-trace for debugging |
nix:dry-run | Dry-run a build target or the current host config |
nix:check-cache | Check whether a store path exists in configured substituters |
nix:deploy | Build locally and deploy system config to a remote Linux host |
Maintenance
| Task | Description |
|---|---|
nix:up | Update a single flake input, or all inputs if none specified |
nix:history | List system profile generations via nix-env |
nix:repl | Open a nix repl with nixpkgs |
nix:clean | Remove system generations older than a retention window |
nix:gc | Garbage-collect unused store entries across the machine |
nix:gcroot | List auto GC roots |
nix:fmt | Format all .nix files with the configured formatter |
nix:trust | Add current user to Nix trusted-users |
nix:uninstall | Fully uninstall Nix from the system |
iTerm2
| Task | Description |
|---|---|
iterm:export | Re-export iTerm2 preferences plist |
Apple Container Tests (macOS only)
| Task | Description |
|---|---|
test:bootstrap | Run Linux bootstrap validation in an Apple container |
Documentation
| Task | Description |
|---|---|
docs:build | Build the mdBook documentation site |
docs:serve | Serve 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
maswith 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
| Check | Files | What it does |
|---|---|---|
nixfmt | **/*.nix | Format Nix files |
shfmt | **/*.sh | Format shell scripts |
pkl_format | **/*.pkl | Format Pkl files |
tombi_format | **/*.toml | Format TOML files |
actionlint | .github/workflows/*.yml | Lint GitHub Actions |
shellcheck | **/*.sh | Lint shell scripts |
mise-fmt | mise.toml | Format the mise config |
mise | mise.toml | Validate mise task definitions |
Security & Hygiene
| Check | What it does |
|---|---|
detect-private-key | Catch accidentally committed private keys |
check-merge-conflict | Detect merge conflict markers |
check-added-large-files | Prevent large file commits |
check-byte-order-marker | Detect UTF-8 BOM |
check-case-conflict | Detect case-only filename conflicts |
check-symlinks | Validate symlink targets exist |
Whitespace
| Check | What it does |
|---|---|
trailing-whitespace | Remove trailing whitespace |
mixed-line-ending | Enforce consistent line endings |
newlines | Ensure 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):
| Step | macOS | Linux |
|---|---|---|
| Install Nix | Yes | Yes |
| Flake check | Yes | Yes |
| Build Darwin configs | Yes | – |
| Build Linux system configs | – | Yes |
| Build Linux HM configs | – | Yes |
Bootstrap test (separate job):
- Runs
./bootstrap.sh ci-bootstrapon 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 -afor 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 NF | MesloLGS Nerd Font |
MesloLGS-NF-Regular | MesloLGSNF-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:
- Check if the bridge agent is registered:
launchctl print gui/$(id -u)/org.nixos.hexbox-bridge | head -20 - If the bridge agent shows “Could not find service”, the launch agent is not
loaded. Run
mise run nix:switchorsudo darwin-rebuild switch --flake .#$(hostname -s). - 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.