# platform/ Cluster-wide **platform services**, Flux-managed (**R-PLAT-01**, **R-GITOPS-01/02/03**). Reconciled by the `platform` Flux Kustomization defined in [`clusters/base/platform.yaml`](../clusters/base/platform.yaml), **before** `apps/`. Governing spec: [`docs/05-gitops-and-delivery.md`](../../home-network-k3s/docs/05-gitops-and-delivery.md), [`docs/01-architecture-overview.md`](../../home-network-k3s/docs/01-architecture-overview.md). > **Phase 0 scaffolding only.** Every component here is a structurally-correct **stub** — a Flux > HelmRelease and/or Kustomization with a values placeholder, a README citing the governing ADR/spec, > and TODOs for the phase that fills it in. **No cluster exists**; nothing reconciles yet. Do not add > working config, secrets, or provisioning under Phase 0 (spec `AGENTS.md` §1/§5/§6, `roadmap/phases.md`). ## Reconciliation flow (how a change lands) ``` git (source of truth) │ operator PR → CI (lint · schema · Kyverno L0 · Chainsaw) → merge to tracked branch ▼ Flux GitRepository (clusters/base/flux-system.yaml) ▼ Flux Kustomization "platform" → ./platform (this dir) ── substituteFrom cluster-vars ──┐ │ prune=true (drift correction, R-GITOPS-03) · wait=true │ R-ENV-02 ▼ │ Flux Kustomization "apps" → ./apps (dependsOn platform) ─ substituteFrom cluster-vars ──┘ ``` Git is authoritative; the cluster conforms to git and drift is auto-corrected (R-GITOPS-01/03). Manual `kubectl apply` of platform state is prohibited (spec `AGENTS.md` §5). ## The dev ↔ prod overlay model (R-ENV-02) Platform manifests are authored **once**. Every value that legitimately differs between environments is a `${TOKEN}` resolved from the `cluster-vars` ConfigMap via Flux `postBuild.substituteFrom`. The `clusters/dev` and `clusters/prod` overlays differ **only** by the values they patch into `cluster-vars` (issuer, replica counts, storage sizes, retention). See [`clusters/README.md`](../clusters/README.md). Base manifests are **never forked per environment**. ## Components (one directory each) | Dir | Component | Governing ADR / spec | Serves | |---|---|---|---| | `cilium/` | CNI + kube-proxy replacement + Gateway API + Egress Gateway + Hubble | [ADR-0002](../../home-network-k3s/decisions/adr-0002-cni-cilium.md), docs/03 | R-NET-* | | `longhorn/` | Replicated block storage; 3-replica default StorageClass + bulk tier | [ADR-0003](../../home-network-k3s/decisions/adr-0003-storage-longhorn.md), docs/04 | R-STATE-* | | `cert-manager/` | TLS via Let's Encrypt DNS-01 (Cloudflare); staging + prod ClusterIssuers | [ADR-0006](../../home-network-k3s/decisions/adr-0006-tls-cert-manager-letsencrypt.md), docs/08 | R-TLS-* | | `externaldns/` | Automatic DNS records for public services (Cloudflare) | docs/09 | R-DNS-* | | `gateway/` | Gateway API GatewayClass + shared HTTPS Gateway + wildcard cert | [ADR-0002](../../home-network-k3s/decisions/adr-0002-cni-cilium.md)/[ADR-0006](../../home-network-k3s/decisions/adr-0006-tls-cert-manager-letsencrypt.md), docs/09 | R-DNS-01, R-TLS-01 | | `monitoring/` | kube-prometheus-stack + Loki + Grafana Alloy + Alertmanager | docs/10 | R-OBS-* | | `velero/` | Off-cluster backup/DR of volumes + resource state | [ADR-0003](../../home-network-k3s/decisions/adr-0003-storage-longhorn.md), docs/04 | R-STATE-03 | | `kubevirt/` | KubeVirt + CDI (IaaS: application VMs on the same K8s surface) | [ADR-0005](../../home-network-k3s/decisions/adr-0005-vm-workloads-kubevirt.md), docs/06 | R-PLAT-03 | | `image-automation/` | Flux image-update-automation (hands-off image tag advance) | [ADR-0009](../../home-network-k3s/decisions/adr-0009-automated-image-updates.md), docs/11 | R-UPD-02 | | `keycloak/` | Platform IdP (OIDC + SAML), config-as-code, PostgreSQL on Longhorn | [ADR-0014](../../home-network-k3s/decisions/adr-0014-idp-keycloak.md), docs/15 | R-IDP-* | | `oauth2-proxy/` | Forward-auth at the Gateway for non-OIDC apps → Keycloak | [ADR-0014](../../home-network-k3s/decisions/adr-0014-idp-keycloak.md), docs/15 | R-IDP-* | | `kyverno/` | **L0 policy-as-code gate — OWNED BY THE POLICY ENGINEER (WS-TEST).** | [ADR-0011](../../home-network-k3s/decisions/adr-0011-test-tooling-kyverno-chainsaw.md), docs/14 | R-TEST-* | ## ⚠️ INTEGRATION TODO(PM): wire kyverno into the platform kustomization `platform/kyverno/` is authored by a **different agent** (the policy engineer, WS-TEST). This workstream did **not** create any files under `platform/kyverno/` and must not. [`platform/kustomization.yaml`](kustomization.yaml) intentionally **omits `- kyverno`**. Once the policy engineer's files land, the **PM** must add `- kyverno` to that list (recommended first, so policies are present before other components reconcile). The line is present as a commented TODO in the kustomization. ## Secrets Every secret referenced below (Cloudflare API token, Velero backup-target creds, Keycloak admin/DB, oauth2-proxy cookie/client secrets) is referenced **by name only** and will be committed **SOPS-encrypted** (WS-SEC, [ADR-0007](../../home-network-k3s/decisions/adr-0007-secrets-sops-age.md)). **No plaintext secret appears anywhere in this tree.** ## Cross-workstream contracts surfaced by these stubs - **cilium (WS-NET ↔ WS-INFRA):** Cilium's *initial* CNI install is done by Ansible at cluster bootstrap (docs/02, Phase 1). The HelmRelease here manages Cilium *config* thereafter; its Helm values MUST match the Ansible bootstrap values (kubeProxyReplacement, k8sServiceHost/Port, API server host) or reconciliation will fight the bootstrap. Contract owner: WS-NET + WS-INFRA. - **gateway + cert-manager + externaldns (WS-NET + WS-SEC):** the shared Gateway listener, the wildcard `*.home.keithjackson.dev` cert, and the ExternalDNS source all reference each other; they form the "Issuer + Gateway TLS" interface in `roadmap/workstreams.md`. - **image-automation (WS-UPDATE):** this dir holds only the *cluster-wide* ImageUpdateAutomation + git write-back. Per-service `ImageRepository`/`ImagePolicy` are rendered from each ServiceDefinition's `update` block (WS-GITOPS/WS-UPDATE), living with `apps/`. - **keycloak/oauth2-proxy (WS-SEC):** the SSO/auth contract (`auth` block → Keycloak client / oauth2-proxy protection) is owned by WS-SEC; these are placeholders pending that contract.