Files
jakman85andClaude Opus 5 9b8979a427 feat(prod): temp Gitea source + wireless disabled + flux bootstrap path
Autonomous prod bring-up, operator away. Three changes that together clear the last
blockers before `flux bootstrap`.

=== 1. TEMPORARY BOOTSTRAP GITEA — the last hard blocker, cleared ===
clusters/prod's source URL could not work: Gitea runs ON the dev cluster (the one slated for
decommission) and split-horizon DNS resolves gitea.home.keithjackson.dev to PROD's OWN
gateway. Stood up the existing bootstrap/gitea/bootstrap-gitea.sh on the operator's
workstation with HOST_NAT_IP=192.168.2.132; all three prod nodes verified reachable
(Gitea 1.22.6 via the API), no firewall change needed — prod is on the same /24, no NAT.

clusters/prod now points at http://192.168.2.132:3000/... under a boxed TEMPORARY banner
with a 5-step cutover checklist ending 'delete this whole block — dev's stanza is what done
looks like'. Repo verified PUBLIC with anonymous read two independent ways (API
"private":false; git ls-remote unauthenticated with GIT_TERMINAL_PROMPT=0), so NO secretRef
is needed — documented so a future auth failure is not misread as a network fault.

bootstrap-gitea.sh's header corrected: it documented HOST_NAT_IP=10.73.0.1, the RETIRED
libvirt NAT, and forbade agent use; the operator has now directed this for prod.

=== 2. WIRELESS DISABLED ON PROD (operator-reported) ===
k3s-prod-3 was on ethernet AND wireless, and the wifi DHCP client was overwriting
/etc/resolv.conf. Root cause of the silence: node_baseline's DNS `contended` detection
checked resolvconf and NetworkManager but NOT dhcpcd, so the role wrote resolvers, reported
success, and dhcpcd reverted them on lease renewal — the exact 'green run that fixed
nothing' the contended branch exists to prevent.

Now IaC, not a one-off: survey wireless hardware, **REFUSE to disable it if the path to the
node runs over the wireless interface** (the guard that meant an agent dying mid-apply cost
nothing), blacklist the driver so it never reloads, stop dhcpcd only where it holds a
wireless interface and nothing else, take links down and unload modules with NO reboot, then
report the resulting state. dhcpcd is now in the contended set without weakening fail-closed.

Applied and verified: node 3's wlp1s0 is GONE from /sys/class/net, dhcpcd inactive and its
unit not-found, ONE default route per node, all three on 8.8.8.8/1.1.1.1 resolving public
names. Safe and idempotent on nodes 1 and 2, whose wifi hardware was already down.

=== 3. flux_bootstrap PATH PARAMETERISED ===
bootstrap/flux/bootstrap.sh already read FLUX_ENV_PATH but the role never exported it, so it
ALWAYS bootstrapped clusters/dev — prod was impossible. Two behaviour-bearing lines: role
default clusters/dev + `environment: FLUX_ENV_PATH`, overridden to clusters/prod in
group_vars/k3s_prod. Proven per host with a read-only Ansible probe: prod hosts resolve
clusters/prod, localhost/hypervisor resolves clusters/dev. Only two definitions repo-wide.

DEV PROVEN BYTE-IDENTICAL for the source change: all 12 substituting Flux paths rendered for
both trees, 27/27 SHA-256 artefacts identical, with TWO POSITIVE CONTROLS (an overlay-level
canary and a deep-path content canary) confirming the harness detects change — and both
canaries proven restored. Honest scope note recorded: kustomize strips comments, so the
harness proves the built stream, not source comments.

L0 substitution check: 106 assertions, 0 hazards. ci/l0.sh and ci/lint.sh exit 0.

=== STILL BLOCKING A FULL PROD RECONCILE (operator decisions) ===
- S3_BACKUP_ENDPOINT remains TBD-PROD-*: prod CANNOT reach dev's versitygw at
  192.168.1.9:9000 (verified UNREACHABLE from all three — the pfSense boundary), and a prod
  backup target is a deferred operator decision. Deliberately NOT invented: a DR target on a
  workstation container would create false confidence. Velero/CNPG backups will fail loudly.
- Prod's public resolvers mean the API server's oidc-issuer-url resolves PUBLICLY, so kubectl
  SSO will not work on prod until split-horizon is solved for it (§4).
- The temp Gitea is a single container on the operator's workstation — a single point of
  failure for prod reconciliation, acceptable for bring-up only.

Serves: R-GITOPS-01, R-DNS-01, R-DNS-03, R-ENV-02, R-ENV-03. Task: T-7.1.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-04 18:53:17 -05:00
..

bootstrap/gitea — Phase-1 host Gitea (GitOps bootstrap source)

Solves the self-hosted-git chicken-and-egg for Phase 1: the platform's Gitea will eventually run in the cluster (Phase 6), but the cluster needs a git source to bootstrap from. So during bring-up we run a single Gitea container on the host, outside the cluster, and Flux pulls this repo from it.

How it fits

  • bootstrap-gitea.sh starts gitea/gitea on the host with a persistent data dir, HTTP on :3000, reachable from the cluster VMs on the k3s-dev NAT gateway 10.73.0.1.
  • The repo is pushed to it as a public repo (Home-Network/home-network-k3s-impl) — public so Flux needs no credentials; safe because this Gitea is NAT-only, not internet-exposed.
  • Flux's flux-system GitRepository is wired to http://10.73.0.1:3000/Home-Network/home-network-k3s-impl.git (inlined in clusters/dev/kustomization.yaml). bootstrap/flux/bootstrap.sh then apply -k clusters/dev and Flux reconciles from here.

Bring-up order (operator, at execution time — not now)

  1. ./bootstrap/gitea/bootstrap-gitea.sh → create admin + public repo → git push bootstrap main.
  2. tofu apply (infra/tofu) — VMs + k3s-dev net.
  3. Ansible site.yml — k3s HA + Cilium.
  4. bootstrap/sops/age-keygen.sh → install-age-key.sh → bootstrap/flux/bootstrap.sh.
  5. Flux reconciles clusters/dev from the host Gitea.

Phase 6 cutover

Once the in-cluster Gitea is onboarded (gitea.home.keithjackson.dev), push the repo there, repoint the flux-system GitRepository URL, and keep an off-cluster mirror (docs/05 "Circularity & DR"). The host Gitea can then be retired.

Notes

  • Pinned gitea/gitea:1.22 (override GITEA_IMAGE); data at ~/.local/share/home-k3s-bootstrap-gitea (override GITEA_DATA_DIR).
  • No secrets here: admin credentials are created interactively at bring-up, never committed.