The operator ruled the implementation repo's main (50c8719) the latest and accepted state. Sync the spec to it. Accepted changes are reflected: one bare-metal environment, TrueNAS bulk storage, R2 off-cluster backup, CloudNativePG, single-node VPN gateway with Cilium fail-closed, inbound WireGuard, per-app auth sidecars, Renovate, the CI gate as approver, and the pm-agent. New ADR-0017..0025; ADR-0016 corrected and accepted; R-* IDs unchanged, eight amended. Where the implementation has simply not met a requirement, the requirement stands and the shortfall is recorded in docs/16-known-gaps.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
112 lines
9.9 KiB
Markdown
112 lines
9.9 KiB
Markdown
# Task Board
|
||
|
||
> **FROZEN — historical seed.** By the operator's ruling of 2026-08-31 this board is not the status
|
||
> surface; the repository is. **Nobody edits it**, and its contents and the instructions below no
|
||
> longer describe live work. Its `T-*` IDs stay stable because the implementation repo cites them.
|
||
> Live status: `./conformance/run.sh --status` in the implementation repo; dated snapshot:
|
||
> [`../docs/16-known-gaps.md`](../docs/16-known-gaps.md).
|
||
|
||
The live who's-doing-what. Agents **claim** an item (set Owner + Status) before starting, respect
|
||
`Blocked-by`, and mark `done` only after its **automated, reproducible behavior test passes green**
|
||
([`definition-of-done.md`](definition-of-done.md), [`../docs/14-testing-and-conformance.md`](../docs/14-testing-and-conformance.md)).
|
||
Seeded from [`../roadmap/phases.md`](../roadmap/phases.md) + [`../roadmap/workstreams.md`](../roadmap/workstreams.md).
|
||
|
||
> The specs, ADRs, and roadmap are **not** editable by agents (R-TEST-06). Every "**Prove …**" task
|
||
> below is an automated conformance test (co-owned with **WS-TEST**), not a manual demo.
|
||
|
||
**Status values:** `todo` · `in-progress` · `blocked` · `review` · `done`
|
||
**Add new items** at the bottom of the relevant phase; keep IDs stable.
|
||
|
||
---
|
||
|
||
## Phase 0 — Foundations
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-0.1 | Create implementation repo structure (platform/apps/catalog/clusters) | WS-GITOPS | R-GITOPS-02 | — | — | todo |
|
||
| T-0.2 | CI: lint + schema validation + build/dry-run gates | WS-GITOPS | R-GITOPS-01 | T-0.1 | — | todo |
|
||
| T-0.3 | Author ServiceDefinition schema + renderer contract | WS-GITOPS | R-GITOPS-02 | T-0.1 | — | todo |
|
||
| T-0.4 | Conformance harness: **Chainsaw** runner + reproducibility contract + JUnit + coverage report + CI merge gate | WS-TEST | R-TEST-01/02/03/04 | T-0.1 | — | todo |
|
||
| T-0.5 | L0 **Kyverno** policy gate (default-deny present, no plaintext secrets, schema-valid ServiceDefinitions) | WS-TEST | R-TEST-01, R-NET-01, R-SEC-01 | T-0.4 | — | todo |
|
||
|
||
## Phase 1 — dev/test Cluster Bring-up
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-1.1 | OpenTofu libvirt module for 3 node VMs (+ Longhorn data disk) | WS-INFRA | R-ENV-01 | — | — | todo |
|
||
| T-1.2 | cloud-init baseline (users, SSH, sysctls, virt/Longhorn prereqs) | WS-INFRA | R-ENV-01 | T-1.1 | — | todo |
|
||
| T-1.3 | Ansible: k3s HA install, Cilium CNI, disable flannel/kube-proxy/Traefik | WS-INFRA | R-PLAT-01 | T-1.2 | — | todo |
|
||
| T-1.4 | Bootstrap Flux + SOPS age key | WS-GITOPS/WS-SEC | R-GITOPS-01, R-SEC-01 | T-1.3 | — | todo |
|
||
| T-1.5 | Prove node destroy/rebuild from IaC (no data loss) | WS-INFRA | R-ENV-01, R-STATE-01 | T-1.4 | — | todo |
|
||
|
||
## Phase 2 — Platform Core (net/storage/TLS/DNS/ingress)
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-2.1 | Longhorn (3-replica StorageClass) + `bulk` external-mount indirection (media lives off-cluster, backing TBD — OQ-2 resolved) | WS-STORAGE | R-STATE-02/04 | T-1.4 | — | todo |
|
||
| T-2.2 | Default-deny network baseline (per-namespace) + Hubble | WS-NET | R-NET-01/03 | T-1.3 | — | todo |
|
||
| T-2.3 | Render `dependencies` → CiliumNetworkPolicy from ServiceDefinition | WS-NET/WS-GITOPS | R-NET-02 | T-0.3, T-2.2 | — | todo |
|
||
| T-2.4p | **Prereq:** operator creates scoped Cloudflare API token (`Zone·DNS·Edit`, `keithjackson.dev`); store SOPS-encrypted | WS-SEC | R-TLS-01, R-SEC-01 | — | — | todo |
|
||
| T-2.4 | cert-manager + Let's Encrypt DNS-01 wildcard `*.home.keithjackson.dev` (via Cloudflare token) | WS-SEC | R-TLS-01/02/03 | T-1.4, T-2.4p | — | todo |
|
||
| T-2.5 | ExternalDNS (Cloudflare token) + Gateway API listeners (VIP via **Cilium L2 announcement**) + split-horizon (PiHole) | WS-NET | R-DNS-01/02/03 | T-2.4 | — | todo |
|
||
| T-2.6 | Prove: default-deny (DNS-only probe), PVC survives rebuild, trusted cert, auto-DNS | WS-NET/STORAGE/SEC | R-NET/STATE/TLS/DNS | T-2.1..2.5 | — | todo |
|
||
| T-2.7 | Deploy **Kyverno in-cluster as admission enforcer** (same policies as the CI L0 gate) | WS-TEST/WS-SEC | R-SEC-02, R-NET-01, R-SEC-01 | T-1.4, T-0.5 | — | todo |
|
||
|
||
## Phase 3 — Egress & VPN (single WAN; no multi-WAN — OQ-1)
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-3.0 | L3 fault-injection harness — **in-cluster Chainsaw Job** (CI-triggered, scoped test SA) using `script`/`try`/`finally` (force tunnel down, kill node, inject bad image, trigger restore) | WS-TEST | R-TEST-05, R-SEC-02 | T-0.4 | — | todo |
|
||
| T-3.1 | Egress classes; Cilium Egress Gateway wiring (steer `vpn` pods → gateway) | WS-NET | R-NET-04/05 | T-2.2 | — | todo |
|
||
| T-3.2p | **Prereq:** provide **PIA username/password + `.ovpn`**; store in **Ansible vault** on the egress-gateway node(s) (ADR-0015) | WS-SEC | R-NET-05 | — | — | todo |
|
||
| T-3.2 | **Node-level PIA/OpenVPN tunnel** (host systemd, Ansible, split-tunnel) on egress-gateway node(s) + **CiliumEgressGatewayPolicy** steering `egress-class=vpn` + **host nftables kill-switch** + host PF helper for qBittorrent (ADR-0015) | WS-NET | R-NET-05/06 | T-3.1, T-3.2p | — | todo |
|
||
| ~~T-3.3~~ | ~~Edge-router multi-WAN split~~ — **out of scope** (single WAN, dumb SOHO edge; OQ-1) | WS-NET | R-NET-04 | — | — | won't-do |
|
||
| T-3.4 | Prove: VPN IP egress, **kill-switch fail-closed (central)**, egress-class flip via git | WS-NET | R-NET-05/06/07 | T-3.0, T-3.2 | — | todo |
|
||
|
||
## Phase 4 — Observability, Backup/DR & Updates
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-4.1 | kube-prometheus-stack + Loki + Alloy + Alertmanager(email) | WS-OBS | R-OBS-01/02/03 | T-2.1 | — | todo |
|
||
| T-4.2 | Platform-health alerts (node/Longhorn/Flux/cert/VPN/backup) | WS-OBS | R-OBS-04 | T-4.1 | — | todo |
|
||
| T-4.3 | Velero + Longhorn off-cluster backup schedule (**needs backup-target choice** — S3/NFS endpoint) | WS-STORAGE | R-STATE-03 | T-2.1, OQ-BKP | — | todo |
|
||
| T-4.4 | DR drill: restore namespace + volumes from off-cluster backup | WS-STORAGE | R-STATE-03 | T-4.3 | — | todo |
|
||
| T-4.5 | Flux image-automation + Renovate + per-service update policy | WS-UPDATE | R-UPD-01/02/03 | T-0.3, T-4.1 | — | todo |
|
||
| T-4.6 | Prove: auto stable update on schedule, bad-update auto-rollback+alert, pin holds | WS-UPDATE | R-UPD-* | T-4.5 | — | todo |
|
||
|
||
## Phase 5 — IaaS Proof (KubeVirt)
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-5.1 | Install KubeVirt + CDI; Longhorn-backed VM disks | WS-VM | R-PLAT-03 | T-2.1, T-2.2 | — | todo |
|
||
| T-5.2 | `kind: vm` ServiceDefinition path; run one representative VM | WS-VM/WS-GITOPS | R-PLAT-03/04 | T-5.1, T-0.3 | — | todo |
|
||
| T-5.3 | Prove: VM lifecycle via kubectl/virtctl, rebuild w/o data loss, default-deny+egress, guest auto-update | WS-VM | R-PLAT-03, R-NET-*, R-UPD-* | T-5.2 | — | todo |
|
||
|
||
## Phase 6 — Service Onboarding + C2
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-6.1 | Onboard **pihole** (DNS underpinning) | WS-APPS | R-DNS-03 | T-2.5 | — | todo |
|
||
| T-6.1a | Onboard **gitea** early (bootstrapping-critical; self-hosts GitOps source + CI/CD) | WS-APPS | R-STATE-03 | T-4.4 | — | todo |
|
||
| T-6.1b | Migrate GitOps source local→Gitea; move CI to **Gitea Actions** (Kyverno gate + Chainsaw Job trigger; PR merge gate); off-cluster repo mirror | WS-GITOPS | R-GITOPS-01, R-TEST-03 | T-6.1a | — | todo |
|
||
| T-6.1c | Onboard **Keycloak** (IdP) + PostgreSQL (Longhorn, DR-critical) + **oauth2-proxy** forward-auth at Gateway; declarative realm/clients/groups; MFA; break-glass path | WS-SEC | R-IDP-01..06 | T-2.5, T-2.6 | — | todo |
|
||
| T-6.1d | Render ServiceDefinition `auth` block → Keycloak OIDC client / oauth2-proxy protection | WS-SEC/WS-GITOPS | R-IDP-02/03 | T-0.3, T-6.1c | — | todo |
|
||
| T-6.2 | Onboard **plex** (bulk tier + public exposure) | WS-APPS | R-STATE-04 | T-2.6, T-6.1 | — | todo |
|
||
| T-6.3 | Onboard **qbittorrent, jackett** (VPN + kill-switch) | WS-APPS | R-NET-05/06 | T-3.4 | — | todo |
|
||
| T-6.4 | Onboard **sonarr, radarr** (VPN + arr↔downloader allows) | WS-APPS | R-NET-02/05 | T-6.3 | — | todo |
|
||
| T-6.5 | Onboard **seerr** (allows to arr + plex) | WS-APPS | R-NET-02 | T-6.2, T-6.4 | — | todo |
|
||
| T-6.6 | Onboard **nextcloud** (DR-critical + backups) | WS-APPS | R-STATE-03 | T-4.4 | — | todo |
|
||
| T-6.7 | Onboard **minecraft, unifi, semaphore, kasm** | WS-APPS | app reqs | T-2.6 | — | todo |
|
||
| T-6.8 | C2 UI: Homepage root + Grafana + Headlamp + Flux status + auth | WS-C2 | R-C2-01/02 | T-4.2, T-6.* | — | todo |
|
||
|
||
## Phase 7 — Production Readiness (CLOSED until operator opens it)
|
||
| ID | Task | WS | Serves | Blocked-by | Owner | Status |
|
||
|---|---|---|---|---|---|---|
|
||
| T-7.1 | Bare-metal provisioning checklist (design-only) | WS-INFRA | R-ENV-03 | — | — | todo |
|
||
| T-7.2 | prod overlay (no base edits) + cutover plan | WS-INFRA/WS-GITOPS | R-ENV-02 | Phases 1–6 done | — | blocked |
|
||
| T-7.3 | Full prod DR drill | WS-STORAGE | R-STATE-03 | T-7.2 | — | blocked |
|
||
|
||
---
|
||
|
||
## Open questions
|
||
| OQ | Question | Blocks | Status |
|
||
|---|---|---|---|
|
||
| OQ-1 | WAN count + edge router | — | ✅ resolved: 1 WAN, dumb SOHO edge; multi-WAN out of scope; VPN + segmentation in-cluster |
|
||
| OQ-2 | Bulk-media home | — | ✅ resolved: media outside cluster, backing TBD later (not blocking) |
|
||
| OQ-3 | VPN provider/protocol + egress steering | — | ✅ resolved: **PIA over OpenVPN-UDP** (ADR-0013); steering = **node-level host tunnel + Cilium Egress Gateway + host nftables kill-switch** (ADR-0015, supersedes the in-cluster-Gluetun steering — a Gluetun pod's tun0 can't be steered by Cilium egress gateway) |
|
||
| OQ-4 | DNS-01 challenge access | — | ✅ resolved: scoped **Cloudflare API token** (Zone·DNS·Edit), SOPS-encrypted; drives cert-manager + ExternalDNS. Operator provides token (T-2.4p) |
|
||
| **OQ-BKP** | **Off-cluster DR backup target** (S3/NFS endpoint) | T-4.3 | ⛔ **OPEN** — needed by Phase 4, not Phase 2 |
|