Repository navigation
Binary Release #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Binary Release | |
| # Auto Release creates the vX.Y.Z tag/release with GITHUB_TOKEN, and a tag | |
| # pushed by GITHUB_TOKEN cannot start a `push: tags` workflow. So this chains | |
| # off Auto Release's completion: it cross-compiles the mcpproxy commands | |
| # (mcpproxyd plus the configcheck / import-catalog / mcpsmoke operator tools) | |
| # and uploads the archives and checksums to that release. | |
| # | |
| # No Homebrew tap here, unlike alcatraz: this repo is private, so a formula | |
| # pointing at its release assets could not be downloaded by `brew install`. | |
| # Add the tap step if and when the repo goes public. | |
| # | |
| # The manual trigger re-publishes binaries for an existing tag — the recovery | |
| # path when a release was cut while this workflow was broken. | |
| on: | |
| workflow_run: | |
| workflows: ["Auto Release"] | |
| types: [completed] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Existing release tag to (re)publish binaries for, without the leading v (e.g. 0.5.0)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| # Serialize runs so back-to-back releases can't race on uploading assets to | |
| # the same release. | |
| concurrency: | |
| group: binary-release | |
| cancel-in-progress: false | |
| jobs: | |
| resolve: | |
| name: Resolve release tag | |
| runs-on: ubuntu-latest | |
| # Auto Release runs on every push to main; act only on successful runs. | |
| # Manual dispatches name their tag explicitly and always proceed. | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| outputs: | |
| publish: ${{ steps.dispatch.outputs.publish || steps.tag.outputs.publish }} | |
| version: ${{ steps.dispatch.outputs.version || steps.tag.outputs.version }} | |
| ref: ${{ steps.dispatch.outputs.ref || steps.tag.outputs.ref }} | |
| steps: | |
| - name: Use the dispatched tag | |
| id: dispatch | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| env: | |
| RAW: ${{ inputs.version }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| # This job has write permissions: refuse to build/upload for a | |
| # malformed version or a tag that has no release. | |
| VERSION="${RAW#v}" | |
| if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "::error::'${RAW}' is not a semver version (want X.Y.Z)" | |
| exit 1 | |
| fi | |
| if ! gh release view "v${VERSION}" --repo "${{ github.repository }}" >/dev/null 2>&1; then | |
| echo "::error::release v${VERSION} does not exist in ${{ github.repository }} — create the release first, this workflow only attaches binaries" | |
| exit 1 | |
| fi | |
| { | |
| echo "publish=true" | |
| echo "version=${VERSION}" | |
| echo "ref=v${VERSION}" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Publishing v${VERSION} (manual dispatch)" | |
| - uses: actions/checkout@v4 | |
| if: ${{ github.event_name == 'workflow_run' }} | |
| with: | |
| # The exact commit Auto Release ran on. fetch-depth: 0 brings the | |
| # tags so we can read the version tag it created at that commit. | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| fetch-depth: 0 | |
| - name: Find the release tag at this commit | |
| id: tag | |
| if: ${{ github.event_name == 'workflow_run' }} | |
| run: | | |
| set -euo pipefail | |
| # Auto Release tags the released commit vX.Y.Z, or tags nothing for | |
| # a skip-release PR or a direct push. Bind the published version to | |
| # that tag so source and version always match; skip when absent. | |
| TAG=$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true) | |
| if [ -z "$TAG" ]; then | |
| echo "No release tag at ${{ github.event.workflow_run.head_sha }}; nothing to publish." | |
| echo "publish=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "publish=true" >> "$GITHUB_OUTPUT" | |
| echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" | |
| echo "ref=${{ github.event.workflow_run.head_sha }}" >> "$GITHUB_OUTPUT" | |
| echo "Publishing ${TAG}" | |
| fi | |
| binaries: | |
| name: Build archives | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.publish == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.resolve.outputs.ref }} | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| - name: Cross-compile and package archives | |
| env: | |
| VERSION: ${{ needs.resolve.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| # CGO_ENABLED=0: the daemon uses no cgo, and a static binary runs on | |
| # any glibc/musl base image. -X main.version is read by | |
| # `mcpproxyd -version`; the other three commands have no version var, | |
| # so the flag is scoped to mcpproxyd only. | |
| for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64; do | |
| os="${target%%/*}"; arch="${target##*/}" | |
| ext=""; [ "$os" = windows ] && ext=".exe" | |
| stage="dist/stage_${os}_${arch}" | |
| mkdir -p "$stage" | |
| for cmd in mcpproxyd configcheck import-catalog mcpsmoke; do | |
| ldflags="-s -w" | |
| [ "$cmd" = mcpproxyd ] && ldflags="$ldflags -X main.version=v${VERSION}" | |
| CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" \ | |
| go build -trimpath -ldflags "$ldflags" \ | |
| -o "${stage}/${cmd}${ext}" "./cmd/${cmd}" | |
| done | |
| cp README.md config.example.yaml "$stage/" | |
| cp -r examples "$stage/examples" | |
| if [ "$os" = windows ]; then | |
| (cd "$stage" && zip -qr "../mcpproxy_${VERSION}_${os}_${arch}.zip" .) | |
| else | |
| tar -czf "dist/mcpproxy_${VERSION}_${os}_${arch}.tar.gz" -C "$stage" . | |
| fi | |
| done | |
| rm -rf dist/stage_* | |
| # Bare globs (no ./ prefix): installers grep checksums.txt for the | |
| # exact archive name. | |
| (cd dist && sha256sum -- *.tar.gz *.zip > checksums.txt && cat checksums.txt) | |
| - name: Smoke test the linux/amd64 build | |
| env: | |
| VERSION: ${{ needs.resolve.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| # Prove the uploaded archive contains a runnable daemon carrying the | |
| # version we claim, rather than trusting that `go build` succeeded. | |
| mkdir -p smoke | |
| tar -xzf "dist/mcpproxy_${VERSION}_linux_amd64.tar.gz" -C smoke | |
| got=$(./smoke/mcpproxyd -version) | |
| echo "$got" | |
| if [ "$got" != "mcpproxyd v${VERSION}" ]; then | |
| echo "::error::built binary reports '${got}', want 'mcpproxyd v${VERSION}'" | |
| exit 1 | |
| fi | |
| ./smoke/configcheck smoke/examples/01-minimal-stdio.yaml | |
| - name: Attach archives to the release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: v${{ needs.resolve.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| # --clobber lets a re-run replace assets instead of failing on | |
| # "asset already exists". checksums.txt is consumed by installers. | |
| gh release upload "$TAG" dist/*.tar.gz dist/*.zip dist/checksums.txt \ | |
| --clobber --repo "${{ github.repository }}" |