From 3253403bcddfe99d1d508dae56d45b3769740190 Mon Sep 17 00:00:00 2001 From: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Date: Mon, 25 May 2026 11:08:28 -0400 Subject: [PATCH] ci(release): add Intel macOS (x86_64) DMG as a release target Adds a release-macos-x64 job that builds, codesigns, notarizes, and uploads a separate x86_64 DMG to the same versioned GitHub release, so Intel Macs get a native installer alongside Apple Silicon. - New release-macos-x64 job mirrors the arm64 release job but builds --target x86_64-apple-darwin and omits the auto-updater steps (the updater manifest stays arm64-only for now; Intel/Linux auto-update is a tracked follow-up). - bundle-sidecars.sh now reads from target//release for cross-target builds (it previously always read target/release, which silently picked up host-arch binaries under a cross --target). - RELEASING.md updated: macOS now ships two DMGs (arm64 + Intel); corrected the stale 'ARM64 only' claims. Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> --- .github/workflows/release.yml | 103 ++++++++++++++++++++++++++++++++++ RELEASING.md | 14 +++-- scripts/bundle-sidecars.sh | 17 ++++-- 3 files changed, 125 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e693496e3..8b3931afa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -185,6 +185,109 @@ jobs: ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }} SIG_PATH: ${{ steps.artifacts.outputs.sig }} + release-macos-x64: + name: Release macOS (Intel) + runs-on: macos-latest + needs: release + timeout-minutes: 60 + permissions: + contents: write + id-token: write # required by block/apple-codesign-action for OIDC + env: + VERSION: ${{ inputs.version }} + TARGET: x86_64-apple-darwin + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ inputs.ref }} + persist-credentials: false + + - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 + + - name: Install desktop dependencies + run: just desktop-install-ci + + - name: Add Rust target + run: rustup target add "$TARGET" + + - name: Patch version + run: | + cd desktop && node scripts/set-version-from-tag.mjs "$VERSION" + cd src-tauri && cargo update --workspace + + - name: Generate release config + run: cd desktop && node scripts/build-release-config.mjs + env: + SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} + SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json + + - name: Build sidecars + run: | + cargo build --release --target "$TARGET" -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli + ./scripts/bundle-sidecars.sh "$TARGET" + + - name: Build unsigned Tauri app + run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json + env: + SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} + SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + CMAKE_POLICY_VERSION_MINIMUM: "3.5" + + - name: Locate unsigned DMG + id: unsigned + run: | + BUNDLE_DIR="desktop/src-tauri/target/${TARGET}/release/bundle" + DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1) + if [[ -z "$DMG" ]]; then + echo "::error::No DMG found in $BUNDLE_DIR/dmg" + exit 1 + fi + echo "dmg=$DMG" >> "$GITHUB_OUTPUT" + + - name: Codesign and Notarize + id: codesign + uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0 + with: + osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }} + codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }} + unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }} + entitlements-plist-path: desktop/src-tauri/Entitlements.plist + artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-x64 + + - name: Replace DMG and signed .app + env: + SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }} + SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }} + UNSIGNED_DMG: ${{ steps.unsigned.outputs.dmg }} + run: | + set -euo pipefail + APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos" + + # Replace the unsigned DMG with the signed/notarized one. + cp "$SIGNED_DMG" "$UNSIGNED_DMG" + + # Swap the unsigned .app for the signed .app from the action's zip, + # so the verify step below checks the signed bundle (matches arm64). + EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64" + rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR" + ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR" + rm -rf "${APP_DIR}/Sprout.app" + cp -R "${EXTRACT_DIR}/Sprout.app" "${APP_DIR}/Sprout.app" + + - name: Verify code signature + run: | + APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Sprout.app" + codesign --verify --deep --strict --verbose=2 "$APP_DIR" + spctl --assess --type execute --verbose=4 "$APP_DIR" + + - name: Upload Intel DMG to versioned GitHub release + run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DMG_PATH: ${{ steps.unsigned.outputs.dmg }} + release-linux: name: Release Linux runs-on: ubuntu-latest diff --git a/RELEASING.md b/RELEASING.md index f5be69822..beb5fc6d7 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -65,9 +65,11 @@ Each release produces two GitHub releases: ## Platform Support -The release workflow currently builds for **macOS ARM64 only** -(`darwin-aarch64`). Intel Mac (`darwin-x86_64`) support would require -adding a matrix build to the workflow. +The release workflow builds **two separate macOS DMGs** — Apple +Silicon (`darwin-aarch64`, the `release` job) and Intel +(`darwin-x86_64`, the `release-macos-x64` job) — plus Linux `.deb` and +`.AppImage`. Both macOS DMGs are codesigned, notarized, and attached to +the same `v` release. Intel users download the `_x64.dmg`. ## Code Signing (macOS) @@ -111,5 +113,7 @@ requires `TAURI_SIGNING_PRIVATE_KEY` and ### Auto-updater reports "no update available" Verify that the `sprout-desktop-latest` release exists and contains a -valid `latest.json`. If the user is on Intel Mac, no update will be -found (ARM64 only). +valid `latest.json`. The auto-updater manifest currently lists +`darwin-aarch64` only, so Intel and Linux users do not yet receive +auto-updates — they download new versions manually from the release +page. (Adding their entries to `latest.json` is a follow-up.) diff --git a/scripts/bundle-sidecars.sh b/scripts/bundle-sidecars.sh index 0e8d2ba96..4baa99ae5 100755 --- a/scripts/bundle-sidecars.sh +++ b/scripts/bundle-sidecars.sh @@ -2,21 +2,30 @@ set -euo pipefail SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout) -TARGET=${1:-$(rustc -vV | sed -n 's|host: ||p')} +HOST=$(rustc -vV | sed -n 's|host: ||p') +TARGET=${1:-$HOST} BINARIES_DIR="desktop/src-tauri/binaries" +# A cross-target build (`cargo build --target `) emits to +# target//release; a host build emits to target/release. +if [[ "$TARGET" == "$HOST" ]]; then + SRC_DIR="target/release" +else + SRC_DIR="target/${TARGET}/release" +fi + missing=() for bin in "${SIDECARS[@]}"; do - [[ -f "target/release/$bin" ]] || missing+=("$bin") + [[ -f "$SRC_DIR/$bin" ]] || missing+=("$bin") done if [[ ${#missing[@]} -gt 0 ]]; then - echo "Error: missing release binaries: ${missing[*]}" >&2 + echo "Error: missing release binaries in $SRC_DIR: ${missing[*]}" >&2 echo "Run 'cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli' first." >&2 exit 1 fi mkdir -p "$BINARIES_DIR" for bin in "${SIDECARS[@]}"; do - cp "target/release/$bin" "$BINARIES_DIR/${bin}-${TARGET}" + cp "$SRC_DIR/$bin" "$BINARIES_DIR/${bin}-${TARGET}" done echo "Sidecars bundled for $TARGET"