diff --git a/.gitea/workflows/gates.yml b/.gitea/workflows/gates.yml new file mode 100644 index 0000000..95731f0 --- /dev/null +++ b/.gitea/workflows/gates.yml @@ -0,0 +1,104 @@ +# gates — re-run this repo's gate entry point on every push, on a machine that does not care who +# pushed or what they typed. +# +# *** THIS REPORTS. IT CANNOT REFUSE. *** +# +# felhom repos push straight to `main` with no pull request, so there is no merge for a status +# check to stand at. The refusing half is `.githooks/pre-push`, which is local to a clone and which +# `git push --no-verify` skips; this half is what notices when that happened. Neither half is the +# whole thing, and both are named in felhom.eu documentation/backlog/OPEN-ITEMS.md R-168. +# +# NO `uses:` STEP ANYWHERE, deliberately: JavaScript actions need a node runtime in the runner, and +# the runner is a host-mode container with python3 and git and nothing else (see +# homelab-manifests/gitea-system/act-runner.yaml for why it is not privileged). Probe P3 measured +# that a plain `git fetch` of the pushed SHA from the in-cluster Gitea service is enough. +# +# A failing run must reach a person — a detector nobody hears is the defect R-29 filed, rebuilt one +# layer up. That is the last step, and it runs ONLY on failure. +name: gates +on: [push] + +jobs: + gates: + runs-on: felhom-gates + steps: + - name: Fetch the pushed commit and the sibling clone it needs + # This repo's entry point invokes a SHARED checker that lives in the felhom.eu clone next + # door and is deliberately never copied here — so CI has to reproduce the workspace's + # sibling layout or the gate fails closed with "gate is MISSING". The sibling is also + # needed for CONTENT: this repo's REUSE.md cites a path that lives in the hub. + run: | + # Shallow, and pinned to the exact SHA that was pushed — not to the branch tip, + # which can move under us if two pushes race. + mkdir -p ws/felhom-controller + cd ws/felhom-controller + git init -q . + git remote add origin http://gitea.gitea-system.svc.cluster.local:3000/admin/felhom-controller.git + git fetch -q --depth 1 origin "$GITHUB_SHA" + git checkout -q FETCH_HEAD + echo "checked out $(git rev-parse HEAD)" + cd .. && git clone -q --depth 1 http://gitea.gitea-system.svc.cluster.local:3000/admin/felhom.eu.git felhom.eu + echo "sibling felhom.eu present at $(cd felhom.eu && git rev-parse --short HEAD)" + + - name: Run the gate entry point + # The ONLY thing CI runs. No go build, no go test, no linting, no deploy. The + # exit code IS the result: no `|| true`, no pipe that could swallow it. + run: cd ws/felhom-controller/controller && python3 scripts/controller_gates.py --fast + + - name: Alarm on failure + # THE POINT OF THE WHOLE THING. Probe P5 measured that a failed run produces NO mail, NO + # notification row and NO log line from Gitea itself — a red tick in a web UI nobody watches + # is exactly the shape R-29 filed against. So the run sends its own alarm, on the project's + # existing transactional path (Resend, the same one the hub uses), and prints the provider's + # accepted id so "a message left the machine" is an observable, not an assumption. + # + # Pure python3 and urllib, NOT curl: the runner image carries python3 and git and nothing + # else on purpose, and the first version of this step died on `curl: command not found`. + # Reaching for a bigger image to send one HTTP request would have been the wrong trade. + if: failure() + env: + RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} + run: | + python3 - <<'PY' + import json, os, sys, urllib.request, urllib.error + + key = os.environ.get("RESEND_API_KEY", "") + if not key: + sys.exit("ALARM FAILED: RESEND_API_KEY is empty — the alarm cannot be sent, and a " + "silent alarm is worse than none. Set the user-level Actions secret.") + + repo = os.environ.get("GITHUB_REPOSITORY", "?") + sha = os.environ.get("GITHUB_SHA", "?") + run = os.environ.get("GITHUB_RUN_NUMBER", "?") + srv = os.environ.get("GITHUB_SERVER_URL", "https://gitea.dooplex.hu") + + body = json.dumps({ + "from": "Felhom CI ", + "to": ["admin@felhom.eu"], + "subject": "[felhom CI] gates FAILED in %s" % repo, + "text": ( + "The gate entry point exited non-zero.\n\n" + "Repository : %s\n" + "Commit : %s\n" + "Run : %s/%s/actions/runs/%s\n\n" + "The failing gate names itself in the run log.\n\n" + "If the local pre-push hook was GREEN for this commit, then CI and the hook\n" + "disagree - that is a finding about the gates themselves, not about CI, and it\n" + "outranks whatever the push was for.\n" + ) % (repo, sha, srv, repo, run), + }).encode() + + req = urllib.request.Request( + "https://api.resend.com/emails", data=body, method="POST", + headers={"Authorization": "Bearer %s" % key, + "Content-Type": "application/json", + # Cloudflare fronts api.resend.com and BLOCKS the default + # "Python-urllib/3.x" agent with its own 403 (error 1010) — which looks + # exactly like an auth failure and is not one. Measured 2026-08-02. + "User-Agent": "felhom-ci/1.0"}) + try: + with urllib.request.urlopen(req, timeout=30) as r: + print("RESEND-ACCEPTED id=%s" % json.load(r)["id"]) + except urllib.error.HTTPError as e: + sys.exit("ALARM FAILED: Resend returned HTTP %s: %s" % (e.code, e.read().decode()[:300])) + PY