mirror of
https://github.com/brues-code/TwitchEmotes.git
synced 2026-10-03 13:00:00 +00:00
A Gitea pull mirror replicates commits and tags only, so the mirror's Releases page shows bare tags with nothing to download. ClassicAPI's mirror-release workflow pushes the notes and attachments over through Gitea's API; this is that workflow pointed at brues/TwitchEmotes. Manual only: workflow_call is dropped, so a release does not trigger it and nothing else calls it. Run it from the Actions UI with a tag, or with the tag blank to backfill every release. Needs its own MIRROR_TOKEN secret on this repo before the first run.
181 lines
7.1 KiB
YAML
181 lines
7.1 KiB
YAML
name: Mirror release
|
|
|
|
# Copy a GitHub release — its notes and the files attached to it — across to
|
|
# the Gitea mirror at https://octowow.st/git/brues/TwitchEmotes.
|
|
#
|
|
# A Gitea pull mirror replicates commits and tags only. Releases are database
|
|
# records with their own attachments, so they never cross: the mirror's
|
|
# Releases page shows bare tags with nothing to download. This workflow pushes
|
|
# them over through Gitea's API.
|
|
#
|
|
# ONE-TIME SETUP: on the mirror, go to Settings -> Applications -> Generate New
|
|
# Token, tick `repository` write, and copy the token. On GitHub, add it under
|
|
# Settings -> Secrets and variables -> Actions as `MIRROR_TOKEN`. The token
|
|
# needs push rights on brues/TwitchEmotes.
|
|
#
|
|
# Manual only — nothing calls it, and a release does not trigger it. Run it from
|
|
# the Actions UI with a `tag` to mirror one release, or with `tag` left blank to
|
|
# backfill every release that exists on GitHub.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Release tag to mirror (e.g. v2.0.0). Blank = every release.'
|
|
required: false
|
|
default: ''
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
mirror:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
# No checkout: the release files come from the GitHub release itself, so
|
|
# this reads the same bytes a user downloads rather than a rebuild.
|
|
- name: Push release to the mirror
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
GH_REPO: ${{ github.repository }}
|
|
MIRROR_TOKEN: ${{ secrets.MIRROR_TOKEN }}
|
|
TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ -z "${MIRROR_TOKEN:-}" ]; then
|
|
echo "::error::MIRROR_TOKEN is not set - see the setup note at the top of .github/workflows/mirror-release.yml"
|
|
exit 1
|
|
fi
|
|
|
|
API=https://octowow.st/git/api/v1
|
|
MIRROR=brues/TwitchEmotes
|
|
RESP=$(mktemp)
|
|
|
|
# Sends one API call. Prints the HTTP status; leaves the body in $RESP.
|
|
req() {
|
|
local method=$1 path=$2
|
|
shift 2
|
|
curl -sS -m 600 -o "$RESP" -w '%{http_code}' -X "$method" \
|
|
-H "Authorization: token $MIRROR_TOKEN" \
|
|
-H 'Accept: application/json' \
|
|
"$API/$path" "$@"
|
|
}
|
|
|
|
fail() {
|
|
echo "::error::$1"
|
|
echo "the mirror replied: $(head -c 2000 "$RESP")"
|
|
exit 1
|
|
}
|
|
|
|
# A release must hang off a tag that is already on the mirror. If the
|
|
# tag is missing the mirror has not pulled yet, so ask it to and wait.
|
|
# Attaching to a tag the mirror does not have would let its next sync
|
|
# turn our release back into a draft.
|
|
ensure_tag() {
|
|
local tag=$1 i
|
|
if [ "$(req GET "repos/$MIRROR/tags/$tag")" = 200 ]; then
|
|
return 0
|
|
fi
|
|
echo " tag $tag is not on the mirror yet - asking it to sync"
|
|
req POST "repos/$MIRROR/mirror-sync" > /dev/null || true
|
|
for i in $(seq 1 30); do
|
|
sleep 10
|
|
if [ "$(req GET "repos/$MIRROR/tags/$tag")" = 200 ]; then
|
|
echo " tag $tag arrived after $(( i * 10 ))s"
|
|
return 0
|
|
fi
|
|
done
|
|
fail "tag $tag never reached the mirror. Sync it, then run this workflow again."
|
|
}
|
|
|
|
mirror_one() {
|
|
local tag=$1 work code id f name size have_id have_size enc
|
|
work=$(mktemp -d)
|
|
echo "=== $tag ==="
|
|
|
|
gh release view "$tag" --json tagName,name,body,isDraft,isPrerelease > "$work/gh.json"
|
|
if [ "$(jq -r .isDraft "$work/gh.json")" = true ]; then
|
|
echo " still a draft on GitHub - skipped"
|
|
return 0
|
|
fi
|
|
|
|
ensure_tag "$tag"
|
|
|
|
jq '{tag_name: .tagName,
|
|
name: (.name // .tagName),
|
|
body: (.body // ""),
|
|
draft: false,
|
|
prerelease: .isPrerelease}' "$work/gh.json" > "$work/release.json"
|
|
|
|
# Gitea's tag sync leaves a bare tag record behind. Patching it
|
|
# promotes that record to a real release instead of colliding.
|
|
code=$(req GET "repos/$MIRROR/releases/tags/$tag")
|
|
if [ "$code" = 200 ]; then
|
|
id=$(jq -r .id "$RESP")
|
|
code=$(req PATCH "repos/$MIRROR/releases/$id" \
|
|
-H 'Content-Type: application/json' --data-binary @"$work/release.json")
|
|
if [ "$code" != 200 ]; then
|
|
fail "could not update release $tag on the mirror (HTTP $code)"
|
|
fi
|
|
echo " updated release #$id"
|
|
else
|
|
code=$(req POST "repos/$MIRROR/releases" \
|
|
-H 'Content-Type: application/json' --data-binary @"$work/release.json")
|
|
if [ "$code" != 201 ]; then
|
|
fail "could not create release $tag on the mirror (HTTP $code)"
|
|
fi
|
|
id=$(jq -r .id "$RESP")
|
|
echo " created release #$id"
|
|
fi
|
|
|
|
mkdir -p "$work/assets"
|
|
# Exits non-zero when the release carries no files, which is fine.
|
|
gh release download "$tag" --dir "$work/assets" --clobber || true
|
|
|
|
code=$(req GET "repos/$MIRROR/releases/$id/assets")
|
|
if [ "$code" != 200 ]; then
|
|
fail "could not list the mirror's attachments for $tag (HTTP $code)"
|
|
fi
|
|
cp "$RESP" "$work/have.json"
|
|
|
|
for f in "$work"/assets/*; do
|
|
[ -f "$f" ] || continue
|
|
name=$(basename "$f")
|
|
size=$(stat -c %s "$f")
|
|
have_id=$(jq -r --arg n "$name" 'map(select(.name == $n)) | .[0].id // ""' "$work/have.json")
|
|
have_size=$(jq -r --arg n "$name" 'map(select(.name == $n)) | .[0].size // ""' "$work/have.json")
|
|
|
|
# Same name and same size: already mirrored. A size mismatch means
|
|
# a re-cut release, so replace it - Gitea would otherwise keep both
|
|
# and serve the stale one from the older link.
|
|
if [ -n "$have_id" ] && [ "$have_size" = "$size" ]; then
|
|
echo " $name is already there"
|
|
continue
|
|
fi
|
|
if [ -n "$have_id" ]; then
|
|
req DELETE "repos/$MIRROR/releases/$id/assets/$have_id" > /dev/null
|
|
fi
|
|
|
|
enc=$(jq -rn --arg n "$name" '$n | @uri')
|
|
code=$(req POST "repos/$MIRROR/releases/$id/assets?name=$enc" -F "attachment=@$f")
|
|
if [ "$code" != 201 ]; then
|
|
fail "could not upload $name (HTTP $code)"
|
|
fi
|
|
echo " uploaded $name ($size bytes)"
|
|
done
|
|
}
|
|
|
|
if [ -n "${TAG:-}" ]; then
|
|
mirror_one "$TAG"
|
|
else
|
|
echo "no tag given - backfilling every release on GitHub"
|
|
gh release list --limit 200 --json tagName --jq '.[].tagName' > /tmp/tags.txt
|
|
# fd 3 keeps gh and curl from eating the tag list off stdin.
|
|
while read -r t <&3; do
|
|
if [ -n "$t" ]; then
|
|
mirror_one "$t"
|
|
fi
|
|
done 3< /tmp/tags.txt
|
|
fi
|