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>
73 lines
2.9 KiB
Bash
Executable file
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."
|