# CLAUDE.md This file provides guidance to Claude Code when working anywhere in this repository. Each stack directory (`01-proxmox-infra`, `02-k8s-bootstrap`, etc.) has its own `CLAUDE.md` with stack-specific commands and architecture — Claude Code loads both this file and the stack's file automatically when working inside a stack directory. ## Project overview Pulumi (TypeScript) IaC for a Proxmox-based k3s homelab. Independent Pulumi projects ("stacks"), each with its own `Pulumi.yaml`/`package.json`, deployed in order and linked via `pulumi.StackReference`: | # | Directory | Pulumi stack | Purpose | | --- | --- | --- | --- | | 1 | `01-proxmox-infra` | `proxmox-infra` | Provisions k3s VMs + DHCP on Proxmox/pfSense | | 2 | `02-k8s-bootstrap` | `k8s-bootstrap` | Installs k3s on the VMs over SSH | | 3 | `03-k8s-infra` | `k8s-infra` | Cluster infra: storage, cert-manager, kube-vip | | 4 | `04-k8s-apps` | `k8s-apps` | Application workloads (e.g. heimdall) | | 5 | `05-monitoring` | `monitoring` | kube-prometheus-stack (Prometheus + Grafana) | All stacks use the Pulumi stack name `dev`. ## Deployment order & StackReferences ``` proxmox-infra → k8s-bootstrap → k8s-infra ↘ k8s-apps → monitoring ``` - `k8s-bootstrap` reads VM IDs, Proxmox credentials, and the SSH private key from `proxmox-infra` via StackReference. - `k8s-infra`, `k8s-apps`, and `monitoring` all read `kubeconfig` from `k8s-bootstrap` via StackReference (`requireOutput("kubeconfig")`) — never set `kubeconfig` as Pulumi config in these stacks. - `monitoring` additionally reads `domain` from `k8s-apps` via StackReference. - StackReference format: `${pulumi.getOrganization()}//dev`. A stack's outputs must exist (i.e. it must have been deployed) before any downstream stack referencing it will succeed. ## Shared conventions - **TypeScript**: `strict: true`, `target: es2024`, `module`/`moduleResolution: nodenext`, `noImplicitReturns`, `noFallthroughCasesInSwitch` — functions need explicit return types where TS can't infer them. - **Secrets**: read via `config.requireSecret()`, never via environment variables or hardcoded values. - **`Pulumi.dev.yaml`**: gitignored (contains the encryption salt) — never delete it or secrets become unrecoverable. Encrypted with `PULUMI_CONFIG_PASSPHRASE`. - **Backend**: self-hosted Postgres state backend, set via `PULUMI_BACKEND_URL`. - Don't add a co-author when committing to git. ## Common commands (run from a stack directory) ```bash npm install pulumi preview pulumi up --yes pulumi destroy pulumi stack output pulumi config ``` Requires `PULUMI_BACKEND_URL` and `PULUMI_CONFIG_PASSPHRASE` in the environment (or `pulumi login`). ## CI/CD (Gitea Actions) One workflow per stack: `.gitea/workflows/0N-deploy-.yaml`, scoped to changes under that stack's directory. - **Pull request → `main`**: `pulumi preview` only. - `01`/`02` (VM provisioning / cluster bootstrap — riskier to automate): deploy only via manual `workflow_dispatch`. - `03`–`05`: deploy on push to `main` *or* `workflow_dispatch` — `pulumi refresh` then `pulumi up`. - Each workflow restores `Pulumi.dev.yaml` from a base64-encoded Gitea secret (`_PULUMI_DEV_YAML`). After changing local config, re-encode and update the secret: ```bash base64 -w 0 0N-/Pulumi.dev.yaml ```