Hybrid cloud cluster monorepo with Ansible, K8s, NixOS, and Terraform. cute.haus
ansible terraform nix k8s
0

Configure Feed

Select the types of activity you want to include in your feed.

add AGENTS.md

Aly Raffauf (Jun 26, 2026, 11:49 AM EDT) 2fae2d0d 83f5aa30

+94 -72
+3 -69
.justfile
··· 11 11 @printf '\033[1;33mUsage:\033[0m just <recipe> [args...]\n\n' 12 12 @just --list --list-heading $'Available recipes:\n\n' 13 13 14 - # Generate {ci,edconfig} files. 14 + # Regenerate editor config (.zed/settings.json). 15 15 [group('flake')] 16 - gen target: 17 - nix run .#{{ if target == "ci" { "render-workflows" } else if target == "edconfig" { "gen-files" } else { error("unknown target: " + target) } }} 16 + gen: 17 + nix run .#gen-files 18 18 19 19 # Update flake inputs. 20 20 [group('flake')] ··· 183 183 sed -i "s|@${CURRENT_DIGEST}|@${UPSTREAM}|" "$TEMPLATE" 184 184 git add "$TEMPLATE" 185 185 git commit -m "k8s/tranquil-pds: ${FLOAT_TAG}@${CURRENT_DIGEST:7:12} → ${FLOAT_TAG}@${UPSTREAM:7:12}" 186 - 187 - # Scaffold a new app chart under k8s/charts/<name>. After running, edit the 188 - # values.yaml and add a release block to k8s/helmfile.yaml. See k8s/charts/README.md. 189 - [group('kubes')] 190 - new-app NAME: 191 - #!/usr/bin/env bash 192 - set -euo pipefail 193 - DIR="k8s/charts/{{ NAME }}" 194 - if [[ -e "$DIR" ]]; then 195 - echo "$DIR already exists; aborting." >&2 196 - exit 1 197 - fi 198 - mkdir -p "$DIR/templates" 199 - cat > "$DIR/Chart.yaml" <<EOF 200 - apiVersion: v2 201 - name: {{ NAME }} 202 - description: TODO 203 - type: application 204 - version: 0.1.0 205 - appVersion: "1.0.0" 206 - dependencies: 207 - - name: common 208 - version: 0.1.0 209 - repository: file://../common 210 - EOF 211 - cat > "$DIR/values.yaml" <<'EOF' 212 - replicaCount: 1 213 - strategy: Recreate 214 - 215 - image: 216 - repository: TODO 217 - tag: TODO@sha256:TODO 218 - pullPolicy: IfNotPresent 219 - 220 - resources: 221 - requests: { cpu: 50m, memory: 64Mi } 222 - limits: { cpu: "1", memory: 256Mi } 223 - 224 - ports: 225 - - name: http 226 - containerPort: 80 227 - 228 - service: 229 - type: ClusterIP 230 - ports: 231 - - name: http 232 - port: 80 233 - targetPort: http 234 - 235 - probes: 236 - readiness: 237 - httpGet: { path: /, port: http } 238 - periodSeconds: 10 239 - 240 - ingress: 241 - enabled: true 242 - className: traefik 243 - routes: 244 - - host: TODO.cute.haus 245 - tlsSecret: cute-haus-tls 246 - EOF 247 - for kind in deployment service ingress; do 248 - echo '{{{{- include "common.'"$kind"'" . }}}}' > "$DIR/templates/$kind.yaml" 249 - done 250 - helm dependency update "$DIR" >/dev/null 251 - echo "scaffolded $DIR. next: edit values.yaml and add a release to k8s/helmfile.yaml."
+88
AGENTS.md
··· 1 + # AGENTS.md 2 + 3 + NixOS homelab + k3s cluster IaC for cute.haus. Single operator. Flake-parts 4 + NixOS configs on bare-metal/VPS hosts; an in-tree helm chart per app on k3s; 5 + SOPS+age secrets; Cloudflare/B2 via Terraform. 6 + 7 + ## Environment 8 + 9 + - `direnv allow` loads the flake (`use flake . --impure`, sets 10 + `NIXPKGS_ALLOW_UNFREE=1`) and decrypts `CLOUDFLARE_API_TOKEN`, 11 + `HCLOUD_TOKEN`, and B2 keys into env from `secrets/*.yaml` (used by 12 + terraform). The devShell runs `gen-files` on entry. 13 + - Everything runs through the Nix flake; there is no global toolchain. The 14 + `.justfile` is the command source of truth — run `just` to list recipes. 15 + 16 + ## Commands 17 + 18 + | Task | Command | 19 + | --- | --- | 20 + | Local pre-push gate | `just check` → `bun scripts/check.ts` + `nix flake check --impure` | 21 + | Build a NixOS host (no deploy) | `just build <host>` (`eterna` `jubilife` `pastoria` `snowpoint`) | 22 + | Deploy all hosts | `just deploy` (nynx `--operation switch`) | 23 + | Offline deploy + reboot | `just deploy-offline` | 24 + | Update flake inputs | `just update [inputs]` / `just update-nixpkgs` | 25 + | Regenerate editor config | `just gen` (writes `.zed/settings.json`) | 26 + | Edit a secret | `just sops-edit <file>` (`secrets/<file>`) | 27 + | Rekey after key change | `just sops-rekey` (after adding/removing `keys/*.pub`) | 28 + | Bump pinned image digests | `just bump <chart>` / `--all` / `--check` | 29 + | Bump private-registry image | `just bump-tranquil` (atcr.io, needs sops creds) | 30 + 31 + `scripts/` is Bun + TypeScript (`bun scripts/<file>.ts`); `scripts/check.ts` 32 + runs three consistency checks (forward-auth, release-names, pinned-images). 33 + 34 + ## CI (`.github/workflows/`) 35 + 36 + - `check-config`: bun checks, `helm template` each chart, ansible 37 + `--syntax-check`, terraform `fmt -check` + `init -backend=false` + `validate`. 38 + - `check-nix`: `nix -Lv flake check`. 39 + - `build-nixos`: builds all 4 host toplevels. 40 + - `build-nix`: builds `devShells.default` + `packages.gen-files` (only on 41 + `flake.lock`/`flake.nix` changes). 42 + - PR + push to `master`; `**/*.md`, `.github/**`, `_img/**` ignored (except 43 + build-nix). Renovate auto-merges minor/patch/digest on a schedule. 44 + 45 + ## Formatting 46 + 47 + Defined in `nix/treefmt.nix` (no checked-in `treefmt.toml`; generated by 48 + treefmt-nix). Enforced via `nix flake check` in `just check`. 49 + 50 + - Nix: **alejandra** (not nixpkgs-fmt) + deadnix + statix. 51 + - YAML/JSON/MD: prettier. Shell: shfmt (2-space) + shellcheck. 52 + - TOML: taplo. HCL: terraform. 53 + 54 + ## Architecture 55 + 56 + - `nix/` — flake-parts + `import-tree ./nix` (modules auto-load; just add a 57 + file). Hosts live in `nix/hosts/<host>.nix` and set 58 + `flake.nixosConfigurations.<host>`. NixOS modules register via 59 + `flake.modules.nixos.<name>` (see `nix/nixos/base/default.nix`). Disk 60 + profiles in `nix/disko.nix`. k3s node config in `nix/nixos/profiles/k3s.nix` 61 + (flannel on `tailscale0`; startup blocks until tailscale0 has an IP). 62 + - `k8s/` — `helmfile.yaml` is the ordered release graph (uses `needs:`). 63 + `charts/<name>/` are in-tree charts with **explicit manifests, no shared 64 + helpers** (see `k8s/charts/README.md`). `values/global.yaml` holds shared 65 + image pins + constants; `values/secrets/<name>.yaml` holds `ref+sops://` 66 + vals refs into `secrets/`. Secret flow: `secrets/*.yaml` → vals → helmfile 67 + → chart `templates/secret.yaml` → app `envFrom`. 68 + - `secrets/` — SOPS, multi-recipient age. `.sops.yaml` is generated; never 69 + hand-edit either. 70 + - `keys/` — `aly_<host>.pub` (user key) + `root_<host>.pub` (host key) age 71 + recipients. Add/remove a `.pub` then `just sops-rekey`. 72 + - `terraform/` — Cloudflare DNS + B2 buckets. Remote state in B2 with **no 73 + locking** (don't run from two places at once). 74 + - `ansible/` — `inventory.ini` (the 4 deployable hosts). 75 + `playbooks/vars/oidc-clients.yml` is the OIDC client source of truth. 76 + 77 + ## Conventions & gotchas 78 + 79 + - **Every deployed chart must sha256-pin its container images** 80 + (`image: repo:tag@sha256:...` or `image: {repository, tag: ...@sha256:...}`), 81 + enforced by `check-pinned-images.ts`. Shared images (alpine, rclone) live 82 + in `k8s/values/global.yaml`. Renovate keeps digests fresh (skips 83 + `tranquil`, which is on private atcr.io — use `just bump-tranquil`). 84 + - **forward-auth OIDC slugs** must be `forward-auth-<app>` and match a key in 85 + `k8s/values/secrets/forward-auth.yaml` (enforced by `check-forward-auth.ts`). 86 + - k3s node labels: `topology.kubernetes.io/zone` (cloud/home), 87 + `cute.haus/ingress=true`, and `cute.haus/intel-gpu=true` (NixOS module). 88 + For GPU pods request the `gpu.intel.com/i915` resource, not a nodeSelector.
+1 -1
flake.nix
··· 1 1 { 2 - description = "Aly's NixOS homelab flake with flake-parts"; 2 + description = "cute.haus infra"; 3 3 4 4 inputs = { 5 5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
+1 -1
k8s/charts/external-routes/Chart.yaml
··· 1 1 apiVersion: v2 2 2 name: external-routes 3 - description: Ingress routes for non-cluster services (homelab hosts via tailscale) 3 + description: Ingress routes for non-cluster services 4 4 type: application 5 5 version: 0.1.0
+1 -1
k8s/charts/watsup/Chart.yaml
··· 1 1 apiVersion: v2 2 2 name: watsup 3 - description: Watsup homelab dashboard 3 + description: Watsup dashboard 4 4 type: application 5 5 version: 0.1.0