#!/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= \ # FDROID_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."