diff --git a/.github/workflows/release-rust.yml b/.github/workflows/release-rust.yml new file mode 100644 index 00000000..be55d4c8 --- /dev/null +++ b/.github/workflows/release-rust.yml @@ -0,0 +1,164 @@ +name: release Rust binary to GitHub Releases + +on: + release: + types: + - published + +# This workflow runs in parallel with the existing PyPI workflow in +# release.yml. Both publish artifacts for the same release tag until +# the Phase 1.6 channel switch replaces the PyPI path entirely. +# +# macOS binaries are signed with a Developer ID Application +# certificate and notarized through Apple's notarytool so users +# can run them without Gatekeeper warnings. The job fails the +# release if any of APPLE_* secrets is missing or wrong. +# +# Required GitHub Actions secrets: +# +# APPLE_CERTIFICATE # base64 of the .p12 Developer ID cert +# APPLE_CERTIFICATE_PASSWORD # password set when exporting the .p12 +# APPLE_SIGNING_IDENTITY # e.g. "Developer ID Application: Mergify SAS (TEAMID)" +# APPLE_API_KEY # base64 of the App Store Connect .p8 API key +# APPLE_API_KEY_ID # 10-char key ID from App Store Connect +# APPLE_API_KEY_ISSUER # issuer UUID from App Store Connect + +jobs: + linux-windows: + name: build ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-24.04 + - target: x86_64-unknown-linux-musl + os: ubuntu-24.04 + - target: aarch64-unknown-linux-gnu + os: ubuntu-24.04 + - target: aarch64-unknown-linux-musl + os: ubuntu-24.04 + - target: x86_64-pc-windows-msvc + os: windows-2025 + runs-on: ${{ matrix.os }} + permissions: + contents: write + steps: + - uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Install Rust toolchain + run: | + rustup toolchain install stable --profile minimal + rustup default stable + + - uses: taiki-e/upload-rust-binary-action@v1 + with: + bin: mergify + target: ${{ matrix.target }} + archive: $bin-$tag-$target + tar: unix + zip: windows + checksum: sha256 + token: ${{ secrets.GITHUB_TOKEN }} + + macos: + name: build ${{ matrix.target }} (signed + notarized) + strategy: + fail-fast: false + matrix: + target: + - x86_64-apple-darwin + - aarch64-apple-darwin + runs-on: macos-15 + permissions: + contents: write + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + steps: + - uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Install Rust toolchain + run: | + rustup toolchain install stable --profile minimal + rustup target add ${{ matrix.target }} + rustup default stable + + - name: Build + run: cargo build --release --target ${{ matrix.target }} + + - name: Import Developer ID certificate into a throwaway keychain + run: | + echo "$APPLE_CERTIFICATE" | base64 --decode -o "$RUNNER_TEMP/cert.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db" + KEYCHAIN_PASSWORD="$(openssl rand -hex 16)" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$RUNNER_TEMP/cert.p12" \ + -k "$KEYCHAIN_PATH" \ + -P "$APPLE_CERTIFICATE_PASSWORD" \ + -T /usr/bin/codesign + security set-key-partition-list \ + -S apple-tool:,apple:,codesign: \ + -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + # Prepend the build keychain so codesign searches it first, + # without dropping the runner's default keychains. + security list-keychains -d user -s \ + "$KEYCHAIN_PATH" \ + $(security list-keychains -d user | tr -d '"') + + - name: Codesign binary (hardened runtime + timestamp) + run: | + codesign \ + --force \ + --options runtime \ + --timestamp \ + --sign "$APPLE_SIGNING_IDENTITY" \ + "target/${{ matrix.target }}/release/mergify" + codesign --verify --strict --verbose=2 \ + "target/${{ matrix.target }}/release/mergify" + + - name: Notarize with App Store Connect API + run: | + KEY_DIR="$HOME/.appstoreconnect/private_keys" + mkdir -p "$KEY_DIR" + KEY_PATH="$KEY_DIR/AuthKey_${APPLE_API_KEY_ID}.p8" + echo "$APPLE_API_KEY" | base64 --decode > "$KEY_PATH" + # notarytool accepts zip, dmg, or pkg; wrap the bare binary + # in a zip so it can be submitted. Stapling isn't possible + # on a Mach-O, but online Gatekeeper checks still approve + # notarized binaries. + NOTARIZE_ZIP="$RUNNER_TEMP/mergify-notarize.zip" + ditto -c -k --keepParent \ + "target/${{ matrix.target }}/release/mergify" \ + "$NOTARIZE_ZIP" + xcrun notarytool submit "$NOTARIZE_ZIP" \ + --key "$KEY_PATH" \ + --key-id "$APPLE_API_KEY_ID" \ + --issuer "$APPLE_API_KEY_ISSUER" \ + --wait + + - name: Archive and upload to GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + ARCHIVE="mergify-${TAG}-${{ matrix.target }}.tar.gz" + tar -C "target/${{ matrix.target }}/release" \ + -czf "$RUNNER_TEMP/$ARCHIVE" mergify + (cd "$RUNNER_TEMP" && shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256") + gh release upload "$TAG" \ + "$RUNNER_TEMP/$ARCHIVE" \ + "$RUNNER_TEMP/$ARCHIVE.sha256" \ + --repo "${{ github.repository }}"