Files
home-network-k3s-impl/platform/homepage
jakman85andClaude Opus 5 02c58aae15 fix(homepage): the C2 root hid a running service — Crafty had no tile
The R-C2-01 check added in the previous commit is RED on main for a real reason.
platform/crafty/httproute.yaml serves crafty.${BASE_DOMAIN} on the shared Gateway,
clusters/base/crafty.yaml reconciles it (dependsOn platform-config, wait:false), the web
UI is Keycloak-gated by a per-pod oauth2-proxy sidecar — and nothing on the operator's
front door linked to it. The service was running and the root did not admit it existed.

R-C2-01: "The root `home.keithjackson.dev` MUST serve a UI that surfaces the services
running in the cluster and links to them." An unsurfaced route is a requirement gap, not
a missing nicety, and it is the exact failure mode nothing else in the gate notices: every
cluster test stays green, the apex answers 200, and the estate is one service larger than
the dashboard says.

This is the SAME defect the unmerged 49c160d found on 2026-09-07 (feat/coverage-debt-
paydown, ~107 commits behind main). That branch was never merged, so the gap survived —
re-fixed here against today's tree, in token form (`crafty.${BASE_DOMAIN}`, not the
literal hostname that branch predated).

Grouped under Media because Crafty manages game servers; Platform would be equally
defensible and purely cosmetic. The tile also does duty for the L4 game ports
(crafty-mc.${BASE_DOMAIN}, TCP 25565-25567 / UDP 19132-19134), which have no browsable URL
of their own — the check reports those as an advisory rather than requiring a link.

ALSO: clusters/base/homepage.yaml's postBuild comment claimed "Homepage references no
${VARS} today; the substitution is retained for parity". That is no longer true and the
claim is dangerous, because it invites someone to delete the substitution as dead weight.
This component now references ${BASE_DOMAIN} in httproute.yaml's apex hostname AND in every
tile href; without substitution the token is applied literally, the apex route binds a host
matching no listener, every link on the root is dead, and Flux still reports Ready. Comment
corrected, and the property is asserted by c2-root-surface (5) so it cannot rot back.

Verified: conformance/tests/l0-static/c2-root-surface -> check OK, 49 assertions.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-13 16:28:21 -05:00
..

platform/homepage

Homepage (gethomepage) — the apex landing dashboard at https://home.keithjackson.dev, aggregating links to the cluster's services.

  • Serves: a single pane of glass for the homelab (docs/09 apex UI).
  • Tier: non-gating post-platform Flux Kustomization (clusters/base/homepage.yaml, dependsOn: platform-config, wait: false) — mirrors headlamp / oauth2-proxy. A leaf UI must never gate the platform tier (DECISIONS-LOG "Headlamp gating stall").
  • Namespace: homepage — a PLATFORM namespace (no app-namespace=true), so the Kyverno app-scoped policies (default-deny gen, auth-mode, egress-class) do not apply; we ship an explicit default-deny + Cilium ingress policy as defense-in-depth.

What this component owns

Plain manifests (not a HelmRelease — the app is one stateless Deployment + a config ConfigMap):

File Purpose
namespace.yaml homepage ns, gateway-access=shared
configmap.yaml Homepage config (settings/services/bookmarks/widgets + empty docker/kubernetes + custom.css/js)
deployment.yaml ghcr.io/gethomepage/homepage (non-root, subPath config mounts, HOMEPAGE_ALLOWED_HOSTS)
service.yaml ClusterIP 80 -> 3000
networkpolicy.yaml default-deny (DNS-only egress) + Cilium Gateway-only ingress on 3000
httproute.yaml apex ingress on the Gateway https-apex listener

Apex nuances (why this isn't just another *.home route)

  1. TLS: the apex home.keithjackson.dev is already a SAN on the wildcard Certificate (platform/gateway/wildcard-certificate.yaml) — no new cert.
  2. Gateway listener: the wildcard listeners' hostname *.home.keithjackson.dev does not match the bare apex. This component's route binds a dedicated https-apex listener added to the shared Gateway (platform/gateway/gateway.yaml); http-apex-redirect covers :80.
  3. HOMEPAGE_ALLOWED_HOSTS: Homepage >= 1.0 returns 403 for any Host not in this list — set to home.keithjackson.dev + the pod IP (for kubelet probes).

Forward-auth — FOLLOW-UP (not yet enforced)

The HTTPRoute is annotated auth-mode: forward-auth (intended posture: oauth2-proxy -> Keycloak). The Gateway ext-authz attachment that actually enforces forward-auth does not exist yet — it is a Phase-6 WS-NET TODO (platform/oauth2-proxy is the authenticator; there is no CiliumEnvoyConfig / ext-authz wiring anywhere in the repo). Until that mechanism lands, Homepage — like every other forward-auth app (jackett, qbittorrent, pihole, radarr, sonarr) — is reachable unauthenticated. When the Gateway ext-authz pattern is built, attach it to this route (and the others) with allowed-groups: platform-admins. PM decision flagged in the deploy report.

Deliberately deferred (avoid scope creep)

  • Service-status widgets (Grafana/Plex/Sonarr/… API tiles) need per-service API-key secrets — not wired; tiles are simple links. Add as SOPS secrets + widgets.yaml later.
  • Kubernetes widget would need a read-only ServiceAccount + RBAC — skipped (kubernetes.yaml empty, automountServiceAccountToken: false).
  • Brand icons (selfh.st/dashboard-icons) need world:443 egress — we use bundled mdi-* icons so egress stays DNS-only.