# home-network-k3s-impl Implementation of the home k3s + Longhorn platform. This repo holds the **reconciled artifacts** (Flux config, Kustomize overlays, KubeVirt/Helm manifests, ServiceDefinitions, Kyverno policies, Chainsaw conformance tests, CI). It is driven by — and subordinate to — the **read-only specification** at [`../home-network-k3s`](../home-network-k3s). ## Rules (from the spec — non-negotiable) - **The spec is read-only.** Never modify anything under `../home-network-k3s`. Implement against it; escalate spec problems, don't edit them (spec `AGENTS.md` §11, R-TEST-06). - **Done = a green, reproducible, requirement-tagged conformance test** — never "merged," never a manual demo (spec `docs/14`, `agents/definition-of-done.md`). - **No work on `main`.** Every change is a feature branch merged into `main` (spec `CONTRIBUTING.md`). - **No plaintext secrets** — SOPS-encrypted only (spec `docs/07`). - **Phase discipline** — only build what the active roadmap phase authorizes (spec `roadmap/phases.md`). ## Layout ``` infra/ node provisioning: OpenTofu (Proxmox VE) + cloud-init + Ansible, and the pinned dev container that carries the whole toolchain bootstrap/ one-time cluster bootstrap: Flux install, SOPS/age key, host Gitea platform/ platform CONTROLLERS (Flux HelmReleases / operator bases): kyverno, cilium, longhorn, external-snapshotter, cert-manager, externaldns, gateway, coredns, vpn-egress, monitoring, cnpg, keycloak, oauth2-proxy, kubevirt platform-config/ platform CONFIGS — the custom resources of the CRDs installed above (ClusterIssuers, Gateway, Kyverno policies, KubeVirt/CDI CRs, Keycloak CRs). Split out because CRs and the CRDs they use cannot apply in one pass. apps/ application tier, reconciled as dependency-ordered WAVES: wave-0/ wave-1/ wave-2/ rendered container apps (see DESIGN-waves.md) vms/ KubeVirt VirtualMachines (non-gating tier) catalog/ ServiceDefinition entries (the onboarding surface) + render.py schemas/ ServiceDefinition JSON Schema + renderer contract clusters/ Flux entrypoints + dev/ and prod/ overlays conformance/ Chainsaw behavior/conformance suite + harness + coverage report ci/ the gate as scripts (lint · schema · Kyverno L0 · L1 render · decision) .gitea/ Gitea Actions CI running that gate on an in-cluster runner ``` ## Status Roadmap phases **0–6 are delivered and deployed** on the dev cluster; **Phase 7 (prod) is not open** — `clusters/prod` is a design-only overlay. | Phase | Delivered | |---|---| | 0 Foundations | ServiceDefinition schema + renderer, Kyverno L0 gate, conformance harness, CI | | 1 Cluster bring-up | 3-node k3s HA on **Proxmox VE** (`192.168.1.50/.51/.52`), Cilium, Flux, SOPS | | 2 Platform core | Cilium · Longhorn · cert-manager (LE-prod wildcard) · ExternalDNS · Gateway API · split-horizon CoreDNS | | 3 Egress / VPN | ADR-0015 node-level OpenVPN split-tunnel + Cilium egress-gateway steering + host kill-switch | | 4 Observability | kube-prometheus-stack · Loki · Alloy · Alertmanager. **Velero still blocked on OQ-BKP**; image-automation authored but disabled | | 5 IaaS | KubeVirt + CDI on real nested KVM; live-migratable RWX VM storage. The UniFi VM runs, but **UniFi OS Server is not yet installed in the guest** | | 6 Identity + CI | Keycloak (Operator + CNPG) · oauth2-proxy forward-auth · Grafana OIDC · **kubectl OIDC SSO with group→RBAC** · in-cluster Gitea Actions merge gate | **Conformance: 36 / 51 requirements covered (70.6%)**, 15 gaps, 1 safety-critical gap (`R-STATE-02`, Longhorn node-loss). Regenerate with `./conformance/run.sh --coverage-only`; the full report is `conformance/_artifacts/coverage/coverage.md`. The running narrative — decisions, root causes, incidents, follow-ups — is [`DECISIONS-LOG.md`](DECISIONS-LOG.md). It, not this table, is the authoritative record. ## Working on this repo Everything the gate needs is baked into the pinned dev container — **do not install the toolchain on the host**: ```bash infra/devcontainer/dev.sh build # once infra/devcontainer/dev.sh run make -C ci gate # the same gate CI runs infra/devcontainer/dev.sh # interactive shell (sops, flux, kubectl, tofu, ansible) ``` `kubectl`/`flux`/`helm` in there talk to the **dev** cluster by default. Prefix `CLUSTER=prod` to target prod instead: ```bash infra/devcontainer/dev.sh run kubectl get nodes # dev (default) CLUSTER=prod infra/devcontainer/dev.sh run kubectl get nodes # prod ``` Both clusters are always present in the container's `KUBECONFIG` as the contexts `k3s-dev` and `k3s-prod`; `CLUSTER` picks which one is current, and each run prints the cluster it is targeting. The container mounts the repo at `/work`, your age keyring, and the sibling spec repo read-only so the spec-immutability check can actually run. See [`infra/devcontainer/README.md`](infra/devcontainer/README.md). A green run prints `MERGE GATE: PASS`. The L2/L3 conformance layers stay `SKIPPED` unless `CLUSTER_AVAILABLE=true` — the static gate never pretends to have run them.