# CLAUDE.md — infrastructure Safety rules and working context for Claude Code in this repository. ## Overall goal Build a complete, auditable Infrastructure as Code system for the `sweet.home` homelab. The end state is: every host, service, and network resource is declared in this repo; Terraform captures and detects drift in infrastructure state; Ansible idempotently configures non-NixOS hosts; NixOS flake manages NixOS hosts; automated CI pipelines generate internal and external documentation daily. See `docs/internal/implementation-plan.md` for the phased roadmap. ## Production boundary — confirm before touching `pve1.sweet.home` and all VMs/LXCs running on it are production. That includes: - `domain-controller.sweet.home` (FreeIPA — if this goes down, auth AND DNS break for everything) - `docker.sweet.home` (Nextcloud, Passbolt, Gitea — live data) **Always ask before running any Ansible playbook with `--apply` or `terraform apply` against production hosts.** Use `--check` (Ansible) or `terraform plan` (Terraform) for read-only validation; these are safe to run without asking. `pve-test.sweet.home` is the sandbox. VMs and containers on it can be created/destroyed without asking. Node-level config (firewall, SSH, unattended-upgrades) still needs confirmation. ## Terraform rules - Always `terraform plan` before `terraform apply`. - Never commit `.tfstate` or `.tfvars` files — they go in `.gitignore`. - API tokens and secrets go in `terraform.tfvars` (git-ignored) or environment variables. - If `plan` shows unexpected destruction of real resources, stop and ask. ## Ansible rules - Always use `--check --diff` before the first real run against any host. - The `proxmox-hardening` role is safe to re-run (idempotent). Still confirm first. - The `freeipa` role is NOT idempotent end-to-end — `ipa-server-install` will refuse to run if IPA is already installed (it checks). That's fine; treat it as a guard. - Never run `ansible-playbook` against production hosts without a `--limit` specifier unless running the full `site.yml` intentionally. ## NixOS rules - The `nixos/` subdirectory is a flake. Use `nix build ./nixos#` to validate before `nixos-rebuild switch`. - Secrets are SOPS-encrypted with age. Never decrypt secrets into plaintext in the repo. - The flake remote URL (used by the `Switch-nix` alias on NixOS hosts) must be updated to point at the new Gitea URL after migration. See implementation plan Phase 4. ## Stacks rules - Changes to `stacks/docker/` or `stacks/raspi/` take effect only when manually deployed. No automated apply runs against live containers. - Test compose changes with `docker compose config` (validates interpolation) before deploying. - `secrets/stack-secrets.env` and `secrets/*.txt` are git-ignored. Never commit real secrets. ## Secrets - Pre-commit hook (installed by `scripts/install-hooks.sh`) runs gitleaks before every commit. - CI also runs gitleaks on push. Both must pass. - If gitleaks false-positives on a known-safe pattern, add it to `.gitleaks.toml` allowlist, not to the ignore-next-line comment. ## Documentation pipeline - Internal docs: `docs/internal/` — full topology, credentials catalog (names only), runbooks. - External docs: `docs/external/` — sanitized, no IPs, no internal hostnames, no credential refs. - Generated content lands in `docs/generated/` — never edit these files by hand. - Run `./scripts/docs-build.sh` locally to preview before pushing. ## Directory notes - `stacks/docker/` and `stacks/raspi/` are populated via `git subtree` — see implementation plan. - `nixos/` is populated via `git subtree` — the flake works from the subdirectory path. - `terraform/dns/` wraps the Dynu provider. Read-only operations only until credentials are configured. - `ansible/roles/` — each role has a `README.md` describing variables and expected host state.