#!/usr/bin/env bash # # Notarize a macOS .app bundle with Apple # Usage: notarize_mac_app.sh # # Environment variables (required): # APPLE_ID - Apple Developer account email # APP_SPECIFIC_PASSWORD - App-specific password for notarytool # TEAM_ID - Apple Developer Team ID set -euxo pipefail APP_PATH="$1" if [ ! -d "$APP_PATH" ]; then echo "ERROR: App not found at $APP_PATH" exit 1 fi if [ -z "${APPLE_ID:-}" ] || [ -z "${APP_SPECIFIC_PASSWORD:-}" ] || [ -z "${TEAM_ID:-}" ]; then echo "ERROR: Required environment variables not set" echo " APPLE_ID: ${APPLE_ID:-}" echo " APP_SPECIFIC_PASSWORD: ${APP_SPECIFIC_PASSWORD:+}" echo " TEAM_ID: ${TEAM_ID:-}" exit 1 fi # Create ZIP for notarization submission ZIP_PATH="${APP_PATH%.app}.zip" echo "=== Creating ZIP for notarization: $ZIP_PATH ===" ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH" echo "=== Submitting to Apple for notarization ===" SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \ --apple-id "$APPLE_ID" \ --password "$APP_SPECIFIC_PASSWORD" \ --team-id "$TEAM_ID" \ --wait 2>&1) || true echo "$SUBMIT_OUTPUT" # Extract submission ID and status (look for " status:" to avoid matching "Current status:") SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "id:" | head -1 | awk '{print $2}') STATUS=$(echo "$SUBMIT_OUTPUT" | grep "^ status:" | awk '{print $2}') echo "Submission ID: $SUBMISSION_ID" echo "Status: $STATUS" # Clean up the zip (use -f to avoid failure if already deleted) rm -f "$ZIP_PATH" 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"