diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 6060303..fac8bbf 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -13,113 +13,7 @@ env: CI: true jobs: - build-macos-binaries: - runs-on: ${{ matrix.runner }} - environment: production-signing - permissions: - contents: read - env: - AGENTDESKTOP_VERSION: ${{ inputs.version }} - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - target: x86_64-apple-darwin - runner: macos-15-intel - - arch: arm64 - target: aarch64-apple-darwin - runner: macos-26 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Install Node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version-file: frontend/.nvmrc - - - name: Install Rust target - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master - with: - toolchain: "1.98" - targets: ${{ matrix.target }} - - - name: Build frontend - shell: bash - working-directory: frontend - run: | - npm install --global pnpm@11.25.0 - pnpm install --frozen-lockfile - pnpm --filter @agentdesktop/desktop-web build - - - name: Build binary - shell: bash - run: cargo build --locked --release --package agentdesktop --target "${{ matrix.target }}" - - - name: Import macOS signing identity - shell: bash - env: - APPLE_APPLICATION_CERTIFICATE: ${{ secrets.APPLE_APPLICATION_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_INSTALLER_CERTIFICATE: ${{ secrets.APPLE_INSTALLER_CERTIFICATE }} - APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} - run: bash .github/scripts/import-macos-signing.sh - - - name: Sign macOS binary - shell: bash - env: - TARGET: ${{ matrix.target }} - run: | - binary="target/${TARGET}/release/agentdesktop" - codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ - --identifier dev.agentdesktop.tray --options runtime --timestamp "$binary" - codesign --verify --strict --verbose=2 \ - -R="anchor apple generic and identifier \"dev.agentdesktop.tray\" and certificate leaf = H\"${APPLE_SIGNING_IDENTITY}\"" "$binary" - - - name: Notarize macOS binary - shell: bash - env: - TARGET: ${{ matrix.target }} - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_ID: ${{ vars.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ vars.APPLE_API_ISSUER }} - run: | - # Bare binaries cannot be stapled; Gatekeeper looks up their ticket online. - archive="${RUNNER_TEMP}/agentdesktop-notarize.zip" - ditto -c -k "target/${TARGET}/release/agentdesktop" "$archive" - bash .github/scripts/notarize-macos.sh "$archive" - - - name: Stage binary and checksum - shell: bash - env: - OUTPUT_NAME: agentdesktop-darwin-${{ matrix.arch }} - run: | - mkdir -p release - cp "target/${{ matrix.target }}/release/agentdesktop" "release/${OUTPUT_NAME}" - (cd release && shasum -a 256 "${OUTPUT_NAME}" > "${OUTPUT_NAME}.sha256") - - - name: Remove macOS signing identity - if: always() - timeout-minutes: 2 - shell: bash - run: | - # Delete exported files first, even if keychain deletion later fails. - rm -f "${RUNNER_TEMP}/agentdesktop-signing.p12" "${RUNNER_TEMP}/agentdesktop-notary.p8" - if [ -f "${RUNNER_TEMP}/agentdesktop-signing.keychain-db" ]; then - security delete-keychain "${RUNNER_TEMP}/agentdesktop-signing.keychain-db" - fi - - - name: Upload binary - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: release-binary-darwin-${{ matrix.arch }} - path: release/agentdesktop-* - if-no-files-found: error - - build-macos-installers: + build-macos: runs-on: ${{ matrix.runner }} environment: production-signing permissions: @@ -186,6 +80,29 @@ jobs: -R="anchor apple generic and identifier \"dev.agentdesktop.tray\" and certificate leaf = H\"${APPLE_SIGNING_IDENTITY}\"" \ "$app/Contents/MacOS/agentdesktop" + - name: Sign and notarize standalone binary + shell: bash + env: + TARGET: ${{ matrix.target }} + OUTPUT_NAME: agentdesktop-darwin-${{ matrix.arch }} + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ vars.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ vars.APPLE_API_ISSUER }} + run: | + mkdir -p release/binary + binary="release/binary/${OUTPUT_NAME}" + cp "target/${TARGET}/release/bundle/macos/agentdesktop.app/Contents/MacOS/agentdesktop" "$binary" + # The bundle signature seals Info.plist and resources, so the extracted copy needs its own. + codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ + --identifier dev.agentdesktop.tray --options runtime --timestamp "$binary" + codesign --verify --strict --verbose=2 \ + -R="anchor apple generic and identifier \"dev.agentdesktop.tray\" and certificate leaf = H\"${APPLE_SIGNING_IDENTITY}\"" "$binary" + # Bare binaries cannot be stapled; Gatekeeper looks up their ticket online. + archive="${RUNNER_TEMP}/agentdesktop-notarize.zip" + ditto -c -k "$binary" "$archive" + bash .github/scripts/notarize-macos.sh "$archive" + (cd release/binary && shasum -a 256 "${OUTPUT_NAME}" > "${OUTPUT_NAME}.sha256") + - name: Stage PKG shell: bash env: @@ -207,7 +124,18 @@ jobs: APPLE_API_ISSUER: ${{ vars.APPLE_API_ISSUER }} run: | bash .github/scripts/notarize-macos.sh "release/${OUTPUT_NAME}" - xcrun stapler staple "release/${OUTPUT_NAME}" + # Ticket lookup via CloudKit fails transiently; retry stapling without resubmitting. + for attempt in 1 2 3 4 5; do + if xcrun stapler staple "release/${OUTPUT_NAME}"; then + break + fi + if (( attempt == 5 )); then + echo "Stapling failed after ${attempt} attempts" >&2 + exit 1 + fi + sleep $((attempt * 15)) + done + xcrun stapler validate "release/${OUTPUT_NAME}" - name: Checksum PKG shell: bash @@ -281,6 +209,13 @@ jobs: security delete-keychain "${RUNNER_TEMP}/agentdesktop-signing.keychain-db" fi + - name: Upload binary + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: release-binary-darwin-${{ matrix.arch }} + path: release/binary/agentdesktop-darwin-* + if-no-files-found: error + - name: Upload PKG uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: diff --git a/frontend/desktop/scripts/package-macos.test.mjs b/frontend/desktop/scripts/package-macos.test.mjs index dc8d0f7..085edba 100644 --- a/frontend/desktop/scripts/package-macos.test.mjs +++ b/frontend/desktop/scripts/package-macos.test.mjs @@ -252,8 +252,8 @@ const signingCleanupSteps = [ return { body, script: run[1].replace(/^ {10}/gm, "") }; }); -test("both release signing cleanups always run with a short timeout", () => { - assert.equal(signingCleanupSteps.length, 2); +test("release signing cleanup always runs with a short timeout", () => { + assert.equal(signingCleanupSteps.length, 1); for (const { body } of signingCleanupSteps) { assert.match(body, /^ {8}if: always\(\)/m); const timeout = body.match(/^ {8}timeout-minutes: (\d+)$/m);