ci: mirror in-app updater to Cloudflare R2 with release-gated sync

- Move the R2 sync out of release.yml into a standalone sync-r2.yml
  triggered on release promotion (release: released) or manual dispatch,
  so the mirror follows the same gate as GitHub's /releases/latest
- Hard-fail on the official repo when R2 secrets are missing (a silently
  stale mirror strands updater users); forks may still skip
- Only the tag that is currently releases/latest may rewrite the root
  manifests or prune old versions; backfills of older tags restore
  versioned files only
- Resolve releases/latest with retries and fail instead of guessing on
  API errors; re-verify right before publishing the root manifests
- Add scripts/rewrite-updater-manifest.mjs to point latest.json download
  URLs at the mirror (minisign signatures cover file contents and stay
  valid); upload the macOS .tar.gz updater payload alongside installers
- Put https://dl.ccswitch.io/latest.json first in the updater endpoints
  with GitHub as connectivity fallback
This commit is contained in:
Jason
2026-07-27 18:41:49 +08:00
parent 708b38791c
commit 2b2f2cfad9
4 changed files with 268 additions and 90 deletions
-90
View File
@@ -737,93 +737,3 @@ jobs:
run: |
set -euxo pipefail
gh release upload "$GITHUB_REF_NAME" latest.json --clobber --repo "$GITHUB_REPOSITORY"
# Mirror the release assets to Cloudflare R2 so ccswitch.io/download can
# serve them directly (dl.ccswitch.io). Skips itself when the R2 secrets
# are not configured (e.g. on forks). Required secrets:
# R2_ACCOUNT_ID / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY
sync-to-r2:
name: Sync release to R2
runs-on: ubuntu-22.04
needs: assemble-latest-json
permissions:
contents: read
env:
R2_BUCKET: cc-switch-releases
R2_PUBLIC_BASE_URL: https://dl.ccswitch.io
KEEP_VERSIONS: "5"
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
# aws-cli >= 2.23 defaults to CRC checksums that R2 rejects.
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
R2_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
steps:
- name: Check R2 secrets
id: r2
run: |
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
else
echo "configured=false" >> "$GITHUB_OUTPUT"
echo "R2 secrets not configured; skipping download mirror sync."
fi
- name: Checkout scripts
if: steps.r2.outputs.configured == 'true'
uses: actions/checkout@v6
with:
sparse-checkout: scripts
- name: Download release assets
if: steps.r2.outputs.configured == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mkdir -p r2-assets
gh release download "$GITHUB_REF_NAME" --dir r2-assets --repo "$GITHUB_REPOSITORY"
ls -la r2-assets
- name: Generate download manifest
if: steps.r2.outputs.configured == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
pub_date=$(gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --json publishedAt --jq .publishedAt)
node scripts/generate-download-manifest.mjs r2-assets "$GITHUB_REF_NAME" "$R2_PUBLIC_BASE_URL" manifest.json "$pub_date"
- name: Upload assets and manifest to R2
if: steps.r2.outputs.configured == 'true'
run: |
set -euo pipefail
# Versioned asset paths never change — cache hard at the edge.
aws s3 cp r2-assets "s3://$R2_BUCKET/$GITHUB_REF_NAME/" \
--recursive \
--exclude "*.sig" --exclude "*.tar.gz" --exclude "latest.json" \
--cache-control "public, max-age=31536000, immutable" \
--endpoint-url "$R2_ENDPOINT"
# The manifest is replaced on every release — keep edge cache short.
aws s3 cp manifest.json "s3://$R2_BUCKET/manifest.json" \
--content-type "application/json" \
--cache-control "public, max-age=300" \
--endpoint-url "$R2_ENDPOINT"
- name: Prune old versions
if: steps.r2.outputs.configured == 'true'
run: |
set -euo pipefail
versions=$(aws s3 ls "s3://$R2_BUCKET/" --endpoint-url "$R2_ENDPOINT" \
| awk '$1 == "PRE" {print $2}' | tr -d '/' | grep -E '^v[0-9]' | sort -V || true)
count=$(printf '%s\n' "$versions" | grep -c . || true)
if [ "$count" -le "$KEEP_VERSIONS" ]; then
echo "Nothing to prune ($count versions, keeping up to $KEEP_VERSIONS)."
exit 0
fi
printf '%s\n' "$versions" | head -n "-$KEEP_VERSIONS" | while read -r old; do
[ -n "$old" ] || continue
echo "Pruning s3://$R2_BUCKET/$old/"
aws s3 rm "s3://$R2_BUCKET/$old/" --recursive --endpoint-url "$R2_ENDPOINT"
done