From ccfe33e49a789a71ee3ef23ba237a7db86b8debe Mon Sep 17 00:00:00 2001 From: MaximEdogawa Date: Mon, 13 Apr 2026 23:43:06 +0200 Subject: [PATCH 1/3] fix: update GitHub Actions workflow and documentation for tool publishing - Adjusted the `tools-publish.yml` workflow to use `github.repository_owner` for Docker login instead of `github.actor`. - Reformatted the table in `manual-publish.md` for improved readability, ensuring consistent alignment of headers and values. --- .github/workflows/tools-publish.yml | 4 ++-- doc/tool-engine/manual-publish.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tools-publish.yml b/.github/workflows/tools-publish.yml index 2451cc2..0208f1c 100644 --- a/.github/workflows/tools-publish.yml +++ b/.github/workflows/tools-publish.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest outputs: matrix: ${{ steps.detect.outputs.matrix }} - skip: ${{ steps.detect.outputs.skip }} + skip: ${{ steps.detect.outputs.skip }} steps: - uses: actions/checkout@v4 with: @@ -58,7 +58,7 @@ jobs: - uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.actor }} + username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - uses: sigstore/cosign-installer@v3 diff --git a/doc/tool-engine/manual-publish.md b/doc/tool-engine/manual-publish.md index 240e067..643ed91 100644 --- a/doc/tool-engine/manual-publish.md +++ b/doc/tool-engine/manual-publish.md @@ -4,9 +4,9 @@ Tool container images for Pengine live on **GitHub Container Registry (GHCR)**. ## Where the images are -| Piece | Value | -|--------|--------| -| Registry host | `ghcr.io` | +| Piece | Value | +| --------------- | ----------------------------------- | +| Registry host | `ghcr.io` | | Repository path | `pengine-ai/tools/pengine-` | The `` comes from the tool `id` in `tools/mcp-tools.json`. Example: id `pengine/file-manager` → image `ghcr.io/pengine-ai/tools/pengine-file-manager`. From ed31ba051ce8b188870af133dd39b39c661c832c Mon Sep 17 00:00:00 2001 From: MaximEdogawa Date: Mon, 13 Apr 2026 23:48:57 +0200 Subject: [PATCH 2/3] feat: implement multi-architecture support for tool image publishing - Removed the deprecated `compute-image-tags.sh` script and introduced `merge-multiarch-manifest.sh` to handle merging single-arch image references into a multi-arch tag. - Updated the `tools-publish.yml` workflow to support building images for both `amd64` and `arm64` architectures, utilizing conditional runners based on architecture. - Enhanced documentation in `manual-publish.md` to clarify the CI build process for different architectures and the implications of using QEMU for arm64 builds. --- .../tools-publish/compute-image-tags.sh | 24 ----- .../tools-publish/merge-multiarch-manifest.sh | 38 ++++++++ .github/workflows/tools-publish.yml | 88 +++++++++++++++---- doc/tool-engine/manual-publish.md | 2 + 4 files changed, 110 insertions(+), 42 deletions(-) delete mode 100755 .github/scripts/tools-publish/compute-image-tags.sh create mode 100755 .github/scripts/tools-publish/merge-multiarch-manifest.sh diff --git a/.github/scripts/tools-publish/compute-image-tags.sh b/.github/scripts/tools-publish/compute-image-tags.sh deleted file mode 100755 index 81a1f4f..0000000 --- a/.github/scripts/tools-publish/compute-image-tags.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -# Writes multiline "tags" to GITHUB_OUTPUT for docker/build-push-action. -# Env: IMAGE, VERSION, REF_TYPE (github.ref_type: branch|tag), GITHUB_REF. -set -euo pipefail - -TAGS="${IMAGE}:${VERSION}" -LATEST=false -if [[ "${REF_TYPE}" == "branch" && "${GITHUB_REF}" == "refs/heads/main" ]]; then - LATEST=true -elif [[ "${REF_TYPE}" == "tag" ]]; then - T="${GITHUB_REF#refs/tags/}" - T="${T#v}" - if [[ "$T" == "$VERSION" ]]; then - LATEST=true - fi -fi -if [[ "$LATEST" == "true" ]]; then - TAGS="${TAGS}"$'\n'"${IMAGE}:latest" -fi -{ - echo 'tags<> "$GITHUB_OUTPUT" diff --git a/.github/scripts/tools-publish/merge-multiarch-manifest.sh b/.github/scripts/tools-publish/merge-multiarch-manifest.sh new file mode 100755 index 0000000..81c11a5 --- /dev/null +++ b/.github/scripts/tools-publish/merge-multiarch-manifest.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Merge two single-arch image refs (image@sha256:...) into one multi-arch tag on GHCR. +# Env: IMAGE, VERSION, REF_TYPE, GITHUB_REF, AMD_FILE, ARM_FILE (paths to one-line image@digest each). +# Appends digest=sha256:... to GITHUB_OUTPUT (manifest list digest for cosign). +set -euo pipefail + +AMD=$(tr -d '[:space:]' <"$AMD_FILE") +ARM=$(tr -d '[:space:]' <"$ARM_FILE") + +ARGS=(-t "${IMAGE}:${VERSION}") +LATEST=false +if [[ "${REF_TYPE}" == "branch" && "${GITHUB_REF}" == "refs/heads/main" ]]; then + LATEST=true +elif [[ "${REF_TYPE}" == "tag" ]]; then + T="${GITHUB_REF#refs/tags/}" + T="${T#v}" + if [[ "$T" == "$VERSION" ]]; then + LATEST=true + fi +fi +if [[ "$LATEST" == "true" ]]; then + ARGS+=(-t "${IMAGE}:latest") +fi + +docker buildx imagetools create "${ARGS[@]}" "$AMD" "$ARM" + +# Index digest for signing (text/json shape varies across buildx versions). +json=$(docker buildx imagetools inspect "${IMAGE}:${VERSION}" --format '{{json .}}' 2>/dev/null || true) +DIGEST=$(echo "$json" | jq -r '.digest // .manifest.digest // .Manifest.Descriptor.Digest // empty' 2>/dev/null || true) +if [[ -z "$DIGEST" || "$DIGEST" == "null" ]]; then + DIGEST=$(docker buildx imagetools inspect "${IMAGE}:${VERSION}" 2>/dev/null | awk '/^[Dd]igest:/ {print $2; exit}') +fi +if [[ -z "$DIGEST" || "$DIGEST" == "null" ]]; then + echo "::error::Could not read manifest list digest from imagetools inspect" >&2 + exit 1 +fi +echo "digest=$DIGEST" >>"$GITHUB_OUTPUT" +echo "Merged ${IMAGE}:${VERSION} -> $DIGEST" diff --git a/.github/workflows/tools-publish.yml b/.github/workflows/tools-publish.yml index 0208f1c..8793c9d 100644 --- a/.github/workflows/tools-publish.yml +++ b/.github/workflows/tools-publish.yml @@ -5,6 +5,9 @@ name: Publish tool images # tools/mcp-tools.json is the registry — one entry per tool. # tools//Dockerfile is the build context. # +# Multi-arch: linux/arm64 is built on ubuntu-24.04-arm (native). Building arm64 +# on ubuntu-latest uses QEMU; Node/npm often crashes with "Illegal instruction". +# # Triggers: # - Push to main touching tools/** → build only changed tools # - Manual dispatch → pick one slug or "all" @@ -44,14 +47,15 @@ jobs: INPUT_TOOL: ${{ github.event.inputs.tool }} run: bash .github/scripts/tools-publish/detect-matrix.sh - publish: + publish-arch: needs: detect if: needs.detect.outputs.skip == 'false' - runs-on: ubuntu-latest strategy: fail-fast: false matrix: slug: ${{ fromJson(needs.detect.outputs.matrix) }} + arch: [amd64, arm64] + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 @@ -60,7 +64,6 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: sigstore/cosign-installer@v3 - name: Read manifest id: cfg @@ -68,38 +71,87 @@ jobs: TOOL_SLUG: ${{ matrix.slug }} run: bash .github/scripts/tools-publish/read-tool-manifest.sh - - name: Compute image tags - id: img_tags - env: - IMAGE: ${{ steps.cfg.outputs.image }} - VERSION: ${{ steps.cfg.outputs.version }} - REF_TYPE: ${{ github.ref_type }} - run: bash .github/scripts/tools-publish/compute-image-tags.sh - - - name: Build and push + - name: Build and push (single arch) id: build uses: docker/build-push-action@v6 with: context: tools/${{ matrix.slug }}/ - platforms: linux/amd64,linux/arm64 + platforms: linux/${{ matrix.arch }} push: true + provenance: false + sbom: false build-args: | UPSTREAM_MCP_NPM_PACKAGE=${{ steps.cfg.outputs.npm_pkg }} UPSTREAM_MCP_NPM_VERSION=${{ steps.cfg.outputs.npm_ver }} - tags: ${{ steps.img_tags.outputs.tags }} + tags: ${{ steps.cfg.outputs.image }}:${{ steps.cfg.outputs.version }}-build-${{ github.run_id }}-${{ matrix.slug }}-${{ matrix.arch }} + + - name: Upload image ref for manifest merge + run: | + echo "${{ steps.cfg.outputs.image }}@${{ steps.build.outputs.digest }}" > image-ref.txt + - uses: actions/upload-artifact@v4 + with: + name: tool-${{ matrix.slug }}-${{ matrix.arch }} + path: image-ref.txt + retention-days: 2 + + publish-merge: + needs: [detect, publish-arch] + if: needs.detect.outputs.skip == 'false' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + slug: ${{ fromJson(needs.detect.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: sigstore/cosign-installer@v3 + + - name: Read manifest + id: cfg + env: + TOOL_SLUG: ${{ matrix.slug }} + run: bash .github/scripts/tools-publish/read-tool-manifest.sh + + - name: Download amd64 image ref + uses: actions/download-artifact@v4 + with: + name: tool-${{ matrix.slug }}-amd64 + path: arch-refs/amd64 + - name: Download arm64 image ref + uses: actions/download-artifact@v4 + with: + name: tool-${{ matrix.slug }}-arm64 + path: arch-refs/arm64 + + - name: Merge manifest list + id: merge + env: + IMAGE: ${{ steps.cfg.outputs.image }} + VERSION: ${{ steps.cfg.outputs.version }} + REF_TYPE: ${{ github.ref_type }} + GITHUB_REF: ${{ github.ref }} + AMD_FILE: arch-refs/amd64/image-ref.txt + ARM_FILE: arch-refs/arm64/image-ref.txt + run: bash .github/scripts/tools-publish/merge-multiarch-manifest.sh - name: Sign image - run: cosign sign --yes "${{ steps.cfg.outputs.image }}@${{ steps.build.outputs.digest }}" + run: cosign sign --yes "${{ steps.cfg.outputs.image }}@${{ steps.merge.outputs.digest }}" - name: Smoke test (MCP init handshake) env: - IMAGE_WITH_DIGEST: ${{ steps.cfg.outputs.image }}@${{ steps.build.outputs.digest }} + IMAGE_WITH_DIGEST: ${{ steps.cfg.outputs.image }}@${{ steps.merge.outputs.digest }} run: bash .github/scripts/tools-publish/smoke-test-mcp.sh - name: Summary env: TOOL_SLUG: ${{ matrix.slug }} TOOL_VERSION: ${{ steps.cfg.outputs.version }} - IMAGE_REF: ${{ steps.cfg.outputs.image }}@${{ steps.build.outputs.digest }} - IMAGE_DIGEST: ${{ steps.build.outputs.digest }} + IMAGE_REF: ${{ steps.cfg.outputs.image }}@${{ steps.merge.outputs.digest }} + IMAGE_DIGEST: ${{ steps.merge.outputs.digest }} run: bash .github/scripts/tools-publish/write-publish-summary.sh diff --git a/doc/tool-engine/manual-publish.md b/doc/tool-engine/manual-publish.md index 643ed91..e81cc7f 100644 --- a/doc/tool-engine/manual-publish.md +++ b/doc/tool-engine/manual-publish.md @@ -103,6 +103,8 @@ This checks the npm registry for newer versions, bumps `mcp-tools.json`, and pri Or just push changes to `tools/` on `main` — CI builds automatically for tools whose version changed. +CI builds **linux/amd64** on `ubuntu-latest` and **linux/arm64** on **`ubuntu-24.04-arm`** (native), then merges a multi-arch manifest with `docker buildx imagetools create`. Building arm64 on an Intel runner uses QEMU; **Node/npm during `docker build` often exits with Illegal instruction** under emulation, so arm64 is not built that way. + --- ## Troubleshooting: 0 commands From 5666d12b604ec7481a1aa5c42f0dd17d4c528c20 Mon Sep 17 00:00:00 2001 From: MaximEdogawa Date: Tue, 14 Apr 2026 00:15:21 +0200 Subject: [PATCH 3/3] feat: add GHCR verification workflow and update tool image references - Introduced a new GitHub Actions workflow (`ghcr-verify.yml`) to verify GHCR login by pushing a minimal image and performing a read check. - Added a Dockerfile for the verification context to facilitate the image push. - Updated tool image references in various files to reflect the new repository structure (`ghcr.io/pengine-ai/pengine-` instead of `ghcr.io/pengine-ai/tools/pengine-`). - Enhanced the `write-publish-summary.sh` script to include optional runner architecture information in the summary output. --- .github/ghcr-verify-context/Dockerfile | 3 + .../tools-publish/merge-multiarch-manifest.sh | 9 +-- .../tools-publish/write-publish-summary.sh | 16 +++- .github/workflows/ghcr-verify.yml | 67 ++++++++++++++++ .github/workflows/tools-publish.yml | 80 ++++++------------- doc/tool-engine/manual-publish.md | 10 +-- src-tauri/src/modules/tool_engine/service.rs | 6 +- src-tauri/src/modules/tool_engine/tools.json | 2 +- src-tauri/src/modules/tool_engine/types.rs | 2 +- tools/mcp-tools.json | 2 +- 10 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 .github/ghcr-verify-context/Dockerfile create mode 100644 .github/workflows/ghcr-verify.yml diff --git a/.github/ghcr-verify-context/Dockerfile b/.github/ghcr-verify-context/Dockerfile new file mode 100644 index 0000000..0eef1a6 --- /dev/null +++ b/.github/ghcr-verify-context/Dockerfile @@ -0,0 +1,3 @@ +# Minimal image to verify Actions can push to ghcr.io/pengine-ai/... +FROM scratch +COPY README.md /README.md diff --git a/.github/scripts/tools-publish/merge-multiarch-manifest.sh b/.github/scripts/tools-publish/merge-multiarch-manifest.sh index 81c11a5..aaf02a8 100755 --- a/.github/scripts/tools-publish/merge-multiarch-manifest.sh +++ b/.github/scripts/tools-publish/merge-multiarch-manifest.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# Merge two single-arch image refs (image@sha256:...) into one multi-arch tag on GHCR. -# Env: IMAGE, VERSION, REF_TYPE, GITHUB_REF, AMD_FILE, ARM_FILE (paths to one-line image@digest each). +# Merge two single-arch refs (image@sha256:...) into one multi-arch tag on GHCR. +# Env: IMAGE, VERSION, REF_TYPE, GITHUB_REF, AMD_REF, ARM_REF. # Appends digest=sha256:... to GITHUB_OUTPUT (manifest list digest for cosign). set -euo pipefail -AMD=$(tr -d '[:space:]' <"$AMD_FILE") -ARM=$(tr -d '[:space:]' <"$ARM_FILE") +AMD="${AMD_REF:?}" +ARM="${ARM_REF:?}" ARGS=(-t "${IMAGE}:${VERSION}") LATEST=false @@ -24,7 +24,6 @@ fi docker buildx imagetools create "${ARGS[@]}" "$AMD" "$ARM" -# Index digest for signing (text/json shape varies across buildx versions). json=$(docker buildx imagetools inspect "${IMAGE}:${VERSION}" --format '{{json .}}' 2>/dev/null || true) DIGEST=$(echo "$json" | jq -r '.digest // .manifest.digest // .Manifest.Descriptor.Digest // empty' 2>/dev/null || true) if [[ -z "$DIGEST" || "$DIGEST" == "null" ]]; then diff --git a/.github/scripts/tools-publish/write-publish-summary.sh b/.github/scripts/tools-publish/write-publish-summary.sh index e60cef8..c1c480a 100755 --- a/.github/scripts/tools-publish/write-publish-summary.sh +++ b/.github/scripts/tools-publish/write-publish-summary.sh @@ -1,16 +1,24 @@ #!/usr/bin/env bash # Appends a job summary section for one published tool. -# Env: TOOL_SLUG, TOOL_VERSION, IMAGE_REF, IMAGE_DIGEST. +# Env: TOOL_SLUG, TOOL_VERSION, IMAGE_REF, IMAGE_DIGEST, RUNNER_ARCH (optional; GitHub runner.arch). set -euo pipefail +RUNNER_LINE="" +if [[ -n "${RUNNER_ARCH:-}" ]]; then + RUNNER_LINE="- **CI host arch:** \`${RUNNER_ARCH}\` — smoke test pulled the matching layer from the **single** multi-arch manifest below (your machine does the same)." +fi + cat >> "$GITHUB_STEP_SUMMARY" <\` / \`:${TOOL_VERSION}-ci-arm64-\` may appear in the package; **use \`:${TOOL_VERSION}\` or the digest above**—not the \`-ci-*\` tags. EOF diff --git a/.github/workflows/ghcr-verify.yml b/.github/workflows/ghcr-verify.yml new file mode 100644 index 0000000..0d87bc4 --- /dev/null +++ b/.github/workflows/ghcr-verify.yml @@ -0,0 +1,67 @@ +name: Verify GHCR login + +# Manual-only: push a tiny image to ghcr.io/... to debug GITHUB_TOKEN + GHCR. + +on: + workflow_dispatch: + inputs: + image: + description: 'Full image name without tag (e.g. ghcr.io/pengine-ai/pengine-file-manager)' + required: false + default: 'ghcr.io/pengine-ai/pengine-ghcr-verify' + +permissions: + contents: read + packages: write + +jobs: + verify: + runs-on: ubuntu-latest + env: + # workflow_dispatch input can be cleared in UI; fall back to default. + VERIFY_IMAGE: ${{ github.event.inputs.image || 'ghcr.io/pengine-ai/pengine-ghcr-verify' }} + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push probe image + id: build + uses: docker/build-push-action@v6 + with: + context: .github/ghcr-verify-context + file: .github/ghcr-verify-context/Dockerfile + platforms: linux/amd64 + push: true + provenance: false + sbom: false + tags: ${{ env.VERIFY_IMAGE }}:verify-${{ github.run_id }} + + - name: Pull probe (read check) + env: + REF: ${{ env.VERIFY_IMAGE }}@${{ steps.build.outputs.digest }} + run: | + set -euo pipefail + docker pull "$REF" + + - name: Summary + env: + REF: ${{ env.VERIFY_IMAGE }}@${{ steps.build.outputs.digest }} + TAG: ${{ env.VERIFY_IMAGE }}:verify-${{ github.run_id }} + run: | + { + echo '### GHCR verify' + echo "Push succeeded for **\`$TAG\`** (digest below)." + echo "Read check: \`docker pull\` by digest succeeded." + echo "" + echo "**Digest:** \`$REF\`" + echo "" + echo 'You can delete the probe tag in **Packages** when finished.' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/tools-publish.yml b/.github/workflows/tools-publish.yml index 8793c9d..e23a503 100644 --- a/.github/workflows/tools-publish.yml +++ b/.github/workflows/tools-publish.yml @@ -5,8 +5,8 @@ name: Publish tool images # tools/mcp-tools.json is the registry — one entry per tool. # tools//Dockerfile is the build context. # -# Multi-arch: linux/arm64 is built on ubuntu-24.04-arm (native). Building arm64 -# on ubuntu-latest uses QEMU; Node/npm often crashes with "Illegal instruction". +# Two build-push steps (amd64, arm64) on ubuntu-24.04-arm, then one merge step +# so GHCR and the job summary show a single multi-arch digest (not two products). # # Triggers: # - Push to main touching tools/** → build only changed tools @@ -47,23 +47,24 @@ jobs: INPUT_TOOL: ${{ github.event.inputs.tool }} run: bash .github/scripts/tools-publish/detect-matrix.sh - publish-arch: + publish: needs: detect if: needs.detect.outputs.skip == 'false' + runs-on: ubuntu-24.04-arm strategy: fail-fast: false matrix: slug: ${{ fromJson(needs.detect.outputs.matrix) }} - arch: [amd64, arm64] - runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - uses: sigstore/cosign-installer@v3 - name: Read manifest id: cfg @@ -71,73 +72,43 @@ jobs: TOOL_SLUG: ${{ matrix.slug }} run: bash .github/scripts/tools-publish/read-tool-manifest.sh - - name: Build and push (single arch) - id: build + - name: Build and push (linux/arm64) + id: build_arm64 uses: docker/build-push-action@v6 with: context: tools/${{ matrix.slug }}/ - platforms: linux/${{ matrix.arch }} + platforms: linux/arm64 push: true provenance: false sbom: false build-args: | UPSTREAM_MCP_NPM_PACKAGE=${{ steps.cfg.outputs.npm_pkg }} UPSTREAM_MCP_NPM_VERSION=${{ steps.cfg.outputs.npm_ver }} - tags: ${{ steps.cfg.outputs.image }}:${{ steps.cfg.outputs.version }}-build-${{ github.run_id }}-${{ matrix.slug }}-${{ matrix.arch }} - - - name: Upload image ref for manifest merge - run: | - echo "${{ steps.cfg.outputs.image }}@${{ steps.build.outputs.digest }}" > image-ref.txt - - uses: actions/upload-artifact@v4 - with: - name: tool-${{ matrix.slug }}-${{ matrix.arch }} - path: image-ref.txt - retention-days: 2 - - publish-merge: - needs: [detect, publish-arch] - if: needs.detect.outputs.skip == 'false' - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - slug: ${{ fromJson(needs.detect.outputs.matrix) }} - steps: - - uses: actions/checkout@v4 - - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - uses: sigstore/cosign-installer@v3 + tags: ${{ steps.cfg.outputs.image }}:${{ steps.cfg.outputs.version }}-ci-arm64-${{ github.run_id }} - - name: Read manifest - id: cfg - env: - TOOL_SLUG: ${{ matrix.slug }} - run: bash .github/scripts/tools-publish/read-tool-manifest.sh - - - name: Download amd64 image ref - uses: actions/download-artifact@v4 - with: - name: tool-${{ matrix.slug }}-amd64 - path: arch-refs/amd64 - - name: Download arm64 image ref - uses: actions/download-artifact@v4 + - name: Build and push (linux/amd64) + id: build_amd64 + uses: docker/build-push-action@v6 with: - name: tool-${{ matrix.slug }}-arm64 - path: arch-refs/arm64 + context: tools/${{ matrix.slug }}/ + platforms: linux/amd64 + push: true + provenance: false + sbom: false + build-args: | + UPSTREAM_MCP_NPM_PACKAGE=${{ steps.cfg.outputs.npm_pkg }} + UPSTREAM_MCP_NPM_VERSION=${{ steps.cfg.outputs.npm_ver }} + tags: ${{ steps.cfg.outputs.image }}:${{ steps.cfg.outputs.version }}-ci-amd64-${{ github.run_id }} - - name: Merge manifest list + - name: Merge multi-arch manifest id: merge env: IMAGE: ${{ steps.cfg.outputs.image }} VERSION: ${{ steps.cfg.outputs.version }} REF_TYPE: ${{ github.ref_type }} GITHUB_REF: ${{ github.ref }} - AMD_FILE: arch-refs/amd64/image-ref.txt - ARM_FILE: arch-refs/arm64/image-ref.txt + AMD_REF: ${{ steps.cfg.outputs.image }}@${{ steps.build_amd64.outputs.digest }} + ARM_REF: ${{ steps.cfg.outputs.image }}@${{ steps.build_arm64.outputs.digest }} run: bash .github/scripts/tools-publish/merge-multiarch-manifest.sh - name: Sign image @@ -154,4 +125,5 @@ jobs: TOOL_VERSION: ${{ steps.cfg.outputs.version }} IMAGE_REF: ${{ steps.cfg.outputs.image }}@${{ steps.merge.outputs.digest }} IMAGE_DIGEST: ${{ steps.merge.outputs.digest }} + RUNNER_ARCH: ${{ runner.arch }} run: bash .github/scripts/tools-publish/write-publish-summary.sh diff --git a/doc/tool-engine/manual-publish.md b/doc/tool-engine/manual-publish.md index e81cc7f..5a40b03 100644 --- a/doc/tool-engine/manual-publish.md +++ b/doc/tool-engine/manual-publish.md @@ -7,15 +7,15 @@ Tool container images for Pengine live on **GitHub Container Registry (GHCR)**. | Piece | Value | | --------------- | ----------------------------------- | | Registry host | `ghcr.io` | -| Repository path | `pengine-ai/tools/pengine-` | +| Repository path | `pengine-ai/pengine-` | -The `` comes from the tool `id` in `tools/mcp-tools.json`. Example: id `pengine/file-manager` → image `ghcr.io/pengine-ai/tools/pengine-file-manager`. +The `` comes from the tool `id` in `tools/mcp-tools.json`. Example: id `pengine/file-manager` → image `ghcr.io/pengine-ai/pengine-file-manager`. Pull examples: ```bash -podman pull ghcr.io/pengine-ai/tools/pengine-file-manager:0.1.0 -podman pull ghcr.io/pengine-ai/tools/pengine-file-manager:latest +podman pull ghcr.io/pengine-ai/pengine-file-manager:0.1.0 +podman pull ghcr.io/pengine-ai/pengine-file-manager:latest ``` Browse packages on GitHub: **https://github.com/orgs/pengine-ai/packages** @@ -103,7 +103,7 @@ This checks the npm registry for newer versions, bumps `mcp-tools.json`, and pri Or just push changes to `tools/` on `main` — CI builds automatically for tools whose version changed. -CI builds **linux/amd64** on `ubuntu-latest` and **linux/arm64** on **`ubuntu-24.04-arm`** (native), then merges a multi-arch manifest with `docker buildx imagetools create`. Building arm64 on an Intel runner uses QEMU; **Node/npm during `docker build` often exits with Illegal instruction** under emulation, so arm64 is not built that way. +CI runs **one job per tool** on **`ubuntu-24.04-arm`**: two `docker/build-push-action` steps (native **linux/arm64**, then **linux/amd64** via QEMU), then **`docker buildx imagetools create`** so **one** multi-arch tag (`:version` / optional `:latest`) and **one digest** appear in the job summary. Staging tags `:version-ci-amd64-` / `:version-ci-arm64-` may still show in the GitHub Packages UI until you delete them; use `:version` or the digest from the summary for production. --- diff --git a/src-tauri/src/modules/tool_engine/service.rs b/src-tauri/src/modules/tool_engine/service.rs index 1327540..523ea95 100644 --- a/src-tauri/src/modules/tool_engine/service.rs +++ b/src-tauri/src/modules/tool_engine/service.rs @@ -211,8 +211,8 @@ fn resolve_current_digest(entry: &ToolEntry) -> Result, String> { /// The OCI image reference for a tool entry. /// -/// - **Production** (real digest): `ghcr.io/pengine-ai/tools/pengine-file-manager@sha256:abc123…` -/// - **Dev** (placeholder digest): `ghcr.io/pengine-ai/tools/pengine-file-manager:0.1.0` (tagged) +/// - **Production** (real digest): `ghcr.io/pengine-ai/pengine-file-manager@sha256:abc123…` +/// - **Dev** (placeholder digest): `ghcr.io/pengine-ai/pengine-file-manager:0.1.0` (tagged) fn image_reference(entry: &ToolEntry) -> Result { match resolve_current_digest(entry)? { Some(digest) => Ok(format!("{}@{}", entry.image, digest)), @@ -654,7 +654,7 @@ mod tests { .expect("file-manager must be in embedded catalog"); assert_eq!(fm.current, "0.1.0"); assert!(!fm.versions.is_empty()); - assert!(fm.image.contains("ghcr.io/pengine-ai/tools/")); + assert!(fm.image.contains("ghcr.io/pengine-ai/pengine-file-manager")); let u = fm .upstream_mcp_npm .as_ref() diff --git a/src-tauri/src/modules/tool_engine/tools.json b/src-tauri/src/modules/tool_engine/tools.json index fc9abbd..5153a48 100644 --- a/src-tauri/src/modules/tool_engine/tools.json +++ b/src-tauri/src/modules/tool_engine/tools.json @@ -9,7 +9,7 @@ "id": "pengine/file-manager", "name": "File Manager", "description": "Filesystem MCP in a container. Add folders in MCP Tools; each mounts at /app/. Install works before any folder is set.", - "image": "ghcr.io/pengine-ai/tools/pengine-file-manager", + "image": "ghcr.io/pengine-ai/pengine-file-manager", "current": "0.1.0", "versions": [ { diff --git a/src-tauri/src/modules/tool_engine/types.rs b/src-tauri/src/modules/tool_engine/types.rs index 924dba9..e033f75 100644 --- a/src-tauri/src/modules/tool_engine/types.rs +++ b/src-tauri/src/modules/tool_engine/types.rs @@ -50,7 +50,7 @@ pub struct ToolEntry { pub id: String, pub name: String, pub description: String, - /// Full OCI image reference (without tag/digest), e.g. "ghcr.io/pengine-ai/tools/pengine-file-manager". + /// Full OCI image reference (without tag/digest), e.g. "ghcr.io/pengine-ai/pengine-file-manager". pub image: String, /// The current (latest non-yanked, non-revoked) version string, e.g. "0.1.0". pub current: String, diff --git a/tools/mcp-tools.json b/tools/mcp-tools.json index 5334036..d6ee129 100644 --- a/tools/mcp-tools.json +++ b/tools/mcp-tools.json @@ -9,7 +9,7 @@ "id": "pengine/file-manager", "name": "File Manager", "description": "Filesystem MCP in a container. Add folders in MCP Tools; each mounts at /app/. Install works before any folder is set.", - "image": "ghcr.io/pengine-ai/tools/pengine-file-manager", + "image": "ghcr.io/pengine-ai/pengine-file-manager", "current": "0.1.0", "versions": [ {