Skip to content

Commit 993ffb8

Browse files
authored
feat(relay): deploy from prebuilt multi-arch image (#2013)
* feat(relay): deploy from published multi-arch image * fix(relay): prefer verified China image route * test(relay): smoke published image platforms * fix(ci): isolate Relay platform smoke pulls
1 parent ca94825 commit 993ffb8

16 files changed

Lines changed: 1305 additions & 1759 deletions

File tree

‎.github/workflows/desktop-package.yml‎

Lines changed: 239 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ on:
1515
required: false
1616
default: false
1717
type: boolean
18+
relay_image_only:
19+
description: "Publish only the Relay Server image and signed descriptor (no Desktop packages)."
20+
required: false
21+
default: false
22+
type: boolean
1823

1924
permissions:
2025
contents: write
26+
packages: write
2127

2228
concurrency:
2329
group: desktop-package-${{ github.event.release.tag_name || inputs.tag_name || github.sha }}
@@ -33,6 +39,7 @@ jobs:
3339
release_tag: ${{ steps.meta.outputs.release_tag }}
3440
upload_to_release: ${{ steps.meta.outputs.upload_to_release }}
3541
checkout_ref: ${{ steps.meta.outputs.checkout_ref }}
42+
relay_image_only: ${{ steps.meta.outputs.relay_image_only }}
3643
steps:
3744
- uses: actions/checkout@v5
3845

@@ -45,6 +52,7 @@ jobs:
4552
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
4653
INPUT_TAG_NAME: ${{ inputs.tag_name }}
4754
INPUT_UPLOAD_TO_RELEASE: ${{ inputs.upload_to_release }}
55+
INPUT_RELAY_IMAGE_ONLY: ${{ inputs.relay_image_only }}
4856
run: |
4957
set -euo pipefail
5058
@@ -56,7 +64,13 @@ jobs:
5664
elif [[ -n "${INPUT_TAG_NAME}" ]]; then
5765
TAG="${INPUT_TAG_NAME}"
5866
VERSION="${TAG#v}"
59-
CHECKOUT_REF="${TAG}"
67+
# A one-off image backfill must use the workflow branch: an older
68+
# tag does not contain Dockerfile.release or this publishing job.
69+
if [[ "${INPUT_RELAY_IMAGE_ONLY}" == "true" ]]; then
70+
CHECKOUT_REF="${GITHUB_SHA}"
71+
else
72+
CHECKOUT_REF="${TAG}"
73+
fi
6074
if [[ "${INPUT_UPLOAD_TO_RELEASE}" == "true" ]]; then
6175
UPLOAD="true"
6276
else
@@ -73,12 +87,14 @@ jobs:
7387
echo "release_tag=$TAG" >> "$GITHUB_OUTPUT"
7488
echo "upload_to_release=$UPLOAD" >> "$GITHUB_OUTPUT"
7589
echo "checkout_ref=$CHECKOUT_REF" >> "$GITHUB_OUTPUT"
90+
echo "relay_image_only=${INPUT_RELAY_IMAGE_ONLY:-false}" >> "$GITHUB_OUTPUT"
7691
7792
# ── Build per platform ─────────────────────────────────────────────
7893
package:
7994
name: Package (${{ matrix.platform.name }})
8095
runs-on: ${{ matrix.platform.os }}
8196
needs: prepare
97+
if: needs.prepare.outputs.relay_image_only != 'true'
8298
env:
8399
NODE_OPTIONS: --max-old-space-size=6144
84100
BITFUN_ENABLE_UPDATER_ARTIFACTS: ${{ needs.prepare.outputs.upload_to_release }}
@@ -239,6 +255,7 @@ jobs:
239255
linux-binaries:
240256
name: Linux CLI and Relay Server
241257
needs: prepare
258+
if: needs.prepare.outputs.relay_image_only != 'true'
242259
uses: ./.github/workflows/linux-binaries.yml
243260
secrets:
244261
release_signing_key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
@@ -249,11 +266,208 @@ jobs:
249266
version: ${{ needs.prepare.outputs.version }}
250267
artifact_prefix: ${{ needs.prepare.outputs.release_tag }}
251268

269+
# Publish the Relay once, as a multi-platform image. User servers only pull
270+
# this image; they no longer download an archive and build a runtime image.
271+
publish-relay-image:
272+
name: Publish Relay Server Image
273+
needs: [prepare, linux-binaries]
274+
if: >-
275+
always() &&
276+
(needs.prepare.outputs.upload_to_release == 'true' ||
277+
needs.prepare.outputs.relay_image_only == 'true') &&
278+
(needs.prepare.outputs.relay_image_only == 'true' ||
279+
needs.linux-binaries.result == 'success')
280+
runs-on: ubuntu-latest
281+
permissions:
282+
contents: write
283+
packages: write
284+
env:
285+
IMAGE: ghcr.io/gcwing/bitfun-relay-server
286+
287+
steps:
288+
- name: Checkout
289+
uses: actions/checkout@v5
290+
with:
291+
ref: ${{ needs.prepare.outputs.checkout_ref }}
292+
293+
- name: Download Relay archives from this release run
294+
if: needs.prepare.outputs.relay_image_only != 'true'
295+
uses: actions/download-artifact@v7
296+
with:
297+
pattern: bitfun-linux-${{ needs.prepare.outputs.release_tag }}-*
298+
path: linux-release-assets
299+
merge-multiple: true
300+
301+
- name: Download Relay archives from the existing release (image-only backfill)
302+
if: needs.prepare.outputs.relay_image_only == 'true'
303+
shell: bash
304+
env:
305+
GH_TOKEN: ${{ github.token }}
306+
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
307+
run: |
308+
set -euo pipefail
309+
mkdir -p linux-release-assets
310+
gh release download "${RELEASE_TAG}" \
311+
--repo GCWing/BitFun \
312+
--dir linux-release-assets \
313+
--pattern 'bitfun-relay-server-*.tar.gz' \
314+
--pattern 'bitfun-relay-server-*.tar.gz.sha256'
315+
316+
- name: Verify image inputs
317+
shell: bash
318+
run: |
319+
set -euo pipefail
320+
test -f linux-release-assets/bitfun-relay-server-x86_64-unknown-linux-gnu.tar.gz
321+
test -f linux-release-assets/bitfun-relay-server-aarch64-unknown-linux-gnu.tar.gz
322+
for archive in linux-release-assets/bitfun-relay-server-*.tar.gz; do
323+
(cd linux-release-assets && sha256sum --check "$(basename "${archive}").sha256")
324+
done
325+
cp src/apps/relay-server/Dockerfile.release linux-release-assets/Dockerfile.release
326+
327+
- name: Set up QEMU
328+
uses: docker/setup-qemu-action@v4
329+
330+
- name: Set up Docker Buildx
331+
uses: docker/setup-buildx-action@v4
332+
333+
- name: Log in to GHCR
334+
uses: docker/login-action@v4
335+
with:
336+
registry: ghcr.io
337+
username: ${{ github.actor }}
338+
password: ${{ github.token }}
339+
340+
- name: Resolve image tags
341+
id: image-tags
342+
shell: bash
343+
env:
344+
GH_TOKEN: ${{ github.token }}
345+
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
346+
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
347+
IMAGE_ONLY: ${{ needs.prepare.outputs.relay_image_only }}
348+
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
349+
run: |
350+
set -euo pipefail
351+
asset_version="${RELEASE_VERSION%%+*}"
352+
{
353+
echo 'value<<EOF'
354+
echo "${IMAGE}:${RELEASE_TAG}"
355+
echo "${IMAGE}:${asset_version}"
356+
if [[ "${IMAGE_ONLY}" == "true" ]]; then
357+
# Backfilling an older release must not roll the floating tag
358+
# backwards. GitHub's latest endpoint excludes prereleases.
359+
latest_release="$(gh api repos/GCWing/BitFun/releases/latest --jq .tag_name)"
360+
if [[ "${RELEASE_TAG}" == "${latest_release}" ]]; then
361+
echo "${IMAGE}:latest"
362+
fi
363+
elif [[ "${RELEASE_PRERELEASE:-false}" != "true" ]]; then
364+
# The normal release workflow can create the GitHub Release only
365+
# after packaging, so it cannot rely on /releases/latest yet.
366+
echo "${IMAGE}:latest"
367+
fi
368+
echo EOF
369+
} >>"$GITHUB_OUTPUT"
370+
371+
- name: Build and push multi-platform image
372+
id: image
373+
uses: docker/build-push-action@v7
374+
with:
375+
context: linux-release-assets
376+
file: linux-release-assets/Dockerfile.release
377+
platforms: linux/amd64,linux/arm64
378+
push: true
379+
provenance: false
380+
sbom: false
381+
build-args: |
382+
VERSION=${{ needs.prepare.outputs.version }}
383+
REVISION=${{ needs.prepare.outputs.release_tag }}
384+
tags: ${{ steps.image-tags.outputs.value }}
385+
386+
- name: Smoke-test published image on both platforms
387+
shell: bash
388+
env:
389+
IMAGE_DIGEST: ${{ steps.image.outputs.digest }}
390+
run: bash scripts/relay/smoke-image.sh "${IMAGE}@${IMAGE_DIGEST}"
391+
392+
- name: Verify manifest and generate signed descriptor
393+
shell: bash
394+
env:
395+
IMAGE_DIGEST: ${{ steps.image.outputs.digest }}
396+
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
397+
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
398+
BITFUN_SIGNING_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
399+
BITFUN_SIGNING_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
400+
BITFUN_SIGNING_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }}
401+
run: |
402+
set -euo pipefail
403+
[[ "${IMAGE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]
404+
docker buildx imagetools inspect "${IMAGE}@${IMAGE_DIGEST}" --raw >relay-image-manifest.json
405+
jq -e '
406+
[.manifests[].platform | .os + "/" + .architecture] as $platforms
407+
| ($platforms | index("linux/amd64")) != null
408+
and ($platforms | index("linux/arm64")) != null
409+
' relay-image-manifest.json >/dev/null
410+
jq -n \
411+
--arg image "${IMAGE}" \
412+
--arg tag "${RELEASE_TAG}" \
413+
--arg version "${RELEASE_VERSION}" \
414+
--arg digest "${IMAGE_DIGEST}" \
415+
'{
416+
schema_version: 1,
417+
image: $image,
418+
tag: $tag,
419+
version: $version,
420+
digest: $digest,
421+
platforms: ["linux/amd64", "linux/arm64"]
422+
}' >relay-image.json
423+
bash scripts/sign-release-assets.sh relay-image.json
424+
test -s relay-image.json.sig
425+
426+
- name: Upload signed image descriptor
427+
uses: actions/upload-artifact@v6
428+
with:
429+
name: bitfun-relay-image-${{ needs.prepare.outputs.release_tag }}
430+
if-no-files-found: error
431+
retention-days: 7
432+
path: |
433+
relay-image.json
434+
relay-image.json.sig
435+
436+
# The package is private on first creation. This deliberately fails until
437+
# its visibility is changed to public, preventing an apparently green
438+
# release that anonymous customer servers cannot pull.
439+
- name: Verify anonymous pull access
440+
shell: bash
441+
env:
442+
IMAGE_DIGEST: ${{ steps.image.outputs.digest }}
443+
run: |
444+
set -euo pipefail
445+
docker logout ghcr.io >/dev/null 2>&1 || true
446+
clean_config="$(mktemp -d)"
447+
trap 'rm -rf "$clean_config"' EXIT
448+
DOCKER_CONFIG="$clean_config" docker buildx imagetools inspect \
449+
"${IMAGE}@${IMAGE_DIGEST}" >/dev/null
450+
451+
- name: Attach descriptor to an existing release (image-only backfill)
452+
if: needs.prepare.outputs.relay_image_only == 'true'
453+
uses: softprops/action-gh-release@v3
454+
with:
455+
tag_name: ${{ needs.prepare.outputs.release_tag }}
456+
files: |
457+
relay-image.json
458+
relay-image.json.sig
459+
fail_on_unmatched_files: true
460+
252461
# ── Upload assets to GitHub Release ────────────────────────────────
253462
upload-release-assets:
254463
name: Upload Release Assets
255-
needs: [prepare, package, linux-binaries]
256-
if: needs.prepare.outputs.upload_to_release == 'true'
464+
needs: [prepare, package, linux-binaries, publish-relay-image]
465+
if: >-
466+
always() &&
467+
needs.prepare.outputs.upload_to_release == 'true' &&
468+
needs.package.result == 'success' &&
469+
needs.linux-binaries.result == 'success' &&
470+
needs.publish-relay-image.result == 'success'
257471
runs-on: ubuntu-latest
258472
env:
259473
REQUIRED_UPDATER_PLATFORMS: windows-x86_64,darwin-x86_64,darwin-aarch64,linux-x86_64,linux-aarch64
@@ -276,12 +490,20 @@ jobs:
276490
path: linux-release-assets
277491
merge-multiple: true
278492

493+
- name: Download Relay image descriptor
494+
uses: actions/download-artifact@v7
495+
with:
496+
name: bitfun-relay-image-${{ needs.prepare.outputs.release_tag }}
497+
path: relay-image-assets
498+
279499
- name: List release assets
280500
run: |
281501
echo "Release assets:"
282502
find release-assets -type f | sort
283503
echo "Linux CLI and Relay Server assets:"
284504
find linux-release-assets -type f | sort
505+
echo "Relay image descriptor:"
506+
find relay-image-assets -type f | sort
285507
286508
- name: Collect updater assets
287509
run: |
@@ -367,6 +589,8 @@ jobs:
367589
linux-release-assets/*.tar.gz.sig
368590
linux-release-assets/*.tar.gz.sha256.sig
369591
linux-release-assets/linux-binaries.json
592+
relay-image-assets/relay-image.json
593+
relay-image-assets/relay-image.json.sig
370594
fail_on_unmatched_files: true
371595

372596
- name: Verify published updater manifest
@@ -391,6 +615,18 @@ jobs:
391615
curl -fsSL --retry 5 --retry-delay 3 "${cli_url}.sha256.sig" -o /dev/null
392616
done < <(jq -r '.platforms[].cli.url' linux-binaries.published.json)
393617
618+
- name: Verify published Relay image descriptor
619+
run: |
620+
curl -fsSL --retry 5 --retry-delay 3 \
621+
"https://github.com/GCWing/BitFun/releases/download/${{ needs.prepare.outputs.release_tag }}/relay-image.json" \
622+
-o relay-image.published.json
623+
test "$(jq -r '.tag' relay-image.published.json)" = "${{ needs.prepare.outputs.release_tag }}"
624+
test "$(jq -r '.image' relay-image.published.json)" = "ghcr.io/gcwing/bitfun-relay-server"
625+
jq -e '.digest | test("^sha256:[0-9a-f]{64}$")' relay-image.published.json >/dev/null
626+
curl -fsSL --retry 5 --retry-delay 3 \
627+
"https://github.com/GCWing/BitFun/releases/download/${{ needs.prepare.outputs.release_tag }}/relay-image.json.sig" \
628+
-o /dev/null
629+
394630
# Nudge the openbitfun.com mirror to sync now instead of on its next
395631
# 10-minute cron tick. Until the mirror has these bytes, CN clients have
396632
# only the GitHub origin to fall back to. Best effort: the cron run is

0 commit comments

Comments
 (0)