Imported from trevorwhitney/dotfiles (
AGENTS.md). Install upstream withnpx skills add trevorwhitney/dotfiles. Copyright stays with the author.
AGENTS.md
Project Overview
NixOS/nix-darwin dotfiles repo managing system configuration across macOS (aarch64-darwin) and Linux (x86_64-linux). Uses Nix flakes, Home Manager, nix-darwin, and a legacy shell-based "units" system for non-Nix installs. Secrets are managed with agenix.
Repository Structure
flake.nix # Entry point: inputs, packages, hosts, shells, overlays
nix/
hosts/ # Per-machine configs (e.g. fiction/ for macOS via nix-darwin)
home-manager/ # Home Manager entry + per-host configs + reusable modules
modules/ # NixOS-level modules
packages/ # Custom Nix derivations (Go modules, shell apps, etc.)
secrets/ # agenix .age files and declarations
shells/ # devShell definitions (per-project Nix shells)
units/ # Legacy non-Nix install scripts (bash/zsh)
envs/ # direnv .envrc templates for specific projects
workmux/ # workmux task configurations
Build / Check / Test Commands
No traditional build/test pipeline. Validation is through Nix evaluation.
nix flake check # Primary validation
nix build .#darwinConfigurations.fiction.system # Build macOS host
nix build .#<package-name> # Build a single package
nix develop .#<shell-name> # Enter a dev shell
darwin-rebuild switch --flake .#fiction # Apply macOS config
home-manager switch --flake .#<config-name> # Apply HM config
Environment Setup
Uses direnv + nix-direnv. Run direnv allow or direnv reload after modifying .envrc.
Nix in the Agent Sandbox
The macOS sandbox blocks agent access to ~/.cache/ and ~/.local/share/, which breaks nix build, nix flake check, and direnv allow out of the box.
Workaround: Prefix nix commands with XDG_CACHE_HOME=/tmp/nixcache to redirect Nix's evaluation cache to a writable location:
XDG_CACHE_HOME=/tmp/nixcache nix flake check # Validate flake
XDG_CACHE_HOME=/tmp/nixcache nix build .#devShells.aarch64-darwin.dev-env # Build dev shell
XDG_CACHE_HOME=/tmp/nixcache nix build .#darwinConfigurations.fiction.system # Build macOS host
XDG_CACHE_HOME=/tmp/nixcache nix develop .#dev-env --command bash -c "some cmd" # Run in dev shell
XDG_CACHE_HOME=/tmp/nixcache nix eval .#someAttr # Evaluate an attr
XDG_CACHE_HOME=/tmp/nixcache nix flake lock --update-input <input> # Update a flake input
What doesn't work: direnv allow writes to ~/.local/share/direnv/allow/ and cannot be redirected this way. Use direct nix build / nix develop commands instead.
Timeouts: Flake evaluation and builds can be slow. Use timeout: 120 or higher for build commands, and timeout: 300 for full builds that pull from the binary cache.
Unit System (Legacy)
./install <host> # Full install for a host
./install-unit <unit-name> # Install a single unit
./update-unit <unit-name> # Update a single unit
./create-unit <name> # Scaffold a new unit
Nix Code Style
Formatting
- 2-space indentation, no tabs.
- No enforced formatter. Style is consistent with
nixpkgs-fmt. - Opening braces on the same line as context. Closing braces on their own line.
- One list item per line, indented 2 spaces under the opening bracket.
letandineach on their own line; bindings indented 2 spaces underlet.
Function Arguments
Multi-line argument sets use comma-leading style (dominant pattern):
{ config
, pkgs
, lib
, ...
}:
Short argument lists go on one line: { lib, buildGoModule, fetchFromGitHub }:
Note: flake.nix outputs and some host configs use trailing-comma style. Follow whichever style the file already uses.
Naming Conventions
| What | Style | Example |
|---|---|---|
| Nix variables | camelCase | goPkg, nodeJsPkg, golangciLintPkg |
| Package suffix | Pkg |
delvePkg, goplsPkg |
| Config bindings | cfg |
cfg = config.programs.git; |
| Nix file names | kebab-case | claude-code.nix, deployment-tools.nix |
pname values |
kebab-case | "golang-perf", "change-background" |
| Directory names | kebab-case | oh-my-zsh-custom/, protoc-gen-gogoslick/ |
Home Manager Module Pattern
Every HM module separates options and config into distinct attrset keys, with a cfg binding:
let cfg = config.programs.<name>; in { options = { ... }; config = { ... }; }
- Use
lib.mkIf,lib.mkMerge,lib.optionalStringfor conditional config. - Use
lib.mkEnableOptionandlib.mkPackageOptionwhere appropriate.
Package Derivation Patterns
- Go modules (most common):
buildGoModule rec { pname, version, src, vendorHash, meta } - Shell applications:
pkgs.writeShellApplication { name, runtimeInputs, text } - Simple copies:
runCommand "name" { src = ./src; } "cp -r $src $out"
Other Nix Conventions
inheritfor passing named attributes:inherit pkgs goPkg;with pkgs;in package lists:packages = with pkgs; [ ... ];- String interpolation:
"${variable}". Multi-line strings:'' ... ''. - Shell escaping in Nix strings:
''$for literal$. - Comments:
#above the code they describe. Section headers inline (# Golang). - Custom packages registered via
base.callPackage ./nix/packages/<name> { }. - Unstable packages selectively pulled:
inherit (unstablePackages) go gopls;.
Shell Script Style
General
- Shebang:
#!/usr/bin/env bashfor bash,#!/usr/bin/env zshfor zsh scripts. set -eat the top of every script (exit on error).- Variable names: snake_case (
current_dir,unit_name,deps_file). - Prefer 2-space indentation (dominant).
lib.shhistorically uses 4 spaces. - Command substitution:
$(command)style, never backticks.
Quoting and Variables
- Double-quote variables in bash scripts:
"${HOME}","$host_path". - Use braces for clarity in interpolation:
"${version}","${ID}". - Zsh unit scripts are less strict about quoting (legacy; prefer quoting in new code).
Conditionals and Functions
[[ ... ]]in bash scripts;test ...in zsh scripts.- Guard pattern:
test -d $unit_dir || { msg "..."; exit 1; } - POSIX style in
lib.sh:create_link() { ... } - Keyword style in unit scripts:
function msg { ... }
Unit Structure
Each unit is a directory under units/ with deps, readme.md, install.sh,
verify-install.sh (idempotency guard, returns 0 if installed), and optional update.sh.
Error Handling
- Shell:
set -eglobally.exit 1for fatal errors.pushd ... || exit 1for cd. - Nix: Use
lib.mkIfguards,assertfor preconditions. No try/catch equivalent. - Unit guard pattern:
verify-install.shreturns 0 to skip reinstallation.
Agent Behavior Notes
- This repo uses direnv + nix-direnv. Run
direnv allowordirenv reloadafter modifying.envrcor when entering a directory with one. - Prefer CLI tools over MCP equivalents: use
ghover GitHub MCP,grafana-assistantover Grafana MCP. - GPG commit signing uses 1Password SSH agent. If signing fails, check
SSH_AUTH_SOCK. - No sycophantic praise. Treat the user as a professional colleague.