fdroid-repo/bin/register-webhooks.sh
randogoth 79e46aef1e Genericise the webhook script's usage comment
The header named the serving host's secret store and age key path. A usage
comment is the second thing someone opening this repo reads, and the script
does not need to know where the secret is kept — it reads it from the
environment either way. The concrete invocation lives with the server config.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-14 12:20:49 +03:00

73 lines
2.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# Point every app repo's release webhook at the F-Droid rebuild endpoint.
#
# Forgejo webhooks are instance state, not repo content, so they cannot be
# declared in a flake and have to be registered once per repo. This does that
# from the single source of truth — apps.nix — so adding an app means editing
# that file and re-running this, not remembering four web forms.
#
# Idempotent: a hook already pointing at the same URL is updated in place
# rather than duplicated, so re-running after a secret rotation is the
# supported way to roll the secret.
#
# The secret must be the same value the serving host reads from
# webhookSecretFile; a mismatch makes every delivery fail the HMAC check and
# the repo silently stops updating.
#
# Both are taken from the environment rather than arguments, so pipe them in
# from wherever they are kept — an argv is visible to every other process on
# the box, and a pasted secret stays in the shell history.
#
# Usage:
# FORGEJO_TOKEN=<token with repo:write on each app repo> \
# FDROID_WEBHOOK_SECRET=<the shared webhook secret> \
# register-webhooks.sh [base-url]
set -euo pipefail
cd "$(dirname "$0")/.."
: "${FORGEJO_TOKEN:?set FORGEJO_TOKEN to a Forgejo access token with repo write access}"
# Env, not an argument: an argv is visible to every other process on the box
# for as long as the script runs.
SECRET="${FDROID_WEBHOOK_SECRET:?set FDROID_WEBHOOK_SECRET to the shared webhook secret}"
BASE="${1:-https://apps.randogoth.com}"
FORGE="${FORGE:-https://code.randogoth.com}"
URL="$BASE/hooks/rebuild-fdroid"
api() {
local method=$1 path=$2
shift 2
curl -sS -X "$method" \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE/api/v1$path" "$@"
}
for repo in $(nix eval --json --file ./apps.nix | jq -r '.[].repo'); do
# Forgejo returns the full hook list; match on the target URL because the
# hook id is assigned by the server and is not knowable in advance.
existing=$(api GET "/repos/$repo/hooks" | jq -r --arg u "$URL" \
'.[] | select(.config.url == $u) | .id' | head -1)
payload=$(jq -n --arg url "$URL" --arg secret "$SECRET" '{
type: "gitea",
active: true,
# "release" alone: a tag push fires before CI has built anything, so a
# push-triggered rebuild would always find the old release and do nothing.
events: ["release"],
config: { url: $url, content_type: "json", secret: $secret }
}')
if [ -n "$existing" ]; then
api PATCH "/repos/$repo/hooks/$existing" -d "$payload" >/dev/null
echo "updated $repo (hook $existing) -> $URL"
else
api POST "/repos/$repo/hooks" -d "$payload" >/dev/null
echo "created $repo -> $URL"
fi
done
echo
echo "Note: the release webhook fires when the release object is created,"
echo "which in these workflows is one API call before the APK is attached."
echo "fdroid-sync waits for the upload to land, so that race is handled."