-
Notifications
You must be signed in to change notification settings - Fork 1.4k
315 lines (291 loc) · 11.2 KB
/
Copy pathpurge-jsdelivr.yml
File metadata and controls
315 lines (291 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
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