Skip to content

feat(rules): add direct domain fastify.cc by Rule-Bot (from Rule-Bot … #1787

feat(rules): add direct domain fastify.cc by Rule-Bot (from Rule-Bot …

feat(rules): add direct domain fastify.cc by Rule-Bot (from Rule-Bot … #1787

name: Purge jsDelivr Cache
on:
# Every main push enters the publisher. The versioned publication contract,
# not a duplicated paths filter, decides whether the run is a fast no-op.
push:
branches:
- main
workflow_dispatch:
inputs:
before_sha:
description: First revision in the repair range (exclusive; defaults to after_sha^)
required: false
type: string
after_sha:
description: Published revision to verify (defaults to the selected main revision)
required: false
type: string
generation_complete:
description: The selected revision already contains all generated outputs
required: false
default: false
type: boolean
workflow_call:
inputs:
before_sha:
description: First revision in the publication range (exclusive)
required: true
type: string
after_sha:
description: Exact validated revision to publish and verify
required: true
type: string
generation_complete:
description: The selected revision already contains all generated outputs
required: false
default: false
type: boolean
permissions:
contents: read
concurrency:
group: jsdelivr-publisher-${{ github.repository }}-main
cancel-in-progress: false
jobs:
purge-jsdelivr:
# Forks validate the code but must never operate the upstream cache.
if: github.repository == 'Aethersailor/Custom_OpenClash_Rules'
runs-on: ubuntu-latest
timeout-minutes: 45
outputs:
before_sha: ${{ steps.range.outputs.before }}
after_sha: ${{ steps.range.outputs.after }}
worker_deployable: ${{ steps.worker-plan.outputs.worker_deployable }}
worker_plan_reason: ${{ steps.worker-plan.outputs.worker_plan_reason }}
defaults:
run:
shell: bash
steps:
- name: Checkout publisher implementation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# Run the publisher implementation from the workflow's triggering
# revision. Manual repair ranges may intentionally end before this
# script and contract existed; their commits are read from Git data.
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
- name: Resolve deterministic publication range
id: range
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_BEFORE: ${{ github.event.before }}
EVENT_SHA: ${{ github.sha }}
REQUEST_BEFORE: ${{ inputs.before_sha }}
REQUEST_AFTER: ${{ inputs.after_sha }}
run: |
if [ -n "$REQUEST_AFTER" ]; then
# Explicit reusable/manual ranges must win over the caller's event context.
mode="complete"
before="$REQUEST_BEFORE"
after="$REQUEST_AFTER"
if [ -z "$before" ]; then
before="$(git rev-parse "${after}^{commit}^")"
fi
elif [ "$EVENT_NAME" = "push" ]; then
mode="direct"
before="$EVENT_BEFORE"
after="$EVENT_SHA"
else
# Manual runs are complete repair ranges and must not defer sources.
mode="complete"
after="$EVENT_SHA"
before="$REQUEST_BEFORE"
if [ -z "$before" ]; then
before="$(git rev-parse "${after}^{commit}^")"
fi
fi
git fetch --no-tags origin main
published="$(git rev-parse FETCH_HEAD)"
{
echo "mode=$mode"
echo "before=$before"
echo "after=$after"
echo "published=$published"
} >> "$GITHUB_OUTPUT"
- name: Purge every changed cache key
env:
REPOSITORY: ${{ github.repository }}
BEFORE_SHA: ${{ steps.range.outputs.before }}
AFTER_SHA: ${{ steps.range.outputs.after }}
PUBLISHED_SHA: ${{ steps.range.outputs.published }}
PUBLICATION_MODE: ${{ steps.range.outputs.mode }}
run: |
python .github/scripts/jsdelivr_purge.py run \
--repository "$REPOSITORY" \
--before "$BEFORE_SHA" \
--after "$AFTER_SHA" \
--published "$PUBLISHED_SHA" \
--mode "$PUBLICATION_MODE"
- name: Plan exact Cloudflare mirror snapshot
id: worker-plan
env:
BEFORE_SHA: ${{ steps.range.outputs.before }}
AFTER_SHA: ${{ steps.range.outputs.after }}
GENERATION_COMPLETE: ${{ inputs.generation_complete || false }}
run: |
generation_args=()
if [ "$GENERATION_COMPLETE" = "true" ]; then
generation_args+=(--generation-complete)
fi
python .github/scripts/jsdelivr_purge.py plan-worker-snapshot \
--before "$BEFORE_SHA" \
--after "$AFTER_SHA" \
--github-output "$GITHUB_OUTPUT" \
"${generation_args[@]}"
validate-worker-snapshot:
needs: purge-jsdelivr
if: needs.purge-jsdelivr.outputs.worker_deployable == 'true'
permissions:
contents: read
uses: ./.github/workflows/validate.yml
with:
revision: ${{ needs.purge-jsdelivr.outputs.after_sha }}
deploy-worker-snapshot:
needs:
- purge-jsdelivr
- validate-worker-snapshot
if: >-
needs.purge-jsdelivr.outputs.worker_deployable == 'true' &&
needs.validate-worker-snapshot.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 15
environment: cloudflare-production
permissions:
contents: read
defaults:
run:
shell: bash
steps:
- name: Checkout exact validated snapshot
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.purge-jsdelivr.outputs.after_sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
- name: Confirm snapshot is still current main
id: current-main
env:
EXPECTED_SHA: ${{ needs.purge-jsdelivr.outputs.after_sha }}
run: |
git fetch --no-tags origin main
current_sha="$(git rev-parse FETCH_HEAD)"
if [ "$current_sha" = "$EXPECTED_SHA" ]; then
echo "current=true" >> "$GITHUB_OUTPUT"
echo "Deploying current main snapshot $EXPECTED_SHA"
else
echo "current=false" >> "$GITHUB_OUTPUT"
echo "Skip stale snapshot $EXPECTED_SHA; current main is $current_sha"
fi
- name: Build exact Static Assets snapshot
if: steps.current-main.outputs.current == 'true'
env:
EXPECTED_SHA: ${{ needs.purge-jsdelivr.outputs.after_sha }}
run: |
python .github/scripts/jsdelivr_purge.py build-worker-assets \
--revision "$EXPECTED_SHA" \
--output workers/repository-mirror/dist
npm ci --prefix workers/repository-mirror
- name: Reconfirm current main immediately before deploy
if: steps.current-main.outputs.current == 'true'
id: pre-deploy-main
env:
EXPECTED_SHA: ${{ needs.purge-jsdelivr.outputs.after_sha }}
run: |
current_sha="$(
git ls-remote --exit-code origin refs/heads/main |
awk 'NR == 1 { print $1 }'
)"
if [ "$current_sha" = "$EXPECTED_SHA" ]; then
echo "current=true" >> "$GITHUB_OUTPUT"
else
echo "current=false" >> "$GITHUB_OUTPUT"
echo "Skip stale snapshot $EXPECTED_SHA; current main is $current_sha"
fi
- name: Deploy Worker and Static Assets
if: >-
steps.current-main.outputs.current == 'true' &&
steps.pre-deploy-main.outputs.current == 'true'
working-directory: workers/repository-mirror
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
CF_REDIRECT_API_TOKEN: ${{ secrets.CF_REDIRECT_API_TOKEN }}
EXPECTED_SHA: ${{ needs.purge-jsdelivr.outputs.after_sha }}
run: |
: "${CLOUDFLARE_API_TOKEN:?missing CLOUDFLARE_API_TOKEN}"
: "${CLOUDFLARE_ACCOUNT_ID:?missing CLOUDFLARE_ACCOUNT_ID}"
: "${CLOUDFLARE_ZONE_ID:?missing CLOUDFLARE_ZONE_ID}"
: "${CF_REDIRECT_API_TOKEN:?missing CF_REDIRECT_API_TOKEN}"
secrets_file="$RUNNER_TEMP/cloudflare-mirror-secrets.env"
umask 077
{
printf 'CF_ZONE_ID=%s\n' "$CLOUDFLARE_ZONE_ID"
printf 'CF_REDIRECT_API_TOKEN=%s\n' "$CF_REDIRECT_API_TOKEN"
} > "$secrets_file"
npm exec -- wrangler deploy \
--secrets-file "$secrets_file" \
--var "SNAPSHOT_SHA:$EXPECTED_SHA" \
--strict
- name: Verify deployed snapshot identity
if: >-
steps.current-main.outputs.current == 'true' &&
steps.pre-deploy-main.outputs.current == 'true'
env:
EXPECTED_SHA: ${{ needs.purge-jsdelivr.outputs.after_sha }}
run: |
manifest="$RUNNER_TEMP/cloudflare-mirror-main.json"
verified=false
for attempt in 1 2 3 4 5 6 7 8 9 10; do
curl --fail --location --show-error \
--retry 3 --retry-delay 2 --retry-all-errors --retry-max-time 60 \
--connect-timeout 15 --max-time 60 \
--output "$manifest" \
"https://git.asailor.org/_mirror/Custom_OpenClash_Rules/main.json"
if python - "$manifest" "$EXPECTED_SHA" <<'PY'
import json
import pathlib
import sys
payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
actual = payload.get("commit")
if actual != sys.argv[2]:
print(f"Cloudflare still serves {actual!r}; expected {sys.argv[2]}")
raise SystemExit(1)
print(
f"Verified Cloudflare snapshot {actual}: "
f"{payload['file_count']} files, {payload['total_bytes']} bytes"
)
PY
then
verified=true
break
fi
if [ "$attempt" -lt 10 ]; then
sleep 3
fi
done
if [ "$verified" != "true" ]; then
echo "Cloudflare snapshot did not converge to $EXPECTED_SHA" >&2
exit 1
fi
current_sha="$(
git ls-remote --exit-code origin refs/heads/main |
awk 'NR == 1 { print $1 }'
)"
if [ "$current_sha" != "$EXPECTED_SHA" ]; then
echo "main advanced to $current_sha after deploying $EXPECTED_SHA" >&2
echo "The queued publisher for the newer revision must replace this snapshot." >&2
exit 1
fi