Skip to content

feat(desktop): improve browser launch and panel startup UX #51

feat(desktop): improve browser launch and panel startup UX

feat(desktop): improve browser launch and panel startup UX #51

name: Container (panel)
# The Stitch (web UI) image. Separate from the bot image so a frontend change doesn't
# rebuild and republish the bot, and so a broken panel build can never hold up a
# bot release.
#
# Multi-arch: linux/amd64 and linux/arm64 on native runners, then a merge job
# stitches the digests into one manifest list. Apple Silicon hosts need the
# arm64 variant — a single-arch amd64 :latest fails with
# "no matching manifest for linux/arm64/v8".
on:
pull_request:
paths:
- '.github/workflows/container-panel.yml'
- 'Cargo.lock'
- 'Cargo.toml'
- 'Dockerfile.panel'
- '.dockerignore'
- 'src/**'
- 'web/**'
push:
branches: [main]
paths:
- '.github/workflows/container-panel.yml'
- 'Cargo.lock'
- 'Cargo.toml'
- 'Dockerfile.panel'
- '.dockerignore'
- 'src/**'
- 'web/**'
workflow_dispatch:
env:
REGISTRY_IMAGE: ghcr.io/textile-protocol/textile-stitch-panel
permissions:
contents: read
packages: write
concurrency:
group: container-panel-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-22.04
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- name: Prepare
run: |
platform='${{ matrix.platform }}'
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Build container image
id: build
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: Dockerfile.panel
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
# PRs only compile-check each arch. Digests are pushed (untagged) on
# main so the merge job can assemble the multi-arch manifest list.
outputs: ${{ github.event_name == 'pull_request' && 'type=cacheonly' || format('type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.REGISTRY_IMAGE) }}
cache-from: type=gha,scope=panel-${{ env.PLATFORM_PAIR }}
# Only write the shared build cache from trusted (non-PR) runs. A PR can
# run arbitrary code during the build; letting it populate the gha cache
# would let it poison a layer later consumed by the main publish build.
cache-to: ${{ github.event_name != 'pull_request' && format('type=gha,mode=max,scope=panel-{0}', env.PLATFORM_PAIR) || '' }}
- name: Export digest
if: github.event_name != 'pull_request'
run: |
mkdir -p "${{ runner.temp }}/digests"
digest='${{ steps.build.outputs.digest }}'
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: digests-panel-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge:
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-22.04
steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: ${{ runner.temp }}/digests
pattern: digests-panel-*
merge-multiple: true
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: |
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
# Require both digests before tagging :latest — a partial publish would
# strand Apple Silicon (arm64) or Linux servers (amd64).
set -euo pipefail
count="$(find . -type f | wc -l | tr -d ' ')"
if [ "$count" -lt 2 ]; then
echo "error: expected digests for linux/amd64 and linux/arm64, found $count" >&2
find . -type f -print >&2 || true
exit 1
fi
# printf '%s@sha256:%s ' IMAGE d1 d2 mis-pairs args (IMAGE@sha256:d1
# then d2@sha256:empty). One printf call per digest file.
# shellcheck disable=SC2046
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(for digest in *; do
[ -f "$digest" ] || continue
printf '%s@sha256:%s ' "$REGISTRY_IMAGE" "$digest"
done)
- name: Verify multi-arch manifest
run: |
set -euo pipefail
tag='${{ steps.meta.outputs.version }}'
docker buildx imagetools inspect "${REGISTRY_IMAGE}:${tag}"
raw="$(docker buildx imagetools inspect --raw "${REGISTRY_IMAGE}:${tag}")"
arches="$(printf '%s' "$raw" | jq -r '
[.manifests[]?
| select(.platform.os == "linux")
| select(.platform.architecture == "amd64" or .platform.architecture == "arm64")
| .platform.architecture]
| unique | sort | join(",")
')"
if [ "$arches" != "amd64,arm64" ]; then
echo "error: ${REGISTRY_IMAGE}:${tag} must publish linux/amd64 and linux/arm64 (got: ${arches:-none})" >&2
exit 1
fi
echo "ok: ${REGISTRY_IMAGE}:${tag} includes linux/amd64 and linux/arm64"