diff --git a/.env.example b/.env.example index 0c6bca8..433188e 100644 --- a/.env.example +++ b/.env.example @@ -68,3 +68,14 @@ AUTHENTIK_TEMPORAL_CLIENT_ID= # ── CI/CD ────────────────────────────────────────────────────────────────────── # Forgejo Personal Access Token (from rock user) for pushing images to registry FORGEJO_RIOTPIAO_PAT= + +# ── Cloudflare Tunnel (remote off-LAN access to kubectl/talosctl) ────────────── +# From Cloudflare Zero Trust dashboard → Networks → Tunnels +# CLOUDFLARE_CONNECTOR_TOKEN: full tunnel token (JWT-like base64 string) +# CLOUDFLARE_ACCOUNT_ID: your account ID (hex string) +# CLOUDFLARE_TUNNEL_ID: tunnel UUID +# CLOUDFLARE_API_TOKEN: API token for programmatic tunnel config (optional) +CLOUDFLARE_CONNECTOR_TOKEN= +CLOUDFLARE_ACCOUNT_ID= +CLOUDFLARE_TUNNEL_ID= +CLOUDFLARE_API_TOKEN= diff --git a/CLAUDE.example.md b/CLAUDE.example.md index 3861c67..34b03f5 100644 --- a/CLAUDE.example.md +++ b/CLAUDE.example.md @@ -2,6 +2,18 @@ **Homelab:** A bare-metal three-node Kubernetes cluster running Talos Linux with a full observability stack, SSO via Authentik, secret management via Vault, and CI/CD infrastructure (Forgejo + Argo CD, deployed). +## Cluster Topology (3 control-plane HA, since 2026-07-20) + +| Node | IP | Zone | Scheduling | Storage | +|------|----|----|-----------|---------| +| `talos-cp-1` | .213 | az-a | schedulable (all workloads) | sole Longhorn node | +| `talos-cp-2` | .163 | az-b | dedicated (`NoSchedule`) | none | +| `talos-cp-3` | .166 | az-c | dedicated (`NoSchedule`) | none | + +3 voting etcd members peering on the LAN. Only `talos-cp-1` runs workloads and +holds storage → stateful services are single-instance (e.g. CNPG `ddb-cluster` += 1 instance). API endpoint is single-homed to `.213` (no VIP yet). + ## Service Integration Routes **New service? Pick your stack below:** @@ -31,9 +43,9 @@ Apps: cicd (Forgejo + Argo CD), sqs (Kafka + kmsvc), temporal, story-crater-back **Architecture principles:** - Immutable OS (Talos — no SSH, declarative config) -- Secrets in Vault (never commit `.env`, credentials in Secret volumes) -- Helmfile = single source of truth (`helmfile.yaml.gotmpl`) -- Pull-based GitOps (Argo CD, no kubeconfig in CI) +- Secrets in Vault + SOPS-encrypted (`*.enc.yaml`, age); never commit `.env` +- ArgoCD app-of-apps = CD source of truth (`k8s/argocd/root` → `k8s/argocd/apps/*`); helmfile is deprecated. Two-phase bootstrap in `k8s/argocd/bootstrap/BOOTSTRAP.md` +- Pull-based GitOps (Argo CD, no kubeconfig in CI); iterate = `git push` to Forgejo → auto-sync - Federated OIDC (Authentik provider for all services) @@ -53,6 +65,8 @@ Apps: cicd (Forgejo + Argo CD), sqs (Kafka + kmsvc), temporal, story-crater-back 3. **Secrets via volumes** — Never `--env` flag in pod specs (exposes in `kubectl describe`) 4. **External services via Ingress** — All public endpoints via TLS (homelab-ca) 5. **Never commit `.env`** — Only `.env.example` in git; real secrets in Vault +6. **Never rename or wipe `talos-cp-1` (.213)** — sole Longhorn storage node; renaming orphans its node CR and faults every volume (permanent data loss). Rename/reprovision only the dedicated CPs. +7. **Control-plane etcd advertises on the LAN** — keep `cluster.etcd.advertisedSubnets: ["192.168.1.0/24"]`, else Talos advertises on WireGuard and new members hang as etcd learners. ## Git & Release diff --git a/k8s/applications/cloudflared/cloudflared-secrets.enc.yaml b/k8s/applications/cloudflared/cloudflared-secrets.enc.yaml new file mode 100644 index 0000000..690cb43 --- /dev/null +++ b/k8s/applications/cloudflared/cloudflared-secrets.enc.yaml @@ -0,0 +1,17 @@ +cloudflared: + tunnelToken: ENC[AES256_GCM,data:GrZNC75M1T304m+MBbgcL9Wa6VD3Cc+joERjUWD7PsI5NlqFpVuorzub4OTaiE/F0sqDXtXdSwQP9jGTkgUnNbviUVEIBDb+zuiyzCfzuhc53oMvoUYRkRNeH5DR9TBEszspj8+mjQMDAKQFKtTmyDodgh8DdPg8LE8YTCqyx6CcpkGZ8yWY06VoKpZfFSOan/gHwRwG2500P8U+rzI676EKVUhWjBFP0iTbwDQR7spI7oz8Gon/0Q==,iv:O3i0v+M5L3i9O7SbBtDAJe5IsQDgw+alI0Y9arZNojs=,tag:/T63FivzvbLFaXEaBsn5FA==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpU2tjOGpJNk9leE9LSnlx + b2tVbFFMMkZ3OHZuU0VhcUp5Tjl4ZDh2R1U4CjFFMURDWFJtN1lETUNpSmEwbU9u + aEIxUW5qWTJGTnlobjV0emlpY00rM28KLS0tIFd0cllZeXFodFhJNTJMNkRLNyt1 + cXVyNnM4Y2pXeUFTYzU1OXlaOXR2RWcKp7/M/YFfpJg4L6a0AcYTV3Rza+bzaOeD + OUIwyns8ZsPtU8ILbRYUUdD2EJFiOPnWP4yX70Ak10v12gfB7vRJ6A== + -----END AGE ENCRYPTED FILE----- + recipient: age1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh + lastmodified: "2026-07-20T17:49:35Z" + mac: ENC[AES256_GCM,data:MhuuuInJjGbzoftxVqMZUov1tJpOt5Vb8GwTr0R12hRCVHwdV+cuC1YeXTQidYaduO7neGnYe0p8ESLEpyY06j4nSpCvGRPPaxTeSeA8IcIk71xNtA1X0FVPv51s59rpvVSjiMDCqrcOv1aJybfQnBZoc/9bfmCYGqhDkD6sg1U=,iv:ThTjUGYE9GiyIgXS+0KDLYS20RPJjMG9DKyMSoKGk/4=,tag:KqZYps5qSuKPUI4U3Iabmw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.2 diff --git a/k8s/argocd/bootstrap/BOOTSTRAP.md b/k8s/argocd/bootstrap/BOOTSTRAP.md index 67a39c9..0823564 100644 --- a/k8s/argocd/bootstrap/BOOTSTRAP.md +++ b/k8s/argocd/bootstrap/BOOTSTRAP.md @@ -66,7 +66,18 @@ argocd app sync forgejo git remote add forgejo http://forgejo.riotpiao.com:3000/riotpiao.com/homelab.git git push forgejo main -# 7. Deploy everything else from git — one sync brings up the whole cluster. +# 7. (Optional) Cloudflared tunnel secret — if the SOPS CMP plugin is NOT yet +# wired up: create the cloudflared-token Secret manually before wave 8 syncs. +# Skip this step if the CMP plugin is already live and handling SOPS decryption. +kubectl create namespace cloudflared --dry-run=client -o yaml | kubectl apply -f - +sops -d k8s/applications/cloudflared/cloudflared-secrets.enc.yaml \ + | yq '.cloudflared.tunnelToken' \ + | kubectl create secret generic cloudflared-token -n cloudflared \ + --from-literal=token=- --dry-run=client -o yaml | kubectl apply -f - +# Once the SOPS CMP plugin is deployed (Phase 1 wave 0), migrate this Secret +# into the plugin's script output and remove this manual step. + +# 8. Deploy everything else from git — one sync brings up the whole cluster. kubectl apply -f k8s/argocd/projects/homelab-project.yaml # AppProject kubectl apply -k k8s/argocd/root # app-of-apps root argocd app sync homelab-root