nix
$
npx mdskill add megalithic/dotfiles/nixProvides expert assistance with Nix ecosystem tools for configuration, package management, and debugging on macOS.
- Helps users manage dotfiles, install packages, develop modules, and resolve evaluation errors.
- Integrates with Bash, file operations, web fetching, and web search tools.
- Relies on user environment details like platform and rebuild commands to offer tailored advice.
- Delivers results through command recommendations, path guidance, and specific workarounds for known issues.
SKILL.md
.github/skills/nixView on GitHub ↗
---
name: nix
description: Expert help with Nix, nix-darwin, home-manager, flakes, and nixpkgs. Use for dotfiles configuration, package management, module development, hash fetching, debugging evaluation errors, and understanding Nix idioms and patterns.
tools: Bash, Read, Grep, Glob, Edit, Write, WebFetch, WebSearch
---
# Nix Ecosystem Expert
## Overview
You are a Nix expert specializing in:
- **nix-darwin** for macOS system configuration
- **home-manager** for user environment management
- **Flakes** for reproducible builds and dependency management
- **nixpkgs** for package definitions and overlays
- **Development shells** for project-specific environments
## User's Environment
- **Platform**: macOS (aarch64-darwin)
- **Dotfiles**: `~/.dotfiles/` (flake-based)
- **Rebuild command**: `just rebuild` (uses workaround script, see below)
- **Package search**: `nix search nixpkgs#<package>` or `nh search <query>`
### CRITICAL: Rebuild Command
**ALWAYS use `just rebuild`** instead of `darwin-rebuild switch` directly:
```bash
# CORRECT - uses workaround script that avoids HM activation hang
just rebuild
# AVOID - can hang at "Activating setupLaunchAgents"
sudo darwin-rebuild switch --flake ./
```
The `just rebuild` command runs `bin/darwin-switch` which patches around an intermittent hang in darwin-rebuild's home-manager activation.
## Key Paths
```
~/.dotfiles/
├── flake.nix # Main flake entry point
├── flake.lock # Locked dependencies
├── hosts/ # Per-machine configs
│ └── megabookpro.nix
├── home/ # Home-manager configs
│ ├── default.nix # Entry point
│ ├── lib.nix # config.lib.mega helpers
│ ├── packages.nix # User packages
│ └── programs/ # Program-specific configs
│ ├── ai/ # AI tools (claude-code, opencode)
│ ├── browsers/ # Browser configs
│ └── *.nix # Individual program configs
├── modules/ # System-level darwin modules
├── lib/ # Custom Nix functions
│ ├── default.nix # mkApp, mkMas, brew-alias, etc.
│ └── mkSystem.nix # System builder
├── pkgs/ # Custom package derivations
├── overlays/ # Package overlays
└── config/ # Out-of-store configs (symlinked)
```
## Package Management Decision Tree
**CRITICAL: NEVER use `brew install`. Always use Nix.**
When you need a tool/package that isn't installed:
```
┌─────────────────────────────────────────────────────────────┐
│ 1. VERIFY PACKAGE EXISTS IN NIXPKGS │
│ nix search nixpkgs#<package> │
│ nh search <package> (faster, prettier) │
│ │
│ If not found: search online nixpkgs, NUR, or flake repos │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. DETERMINE USAGE PATTERN │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ One-time use │ │ Project-only │ │ System-wide │ │
│ │ (test/debug) │ │ (dev env) │ │ (always avail) │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ nix run/shell Add to flake Add to dotfiles │
│ devShell home/packages.nix │
└─────────────────────────────────────────────────────────────┘
```
### Step 1: Check Package Availability
```bash
# Search nixpkgs (ALWAYS do this first)
nix search nixpkgs tilt
nix search nixpkgs <package> --json # For scripting
# Faster alternative with nh (if configured)
nh search tilt # May fail if channel not configured
# If not found in nixpkgs, check:
# - NUR: https://nur.nix-community.org/
# - Flake repos (e.g., github:owner/repo#package)
# - The package might have a different name (e.g., 'ripgrep' not 'rg')
```
### Step 2a: Temporary/One-Time Usage
For testing, debugging, or one-off commands:
```bash
# Run a command directly (doesn't pollute environment)
nix run nixpkgs#tilt -- version
nix run nixpkgs#cowsay -- "Hello"
nix run nixpkgs#jq -- --help
# Enter a shell with the package available
nix shell nixpkgs#tilt nixpkgs#kubectl
# Now 'tilt' and 'kubectl' are in PATH until you exit
# Run with specific nixpkgs version (pinned)
nix run github:NixOS/nixpkgs/nixos-24.05#tilt -- version
```
### Step 2b: Project-Specific (devShell)
For tools needed only in a specific project:
```nix
# In the project's flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { nixpkgs, ... }:
let
system = "aarch64-darwin";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
tilt
kubectl
# Add other project-specific tools
];
};
};
}
```
Then use `nix develop` or `direnv` to automatically enter the shell.
### Step 2c: System-Wide (Permanent)
For tools you want always available:
**Location**: `~/.dotfiles/home/packages.nix`
```nix
# In home/packages.nix, add to appropriate category:
home.packages = with pkgs; [
# Development tools
tilt
kubectl
# ...
];
```
Then rebuild: `just rebuild`
### Package Name Discovery
Sometimes package names differ from command names:
```bash
# Search by description if name doesn't match
nix search nixpkgs "kubernetes development"
# Check package metadata
nix eval nixpkgs#tilt.meta.description --raw
# List executables a package provides
nix eval nixpkgs#tilt.meta.mainProgram --raw 2>/dev/null || \
ls $(nix build nixpkgs#tilt --print-out-paths --no-link)/bin/
```
### Common Package Name Mappings
| Command | Package Name |
|---------|--------------|
| `rg` | `ripgrep` |
| `fd` | `fd` |
| `bat` | `bat` |
| `code` | `vscode` |
| `subl` | `sublime4` |
## Common Tasks
### 1. Validate Configuration
```bash
# Quick syntax/eval check (no build)
nix flake check --no-build
# Full check with build
nix flake check
# Show what would be built
nix build .#darwinConfigurations.megabookpro.system --dry-run
```
### 2. Rebuild System
```bash
# Standard rebuild (ALWAYS USE THIS)
just rebuild
# Build without switching (test only)
darwin-rebuild build --flake .
# With verbose output for debugging (if just rebuild fails)
./bin/darwin-switch --show-trace
```
**IMPORTANT**: Never use `sudo darwin-rebuild switch` directly - it can hang. Use `just rebuild` which runs the workaround script.
### 3. Fetch Hashes for Packages
```bash
# For fetchFromGitHub
nix-prefetch-github owner repo --rev <commit-or-tag>
# For fetchurl (URLs)
nix-prefetch-url <url>
# For fetchzip
nix-prefetch-url --unpack <url>
# For any fetcher (using nix hash)
nix hash to-sri --type sha256 <hash>
# Quick SRI hash from URL
nix-prefetch-url <url> 2>/dev/null | xargs nix hash to-sri --type sha256
```
### 4. Search Packages
```bash
# Using nh (PREFERRED - faster, prettier output)
nh search <query>
# Search nixpkgs (native - slower)
nix search nixpkgs#<query>
# Search with JSON output (for scripting)
nix search nixpkgs#<query> --json
# Show package info
nix eval nixpkgs#<package>.meta.description --raw
# List package outputs
nix eval nixpkgs#<package>.outputs --json
```
### 5. Search Home-Manager Options
Use the web interface to search for home-manager options:
```
https://home-manager-options.extranix.com/?query=<search-term>
```
**Examples:**
- Find git options: `https://home-manager-options.extranix.com/?query=programs.git`
- Find all program options: `https://home-manager-options.extranix.com/?query=programs`
- Find xdg options: `https://home-manager-options.extranix.com/?query=xdg`
Use `WebFetch` tool to query this URL when helping the user find home-manager configuration options.
### 6. Using nh (Yet Another Nix Helper)
`nh` provides a nicer UX for common nix operations:
```bash
# Search packages (faster than nix search)
nh search <query>
# Darwin rebuild (equivalent to darwin-rebuild switch --flake .)
nh darwin switch .
nh darwin switch ~/.dotfiles
# Build without switching
nh darwin build .
# With diff showing what changed
nh darwin switch . --diff
# Home-manager operations
nh home switch .
# Clean old generations
nh clean all # Clean everything
nh clean all --keep 5 # Keep last 5 generations
```
### 7. Using NUR (Nix User Repository)
NUR provides community packages not in nixpkgs:
```bash
# Search NUR packages online
# https://nur.nix-community.org/
# In flake.nix, add NUR input then use:
# nur.repos.<user>.<package>
```
### 8. Debug Evaluation Errors
```bash
# Show full trace
nix eval .#darwinConfigurations.megabookpro.config --show-trace
# Enter REPL for exploration
nix repl
:lf . # Load flake
darwinConfigurations.megabookpro.config.<path>
# Check specific module
nix eval .#darwinConfigurations.megabookpro.config.home-manager.users.seth.<option>
```
### 9. Working with Project Flakes
```bash
# Initialize new flake
nix flake init
# Enter dev shell
nix develop
# Run from flake
nix run .#<app>
# Build package
nix build .#<package>
# Update flake inputs
nix flake update
# Update specific input
nix flake update <input-name>
```
## Nix Language Patterns
### Option Definitions (for modules)
```nix
options.services.myservice = {
enable = lib.mkEnableOption "my service";
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Port to listen on";
};
};
```
### Conditional Attributes
```nix
# mkIf for conditional config
config = lib.mkIf config.services.myservice.enable {
# ...
};
# optionalAttrs for conditional attrsets
{ } // lib.optionalAttrs condition { key = value; }
# optional for conditional list items
[ ] ++ lib.optional condition item
++ lib.optionals condition [ item1 item2 ]
```
### Package Overrides
```nix
# Override package inputs
pkg.override { dependency = newDep; }
# Override derivation attributes
pkg.overrideAttrs (old: {
version = "2.0";
src = newSrc;
})
# Override python packages
python3.withPackages (ps: [ ps.requests ps.numpy ])
```
### Fetchers
```nix
# GitHub
fetchFromGitHub {
owner = "owner";
repo = "repo";
rev = "v1.0.0"; # or commit SHA
sha256 = "sha256-AAAA..."; # SRI format
}
# URL
fetchurl {
url = "https://example.com/file.tar.gz";
sha256 = "sha256-AAAA...";
}
# Git (for specific refs)
fetchgit {
url = "https://github.com/owner/repo";
rev = "abc123";
sha256 = "sha256-AAAA...";
}
```
## Home-Manager Patterns
### XDG Config Files
```nix
# In-store (immutable, from nix expression)
xdg.configFile."app/config".text = "content";
xdg.configFile."app/config".source = ./path/to/file;
# Out-of-store (mutable, symlinked)
xdg.configFile."app".source = config.lib.mega.linkConfig "app";
```
### Programs Module
```nix
programs.git = {
enable = true;
userName = "Name";
extraConfig = {
init.defaultBranch = "main";
};
};
```
### Activation Scripts
```nix
home.activation.myScript = lib.hm.dag.entryAfter ["writeBoundary"] ''
# Shell script here
mkdir -p $HOME/.local/share/myapp
'';
```
## Darwin-Specific
### System Defaults
```nix
system.defaults = {
dock.autohide = true;
finder.AppleShowAllFiles = true;
NSGlobalDomain = {
AppleKeyboardUIMode = 3;
InitialKeyRepeat = 15;
KeyRepeat = 2;
};
};
```
### Homebrew Integration
```nix
homebrew = {
enable = true;
onActivation.cleanup = "zap";
brews = [ "mas" ];
casks = [ "firefox" ];
masApps = { "Xcode" = 497799835; };
};
```
## User's Custom Helpers (lib.mega namespace)
All custom helpers are under `lib.mega.*`:
**In `lib/default.nix` (flake-level):**
- `lib.mega.mkApp` - Build macOS apps from DMG/ZIP/PKG (see detailed guide below)
- `lib.mega.mkApps` - Build multiple apps from a list
- `lib.mega.mkMas` - Install Mac App Store apps
- `lib.mega.mkAppActivation` - Symlink apps to /Applications
- `lib.mega.brewAlias` - Create wrappers for Homebrew binaries
- `lib.mega.capitalize` - Capitalize first letter of string
- `lib.mega.compactAttrs` - Filter null values from attrset
- `lib.mega.imports` - Smart module path resolution
## mkApp - Installing macOS Applications
The `mkApp` function in `lib/mkApp.nix` supports three install methods. **ALWAYS verify which method is needed before choosing.**
### Install Methods
| Method | Use Case | Config Location |
|--------|----------|-----------------|
| `extract` (default) | Most apps - DMG, ZIP, or simple PKG | `home/packages.nix` |
| `native` | Apps with system extensions | `hosts/*.nix` + enable service |
| `mas` | Mac App Store apps | Either |
### How to Determine the Correct Method for PKG Files
**IMPORTANT: Most PKG files do NOT need native installation!**
```bash
# Step 1: Download the PKG and get its hash
nix-prefetch-url --name "safe-name.pkg" "https://example.com/Install%20App.pkg"
# Step 2: Inspect PKG contents
pkgutil --payload-files /nix/store/...-safe-name.pkg | head -30
```
**Decision tree:**
1. If output shows ONLY `./Applications/SomeApp.app/*` → **Use extract method**
```nix
mkApp {
pname = "myapp";
version = "1.0";
appName = "MyApp.app";
src = { url = "..."; sha256 = "..."; };
artifactType = "pkg"; # <-- This is the key!
}
```
2. If output shows ANY of these → **Use native method** (verify with postinstall check):
- `./Library/SystemExtensions/*` (DriverKit)
- `./Library/LaunchDaemons/*` or `./Library/LaunchAgents/*`
- `./Library/PrivilegedHelperTools/*`
- `./usr/local/bin/*` (privileged binaries)
3. To verify postinstall scripts need privilege:
```bash
pkgutil --expand /path/to/installer.pkg /tmp/pkg-expanded
cat /tmp/pkg-expanded/*/Scripts/postinstall
# Look for: systemextensionsctl, launchctl load, SMJobBless
```
### Examples
**Simple app from DMG (most common):**
```nix
# In pkgs/default.nix
fantastical = mkApp {
pname = "fantastical";
version = "4.1.5";
appName = "Fantastical.app";
src = {
url = "https://cdn.flexibits.com/Fantastical_4.1.5.zip";
sha256 = "...";
};
};
```
**App from PKG (extracts .app, NO native installer needed):**
```nix
# In pkgs/default.nix
talktastic = mkApp {
pname = "talktastic";
version = "beta";
appName = "TalkTastic.app";
src = {
url = "https://storage.googleapis.com/oasis-desktop/installer/Install%20TalkTastic.pkg";
sha256 = "...";
};
artifactType = "pkg"; # Extracts .app from PKG payload
};
```
**App requiring native PKG installer (rare - verify first!):**
```nix
# In pkgs/karabiner-elements.nix (separate file)
lib.mega.mkApp {inherit pkgs lib;} {
pname = "karabiner-elements";
version = "15.7.0";
src = { url = "..."; sha256 = "..."; };
installMethod = "native"; # Runs /usr/sbin/installer
pkgName = "Karabiner-Elements.pkg";
# Also needs: services.native-pkg-installer.enable = true; in host config
}
```
### Real-World Examples of Native vs Extract
| App | Method | Reason |
|-----|--------|--------|
| TalkTastic | `extract` | PKG only contains `./Applications/TalkTastic.app/*` |
| Fantastical | `extract` | Standard ZIP with .app bundle |
| Brave Browser | `extract` | Standard DMG with .app bundle |
| Karabiner-Elements | `native` | Has DriverKit virtual HID extension |
| Little Snitch | `native` | Has network kernel extension |
**In `home/lib.nix` (home-manager module, via `config.lib.mega`):**
- `config.lib.mega.linkConfig "path"` - Symlink to `~/.dotfiles/config/{path}`
- `config.lib.mega.linkHome "path"` - Symlink to `~/.dotfiles/home/{path}`
- `config.lib.mega.linkBin` - Symlink to `~/.dotfiles/bin`
- `config.lib.mega.linkDotfile "path"` - Generic dotfiles symlink
## Best Practices
1. **Use `lib.mkDefault`** for overridable defaults
2. **Use `lib.mkForce`** sparingly (only when necessary)
3. **Prefer `lib.mkIf`** over inline conditionals for clarity
4. **Use SRI hashes** (`sha256-...`) not old hex format
5. **Pin flake inputs** for reproducibility
6. **Use overlays** for package modifications, not inline overrides
7. **Separate concerns**: system config in modules/, user config in home/
## Debugging Tips
1. **Infinite recursion**: Usually caused by self-referential options. Use `--show-trace`
2. **Attribute not found**: Check spelling, imports, and that module is loaded
3. **Hash mismatch**: Use `nix-prefetch-*` tools to get correct hash
4. **Build failures**: Check `nix log /nix/store/<drv>` for build logs
5. **"Too many open files"**: See macOS file descriptor limits section below
## macOS File Descriptor Limits
### Problem
macOS defaults `launchctl limit maxfiles` to 256 (soft limit), which is too low for complex nix evaluations. You'll see errors like:
```
error: creating git packfile indexer: failed to create temporary file ... Too many open files
error: cannot enqueue a work item while the thread pool is shutting down
```
### Solution
The dotfiles include a LaunchDaemon that sets maxfiles to 524288 at boot (`modules/system.nix`). If you see this error:
```bash
# 1. Apply limit immediately (until next reboot)
sudo launchctl limit maxfiles 524288 524288
# 2. Clear corrupted cache
rm -rf ~/.cache/nix/tarball-cache
# 3. Rebuild
just rebuild
```
### Why This Is Necessary
Modern macOS has **no declarative kernel parameter config**. Unlike Linux with `/etc/sysctl.conf`, the only persistent way to set `kern.maxfiles` is via a LaunchDaemon that runs at boot. This is Apple's officially recommended approach.
The LaunchDaemon in `modules/system.nix`:
```nix
launchd.daemons.limit-maxfiles = {
serviceConfig = {
Label = "limit.maxfiles";
ProgramArguments = ["launchctl" "limit" "maxfiles" "524288" "524288"];
RunAtLoad = true;
LaunchOnlyOnce = true;
};
};
```
## Flake Structure Verification
Before adding packages to any flake, verify its structure:
### Checking a Project Flake
```bash
# Verify flake is valid
nix flake check
# Show flake structure (inputs, outputs)
nix flake show
# Show flake metadata
nix flake metadata
# List available outputs
nix flake show --json | jq 'keys'
# Check if devShell exists
nix flake show | grep -E "devShell|devShells"
```
### Verifying Package Can Be Added
```bash
# 1. Verify package exists in nixpkgs
nix search nixpkgs#<package>
# 2. Verify package builds on this system (aarch64-darwin)
nix build nixpkgs#<package> --dry-run
# 3. Check if package has darwin support
nix eval nixpkgs#<package>.meta.platforms --json | jq 'map(select(contains("darwin")))'
# 4. Test the package works before committing
nix shell nixpkgs#<package> -c <command> --version
```
### Adding to Existing Flake devShell
```bash
# Find where devShell is defined
rg "devShells|mkShell" flake.nix -A 10
# Common patterns to look for:
# - packages = [ ... ]; (add here)
# - buildInputs = [ ... ]; (legacy, but works)
# - nativeBuildInputs = [ ... ]; (build-time only)
```
### Creating a New Flake
```bash
# Initialize with template
nix flake init
# Or use a specific template
nix flake init -t templates#trivial
# Minimal flake.nix for a dev environment:
```
```nix
{
description = "Project dev environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
# Add packages here
];
};
}
);
}
```
### Troubleshooting Flake Issues
```bash
# Lock file out of sync
nix flake update
# Update specific input
nix flake update nixpkgs
# Clear evaluation cache (if weird errors)
rm -rf ~/.cache/nix/eval-cache-v*
# Show why something failed
nix build .#<output> --show-trace
# Check flake in nix repl
nix repl
:lf .
# Now explore: outputs.<TAB>
```
## Common Gotchas
- `home.file` vs `xdg.configFile` - former is `$HOME/`, latter is `~/.config/`
- `mkOutOfStoreSymlink` requires absolute path at eval time
- Darwin modules use `system.*`, not `services.*` for most things
- `environment.systemPackages` is system-wide, `home.packages` is per-user
- **Package not found**: Try different names (`ripgrep` not `rg`), or check NUR
- **Platform unsupported**: Check `meta.platforms` - some packages don't build on darwin
- **Flake not recognized**: Ensure `flake.nix` exists and git-tracked (`git add flake.nix`)
More from megalithic/dotfiles
- brave-searchWeb search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required.
- cli-toolsModern CLI tool usage (fd, rg) for fast file and content searching. Critical for Nix store searches and large codebases. Use when searching files or content, especially in /nix/store.
- hsComprehensive guide for Hammerspoon development in this dotfiles repo. Covers config patterns, debugging decision trees, API reference, performance monitoring, and troubleshooting.
- image-handlingImage handling for Claude API constraints (5MB max, 8000px max dimension). Use when working with images, screenshots, or MCP browser tools.
- jjJujutsu (jj) version control workflow, commands, and best practices. Use when working with version control in jj-enabled repos. Covers commits, bookmarks, workspaces, and safe push patterns.
- notesExpert help with the meganote system - cross-tool note capture, daily notes, and obsidian.nvim integration. Covers Hammerspoon, Shade, nvim, and the full capture → daily note linking pipeline.
- nvimComprehensive guide for Neovim configuration in this dotfiles repo. Covers plugin management, LSP debugging, treesitter, keymaps, performance, and troubleshooting decision trees.
- previewDisplay code, diffs, images, and other content in a tmux pane or popup. Auto-detects nvim/megaterm for floating popups.
- shadeExpert help with Shade - the native Swift note capture app. Use for debugging Shade issues, understanding IPC protocols, implementing Hammerspoon integration, nvim RPC, context gathering, and meganote workflows.
- smart-ntfySend intelligent notifications via ~/bin/ntfy with context-aware channel selection. Use when completing tasks, asking questions, encountering errors, or reaching milestones.