zc2 0.0.25

P2P compute broker with credit-based billing, WAL, and broker mesh support
name: Docker

on:
  # No `pull_request` trigger: the amd64 image build ran 3-6 min on PRs
  # and pushed nothing (push=false on PRs), so it only slowed the gate.
  # The image is built+pushed post-merge on the trunk (master) and built
  # multi-arch on a published release. A broken Dockerfile is caught on
  # the master run before any release is cut — fix-forward on trunk.
  push:
    branches:
      - master
      - main
      - 'release/**'
    paths-ignore:
      - "**.md"
      - "docs/**"
      - ".github/workflows/security.yml"
      - ".github/workflows/auto-release.yml"
      - ".github/workflows/release.yml"
      - ".pre-commit-config.yaml"
  # The arm64 cross-build only runs here — when a GitHub Release is
  # published (the tail end of the auto-release flow). See the matrix
  # comment below.
  release:
    types: [published]
  # Manual trigger — lets the amd64 build (and its cache behavior / timing)
  # be validated on a branch without merging to master first.
  workflow_dispatch:

concurrency:
  group: docker-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ !startsWith(github.ref, 'refs/heads/master') && !startsWith(github.ref, 'refs/heads/main') && !startsWith(github.ref, 'refs/heads/release/') }}

jobs:
  build:
    runs-on: cpu
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        # arm64 under QEMU takes 30-60 min, so it is reserved for the
        # release CI: only a published GitHub Release builds the full
        # multi-arch matrix. Every other trigger — PRs, feature branches,
        # and master / main / release branch pushes — builds amd64 only,
        # which almost always reproduces what arm64 would have caught.
        platform: ${{ github.event_name == 'release' && fromJSON('["linux/amd64", "linux/arm64"]') || fromJSON('["linux/amd64"]') }}
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # v7.0.0

      # zakuro-client is a git dep on the private zakuro-ai/zakuro-drive repo.
      # Load the read-only deploy key into an ssh-agent and forward it into
      # the image build (build-push-action `ssh:` → Dockerfile
      # `RUN --mount=type=ssh`), so the key never lands in a layer.
      - name: Start ssh-agent with the drive deploy key
        run: |
          eval "$(ssh-agent -s)"
          echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
          echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV"
          printf '%s\n' "${{ secrets.DRIVE_DEPLOY_KEY }}" | ssh-add -

      - name: Set up QEMU
        # Only needed for the arm64 cross-build, which runs solely on release
        # events. On every other trigger the matrix is amd64-only, where QEMU
        # is pure overhead (~2-3 min of binfmt setup for nothing), so gate it
        # on the same condition that adds arm64 to the matrix.
        if: github.event_name == 'release'
        uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3  # v4
        with:
          platforms: arm64

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5  # v4

      - name: Log in to Docker Hub
        if: github.event_name != 'pull_request'
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee  # v4
        with:
          username: zakuroai
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf  # v7
        with:
          context: .
          file: docker/Dockerfile
          platforms: ${{ matrix.platform }}
          ssh: default=${{ env.SSH_AUTH_SOCK }}
          outputs: type=image,name=zakuroai/zc,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
          cache-from: type=gha,scope=${{ matrix.platform }}
          cache-to: type=gha,scope=${{ matrix.platform }},mode=max

      - name: Export digest
        if: github.event_name != 'pull_request'
        run: |
          mkdir -p /tmp/digests
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digest
        if: github.event_name != 'pull_request'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a  # v7
        with:
          # matrix.platform is "linux/amd64" / "linux/arm64"; the slash
          # breaks artifact names, so swap it for a dash.
          name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    runs-on: cpu
    timeout-minutes: 10
    if: github.event_name != 'pull_request'
    needs: build
    steps:
      - name: Download digests
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c  # v8
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5  # v4

      - name: Log in to Docker Hub
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee  # v4
        with:
          username: zakuroai
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Create manifest list and push
        working-directory: /tmp/digests
        run: |
          docker buildx imagetools create -t zakuroai/zc:latest \
            $(printf 'zakuroai/zc@sha256:%s ' *)