9530de722d
gates / gates (push) Failing after 8s
Resend sits behind Cloudflare, which blocks the default 'Python-urllib/3.x' agent with its own 403 (error 1010). That failure looks exactly like an auth failure and is not one, so the reason is recorded next to the header. Verified from the runner image with a deliberately invalid payload: with the agent set, Resend answers 422 missing-field, i.e. the request now reaches the API.
98 lines
5.1 KiB
YAML
98 lines
5.1 KiB
YAML
# 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 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
|
|
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. Probe P3 proved the two are equal when done this way.
|
|
git init -q .
|
|
git remote add origin http://gitea.gitea-system.svc.cluster.local:3000/admin/felhom.eu.git
|
|
git fetch -q --depth 1 origin "$GITHUB_SHA"
|
|
git checkout -q FETCH_HEAD
|
|
echo "checked out $(git rev-parse HEAD)"
|
|
|
|
- name: Run the gate entry point
|
|
# The ONLY thing CI runs. No go build, no go test, no linting, no deploy — those are either
|
|
# already reliably run by a person or none of CI's business. The exit code IS the result:
|
|
# no `|| true`, no pipe that could swallow it.
|
|
run: python3 scripts/repo_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 <monitoring@felhom.eu>",
|
|
"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
|