What it is
I treat Claude Code as load-bearing infrastructure: a declarative NixOS / Home Manager module generates ~/.claude/settings.json (allow-rules, deny-rules, hooks, MCP wiring), ships 32 custom slash-command skills for repeated infra ops, and auto-loads path-scoped rules when editing matching files. Every machine in the homelab runs the same AI operator with the same guardrails.
Why it exists
Most Claude Code setups live as a settings.json someone edited once, a .claude/commands/*.md pile that grew organically, and some mcp__* tools bolted on ad-hoc. That's fine for a single machine. It breaks the moment you have 16 NixOS profiles and want the same agent to behave identically on each — same permissions, same deny list, same hooks, same skill set.
I wanted Claude Code to be a managed OS component, not a hand-configured tool: one Nix module produces ~/.claude/settings.json, one set of skills ships with the flake, one hook runs before every tool use, and one MCP config wires every server with env vars from git-crypt-encrypted secrets. Rebuild any node and Claude Code comes up fully configured, no manual steps.
Architecture
user/app/claude-code/claude-code.nix is the whole story. It generates the user-level settings file from typed Nix attributes, symlinks the repo's .claude/{commands,rules,hooks}/* into ~/.claude/, and exposes the MCP server wiring via ~/.dotfiles/.mcp.json. Hooks fire on every PreToolUse and PostToolUse — primarily the sensitive-file block-list that refuses reads/writes into SSH keys, credential files, ~/.gnupg/, /etc/shadow, etc. Skills are markdown files; rules are markdown files with a paths: frontmatter glob so Claude Code auto-loads the right context when you open a matching file.
What I built
- Declarative Claude Code Nix module —
user/app/claude-code/claude-code.nix(14 KB) generates~/.claude/settings.jsonwith 82 allow rules (tight globs for every Bash pattern I actually need, plus every Read/Edit/Write/Grep/Glob I use), 34 deny rules (SSH private keys,/etc/shadow,~/.gnupg/, credential files,~/.aws/,~/.kube/config, browser session files, crypto wallets,.git-crypt/, etc.), and hook registrations. - PreToolUse / PostToolUse hooks —
.claude/hooks/block-sensitive-files.shintercepts every Bash / Read / Edit / Write / Grep / Glob call. Refuses operations on sensitive paths even if a clever prompt convinces the model to try. Prompt-injection defense at the harness level, not the model level. - 32 custom skill commands —
.claude/commands/*.md— each is a declarative slash command with a short description, tool allowlist, and a small shell / MCP body. Examples:/deploy-lxc(remote build + switch on an LXC profile),/check-database//check-redis//check-kuma(health-checks),/manage-pfsense//manage-nas//manage-proxmox//manage-matrix//manage-tailscale(node admin),/audit-infrastructure//audit-project-security//audit-project-docs(audit workflows),/network-performance,/docker-startup-nas,/wake-on-lan-nas,/unlock-nas,/install-app,/remove-app,/list-apps,/merge-branches,/docs-health,/deploy-finance-tagger,/deploy-liftcraft-test,/sync-vivaldi,/update-infra-vps-and-nas, and more. - 6 path-scoped rule files —
.claude/rules/{nixos,deployment,darwin,docs,sway,gaming}.mdwithpaths:frontmatter glob that scopes activation. Editing a**/*.nixfile loadsnixos.md(feature-flag patterns, profile inheritance, secrets imports). Editing**/deploy.shloadsdeployment.md. No full-context bloat — the relevant rule lands in context only when it applies. - 5 MCP servers wired from the same flake —
~/.dotfiles/.mcp.jsonis git-tracked; env vars (PLANE_API_KEY,GRAFANA_URL,POSTGRES_MCP_CONNECTION_STRING, etc.) come from git-crypt-encrypted secrets. Servers: Plane (self-hosted project management), Perplexity (cited web search replacing WebSearch), Grafana (dashboards + PromQL, read/write), PostgreSQL (scoped read-only for safety), n8n (workflow automation over my self-hosted instance). - Per-machine sync via symlinks —
.claude/commands/,.claude/rules/, and.claude/hooks/live in the dotfiles repo; the Nix module symlinks them into~/.claude/on every profile so edits propagate onaku syncwithout manual copying. - Fleet-consistent — 16 flake profiles (desktops, laptops, VPS, NAS, Proxmox LXCs, nix-darwin macOS) all get the same Claude Code setup. A change to the module or the rules ripples to every machine on the next rebuild.
- Plane workflow integration —
AKUworkspace in self-hosted Plane holds the ticketing for both my own infra and the LiftCraft dev work. Claude can create work items, comment, transition status, query cycles — all via MCP. Closes the "what was I doing yesterday" gap without leaving the terminal.
Results
- 32 custom skills available as
/slash-commandsfrom any Claude Code session. - 82 allow rules / 34 deny rules — tight permissions, explicit denies for sensitive files.
- 6 path-scoped rule files that load on glob match, not on every turn.
- 5 MCP servers wired from the same Nix module + git-crypt secrets.
- One git push → every machine on the fleet updates via
aku sync. - Zero hand-configured Claude Code installs across the homelab.
Stack
NixOS, Home Manager, Claude Code, Model Context Protocol (MCP), Plane (self-hosted), Grafana, PostgreSQL, n8n, Perplexity, Bash, git-crypt.
Status
- Repo: private (lives inside
my-nixos-infrastructure); the Claude Code Nix module is ~400 lines. - Running: every NixOS profile since late 2025; daily driver for infra + application work.
- Related:
my-nixos-infrastructure(the flake that ships it),openclaw-ai-gateway(my parallel ambient-agent platform),workout-app(the MCP-integrated app I built).
