Files
home-network-k3s-impl/infra
jakman85andClaude Opus 5 e6f6e4febb
gate / gate (pull_request) Successful in 1m34s
gate / conformance (pull_request) Has been skipped
feat(ci): detect Gitea settings drift -- the blind spot that hid a month of outage
Every check in this repo reads the repository, the rendered manifests, or the cluster.
NONE could see Gitea's own database, and that is where branch protection and runner
registration live. Three failures came out of that blind spot on 2026-09-29, all the same
shape -- a setting that READ as enforced while not being, with nothing able to notice:

  * main had NO branch protection for ~a month while DECISIONS-LOG recorded it as a
    required status check;
  * ZERO Actions runners were registered, so PR #1 -- the first PR this repo ever had --
    sat pending for 30.6 minutes with nothing to execute it;
  * after enable_status_check and required_approvals were turned on they STILL did not
    bite, because block_admin_merge_override was false: PRs #5-#9 merged with zero
    approvals recorded against required_approvals: 1.

infra/gitea/*.sh ASSERT the desired state. Nothing DETECTED drift from it. This is the
detector.

ci/gitea-settings.sh runs as a merge-gate stage: it diffs the live rule at
/branch_protections/main against infra/gitea/expected-branch-protection.json and asserts at
least one runner is online. It reads the RULE object, not /branches/main -- the branch view
does not expose block_admin_merge_override and showed all-green while the rule was
overridable by the only account that merges.

Verified by test, not assumed:
  no token            -> exit 3, skips loudly, does NOT block
  live == declaration -> exit 0
  injected drift      -> exit 1, blocks, naming both the drifted key and the runner count

The skip is DELIBERATELY not in gate.sh's strict-skips loop. Making an absent credential
block every PR is precisely the lock-yourself-out failure this repo keeps walking into, so
a skip denies a PASS (exit 3) and says loudly what is unverified. REQUIRE_GITEA_SETTINGS=true
upgrades it to a block once the token is wired.

gate.yml's `|| [ "$?" -eq 3 ]` tolerance now covers a second stage. That widening is
declared in the file rather than left to be discovered, and it narrows back the moment
REQUIRE_GITEA_SETTINGS is set.

infra/gitea/set-ci-token.sh mints the token and stores it as the repo Actions secret without
ever printing the value.

UNTIL THAT TOKEN EXISTS THIS STAGE SKIPS, AND A SKIPPED DETECTOR DETECTS NOTHING.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-29 02:52:28 -05:00
..

infra/ — node provisioning and the toolchain

OpenTofu (Proxmox VE) + cloud-init + Ansible bring up the 3-node k3s/Cilium dev cluster. Spec: ../home-network-k3s/docs/02-environments-and-provisioning.md.

Path What
tofu/ The "Create" stage: 3 cloud-init'd VMs on the Proxmox box (bpg/proxmox provider)
cloud-init/ First-boot baseline shipped as a Proxmox user-data snippet
ansible/ The "Baseline"/"Configure" stages: k3s HA, Cilium, kube-vip, the VPN egress gateway
devcontainer/ The pinned toolchain (tofu, ansible, sops, age, flux, kubectl, helm and the whole CI gate toolchain)
cluster.sh RETIRED — drove the old libvirt VMs; does not work against Proxmox
PHASE1-STATUS.md Current state of the cluster and how to reconnect to it
PHASE1-SCOPE.md Historical Phase-1 scoping doc (libvirt era — superseded)

Where the cluster actually lives

The dev cluster is not on this workstation. It runs on a Proxmox VE host; the nodes sit on the Proxmox LAN and are reached across a pfSense NAT:

Nodes k3s-dev-1/2/3 at 192.168.1.50/.51/.52 — 6 vCPU, 24 GiB, Debian 13, k3s v1.36.4
kube-API kube-vip VIP 192.168.1.49:6443, reached from the workstation via NAT 192.168.2.50:6443
Node SSH pfSense NAT 192.168.2.102:2250/2251/2252 → node :22 (Ansible only)
Ingress VIP 192.168.1.53 (Cilium L2, shared Gateway); .53–.60 reserved for LB VIPs

The earlier libvirt/KVM cluster on 10.73.0.0/24 is retired. Any doc or command referencing virsh, qemu:///system, or 10.73.0.x for the cluster is historical.