name: Verify and publish ARM64 image on: pull_request: paths: - Dockerfile - .dockerignore - pyproject.toml - uv.lock - jarca_ledger_mcp/** - dashboard/** - .github/workflows/publish-image.yml push: tags: - "v*" workflow_dispatch: permissions: contents: read concurrency: group: image-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: verify-arm64: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 with: platforms: arm64 - uses: docker/setup-buildx-action@v3 - name: Build ARM64 verification image uses: docker/build-push-action@v6 with: context: . platforms: linux/arm64 load: true push: false tags: jarca-ledger-mcp:arm64-verify cache-from: type=gha,scope=jarca-ledger-arm64 cache-to: type=gha,mode=max,scope=jarca-ledger-arm64 - name: Verify image platform, imports, and SQLite run: | set -euo pipefail test "$(docker image inspect jarca-ledger-mcp:arm64-verify --format '{{.Architecture}}')" = arm64 docker run --rm --platform linux/arm64 \ -e JARCA_LEDGER_DB_PATH=/tmp/ledger.sqlite3 \ jarca-ledger-mcp:arm64-verify \ python -c 'import platform, cryptography, numpy, pandas, pyarrow, pymupdf; from jarca_ledger_mcp import service; result=service.health_check(); assert platform.machine() == "aarch64"; assert result["success"], result; print(result["schema_version"])' - name: Verify MCP and Dashboard startup run: | set -euo pipefail trap 'docker rm -f jarca-mcp-smoke jarca-dashboard-smoke >/dev/null 2>&1 || true' EXIT docker run -d --name jarca-mcp-smoke --platform linux/arm64 \ jarca-ledger-mcp:arm64-verify for _ in $(seq 1 60); do status=$(docker inspect --format '{{.State.Health.Status}}' jarca-mcp-smoke) test "$status" = healthy && break test "$status" = unhealthy && { docker logs jarca-mcp-smoke; exit 1; } sleep 1 done test "$(docker inspect --format '{{.State.Health.Status}}' jarca-mcp-smoke)" = healthy docker run -d --name jarca-dashboard-smoke --platform linux/arm64 \ -p 127.0.0.1::8501 jarca-ledger-mcp:arm64-verify \ python -m streamlit run dashboard/app.py \ --server.address 0.0.0.0 --server.port 8501 \ --server.headless true --browser.gatherUsageStats false port=$(docker port jarca-dashboard-smoke 8501/tcp | sed 's/.*://') code=000 for _ in $(seq 1 90); do code=$(curl -sS -o /tmp/dashboard-health -w '%{http_code}' \ "http://127.0.0.1:${port}/_stcore/health" || true) test "$code" = 200 && break sleep 1 done test "$code" = 200 test "$(tr -d '\n' < /tmp/dashboard-health)" = ok publish: if: startsWith(github.ref, 'refs/tags/v') needs: verify-arm64 runs-on: ubuntu-latest permissions: contents: read packages: write outputs: digest: ${{ steps.build.outputs.digest }} steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 with: platforms: arm64 - uses: docker/setup-buildx-action@v3 - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and publish immutable ARM64 image id: build uses: docker/build-push-action@v6 with: context: . platforms: linux/arm64 push: true tags: | ghcr.io/genichin/jarca-ledger-mcp:${{ github.ref_name }} ghcr.io/genichin/jarca-ledger-mcp:sha-${{ github.sha }} labels: | org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.version=${{ github.ref_name }} cache-from: type=gha,scope=jarca-ledger-arm64 cache-to: type=gha,mode=max,scope=jarca-ledger-arm64 provenance: mode=max sbom: true - name: Report published digest run: | echo "ghcr.io/genichin/jarca-ledger-mcp:${GITHUB_REF_NAME}@${{ steps.build.outputs.digest }}"