Repository navigation
185 lines (179 loc) · 8.62 KB
/
Copy pathpublish.yml
File metadata and controls
185 lines (179 loc) · 8.62 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
name: Publish
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: registry-production
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
candidate:
name: Build and verify registry v2
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
sha256: ${{ steps.inspect.outputs.sha256 }}
bytes: ${{ steps.inspect.outputs.bytes }}
artifact_id: ${{ steps.upload.outputs.artifact-id }}
artifact_sha256: ${{ steps.upload.outputs.artifact-digest }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
- name: Require current main
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
test "$GITHUB_REF" = refs/heads/main
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq .object.sha)" = "$GITHUB_SHA"
- run: pnpm install --frozen-lockfile
- run: pnpm check
- run: pnpm test
- run: pnpm validate
- run: pnpm aggregate
- name: Record exact candidate bytes
id: inspect
run: |
set -euo pipefail
pnpm exec tsx scripts/registry-artifact.ts dist/plugins.json > "$RUNNER_TEMP/registry-identity.json"
jq -r '"sha256=\(.sha256)", "bytes=\(.bytes)"' "$RUNNER_TEMP/registry-identity.json" >> "$GITHUB_OUTPUT"
{
echo "Registry source: $GITHUB_SHA"
echo 'Candidate identity:'
cat "$RUNNER_TEMP/registry-identity.json"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload the raw registry candidate
id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: dist/plugins.json
archive: false
if-no-files-found: error
retention-days: 90
- name: Bind uploaded artifact to the validated bytes
env:
EXPECTED_SHA: ${{ steps.inspect.outputs.sha256 }}
ARTIFACT_SHA: ${{ steps.upload.outputs.artifact-digest }}
run: test "$ARTIFACT_SHA" = "$EXPECTED_SHA"
publish:
name: Approve and publish the verified registry
needs: candidate
runs-on: ubuntu-latest
timeout-minutes: 20
environment: plugin-publishing
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile --ignore-scripts
- name: Download only the approved artifact id
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.candidate.outputs.artifact_id }}
path: ${{ runner.temp }}/registry-candidate
skip-decompress: true
digest-mismatch: error
- name: Recheck artifact identity and source after approval
env:
GH_TOKEN: ${{ github.token }}
EXPECTED_SHA: ${{ needs.candidate.outputs.sha256 }}
EXPECTED_BYTES: ${{ needs.candidate.outputs.bytes }}
ARTIFACT_SHA: ${{ needs.candidate.outputs.artifact_sha256 }}
run: |
set -euo pipefail
test "$GITHUB_REF" = refs/heads/main
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
test "$ARTIFACT_SHA" = "$EXPECTED_SHA"
test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq .object.sha)" = "$GITHUB_SHA"
pnpm exec tsx scripts/registry-artifact.ts \
"$RUNNER_TEMP/registry-candidate/plugins.json" "$EXPECTED_SHA" "$EXPECTED_BYTES"
- name: Back up, conditionally publish, and verify R2
env:
GH_TOKEN: ${{ github.token }}
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_REGION: auto
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
EXPECTED_SHA: ${{ needs.candidate.outputs.sha256 }}
EXPECTED_BYTES: ${{ needs.candidate.outputs.bytes }}
run: |
set -euo pipefail
candidate="$RUNNER_TEMP/registry-candidate/plugins.json"
previous="$RUNNER_TEMP/registry-previous.json"
# Fail before writes if this runner's AWS CLI lacks conditional PutObject.
aws s3api put-object --generate-cli-skeleton input > "$RUNNER_TEMP/put-object-input.json"
jq -e 'has("IfMatch") and has("IfNoneMatch")' "$RUNNER_TEMP/put-object-input.json" > /dev/null
aws s3api get-object --endpoint-url "$R2_ENDPOINT" \
--bucket motrix-registry --key plugins.json "$previous" > "$RUNNER_TEMP/previous-object.json"
previous_etag="$(jq -er '.ETag' "$RUNNER_TEMP/previous-object.json")"
pnpm exec tsx scripts/registry-artifact.ts "$previous" > "$RUNNER_TEMP/previous-identity.json"
previous_sha="$(jq -er '.sha256' "$RUNNER_TEMP/previous-identity.json")"
backup_key="private/backups/plugins/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${previous_sha}.json"
aws s3api put-object --endpoint-url "$R2_ENDPOINT" \
--bucket motrix-registry --key "$backup_key" --body "$previous" \
--if-none-match '*' --content-type 'application/json; charset=utf-8' > "$RUNNER_TEMP/backup-object.json"
aws s3api get-object --endpoint-url "$R2_ENDPOINT" \
--bucket motrix-registry --key "$backup_key" "$RUNNER_TEMP/backup-readback.json" > /dev/null
cmp "$previous" "$RUNNER_TEMP/backup-readback.json"
# Recheck main and the exact candidate immediately before replacing the live key.
test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq .object.sha)" = "$GITHUB_SHA"
pnpm exec tsx scripts/registry-artifact.ts "$candidate" "$EXPECTED_SHA" "$EXPECTED_BYTES"
if [ -d assets ]; then
aws s3 sync assets/ s3://motrix-registry/assets/ \
--endpoint-url "$R2_ENDPOINT" --no-follow-symlinks
fi
test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq .object.sha)" = "$GITHUB_SHA"
aws s3api put-object --endpoint-url "$R2_ENDPOINT" \
--bucket motrix-registry --key plugins.json --body "$candidate" \
--if-match "$previous_etag" --content-type 'application/json; charset=utf-8' > "$RUNNER_TEMP/published-object.json"
published_etag="$(jq -er '.ETag' "$RUNNER_TEMP/published-object.json")"
aws s3api get-object --endpoint-url "$R2_ENDPOINT" \
--bucket motrix-registry --key plugins.json --if-match "$published_etag" \
"$RUNNER_TEMP/published-readback.json" > /dev/null
pnpm exec tsx scripts/registry-artifact.ts \
"$RUNNER_TEMP/published-readback.json" "$EXPECTED_SHA" "$EXPECTED_BYTES"
{
echo "Published registry source: $GITHUB_SHA"
echo "SHA-256: $EXPECTED_SHA"
echo "Bytes: $EXPECTED_BYTES"
echo "Previous object backup: $backup_key"
echo "Published ETag: $published_etag"
echo 'The website remains a separate manual deployment.'
} >> "$GITHUB_STEP_SUMMARY"
- name: Verify the public registry
env:
EXPECTED_SHA: ${{ needs.candidate.outputs.sha256 }}
EXPECTED_BYTES: ${{ needs.candidate.outputs.bytes }}
run: |
set -euo pipefail
for attempt in $(seq 1 10); do
if curl --fail --silent --show-error --max-time 15 --max-filesize 4194304 \
"https://dl.motrix.app/registry/plugins.json?sha256=$EXPECTED_SHA" \
--output "$RUNNER_TEMP/public-registry.json" &&
pnpm exec tsx scripts/registry-artifact.ts \
"$RUNNER_TEMP/public-registry.json" "$EXPECTED_SHA" "$EXPECTED_BYTES"; then
exit 0
fi
if [ "$attempt" -lt 10 ]; then sleep 5; fi
done
echo 'R2 was updated and verified, but the public endpoint has not converged. Inspect the release before retrying.' >&2
exit 1