4.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repository purpose
Personal NixOS configuration managing multiple hosts via a single flake. Each host is a separate nixosConfiguration output in flake.nix. Home Manager is wired in as a NixOS module per host. Upstream is nixos-25.11.
Hosts
Defined in flake.nix under nixosConfigurations:
fuji— x86_64 laptop, Plasma6 + Sway, intel 13th gen, dual wireguard (one in a network namespace for ProtonVPN routed viadnscrypt-proxy)nixy— x86_64 workstation (also builds thenixy_isoinstaller image)mediabox— x86_64 media server (jellyfin/qbittorrent; uses the localmodules/qbittorrent.nix)blue— x86_64 (no sops-nix)magpie— aarch64, qemu guest, runssimple-nixos-mailserver; the wireguard hub other hosts dial into
When adding a new host, follow the pattern in flake.nix: include common/packages.nix, common/suspend.nix, the host's configuration.nix + hardware-configuration.nix, sops-nix.nixosModules.sops (if secrets needed), and a home-manager block pointing at home/<host>/home.nix.
Common commands
# Rebuild the current host (uses hostname to select config)
sudo nixos-rebuild switch --flake .#
# Rebuild a specific host
sudo nixos-rebuild switch --flake .#fuji
# Test without making the new generation the default boot entry
sudo nixos-rebuild test --flake .#fuji
# Build the nixy installer ISO
nix build .#nixosConfigurations.nixy_iso.config.system.build.isoImage
# Update a single flake input
nix flake update nixpkgs
# Format Nix files (formatter is alejandra)
nix fmt
# Enter the dev shell (sops, ssh-to-age, age available)
nix develop
# Cross-build magpie (aarch64) from x86_64 — requires binfmt or remote builder
nix build .#nixosConfigurations.magpie.config.system.build.toplevel
fuji has boot.binfmt.emulatedSystems for wasm32-wasi + x86_64-windows but not aarch64, so building magpie locally from fuji needs a remote builder or adding aarch64 to that list.
Directory layout
<host>/configuration.nix+<host>/hardware-configuration.nix— per-host NixOS config<host>/secrets/*.yaml— sops-encrypted secrets, decrypted at activation via the host's SSH host key (/etc/ssh/ssh_host_ed25519_key→ age)common/— modules imported by every host (packages.nix,suspend.nix) plus shared wireguard pubkeys andcommon/secrets/for cross-host secrets like the wireguard preshared keyhome/<host>/home.nix— entry point for that host's home-manager confighome/common/— shared home-manager modules (zsh, sway, i3, i3status-rust, firefox, etc.) imported from per-hosthome.nixmodules/— local NixOS modules not yet upstream-ready (currentlyqbittorrent.nix,nextcloud.nix)packages/— derivations for packages built locally (bubblewrap,viber)
Architectural notes worth knowing before editing
Inputs are passed as _module.args to every module. That means configuration.nix files receive nvim, zremap, swaysw, system, etc. as function arguments — they aren't imported explicitly. When you see an unfamiliar identifier in a host module's arg list, check flake.nix inputs.
sops-nix wiring. Secrets decrypt using the host's SSH ed25519 host key converted to age. Each host's sops.secrets.<name> references either ./secrets/<file>.yaml (host-local) or ../common/secrets/<file>.yaml (shared). config.sops.secrets.<name>.path is the runtime decrypted path — pass it to systemd units, never read the file at eval time.
Fuji's split-tunnel ProtonVPN. fuji/configuration.nix builds a wg network namespace, brings up proton_wg inside it, and runs dnscrypt-proxy_proton bound to that namespace. Anything that should egress over Proton must be launched with ip netns exec wg .... The main-host wg0 interface is unrelated and connects to magpie for the personal mesh (10.100.0.0/24).
Home-manager backup extension. backupFileExtension = "home_backup" is set on most hosts — if a switch fails on file conflicts, look for *.home_backup files in $HOME.
Hardening already in place on fuji (mirror to other hosts when relevant): nftables firewall, scudo allocator, AppArmor, sysctl hardening (kptr_restrict, dmesg_restrict, rp_filter, redirect blocking), sudo.execWheelOnly, firewall.logRefusedConnections, doas, firejail, no coredumps, KillUserProcesses, ro nix store mount, systemd-boot editor disabled.
Editing secrets
nix develop # gets sops + age + ssh-to-age
sops <host>/secrets/<file>.yaml
.sops.yaml (if present at repo root, otherwise inferred) defines which age keys may decrypt which paths. When adding a new host, derive its age pubkey from the SSH host key with ssh-to-age and add it to the .sops.yaml creation rules before re-encrypting.