From 6c3a09da860e1469de70885c0e373f5db07a9abc Mon Sep 17 00:00:00 2001 From: Cita Date: Thu, 19 Mar 2026 20:49:21 +0800 Subject: [PATCH] fix: harden mac release architecture verification --- .github/workflows/release.yml | 860 +++++++++++++++++----------------- 1 file changed, 417 insertions(+), 443 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0860aca..b95a5d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,443 +1,417 @@ -name: Release - -on: - push: - tags: ['v*'] - workflow_dispatch: - -permissions: - contents: write - packages: write - -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: false - -jobs: - verify: - name: Verify - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Run tests - run: npm test - - - name: Build - run: npm run build - - - name: Upload built dist - uses: actions/upload-artifact@v7 - with: - name: metapi-dist-${{ github.sha }} - path: dist - if-no-files-found: error - - build-packages: - name: Build Desktop Package (${{ matrix.platform }}) - needs: verify - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - include: - - platform: linux - runner: ubuntu-latest - artifactLabel: linux-x64 - - platform: windows - runner: windows-latest - artifactLabel: windows-x64 - - platform: mac-arm64 - runner: macos-15 - artifactLabel: mac-arm64 - packageCommand: npm run package:desktop -- --mac --arm64 - expectedMacArch: arm64 - - platform: mac-x64 - runner: macos-15-intel - artifactLabel: mac-x64 - packageCommand: npm run package:desktop:mac:intel - expectedMacArch: x64 - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: npm - - - name: Restore Electron cache (Windows) - if: runner.os == 'Windows' - uses: actions/cache@v5 - with: - path: | - ${{ runner.temp }}/.cache/electron - ${{ runner.temp }}/.cache/electron-builder - ${{ env.LOCALAPPDATA }}\electron\Cache - ${{ env.LOCALAPPDATA }}\electron-builder\Cache - key: electron-cache-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'electron-builder.yml') }} - restore-keys: | - electron-cache-${{ runner.os }}- - - - name: Restore Electron cache (Linux) - if: runner.os == 'Linux' - uses: actions/cache@v5 - with: - path: | - ${{ runner.temp }}/.cache/electron - ${{ runner.temp }}/.cache/electron-builder - ${{ runner.temp }}/.cache/appimage-tools - ~/.cache/electron - ~/.cache/electron-builder - key: electron-cache-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'electron-builder.yml') }} - restore-keys: | - electron-cache-${{ runner.os }}- - - - name: Restore Electron cache (macOS) - if: runner.os == 'macOS' - uses: actions/cache@v5 - with: - path: | - ${{ runner.temp }}/.cache/electron - ${{ runner.temp }}/.cache/electron-builder - ~/Library/Caches/electron - ~/Library/Caches/electron-builder - key: electron-cache-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'electron-builder.yml') }} - restore-keys: | - electron-cache-${{ runner.os }}- - - - name: Install dependencies - run: npm ci - - - name: Download built dist - uses: actions/download-artifact@v8 - with: - name: metapi-dist-${{ github.sha }} - path: dist - - - name: Build desktop artifacts (Windows) - if: runner.os == 'Windows' - shell: pwsh - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - npm_config_cache: ${{ runner.temp }}/.npm - ELECTRON_CACHE: ${{ runner.temp }}/.cache/electron - ELECTRON_BUILDER_CACHE: ${{ runner.temp }}/.cache/electron-builder - ELECTRON_BUILDER_BINARIES_MIRROR: https://github.com/electron-userland/electron-builder-binaries/releases/download/ - npm_config_electron_builder_binaries_mirror: https://github.com/electron-userland/electron-builder-binaries/releases/download/ - WIN_CSC_LINK_INPUT: ${{ secrets.CSC_LINK }} - WIN_CSC_KEY_PASSWORD_INPUT: ${{ secrets.CSC_KEY_PASSWORD }} - run: | - if ($env:WIN_CSC_LINK_INPUT) { $env:WIN_CSC_LINK = $env:WIN_CSC_LINK_INPUT } - if ($env:WIN_CSC_KEY_PASSWORD_INPUT) { $env:WIN_CSC_KEY_PASSWORD = $env:WIN_CSC_KEY_PASSWORD_INPUT } - for ($attempt = 1; $attempt -le 2; $attempt++) { - Write-Host "Desktop build attempt $attempt/2" - npm run package:desktop - if ($LASTEXITCODE -eq 0) { - break - } - if ($attempt -eq 2) { - exit $LASTEXITCODE - } - Start-Sleep -Seconds 90 - } - - - name: Smoke test packaged native modules (Windows) - if: runner.os == 'Windows' - shell: pwsh - env: - ELECTRON_RUN_AS_NODE: 1 - run: | - $appRoot = Join-Path $PWD 'release\win-unpacked\resources\app' - $exePath = Join-Path $PWD 'release\win-unpacked\Metapi.exe' - Push-Location $appRoot - try { - & $exePath -e "require('./node_modules/better-sqlite3'); console.log('better-sqlite3 ok')" - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } - } finally { - Pop-Location - } - - - name: Build desktop artifacts (Linux) - if: runner.os == 'Linux' - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - npm_config_cache: ${{ runner.temp }}/.npm - ELECTRON_CACHE: ${{ runner.temp }}/.cache/electron - ELECTRON_BUILDER_CACHE: ${{ runner.temp }}/.cache/electron-builder - ELECTRON_BUILDER_BINARIES_MIRROR: https://github.com/electron-userland/electron-builder-binaries/releases/download/ - npm_config_electron_builder_binaries_mirror: https://github.com/electron-userland/electron-builder-binaries/releases/download/ - run: | - set -euo pipefail - for attempt in 1 2; do - echo "Desktop build attempt ${attempt}/2" - if npm run package:desktop; then - break - fi - if [ "$attempt" -eq 2 ]; then - exit 1 - fi - sleep 90 - done - - - name: Build desktop artifacts (macOS) - if: startsWith(matrix.platform, 'mac-') - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - npm_config_cache: ${{ runner.temp }}/.npm - ELECTRON_CACHE: ${{ runner.temp }}/.cache/electron - ELECTRON_BUILDER_CACHE: ${{ runner.temp }}/.cache/electron-builder - ELECTRON_BUILDER_BINARIES_MIRROR: https://github.com/electron-userland/electron-builder-binaries/releases/download/ - npm_config_electron_builder_binaries_mirror: https://github.com/electron-userland/electron-builder-binaries/releases/download/ - APPLE_ID_INPUT: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD_INPUT: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APPLE_TEAM_ID_INPUT: ${{ secrets.APPLE_TEAM_ID }} - PACKAGE_COMMAND: ${{ matrix.packageCommand }} - run: | - set -euo pipefail - if [ -n "${APPLE_ID_INPUT:-}" ]; then export APPLE_ID="$APPLE_ID_INPUT"; fi - if [ -n "${APPLE_APP_SPECIFIC_PASSWORD_INPUT:-}" ]; then export APPLE_APP_SPECIFIC_PASSWORD="$APPLE_APP_SPECIFIC_PASSWORD_INPUT"; fi - if [ -n "${APPLE_TEAM_ID_INPUT:-}" ]; then export APPLE_TEAM_ID="$APPLE_TEAM_ID_INPUT"; fi - for attempt in 1 2; do - echo "Desktop build (${PACKAGE_COMMAND}) attempt ${attempt}/2" - if eval "${PACKAGE_COMMAND}"; then - break - fi - if [ "$attempt" -eq 2 ]; then - exit 1 - fi - sleep 90 - done - - - name: Verify packaged macOS architecture - if: startsWith(matrix.platform, 'mac-') - shell: bash - env: - expectedMacArch: ${{ matrix.expectedMacArch }} - run: | - set -euo pipefail - mapfile -t app_binaries < <(find release -type f -path '*/Metapi.app/Contents/MacOS/Metapi') - if [ "${#app_binaries[@]}" -eq 0 ]; then - echo "No packaged macOS app binary found under release/" - exit 1 - fi - - for binary in "${app_binaries[@]}"; do - archs="$(lipo -archs "$binary" 2>/dev/null | xargs)" - echo "$binary => ${archs:-unknown}" - if [ -z "${archs}" ]; then - echo "Unable to detect architecture via lipo" - exit 1 - fi - - if [ "${expectedMacArch}" = "x64" ]; then - [ "${archs}" = "x86_64" ] || { - echo "Expected x86_64-only binary but got: ${archs}" - exit 1 - } - else - [ "${archs}" = "arm64" ] || { - echo "Expected arm64-only binary but got: ${archs}" - exit 1 - } - fi - done - - - name: Upload desktop package - uses: actions/upload-artifact@v7 - with: - name: metapi-desktop-${{ github.run_id }}-${{ matrix.artifactLabel }} - path: | - release/*.exe - release/*.dmg - release/*.zip - release/*.AppImage - release/*.deb - release/*.blockmap - release/latest*.yml - release/latest*.json - release/builder-debug.yml - if-no-files-found: error - - publish-release: - name: Publish GitHub Release - needs: build-packages - runs-on: ubuntu-latest - timeout-minutes: 15 - if: startsWith(github.ref, 'refs/tags/') - - steps: - - name: Download package artifacts - uses: actions/download-artifact@v8 - with: - path: release-assets - merge-multiple: true - - - name: Show release files - run: ls -la release-assets - - - name: Upload Release Assets - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - generate_release_notes: true - files: release-assets/* - - publish-docker-arch: - name: Publish Docker Image (${{ matrix.arch }}) - needs: build-packages - runs-on: ${{ matrix.runner }} - timeout-minutes: 45 - if: startsWith(github.ref, 'refs/tags/') - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-latest - - arch: arm64 - platform: linux/arm64 - runner: ubuntu-24.04-arm - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Ensure Docker Hub secrets are configured - run: | - if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then - echo "Missing Docker Hub secrets: DOCKERHUB_USERNAME / DOCKERHUB_TOKEN" - exit 1 - fi - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Docker Hub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Docker metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: | - 1467078763/metapi - ghcr.io/${{ github.repository }} - tags: | - type=ref,event=tag - type=raw,value=latest - - - name: Add architecture suffix to tags - id: arch-tags - shell: bash - run: | - set -euo pipefail - rm -f tags.txt - while IFS= read -r tag; do - [ -n "$tag" ] || continue - echo "${tag}-${{ matrix.arch }}" >> tags.txt - done <<< "${{ steps.meta.outputs.tags }}" - { - echo 'tags<> "$GITHUB_OUTPUT" - - - name: Build and push ${{ matrix.platform }} image - uses: docker/build-push-action@v7 - with: - context: . - file: docker/Dockerfile - platforms: ${{ matrix.platform }} - push: true - tags: ${{ steps.arch-tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha,scope=release-${{ matrix.arch }} - cache-to: type=gha,mode=max,scope=release-${{ matrix.arch }} - - publish-docker: - name: Publish Docker Manifests - needs: publish-docker-arch - runs-on: ubuntu-latest - timeout-minutes: 15 - if: startsWith(github.ref, 'refs/tags/') - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Ensure Docker Hub secrets are configured - run: | - if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then - echo "Missing Docker Hub secrets: DOCKERHUB_USERNAME / DOCKERHUB_TOKEN" - exit 1 - fi - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Docker Hub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Docker metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: | - 1467078763/metapi - ghcr.io/${{ github.repository }} - tags: | - type=ref,event=tag - type=raw,value=latest - - - name: Create multi-arch manifests - shell: bash - run: | - set -euo pipefail - while IFS= read -r tag; do - [ -n "$tag" ] || continue - docker buildx imagetools create \ - --tag "$tag" \ - "${tag}-amd64" \ - "${tag}-arm64" - done <<< "${{ steps.meta.outputs.tags }}" +name: Release + +on: + push: + tags: ['v*'] + workflow_dispatch: + +permissions: + contents: write + packages: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + verify: + name: Verify + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build + run: npm run build + + - name: Upload built dist + uses: actions/upload-artifact@v7 + with: + name: metapi-dist-${{ github.sha }} + path: dist + if-no-files-found: error + + build-packages: + name: Build Desktop Package (${{ matrix.platform }}) + needs: verify + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - platform: linux + runner: ubuntu-latest + artifactLabel: linux-x64 + - platform: windows + runner: windows-latest + artifactLabel: windows-x64 + - platform: mac-arm64 + runner: macos-15 + artifactLabel: mac-arm64 + packageCommand: npm run package:desktop -- --mac --arm64 + expectedMacArch: arm64 + - platform: mac-x64 + runner: macos-15-intel + artifactLabel: mac-x64 + packageCommand: npm run package:desktop:mac:intel + expectedMacArch: x64 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Restore Electron cache (Windows) + if: runner.os == 'Windows' + uses: actions/cache@v5 + with: + path: | + ${{ runner.temp }}/.cache/electron + ${{ runner.temp }}/.cache/electron-builder + ${{ env.LOCALAPPDATA }}\electron\Cache + ${{ env.LOCALAPPDATA }}\electron-builder\Cache + key: electron-cache-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'electron-builder.yml') }} + restore-keys: | + electron-cache-${{ runner.os }}- + + - name: Restore Electron cache (Linux) + if: runner.os == 'Linux' + uses: actions/cache@v5 + with: + path: | + ${{ runner.temp }}/.cache/electron + ${{ runner.temp }}/.cache/electron-builder + ${{ runner.temp }}/.cache/appimage-tools + ~/.cache/electron + ~/.cache/electron-builder + key: electron-cache-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'electron-builder.yml') }} + restore-keys: | + electron-cache-${{ runner.os }}- + + - name: Restore Electron cache (macOS) + if: runner.os == 'macOS' + uses: actions/cache@v5 + with: + path: | + ${{ runner.temp }}/.cache/electron + ${{ runner.temp }}/.cache/electron-builder + ~/Library/Caches/electron + ~/Library/Caches/electron-builder + key: electron-cache-${{ runner.os }}-${{ hashFiles('package-lock.json', 'package.json', 'electron-builder.yml') }} + restore-keys: | + electron-cache-${{ runner.os }}- + + - name: Install dependencies + run: npm ci + + - name: Download built dist + uses: actions/download-artifact@v8 + with: + name: metapi-dist-${{ github.sha }} + path: dist + + - name: Build desktop artifacts (Windows) + if: runner.os == 'Windows' + shell: pwsh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + npm_config_cache: ${{ runner.temp }}/.npm + ELECTRON_CACHE: ${{ runner.temp }}/.cache/electron + ELECTRON_BUILDER_CACHE: ${{ runner.temp }}/.cache/electron-builder + ELECTRON_BUILDER_BINARIES_MIRROR: https://github.com/electron-userland/electron-builder-binaries/releases/download/ + npm_config_electron_builder_binaries_mirror: https://github.com/electron-userland/electron-builder-binaries/releases/download/ + WIN_CSC_LINK_INPUT: ${{ secrets.CSC_LINK }} + WIN_CSC_KEY_PASSWORD_INPUT: ${{ secrets.CSC_KEY_PASSWORD }} + run: | + if ($env:WIN_CSC_LINK_INPUT) { $env:WIN_CSC_LINK = $env:WIN_CSC_LINK_INPUT } + if ($env:WIN_CSC_KEY_PASSWORD_INPUT) { $env:WIN_CSC_KEY_PASSWORD = $env:WIN_CSC_KEY_PASSWORD_INPUT } + for ($attempt = 1; $attempt -le 2; $attempt++) { + Write-Host "Desktop build attempt $attempt/2" + npm run package:desktop + if ($LASTEXITCODE -eq 0) { + break + } + if ($attempt -eq 2) { + exit $LASTEXITCODE + } + Start-Sleep -Seconds 90 + } + + - name: Smoke test packaged native modules (Windows) + if: runner.os == 'Windows' + shell: pwsh + env: + ELECTRON_RUN_AS_NODE: 1 + run: | + $appRoot = Join-Path $PWD 'release\win-unpacked\resources\app' + $exePath = Join-Path $PWD 'release\win-unpacked\Metapi.exe' + Push-Location $appRoot + try { + & $exePath -e "require('./node_modules/better-sqlite3'); console.log('better-sqlite3 ok')" + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } finally { + Pop-Location + } + + - name: Build desktop artifacts (Linux) + if: runner.os == 'Linux' + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + npm_config_cache: ${{ runner.temp }}/.npm + ELECTRON_CACHE: ${{ runner.temp }}/.cache/electron + ELECTRON_BUILDER_CACHE: ${{ runner.temp }}/.cache/electron-builder + ELECTRON_BUILDER_BINARIES_MIRROR: https://github.com/electron-userland/electron-builder-binaries/releases/download/ + npm_config_electron_builder_binaries_mirror: https://github.com/electron-userland/electron-builder-binaries/releases/download/ + run: | + set -euo pipefail + for attempt in 1 2; do + echo "Desktop build attempt ${attempt}/2" + if npm run package:desktop; then + break + fi + if [ "$attempt" -eq 2 ]; then + exit 1 + fi + sleep 90 + done + + - name: Build desktop artifacts (macOS) + if: startsWith(matrix.platform, 'mac-') + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + npm_config_cache: ${{ runner.temp }}/.npm + ELECTRON_CACHE: ${{ runner.temp }}/.cache/electron + ELECTRON_BUILDER_CACHE: ${{ runner.temp }}/.cache/electron-builder + ELECTRON_BUILDER_BINARIES_MIRROR: https://github.com/electron-userland/electron-builder-binaries/releases/download/ + npm_config_electron_builder_binaries_mirror: https://github.com/electron-userland/electron-builder-binaries/releases/download/ + APPLE_ID_INPUT: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD_INPUT: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID_INPUT: ${{ secrets.APPLE_TEAM_ID }} + PACKAGE_COMMAND: ${{ matrix.packageCommand }} + run: | + set -euo pipefail + if [ -n "${APPLE_ID_INPUT:-}" ]; then export APPLE_ID="$APPLE_ID_INPUT"; fi + if [ -n "${APPLE_APP_SPECIFIC_PASSWORD_INPUT:-}" ]; then export APPLE_APP_SPECIFIC_PASSWORD="$APPLE_APP_SPECIFIC_PASSWORD_INPUT"; fi + if [ -n "${APPLE_TEAM_ID_INPUT:-}" ]; then export APPLE_TEAM_ID="$APPLE_TEAM_ID_INPUT"; fi + for attempt in 1 2; do + echo "Desktop build (${PACKAGE_COMMAND}) attempt ${attempt}/2" + if eval "${PACKAGE_COMMAND}"; then + break + fi + if [ "$attempt" -eq 2 ]; then + exit 1 + fi + sleep 90 + done + + - name: Verify packaged macOS architecture + if: startsWith(matrix.platform, 'mac-') + shell: bash + env: + expectedMacArch: ${{ matrix.expectedMacArch }} + run: | + node scripts/desktop/verifyMacArchitecture.mjs --release-dir release --expected-arch "$expectedMacArch" + + - name: Upload desktop package + uses: actions/upload-artifact@v7 + with: + name: metapi-desktop-${{ github.run_id }}-${{ matrix.artifactLabel }} + path: | + release/*.exe + release/*.dmg + release/*.zip + release/*.AppImage + release/*.deb + release/*.blockmap + release/latest*.yml + release/latest*.json + release/builder-debug.yml + if-no-files-found: error + + publish-release: + name: Publish GitHub Release + needs: build-packages + runs-on: ubuntu-latest + timeout-minutes: 15 + if: startsWith(github.ref, 'refs/tags/') + + steps: + - name: Download package artifacts + uses: actions/download-artifact@v8 + with: + path: release-assets + merge-multiple: true + + - name: Show release files + run: ls -la release-assets + + - name: Upload Release Assets + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + generate_release_notes: true + files: release-assets/* + + publish-docker-arch: + name: Publish Docker Image (${{ matrix.arch }}) + needs: build-packages + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + if: startsWith(github.ref, 'refs/tags/') + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-latest + - arch: arm64 + platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Ensure Docker Hub secrets are configured + run: | + if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then + echo "Missing Docker Hub secrets: DOCKERHUB_USERNAME / DOCKERHUB_TOKEN" + exit 1 + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: | + 1467078763/metapi + ghcr.io/${{ github.repository }} + tags: | + type=ref,event=tag + type=raw,value=latest + + - name: Add architecture suffix to tags + id: arch-tags + shell: bash + run: | + set -euo pipefail + rm -f tags.txt + while IFS= read -r tag; do + [ -n "$tag" ] || continue + echo "${tag}-${{ matrix.arch }}" >> tags.txt + done <<< "${{ steps.meta.outputs.tags }}" + { + echo 'tags<> "$GITHUB_OUTPUT" + + - name: Build and push ${{ matrix.platform }} image + uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile + platforms: ${{ matrix.platform }} + push: true + tags: ${{ steps.arch-tags.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=release-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=release-${{ matrix.arch }} + + publish-docker: + name: Publish Docker Manifests + needs: publish-docker-arch + runs-on: ubuntu-latest + timeout-minutes: 15 + if: startsWith(github.ref, 'refs/tags/') + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Ensure Docker Hub secrets are configured + run: | + if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then + echo "Missing Docker Hub secrets: DOCKERHUB_USERNAME / DOCKERHUB_TOKEN" + exit 1 + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: | + 1467078763/metapi + ghcr.io/${{ github.repository }} + tags: | + type=ref,event=tag + type=raw,value=latest + + - name: Create multi-arch manifests + shell: bash + run: | + set -euo pipefail + while IFS= read -r tag; do + [ -n "$tag" ] || continue + docker buildx imagetools create \ + --tag "$tag" \ + "${tag}-amd64" \ + "${tag}-arm64" + done <<< "${{ steps.meta.outputs.tags }}"