diff --git a/.github/workflows/publish-dvm-image.yml b/.github/workflows/publish-dvm-image.yml new file mode 100644 index 0000000..e9c17a0 --- /dev/null +++ b/.github/workflows/publish-dvm-image.yml @@ -0,0 +1,125 @@ +# Publish DVM Image to GHCR +# +# Trigger scenarios: +# - Push of tag v* → build + push versioned tag + :latest, sign digest. +# - workflow_dispatch → build + push versioned tag only (NO :latest move), for rehearsals. +# +# Single-platform: linux/amd64 only. arm64 deferred — QEMU crashes on the +# Solana/Rust dependency chain ("Illegal instruction"). Native arm64 runners +# can be added later via per-arch matrix + imagetools create manifest-merge. +# +# Prerequisite: Dockerfile.dvm must build standalone from this repo's context +# (issue #2 — trim to dvm-only). Until #2 is merged the build step will fail +# because the Dockerfile still references monorepo paths (packages/*, docker/). +# +# Cosign verify (post-publish runbook): +# digest=$(docker buildx imagetools inspect ghcr.io/toon-protocol/store:latest \ +# --format '{{.Manifest.Digest}}') +# cosign verify ghcr.io/toon-protocol/store@$digest \ +# --certificate-identity-regexp '^https://github.com/toon-protocol/store/' \ +# --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' + +name: Publish DVM Image + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Version tag for image (e.g. 0.1.0-rc1). Required for workflow_dispatch.' + required: true + type: string + +concurrency: + group: '${{ github.workflow }}-${{ github.ref }}' + cancel-in-progress: false + +permissions: + contents: read + packages: write + id-token: write # required for cosign keyless OIDC token + +env: + IMAGE: ghcr.io/toon-protocol/store + +jobs: + build-and-push: + runs-on: ubuntu-latest + timeout-minutes: 90 + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Log in to GHCR + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image metadata (tags) + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable=${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') }} + type=match,pattern=v(.*),group=1 + type=raw,value=${{ inputs.version }},enable=${{ inputs.version != '' }} + type=sha,prefix=sha- + + # Pass 1: build only — load into local daemon for smoke test (no push yet) + - name: Build (no push) + id: build-local + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + file: Dockerfile.dvm + platforms: linux/amd64 + load: true + tags: smoke-test-local:latest + cache-from: type=gha,scope=store-linux-amd64 + cache-to: type=gha,mode=max,scope=store-linux-amd64 + + - name: Smoke-test DVM image (pre-push) + run: | + set -euo pipefail + IMG="smoke-test-local:latest" + docker run --rm --entrypoint sh "${IMG}" -c \ + "test -s '/app/entrypoint-dvm.js'" \ + || { echo "FAIL: /app/entrypoint-dvm.js missing or empty"; exit 1; } + NODE_VERSION=$(docker run --rm --entrypoint node "${IMG}" --version) + case "${NODE_VERSION}" in + v20.*) ;; + *) echo "FAIL: expected node v20.x, got ${NODE_VERSION}"; exit 1 ;; + esac + echo "/app/entrypoint-dvm.js present + ${NODE_VERSION}" + + # Pass 2: push only — BuildKit GHA cache makes this near-instant (no rebuild) + - name: Push + id: build + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + file: Dockerfile.dvm + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=store-linux-amd64 + + - name: Install cosign + uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1 + + - name: Sign image digest + env: + COSIGN_YES: 'true' + DIGEST: ${{ steps.build.outputs.digest }} + run: | + cosign sign "${IMAGE}@${DIGEST}"