Files
home-network-k3s-impl/infra/gitea
jakman85andClaude Opus 5 fe862ab527
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled
gate / gate (pull_request) Successful in 1m32s
gate / conformance (pull_request) Has been skipped
infra(gitea): recover the Actions runner before anything requires the gate
No runner is registered at repo, org or site level, so gate.yml has never executed.
PR #1 sat pending for 30.6 minutes across 122 polls and never reported. Requiring the
gate as a status check in that state would lock the repo permanently.

Adds runner-token-rotate.sh: mints a fresh instance-level registration token from the
Gitea API and writes it into the SOPS secret via sops set, without ever printing the
value.

Records the likely root cause, which a new token alone does NOT fix. act_runner
registers once and persists its identity in /data/.runner on the act-runner-data PVC.
If Gitea no longer holds a matching runner row, the pod starts, sees .runner, SKIPS
registration entirely, and polls with an identity Gitea does not recognise. Symptom:
zero runners, pod running, nothing obviously broken. Clearing the PVC is what forces a
genuine re-registration.

The README now sequences runner-first, enforcement-second, and requires CONFIRMING the
runner reports online rather than assuming it. That assumption is the whole defect:
DECISIONS-LOG recorded the runner as online on 2026-08-31 and nobody checked since.

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

infra/gitea — platform settings that are NOT GitOps-managed

Gitea's own repo settings (branch protection, required status checks, approvals) live in Gitea's database, not in a manifest Flux reconciles. They are therefore drift-prone and invisible to the gate — exactly how the state below went unnoticed.

The gap this closes

Verified against the live API on 2026-09-29, against a DECISIONS-LOG.md entry (2026-08-31) asserting the opposite:

Claimed Measured
main is a protected branch, gate is a required status check branch_protections → []; protected:false, enable_status_check:false, required_approvals:0, user_can_push:true
Runner incluster-dind online zero runners registered at repo, org and site level

So main accepted direct pushes, and the gate could not run at all. CLAUDE.md is the rule; this directory is how the rule gets enforced by the machine.

branch-protection.sh

Blocks direct pushes to main. Run it, then run the push probe it prints — verify, do not trust the API response, since Gitea may let site admins bypass protection.

./infra/gitea/branch-protection.sh

It authenticates with the credential git already has stored for this host, and never prints the secret.

runner-token-rotate.sh

Mints a fresh instance-level registration token from the Gitea API and writes it into apps/wave-1/gitea-actions-runner-secret.sops.yaml via sops set. The token value is never printed.

./infra/gitea/runner-token-rotate.sh

A fresh token is usually not sufficient on its own. act_runner registers exactly once and persists its identity in /data/.runner on the act-runner-data PVC. If Gitea no longer holds a matching runner row, the pod starts, sees .runner, skips registration, and polls with an identity Gitea does not recognise — so it never appears in the runner list. That is precisely the observed symptom: zero runners, pod running, no error anyone looks at. Clearing the PVC is what forces a real re-registration; the script prints the commands.

Sequence — order matters

  1. Get an Actions runner registered — runner-token-rotate.sh, then clear the PVC so the runner actually re-registers, then confirm it shows online in the API. Do not skip the confirmation: the whole reason this went unnoticed is that a runner was recorded as online without anyone checking.
  2. Watch the gate actually report on a real PR, and note the context name it produced.
  3. Only then set enable_status_check: true with that exact context.
  4. Add the human reviewer as a repo collaborator, then raise required_approvals to 1.

Do not do step 3 before step 1. A required status check with no runner leaves every PR permanently pending — including the PR that would fix it. The same applies to gate.yml's conformance job: it is if: false and never runs, yet still publishes a gate / conformance (pull_request) context. Never list that one as required.