provisioning: stop deleting the pre-rename hook and subscription

Retiring gitea-events is a one-off migration, not something worth re-running
on every boot. Both units now only touch what they own: jupiter's creates or
updates its own hook and deletes nothing, and mars's removes only the route
it is about to re-subscribe, as the idempotency step for `subscribe`.

Keeping the deletes would have meant a redeploy could silently remove a hook
or route someone added deliberately -- a real risk now that sibling hooks
for other Hermes routes are the intended pattern.

README carries the manual commands, and the note that both hooks fire until
the old one is removed by hand, so events arrive twice in the meantime.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S94o42aQ8VkBmEWvDem5xa
This commit is contained in:
2026-08-23 06:57:58 +02:00
co-authored by Claude Opus 5
parent f982c6dc14
commit 3567591ecf
3 changed files with 31 additions and 18 deletions
+22
View File
@@ -53,6 +53,28 @@ is not tied to any one subscription: another Hermes route needs a
`/gitea/<name>`, and no relay change. Route names are validated against a `/gitea/<name>`, and no relay change. Route names are validated against a
strict charset before being used in the outbound URL. strict charset before being used in the outbound URL.
Neither provisioning unit deletes anything: Jupiter's only creates or updates
its own hook, and Mars's only removes the route it is about to re-subscribe.
Retiring the pre-rename `gitea-events` route is therefore a one-off, done by
hand after the first deploy of both hosts:
```
# on mars — drop the old subscription
sudo podman exec hermes-agent hermes webhook remove gitea-events
# on jupiter — delete the old hook (it posts to the relay's bare /gitea path)
api=http://127.0.0.1:3000/api/v1; repo=darman/homelab
tok="$(sudo cat /run/secrets/gitea_provisioning_token)"
old="$(curl -fsS -H "Authorization: token $tok" "$api/repos/$repo/hooks" \
| jq -r '.[] | select(.config.url == "http://mars.orbit.sol:8645/gitea") | .id')"
for id in $old; do
curl -fsS -H "Authorization: token $tok" -X DELETE "$api/repos/$repo/hooks/$id"
done
```
Check `hermes webhook list` and the repo's webhook page afterwards; until the
old hook is gone both it and the new one fire, so events arrive twice.
That one header copy is the entire reason the relay exists. Gitea signs every That one header copy is the entire reason the relay exists. Gitea signs every
webhook with `X-Hub-Signature-256` in GitHub's exact format, which Hermes webhook with `X-Hub-Signature-256` in GitHub's exact format, which Hermes
already accepts on any route — so authentication would work pointing Gitea already accepts on any route — so authentication would work pointing Gitea
+4 -5
View File
@@ -138,11 +138,10 @@ in
sleep 1 sleep 1
done done
# gitea-events is the old name of this route (renamed to say what it # Idempotency for the subscribe below, not cleanup: this removes only
# actually handles); removing it keeps a redeployed host from serving # the route this unit owns. The pre-rename gitea-events subscription is
# both. The second remove is the idempotency step for the subscribe # left alone retiring it is a one-off migration done by hand, so that
# below, not cleanup. # a redeploy never silently deletes a route someone added on purpose.
podman exec hermes-agent hermes webhook remove gitea-events >/dev/null 2>&1 || true
podman exec hermes-agent hermes webhook remove gitea-pr-comments >/dev/null 2>&1 || true podman exec hermes-agent hermes webhook remove gitea-pr-comments >/dev/null 2>&1 || true
# `set -eu` inside the container shell is load-bearing: without it a # `set -eu` inside the container shell is load-bearing: without it a
# missing prompt file makes `cat` fail, the command substitution yields # missing prompt file makes `cat` fail, the command substitution yields
+5 -13
View File
@@ -338,11 +338,10 @@ in
relay="http://mars.orbit.sol:8645" relay="http://mars.orbit.sol:8645"
target="$relay/gitea/gitea-pr-comments" target="$relay/gitea/gitea-pr-comments"
# Retire the pre-rename hook, which posted to the relay's bare path and # This unit only ever creates or updates $target. It deliberately does
# would now double-deliver alongside $target. Matched by its EXACT old # NOT delete anything, including the pre-rename hook on the relay's bare
# URL, deliberately: anything else pointing at $relay is a hook for a # path that is a one-off migration, done by hand, not a thing this
# different Hermes route and must survive. # runs on every boot. See the README for the command.
legacy_target="$relay/gitea"
# Same readiness gate as gitea-ci-bot-provision / gitea-luna-provision # Same readiness gate as gitea-ci-bot-provision / gitea-luna-provision
# above: After=gitea.service only means the process started, not that it # above: After=gitea.service only means the process started, not that it
@@ -361,14 +360,7 @@ in
--argjson events '${builtins.toJSON giteaWebhookEvents}' \ --argjson events '${builtins.toJSON giteaWebhookEvents}' \
'{type: "gitea", config: {content_type: "json", url: $url, secret: $secret}, events: $events, active: true}')" '{type: "gitea", config: {content_type: "json", url: $url, secret: $secret}, events: $events, active: true}')"
hooks="$(curl -fsS "''${auth[@]}" "$api/repos/darman/homelab/hooks")" hook_id="$(curl -fsS "''${auth[@]}" "$api/repos/darman/homelab/hooks" \
for stale in $(printf '%s' "$hooks" \
| jq -r --arg url "$legacy_target" '.[] | select(.type == "gitea" and .config.url == $url) | .id'); do
curl -fsS "''${auth[@]}" -X DELETE "$api/repos/darman/homelab/hooks/$stale" >/dev/null
done
hook_id="$(printf '%s' "$hooks" \
| jq -r --arg url "$target" 'first(.[] | select(.type == "gitea" and .config.url == $url)) | .id // empty')" | jq -r --arg url "$target" 'first(.[] | select(.type == "gitea" and .config.url == $url)) | .id // empty')"
if [ -n "$hook_id" ]; then if [ -n "$hook_id" ]; then
printf '%s' "$body" | curl -fsS "''${auth[@]}" -H 'Content-Type: application/json' \ printf '%s' "$body" | curl -fsS "''${auth[@]}" -H 'Content-Type: application/json' \