mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:35:42 +00:00
Split Unity builds across dedicated runners with async notarization (#5291)
Add dedicated self-hosted runners for parallel Unity builds: - unity-mac: Mac Unity builds and deployment - unity-windows: Windows Unity builds (cross-compiled on Mac) - notarize: Notarization waiting (lightweight, doesn't block builds) Split mac_build.yml into 3 jobs: 1. build-and-sign (unity-mac): Build, sign, submit to Apple 2. wait-notarization (notarize): Wait for Apple, staple ticket 3. deploy (unity-mac): Deploy notarized app This allows: - Mac and Windows Unity builds to run in parallel - Notarization waiting doesn't block other builds - All runners share the same Mac Mini hardware New scripts: - notarize_submit.sh: Submit without waiting, output submission ID - notarize_wait.sh: Wait for submission ID, staple ticket 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,8 @@ on:
|
||||
- "scripts/build_mac_plugin.sh"
|
||||
- "scripts/inject_sparkle.sh"
|
||||
- "scripts/codesign_mac_app.sh"
|
||||
- "scripts/notarize_mac_app.sh"
|
||||
- "scripts/notarize_submit.sh"
|
||||
- "scripts/notarize_wait.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/mac/**"
|
||||
@@ -24,7 +25,8 @@ on:
|
||||
- "scripts/build_mac_plugin.sh"
|
||||
- "scripts/inject_sparkle.sh"
|
||||
- "scripts/codesign_mac_app.sh"
|
||||
- "scripts/notarize_mac_app.sh"
|
||||
- "scripts/notarize_submit.sh"
|
||||
- "scripts/notarize_wait.sh"
|
||||
- "ci/github_actions/build_mac.sh"
|
||||
- "ci/github_actions/build_unity_mac.sh"
|
||||
- "ci/mac/**"
|
||||
@@ -40,8 +42,11 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
mac-unity:
|
||||
runs-on: self-hosted
|
||||
build-and-sign:
|
||||
runs-on: [self-hosted, macOS, unity-mac]
|
||||
outputs:
|
||||
submission_id: ${{ steps.notarize-submit.outputs.submission_id }}
|
||||
should_deploy: ${{ steps.check-deploy.outputs.should_deploy }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -70,8 +75,21 @@ jobs:
|
||||
chmod +x ./scripts/inject_sparkle.sh
|
||||
./scripts/inject_sparkle.sh "/tmp/eagle0/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Check if should deploy
|
||||
id: check-deploy
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.skip_signing }}" == "true" ]]; then
|
||||
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Import Code Signing Certificate
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
@@ -101,25 +119,93 @@ jobs:
|
||||
rm certificate.p12
|
||||
|
||||
- name: Code Sign App
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
env:
|
||||
SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }}
|
||||
run: |
|
||||
chmod +x ./scripts/codesign_mac_app.sh
|
||||
./scripts/codesign_mac_app.sh "/tmp/eagle0/eagle0MAC/eagle0.app" "ci/mac/eagle0.entitlements"
|
||||
|
||||
- name: Notarize App
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
- name: Submit for Notarization
|
||||
id: notarize-submit
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
chmod +x ./scripts/notarize_mac_app.sh
|
||||
./scripts/notarize_mac_app.sh "/tmp/eagle0/eagle0MAC/eagle0.app"
|
||||
chmod +x ./scripts/notarize_submit.sh
|
||||
./scripts/notarize_submit.sh "/tmp/eagle0/eagle0MAC/eagle0.app" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cleanup Keychain
|
||||
if: always()
|
||||
run: |
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
- name: Upload signed app
|
||||
if: success() && steps.check-deploy.outputs.should_deploy == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC/eagle0.app
|
||||
retention-days: 1
|
||||
|
||||
- name: Archive Build Log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: editor_mac.log
|
||||
path: /tmp/eagle0/editor_mac.log
|
||||
|
||||
wait-notarization:
|
||||
needs: build-and-sign
|
||||
if: needs.build-and-sign.outputs.should_deploy == 'true'
|
||||
runs-on: [self-hosted, macOS, notarize]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: scripts
|
||||
|
||||
- name: Download signed app
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: signed-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC/eagle0.app
|
||||
|
||||
- name: Wait for Notarization and Staple
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
chmod +x ./scripts/notarize_wait.sh
|
||||
./scripts/notarize_wait.sh "${{ needs.build-and-sign.outputs.submission_id }}" "/tmp/eagle0/eagle0MAC/eagle0.app"
|
||||
|
||||
- name: Upload notarized app
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: notarized-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC/eagle0.app
|
||||
retention-days: 1
|
||||
|
||||
deploy:
|
||||
needs: [build-and-sign, wait-notarization]
|
||||
if: needs.build-and-sign.outputs.should_deploy == 'true'
|
||||
runs-on: [self-hosted, macOS, unity-mac]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # For version numbering
|
||||
|
||||
- name: Download notarized app
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: notarized-mac-app
|
||||
path: /tmp/eagle0/eagle0MAC/eagle0.app
|
||||
|
||||
- name: Deploy Mac Build
|
||||
if: success() && ((github.event_name == 'workflow_dispatch' && github.event.inputs.skip_signing != 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
|
||||
env:
|
||||
ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
@@ -140,15 +226,3 @@ jobs:
|
||||
"$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
rm "$SPARKLE_PRIVATE_KEY_PATH"
|
||||
|
||||
- name: Cleanup Keychain
|
||||
if: always()
|
||||
run: |
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
- name: Archive Build Log
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: editor_mac.log
|
||||
path: /tmp/eagle0/editor_mac.log
|
||||
|
||||
@@ -37,7 +37,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
windows-unity:
|
||||
runs-on: self-hosted
|
||||
runs-on: [self-hosted, macOS, unity-windows]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Submit a macOS .app bundle to Apple for notarization (no waiting)
|
||||
# Usage: notarize_submit.sh <app_path>
|
||||
# Outputs: submission_id=<id> to stdout (for GitHub Actions)
|
||||
#
|
||||
# Environment variables (required):
|
||||
# APPLE_ID - Apple Developer account email
|
||||
# APP_SPECIFIC_PASSWORD - App-specific password for notarytool
|
||||
# TEAM_ID - Apple Developer Team ID
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
APP_PATH="$1"
|
||||
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "ERROR: App not found at $APP_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${APPLE_ID:-}" ] || [ -z "${APP_SPECIFIC_PASSWORD:-}" ] || [ -z "${TEAM_ID:-}" ]; then
|
||||
echo "ERROR: Required environment variables not set" >&2
|
||||
echo " APPLE_ID: ${APPLE_ID:-<not set>}" >&2
|
||||
echo " APP_SPECIFIC_PASSWORD: ${APP_SPECIFIC_PASSWORD:+<set>}" >&2
|
||||
echo " TEAM_ID: ${TEAM_ID:-<not set>}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create ZIP for notarization submission
|
||||
ZIP_PATH="${APP_PATH%.app}.zip"
|
||||
echo "=== Creating ZIP for notarization: $ZIP_PATH ===" >&2
|
||||
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
|
||||
|
||||
echo "=== Submitting to Apple for notarization ===" >&2
|
||||
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$TEAM_ID" 2>&1)
|
||||
|
||||
echo "$SUBMIT_OUTPUT" >&2
|
||||
|
||||
# Extract submission ID
|
||||
SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "id:" | head -1 | awk '{print $2}')
|
||||
|
||||
if [ -z "$SUBMISSION_ID" ]; then
|
||||
echo "ERROR: Failed to get submission ID" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up the zip
|
||||
rm "$ZIP_PATH"
|
||||
|
||||
echo "Submission ID: $SUBMISSION_ID" >&2
|
||||
|
||||
# Output for GitHub Actions
|
||||
echo "submission_id=$SUBMISSION_ID"
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Wait for Apple notarization to complete and staple the ticket
|
||||
# Usage: notarize_wait.sh <submission_id> <app_path>
|
||||
#
|
||||
# Environment variables (required):
|
||||
# APPLE_ID - Apple Developer account email
|
||||
# APP_SPECIFIC_PASSWORD - App-specific password for notarytool
|
||||
# TEAM_ID - Apple Developer Team ID
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SUBMISSION_ID="$1"
|
||||
APP_PATH="$2"
|
||||
|
||||
if [ -z "$SUBMISSION_ID" ]; then
|
||||
echo "ERROR: submission_id is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "ERROR: App not found at $APP_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${APPLE_ID:-}" ] || [ -z "${APP_SPECIFIC_PASSWORD:-}" ] || [ -z "${TEAM_ID:-}" ]; then
|
||||
echo "ERROR: Required environment variables not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Waiting for notarization of submission $SUBMISSION_ID ==="
|
||||
WAIT_OUTPUT=$(xcrun notarytool wait "$SUBMISSION_ID" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$TEAM_ID" 2>&1) || true
|
||||
|
||||
echo "$WAIT_OUTPUT"
|
||||
|
||||
# Extract status (look for " status:" to avoid matching "Current status:")
|
||||
STATUS=$(echo "$WAIT_OUTPUT" | grep "^ status:" | awk '{print $2}')
|
||||
|
||||
echo "Status: $STATUS"
|
||||
|
||||
if [ "$STATUS" != "Accepted" ]; then
|
||||
echo "=== Notarization failed! Fetching log for details ==="
|
||||
xcrun notarytool log "$SUBMISSION_ID" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$TEAM_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Stapling notarization ticket to app ==="
|
||||
# Retry stapling - Apple's CloudKit can have a brief delay after notarization completes
|
||||
MAX_STAPLE_ATTEMPTS=5
|
||||
STAPLE_ATTEMPT=1
|
||||
while [ $STAPLE_ATTEMPT -le $MAX_STAPLE_ATTEMPTS ]; do
|
||||
echo "Stapling attempt $STAPLE_ATTEMPT/$MAX_STAPLE_ATTEMPTS..."
|
||||
if xcrun stapler staple "$APP_PATH"; then
|
||||
echo "Stapling successful"
|
||||
break
|
||||
fi
|
||||
if [ $STAPLE_ATTEMPT -eq $MAX_STAPLE_ATTEMPTS ]; then
|
||||
echo "ERROR: Stapling failed after $MAX_STAPLE_ATTEMPTS attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Stapling failed, waiting 10 seconds before retry..."
|
||||
sleep 10
|
||||
STAPLE_ATTEMPT=$((STAPLE_ATTEMPT + 1))
|
||||
done
|
||||
|
||||
echo "=== Verifying notarization ==="
|
||||
xcrun stapler validate "$APP_PATH"
|
||||
spctl --assess --type exec -v "$APP_PATH"
|
||||
|
||||
echo "Notarization complete: $APP_PATH"
|
||||
Reference in New Issue
Block a user