From feb305a3adbe5d23a204c0cb2b15ae8a72e09e28 Mon Sep 17 00:00:00 2001 From: theBlanketBaron Date: Thu, 5 Nov 2026 15:45:22 -0500 Subject: [PATCH 1/6] Build prebuilt binaries for macOS arm64 and Linux x86_64 (MUS-14) A release workflow builds muster-shim and muster-daemon with --locked on macOS (Apple silicon) and Ubuntu 22.04 (glibc 2.35, OpenSSL 3), smoke-runs each one, and names them muster--, as scripts/install-codex.sh expects. One job writes and checks a SHA256SUMS over every binary and install-codex.sh. workflow_dispatch is a dry run that only uploads the files as an artifact. A pushed v* tag also creates a draft release with them, which nobody sees until it's published by hand. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 129 ++++++++++++++++++++++++++++++++++ docs/roadmap.md | 4 ++ 2 files changed, 133 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..5b19337b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,129 @@ +name: Release + +# MUS-14: prebuilt muster-shim and muster-daemon for macOS arm64 and Linux x86_64, so neither +# needs a Rust toolchain to install. scripts/install-codex.sh fetches +# releases/latest/download/muster-shim-, so the assets are raw binaries with those names. +# +# - workflow_dispatch builds everything and uploads it as a workflow artifact only: a dry run that +# touches no release and no tag. +# - Pushing a `v*` tag does the same, then creates a DRAFT release with the files attached. Nothing +# is public until someone publishes the draft by hand. +on: + push: + tags: ["v*"] + workflow_dispatch: + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: build (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 40 + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest # Apple silicon + target: aarch64-apple-darwin + # The oldest supported Ubuntu image, so the binaries need the oldest glibc we can give + # them (2.35). They link OpenSSL 3 (native-tls) dynamically: Ubuntu 22.04+, Debian 12+, + # Fedora 36+ and other distributions with libssl.so.3. + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + steps: + - uses: actions/checkout@v4 + - name: Install build dependencies (Linux) + if: runner.os == 'Linux' + timeout-minutes: 8 + run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config libssl-dev + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + key: release-${{ matrix.target }} + - name: Build + run: cargo build --release --locked --target ${{ matrix.target }} -p muster-shim -p muster-daemon + - name: Name the binaries for the release + run: | + mkdir dist + for bin in muster-shim muster-daemon; do + cp "target/${{ matrix.target }}/release/$bin" "dist/$bin-${{ matrix.target }}" + done + ls -l dist + # Neither binary has --version; both answer an unknown flag with an error or their usage. + # Getting that text back shows the binary loads its libraries and runs on this runner. + - name: Smoke test + run: | + for bin in muster-shim muster-daemon; do + out=$("./dist/$bin-${{ matrix.target }}" --release-smoke-test 2>&1 || true) + echo "$bin: $out" | head -3 + echo "$out" | grep -qiE "usage|unknown argument" || { echo "::error::$bin didn't run"; exit 1; } + done + file dist/* + - uses: actions/upload-artifact@v4 + with: + name: bin-${{ matrix.target }} + path: dist/* + if-no-files-found: error + + # One SHA256SUMS over every asset, made and checked in one place. + package: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + pattern: bin-* + path: dist + merge-multiple: true + - name: Checksums + working-directory: dist + run: | + cp ../scripts/install-codex.sh . + chmod +x muster-* + sha256sum muster-* install-codex.sh > SHA256SUMS + sha256sum -c SHA256SUMS + cat SHA256SUMS + # Every asset scripts/install-codex.sh can ask for is here. + for target in aarch64-apple-darwin x86_64-unknown-linux-gnu; do + for bin in muster-shim muster-daemon; do + grep -q " $bin-$target\$" SHA256SUMS || { echo "::error::missing $bin-$target"; exit 1; } + done + done + - uses: actions/upload-artifact@v4 + with: + name: muster-release + path: dist/* + if-no-files-found: error + + release: + needs: package + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + name: muster-release + path: dist + - name: Create a draft release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + run: | + gh release create "$TAG" dist/* \ + --repo "$GITHUB_REPOSITORY" \ + --draft \ + --verify-tag \ + --title "muster $TAG" \ + --notes "Prebuilt muster-shim and muster-daemon for macOS (Apple silicon) and Linux (x86_64, glibc 2.35+ with OpenSSL 3). Check downloads against SHA256SUMS: \`sha256sum -c SHA256SUMS --ignore-missing\`." diff --git a/docs/roadmap.md b/docs/roadmap.md index 614d4439..4bb8e8bb 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -395,6 +395,10 @@ Merged and needing a live run: MUS-36 and MUS-37. - [ ] **MUS-14: Publish prebuilt binaries.** There are no releases yet, so `scripts/install-codex.sh` can't work and the plugins aren't on npm. Done when: macOS and Linux can install without a Rust toolchain. + `.github/workflows/release.yml` now builds muster-shim and muster-daemon for macOS arm64 and + Linux x86_64 under the names install-codex.sh asks for, with one SHA256SUMS. workflow_dispatch + is a dry run that only uploads artifacts; a `v*` tag also makes a draft release, which stays + unpublished until Josh publishes it. Left: a tag and a published release, on Josh's word. - [ ] **MUS-91: Keep `windows-cross-check` from timing out.** It hit its 20-minute limit on #110, #113 and #124, and a re-run passed each time. Every time it was the apt step: `mingw-w64` pulls 278 MB (the i686 and x86_64 toolchains, C and C++), which normally takes 7 s, but the Azure From 66f10d7b1bd209273602339c17645028bc3d77e0 Mon Sep 17 00:00:00 2001 From: theBlanketBaron Date: Thu, 5 Nov 2026 15:45:22 -0500 Subject: [PATCH 2/6] TEMPORARY: run the release workflow on this PR to test it (to be reverted) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b19337b..339d8cc3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,8 @@ on: push: tags: ["v*"] workflow_dispatch: + # TEMPORARY, to test this workflow on its own PR before it's on main; reverted before review. + pull_request: permissions: contents: read From ae98e86c37c5f1db9e99a9f3609be125ab5c08f9 Mon Sep 17 00:00:00 2001 From: theBlanketBaron Date: Thu, 5 Nov 2026 15:54:29 -0500 Subject: [PATCH 3/6] Revert the temporary pull_request trigger; the workflow runs on tags and workflow_dispatch only Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 339d8cc3..5b19337b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,6 @@ on: push: tags: ["v*"] workflow_dispatch: - # TEMPORARY, to test this workflow on its own PR before it's on main; reverted before review. - pull_request: permissions: contents: read From 214e1b137f57669742a26c3e68c94c4659a478a1 Mon Sep 17 00:00:00 2001 From: theBlanketBaron Date: Thu, 5 Nov 2026 15:56:25 -0500 Subject: [PATCH 4/6] Pin the release workflow's actions to SHAs, build with no cache, pin macos-15 (MUS-14) Review asked that a moved tag can't change what builds a release and a poisoned cache entry can't reach a shipped binary: every action is now pinned to a full commit SHA with its version in a comment, and the build job no longer restores Swatinem/rust-cache. The macOS runner is pinned to macos-15, and the smoke test has a 2-minute limit in case a binary ever ignores the unknown flag and keeps running. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b19337b..490efc7d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,10 @@ on: permissions: contents: read +# Every action is pinned to a full commit SHA (version in the comment), so a moved tag can't change +# what builds a release, and the build restores no cache, so a poisoned cache entry can't reach a +# shipped binary. Releases are rare; the cold build's extra minutes don't matter. + env: CARGO_TERM_COLOR: always @@ -28,7 +32,7 @@ jobs: fail-fast: false matrix: include: - - os: macos-latest # Apple silicon + - os: macos-15 # Apple silicon, pinned so the image doesn't drift under a release target: aarch64-apple-darwin # The oldest supported Ubuntu image, so the binaries need the oldest glibc we can give # them (2.35). They link OpenSSL 3 (native-tls) dynamically: Ubuntu 22.04+, Debian 12+, @@ -36,17 +40,16 @@ jobs: - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Install build dependencies (Linux) if: runner.os == 'Linux' timeout-minutes: 8 run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config libssl-dev - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable branch, 2026-10-16 with: + # Pinned to a SHA, the action can't read the toolchain from its ref, so name it. + toolchain: stable targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v2 - with: - key: release-${{ matrix.target }} - name: Build run: cargo build --release --locked --target ${{ matrix.target }} -p muster-shim -p muster-daemon - name: Name the binaries for the release @@ -59,6 +62,8 @@ jobs: # Neither binary has --version; both answer an unknown flag with an error or their usage. # Getting that text back shows the binary loads its libraries and runs on this runner. - name: Smoke test + # In case a binary ever ignores the unknown flag and keeps running. + timeout-minutes: 2 run: | for bin in muster-shim muster-daemon; do out=$("./dist/$bin-${{ matrix.target }}" --release-smoke-test 2>&1 || true) @@ -66,7 +71,7 @@ jobs: echo "$out" | grep -qiE "usage|unknown argument" || { echo "::error::$bin didn't run"; exit 1; } done file dist/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: bin-${{ matrix.target }} path: dist/* @@ -78,8 +83,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: bin-* path: dist @@ -98,7 +103,7 @@ jobs: grep -q " $bin-$target\$" SHA256SUMS || { echo "::error::missing $bin-$target"; exit 1; } done done - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: muster-release path: dist/* @@ -112,7 +117,7 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: muster-release path: dist From 26b1ab7dd37d5b6208d823485431a1e60d1f479e Mon Sep 17 00:00:00 2001 From: theBlanketBaron Date: Thu, 5 Nov 2026 15:56:32 -0500 Subject: [PATCH 5/6] TEMPORARY: run the release workflow on this PR to test the pinned, uncached build (to be reverted) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 490efc7d..5260d0a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,8 @@ on: push: tags: ["v*"] workflow_dispatch: + # TEMPORARY, to test this workflow on its own PR before it's on main; reverted before review. + pull_request: permissions: contents: read From 036cea547206dd33dc1699be93280a0018fd9d3e Mon Sep 17 00:00:00 2001 From: theBlanketBaron Date: Thu, 5 Nov 2026 16:04:16 -0500 Subject: [PATCH 6/6] Revert the temporary pull_request trigger; the workflow runs on tags and workflow_dispatch only Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5260d0a8..490efc7d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,6 @@ on: push: tags: ["v*"] workflow_dispatch: - # TEMPORARY, to test this workflow on its own PR before it's on main; reverted before review. - pull_request: permissions: contents: read