name: Notarize macOS on: workflow_run: workflows: ["Release macOS"] types: [completed] workflow_dispatch: inputs: version: description: "Version (auto-detected from latest release if empty)" required: false run_id: description: "Release macOS workflow run ID (auto-detected if empty)" required: false permissions: contents: write actions: write concurrency: group: notarize-${{ github.event.workflow_run.head_branch || github.run_id }} cancel-in-progress: true jobs: # =================================================================== # Resolve version + macOS build run_id automatically # =================================================================== resolve: runs-on: ubuntu-latest if: > github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'v')) outputs: version: ${{ steps.resolve.outputs.version }} run_id: ${{ steps.resolve.outputs.run_id }} steps: - name: Resolve version and run_id id: resolve env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then VERSION="${{ github.event.inputs.version }}" RUN_ID="${{ github.event.inputs.run_id }}" if [ -z "$VERSION" ]; then TAG=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName -q '.tagName') VERSION="${TAG#v}" fi if [ -z "$RUN_ID" ]; then RUN_ID=$(gh run list --repo "$GITHUB_REPOSITORY" \ --workflow="Release macOS" --branch="v${VERSION}" --limit=1 \ --json databaseId,conclusion --jq '.[] | select(.conclusion=="success") | .databaseId') fi else TAG="${{ github.event.workflow_run.head_branch }}" VERSION="${TAG#v}" RUN_ID="${{ github.event.workflow_run.id }}" fi if [ -z "$VERSION" ] || [ -z "$RUN_ID" ]; then echo "::error::Failed to resolve version='${VERSION}' run_id='${RUN_ID}'" exit 1 fi echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "run_id=${RUN_ID}" >> $GITHUB_OUTPUT echo "Resolved: version=${VERSION} run_id=${RUN_ID}" # =================================================================== # Notarize Yao binaries (arm64 + amd64) # =================================================================== notarize: needs: resolve runs-on: macos-latest strategy: matrix: arch: [arm64, amd64] steps: - name: Download Yao Binary uses: actions/download-artifact@v4 with: name: yao-darwin-${{ matrix.arch }} path: bin run-id: ${{ needs.resolve.outputs.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Install Certificates env: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | mkdir -p certs echo "${{ secrets.APPLE_DEVELOPERIDG2CA }}" | base64 --decode > certs/DeveloperIDG2CA.cer echo "${{ secrets.APPLE_DISTRIBUTION }}" | base64 --decode > certs/distribution.cer echo "${{ secrets.APPLE_PRIVATE_KEY }}" | base64 --decode > certs/private_key.p12 security verify-cert -c certs/DeveloperIDG2CA.cer security verify-cert -c certs/distribution.cer - name: Import Certificates env: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db 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 ./certs/DeveloperIDG2CA.cer -k $KEYCHAIN_PATH -T /usr/bin/codesign security import ./certs/distribution.cer -k $KEYCHAIN_PATH -T /usr/bin/codesign security import ./certs/private_key.p12 -k $KEYCHAIN_PATH -P "${{ secrets.APPLE_PRIVATE_KEY_PASSWORD }}" -T /usr/bin/codesign security list-keychain -d user -s $KEYCHAIN_PATH - name: Verify Signature run: codesign --verify --deep --strict --verbose=2 bin/yao - name: Notarize Yao ${{ matrix.arch }} timeout-minutes: 15 env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_TEAME_ID: ${{ secrets.APPLE_TEAME_ID }} APPLE_APP_SPEC_PASS: ${{ secrets.APPLE_APP_SPEC_PASS }} run: | zip -j bin/yao.zip bin/yao SUBMIT_OUT=$(xcrun notarytool submit bin/yao.zip \ --apple-id "$APPLE_ID" \ --team-id "$APPLE_TEAME_ID" \ --password "$APPLE_APP_SPEC_PASS" \ --wait --timeout 10m --output-format json 2>&1) || true echo "$SUBMIT_OUT" STATUS=$(echo "$SUBMIT_OUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null || true) SUB_ID=$(echo "$SUBMIT_OUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) if [ "$STATUS" != "Accepted" ]; then echo "::error::Yao ${{ matrix.arch }} notarization failed (status: $STATUS)" [ -n "$SUB_ID" ] && xcrun notarytool log "$SUB_ID" \ --apple-id "$APPLE_ID" \ --team-id "$APPLE_TEAME_ID" \ --password "$APPLE_APP_SPEC_PASS" || true exit 1 fi echo "Yao ${{ matrix.arch }} notarization accepted." # =================================================================== # After both architectures finish: wait for Linux R2, then trigger CDN # =================================================================== finalize: needs: [resolve, notarize] runs-on: ubuntu-latest if: success() env: AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_ENDPOINTS: ${{ secrets.R2_ENDPOINTS }} R2_BUCKET: ${{ secrets.R2_BUCKET || 'releases' }} steps: - name: Checkout (for gh CLI context) uses: actions/checkout@v4 with: sparse-checkout: .github - name: Configure AWS CLI run: | aws configure set default.region us-east-1 aws configure set default.s3.signature_version s3v4 - name: Wait for all platform assets on R2 run: | VERSION="${{ needs.resolve.outputs.version }}" PREFIX="yao/${VERSION}" PLATFORMS=( "darwin-arm64" "darwin-amd64" "linux-amd64" "linux-arm64" ) for ATTEMPT in $(seq 1 30); do MISSING=0 for P in "${PLATFORMS[@]}"; do KEY="${PREFIX}/yao-${VERSION}-${P}" if ! aws s3 ls "s3://${R2_BUCKET}/${KEY}" --endpoint-url "$R2_ENDPOINTS" >/dev/null 2>&1; then MISSING=$((MISSING+1)) fi if ! aws s3 ls "s3://${R2_BUCKET}/${KEY}.sha256" --endpoint-url "$R2_ENDPOINTS" >/dev/null 2>&1; then MISSING=$((MISSING+1)) fi done if [ "$MISSING" -eq 0 ]; then echo "All 4 platform assets verified on R2." exit 0 fi echo "Attempt $ATTEMPT: $MISSING asset(s) still missing, waiting 30s..." sleep 30 done echo "::error::Timed out waiting for all platform assets on R2." exit 1 - name: Trigger CDN latest.json update env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ needs.resolve.outputs.version }}" gh workflow run update-cdn-latest.yml \ -f version="${VERSION}" \ -f mark_latest="true" echo "Triggered update-cdn-latest.yml for ${VERSION}"