The drift detector merged in #11 was inert and would have stayed inert silently. WHAT HAPPENED. infra/gitea/set-ci-token.sh minted a token and PUT it to the repo Actions secret GITEA_SETTINGS_TOKEN. Gitea RESERVES the `GITEA_` prefix for secret names -- exactly as GitHub reserves `GITHUB_` -- and rejects that PUT with: HTTP 400 {"message":"invalid secret name"} The script sent the PUT to /dev/null and printed "==> stored." unconditionally, and `curl -sS` exits 0 on an HTTP error, so it reported success for a store that never happened. Measured afterwards: the token existed in the user's token list, and the repo's secret list was EMPTY. ci/gitea-settings.sh would have skipped on every CI run forever, and with REQUIRE_GITEA_SETTINGS=true it would instead have BLOCKED EVERY PR -- including the one fixing it. Three changes: * the secret is SETTINGS_API_TOKEN; ci/gitea-settings.sh reads it, keeping GITEA_SETTINGS_TOKEN as a local-run fallback; * set-ci-token.sh checks the PUT's HTTP status AND reads the secret back, refusing to claim success otherwise. That missing verification is the actual defect -- the wrong name was just what it hid; * gate.yml maps the secret and ENABLES REQUIRE_GITEA_SETTINGS. WHY ENABLING IT HERE IS SAFE: this PR's own CI run is the proof. If the stage were still skipping, REQUIRE turns that into a failure and this PR goes RED rather than merging. Green means the token resolved and live Gitea matched infra/gitea/expected-branch-protection.json. Verified locally before pushing: REQUIRE=true, token present -> MERGE GATE: PASS REQUIRE=true, no token -> gitea-settings SKIPPED -> BLOCKED (not a silent skip) The secret is already stored and read back as present, so CI has what it needs. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
57 lines
3.1 KiB
Bash
Executable File
57 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Mint a Gitea API token and store it as the repo Actions secret GITEA_SETTINGS_TOKEN,
|
|
# so ci/gitea-settings.sh can read the live branch-protection rule during the gate.
|
|
#
|
|
# The token value is NEVER printed. It goes from the token API into the secrets API and
|
|
# nowhere else -- not to stdout, not to a file, not into an agent transcript.
|
|
#
|
|
# Scope: `read:repository` + `read:admin` are what the check needs (the rule object, and the
|
|
# runner list). Gitea token scopes are coarse; grant the least that works on your version and
|
|
# confirm the check goes green rather than assuming the scope name.
|
|
set -euo pipefail
|
|
HOST=gitea.home.keithjackson.dev; OWNER=Home-Network; REPO=home-network-k3s-impl
|
|
# NOT GITEA_* — Gitea reserves that prefix for secret names and returns HTTP 400
|
|
# "invalid secret name". The first version of this script used GITEA_SETTINGS_TOKEN, sent the
|
|
# PUT to /dev/null, and printed "stored" regardless; the secret never existed and nobody knew.
|
|
SECRET=SETTINGS_API_TOKEN
|
|
NAME="ci-gitea-settings-$(date +%Y%m%d%H%M%S)"
|
|
creds="$(printf 'protocol=https\nhost=%s\n\n' "$HOST" | git credential fill)"
|
|
U="$(sed -n 's/^username=//p' <<<"$creds")"; P="$(sed -n 's/^password=//p' <<<"$creds")"
|
|
basic() { curl -sS --config <(printf 'user = "%s:%s"\n' "$U" "$P") "$@"; }
|
|
|
|
echo "==> minting token '${NAME}' as ${U}"
|
|
TOKEN="$(basic -X POST -H 'Content-Type: application/json' \
|
|
-d "{\"name\":\"${NAME}\",\"scopes\":[\"read:repository\",\"read:admin\"]}" \
|
|
"https://${HOST}/api/v1/users/${U}/tokens" | jq -r '.sha1 // empty')"
|
|
[ -n "$TOKEN" ] || { echo "token creation failed (check scopes for your Gitea version)" >&2; exit 1; }
|
|
echo "==> got a ${#TOKEN}-character token (value not shown)"
|
|
|
|
echo "==> storing it as repo Actions secret ${SECRET}"
|
|
code="$(basic -o /tmp/set-ci-token.out -w '%{http_code}' -X PUT -H 'Content-Type: application/json' \
|
|
-d "$(jq -n --arg d "$TOKEN" '{data:$d}')" \
|
|
"https://${HOST}/api/v1/repos/${OWNER}/${REPO}/actions/secrets/${SECRET}")"
|
|
unset TOKEN
|
|
if [ "$code" != 201 ] && [ "$code" != 204 ]; then
|
|
echo " PUT failed: HTTP ${code} -- $(head -c 200 /tmp/set-ci-token.out)" >&2
|
|
rm -f /tmp/set-ci-token.out; exit 1
|
|
fi
|
|
rm -f /tmp/set-ci-token.out
|
|
|
|
# VERIFY BY READING IT BACK. curl -sS exits 0 on an HTTP error, so without this the script
|
|
# reports success for a store that never happened -- which is exactly what it did.
|
|
names="$(basic "https://${HOST}/api/v1/repos/${OWNER}/${REPO}/actions/secrets" \
|
|
| jq -rc '[.. | objects | select(has("name")) | .name] | unique')"
|
|
grep -q "$SECRET" <<<"$names" \
|
|
|| { echo " stored HTTP ${code} but ${SECRET} is NOT in ${names} -- refusing to claim success" >&2; exit 1; }
|
|
echo "==> stored and VERIFIED present: ${names}"
|
|
|
|
cat <<'NOTE'
|
|
|
|
Next:
|
|
1. .gitea/workflows/gate.yml already passes this secret to the gate step.
|
|
2. Open any PR and confirm the gate prints "gitea-settings: green" -- it SKIPS silently
|
|
without the token, so a green run is the only proof the wiring works.
|
|
3. THEN set REQUIRE_GITEA_SETTINGS=true in gate.yml, so a future missing token BLOCKS
|
|
instead of skipping. Do that only after you have seen it green, or you lock the repo.
|
|
NOTE
|