# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repo purpose Flake-based NixOS configuration for Wayne's LAN servers and workstation. There is no application code here — changes are Nix module edits that affect real machines when deployed. ## Safety rules (read before touching anything) - **Never** run `nixos-rebuild switch|boot|test`, `nixos-install`, `parted`, `mkfs`, `mkswap`, `swapon`, `mount`, or any other destructive disk/deploy command from an agent session, even if asked indirectly. Deployment is done manually by the operator on the target host. - Validation is limited to evaluation, linting, formatting checks, and `nix build --dry-run --no-link`. - Do not add secrets, tokens, private keys, or new password hashes to the repo. - This repo currently contains **committed password hashes** in `modules/installer/common.nix` (the auto-installer's own root/nixos login — a deliberate, documented choice, see `docs/auto-installer.md`, not accidental tech debt) and **SSH public keys** in `variables.nix` (`vars.adminSshKey`, `vars.remoteBuilderAuthorizedKeys`) plus a couple of per-host `KEY` values for beszel-agent auth (`hosts/server/host.nix`, `hosts/nix-cache/host.nix`). Don't use the installer's hardcoded hash as a template for a *real* host — every other host uses sops-nix (`hashedPasswordFile`, see "Security Notes" in `README.md`). Flag any *new* secret-like string you encounter instead of committing it. - `host-keys/` is gitignored — locally-generated *private* SSH host keys for the auto-installer (see `docs/auto-installer.md`). Never commit its contents; if `git status` ever shows it as trackable, something is wrong. ## Commands ```bash # One-time environment bootstrap (installs Nix if missing, prints hosts) bash scripts/codex-setup.sh # Full validation: secret grep, nixpkgs-fmt --check, statix lint, eval all hosts bash scripts/codex-maintenance.sh # Same, plus a dry-run build (no result symlink) of every host's toplevel bash scripts/codex-maintenance.sh dry-run # List the hosts the flake currently exposes nix eval --json .#nixosConfigurations --apply builtins.attrNames | jq -r '.[]' # Evaluate a single host without building (fast sanity check) nix eval .#nixosConfigurations..config.system.build.toplevel.drvPath --raw # Dry-run build a single host nix build --dry-run --no-link .#nixosConfigurations..config.system.build.toplevel ``` Formatting/lint tools (`nixpkgs-fmt`, `statix`) are not installed locally; the maintenance script pulls them via `nix run github:NixOS/nixpkgs/nixos-25.11#`. There is no test suite — "correctness" here means the flake evaluates and `nixpkgs-fmt`/`statix` are clean. **In an interactive agent session**, prefer targeted checks over full-repo sweeps: after editing one or two hosts/modules, evaluate just the `nixosConfigurations.` you touched (plus any `config.system.build.tarball` /`diskoImagesScript`/package output affected) rather than looping over every host — `codex-maintenance.sh` evaluates 18 hosts plus every package/tarball/ image variant now and is slow to run after each small change. Reserve a full `codex-maintenance.sh` run for changes that plausibly affect every host (`modules/common/*`, `flake.nix`, `variables.nix`) or as a final check before committing. This is a session-workflow preference only — it does not apply to CI, which should keep running the full script on every push/PR regardless of diff size; that's the point of it. ## Scripts Beyond `codex-setup.sh`/`codex-maintenance.sh` above, `scripts/` also has: - `scripts/sync-host-keys.sh` — generates/registers SSH host keys and their `.sops.yaml`/`secrets/*.yaml` recipients for flake targets, idempotently (`--all`, ``, `--remove`, `--regenerate-all-keys`, all with `--dry-run`). The primary tool for provisioning a new host's secrets access — see "Creating a new machine" in `docs/auto-installer.md`. - `scripts/prepare-host-key.sh` — narrower predecessor: generates a key by an arbitrary name without touching `.sops.yaml`. Still useful to pre-generate a key before its flake target exists yet, since `sync-host-keys.sh` can only act on targets `nixosConfigurations` already has. - `scripts/create-proxmox-resource.sh` — builds a `lxc-*`/`proxmox-*` target's tarball/disk image and creates it on a real Proxmox node (`pct restore` / `qm create`+`importdisk`), or reconfigures an existing resource's cores/memory/disk size (`--modify`, always requires typing the VMID back to confirm). Refuses to create a target whose host identity already has a real deployment elsewhere (`variables.nix`'s `deployedTargets`) unless `--allow-duplicate-host` is passed. `--dry-run` throughout both modes. - `scripts/env.sh` — shared config (`PROXMOX_HOST`, storage pool, bridge, default cores/memory) sourced by `create-proxmox-resource.sh`. Add new cross-script config here instead of duplicating it per-script. - `scripts/bump-nixpkgs-release.sh` — bumps `flake.nix`'s `nixpkgs.url`/ `home-manager.url` in place. Exists because flake input URLs can't reference `variables.nix` (confirmed empirically — `nix flake metadata` errors on it), so this is the closest equivalent to a single source of truth for the tracked release. `sync-host-keys.sh` and `create-proxmox-resource.sh` genuinely mutate real state when run for real (not `--dry-run`): real `secrets/*.yaml` recipients, real Proxmox VMs/containers. They require the operator's own SSH/sops access, which an agent session doesn't have — but don't suggest running either non-dry-run without the operator's explicit go-ahead even if it becomes technically reachable. ## Architecture `flake.nix` is the single entry point. It generates one `nixosConfigurations.-` attribute per target via the `mkTarget` function, composed from: ``` nixosSystem { modules = [ disko.nixosModules.disko sops-nix.nixosModules.sops ./modules/common/configuration.nix ./modules/platforms/${platform}.nix # what it runs on ./modules/build-types/${buildType}.nix # what it's for hostPath # hosts//host.nix — per-machine identity home-manager.nixosModules.home-manager { ... } ] ++ (client-only modules, for every buildType except "nix-cache" itself) } ``` Platforms: `linode`, `proxmox`, `lxc`. Build types: `minimal`, `nix-cache`, `server`, `docker`, `gui`, `pxe-boot`. Not every combination is built — e.g. `pxe-boot` has no `linode` variant (PXE/DHCP/TFTP need LAN L2 adjacency a Linode VPS doesn't have). Treat `flake.nix`'s `generatedTargets` as the source of truth for which hosts exist — `README.md`, `AGENTS.md`, `docs/flake-lock-automation.md`, and the CI eval workflows (`.github/workflows/check-nixos.yml`, `.gitea/workflows/check-nixos.yml`) list hosts by hand (or, for the CI workflows, evaluate the flake dynamically) and can drift from it, so re-check them against `flake.nix` when adding or removing a host. ### Composition pattern - `hosts//host.nix` — per-machine identity **only**: hostname, hostId, per-machine secrets, `system.stateVersion`. These files carry no `imports` of their own beyond narrow parameterized helpers (see `modules/beszel/host-token.nix` below) — all shared behavior comes from the platform/build-type modules composed in `flake.nix`, not from the host file. - `modules/platforms/{linode,proxmox,lxc}.nix` — platform-specific config: boot method, guest tooling, and (for linode/proxmox) the hypervisor-specific hardware config, imported directly by the platform module itself (`../hardware-configuration/vm/{proxmox,linode}.nix`) — **not** wired in from `flake.nix`. `lxc.nix` has no hardware-configuration counterpart since containers share the host kernel; instead it imports nixpkgs' own `virtualisation/proxmox-lxc.nix`, which gives every `lxc-*` host a `config.system.build.tarball` output (`pct restore`-ready, no install step — see `docs/auto-installer.md`). - `modules/build-types/*.nix` — what a system is for: minimal/server/docker/gui/pxe-boot/nix-cache. - `modules/common/configuration.nix` — base NixOS config imported by every host: locale, users, nix settings, git. - `modules/common/home.nix` / `hosts/nixos/home.nix` — Home Manager config for the `nixos` user; the `nixos` workstation (`gui` build type) has its own, other hosts share `modules/common/home.nix`. - `modules/disko/proxmox.nix` — declarative disk layout (GPT: ESP + swap + ext4 root) via disko, used by all Proxmox-VM hosts (`proxmox-*`, not `lxc-*`). Also carries `imageSize`/`imageName`, letting every `proxmox-*` host be built as a standalone, `qm importdisk`-ready `.raw` image with no install step — see `docs/proxmox-images.md`. - `modules/disko/linode.nix` — `linode-*`'s disko config, deliberately different in kind from the Proxmox one: Linode provisions and sizes `/dev/sda`/`/dev/sdb` itself as whole, unpartitioned devices before the OS boots, so this declares them with `destroy = false` (disko never wipes them) and a bare `filesystem`/`swap` content type instead of a partition table — idempotent against an already-provisioned disk, never destructive. - `modules/boot/efi.nix` — systemd-boot + EFI vars, paired with the disko module. - `modules/installer/` — the auto-installer environment (ISO, also served as PXE netboot): `common.nix` (shared config + the generated `auto-install.sh`), `iso.nix`, `host-keys.nix` (optionally bakes `host-keys/` into the image under `--impure`). See `docs/auto-installer.md`. - `modules/pxe-boot/stage-installer-artifacts.nix` — builds the installer's netboot image and stages it on the `pxe-boot` host so its iPXE menu can chain straight to it. See `docs/pxe-boot.md`. - `modules/nix-cache/{client,server,remote-builder-client}.nix` — binary cache substituter + SSH remote-builder wiring; see `docs/nix-cache.md` for the full design (per-host local stores, no shared `/nix/store`, and how the `nixremote` signing/SSH keys fit together). - `modules/beszel/host-token.nix` — parameterized helper module (`{ name, sopsFile }`) that wires a host's beszel-agent sops secret/template and `environmentFile`; used by `hosts/server/host.nix` and `hosts/nix-cache/host.nix` to avoid duplicating that boilerplate. - `modules/tailscale/`, `modules/docker/`, `modules/networking/`, `modules/traefik/`, `modules/services/*` — single-purpose, single-host feature modules (e.g. `docker/enable-service.nix`, `services/zfs/enable-service.nix`). Grep `modules/build-types/*.nix` for each build type's `imports` list to see which modules apply where. New host = new `hosts//host.nix` + a matching `mkTarget { platform; buildType; hostPath; }` entry added to `flake.nix`'s `generatedTargets`, composed from existing `modules/*` pieces rather than duplicating config. ### Other docs worth reading before touching these areas - `docs/nix-cache.md` — nix-cache binary cache/remote-builder design and key handling. - `docs/pxe-boot.md` — the `pxe-boot` host's iPXE/TFTP/HTTP boot chain and directory layout under `/srv/pxe`. - `docs/auto-installer.md` — the installer environment (ISO/netboot/Proxmox LXC), `host-keys/` and the sops-nix pre-seeding problem it solves, and why `lxc-*` hosts are deliberately excluded from its menu. - `docs/proxmox-images.md` — building `proxmox-*` hosts as standalone `.raw` disk images (disko's image builder) instead of installing, and deploying the result to Proxmox. - `docs/flake-lock-automation.md` — how `flake.lock` updates flow through CI (scheduled `nix flake update` PR + host-eval-on-PR workflow) and why hosts should track the committed lock file rather than `nixos-rebuild --upgrade-all`.