From 835074f332a7799333234deb4512fe0c0a3efd83 Mon Sep 17 00:00:00 2001 From: Ozgur Ersoy Date: Wed, 16 Apr 2025 19:03:33 +0200 Subject: [PATCH] fix(actions): enhance macOS notarization workflow by improving input descriptions, adding detailed logging, and refining signing and notarization processes for better error handling and traceability --- .gitea/actions/macos-notarize/action.yml | 1341 ++++++++++++---------- .gitea/workflows/test-macos-build.yml | 219 ++-- 2 files changed, 798 insertions(+), 762 deletions(-) diff --git a/.gitea/actions/macos-notarize/action.yml b/.gitea/actions/macos-notarize/action.yml index e77e590b..36c3e52e 100644 --- a/.gitea/actions/macos-notarize/action.yml +++ b/.gitea/actions/macos-notarize/action.yml @@ -1,704 +1,825 @@ -name: macOS Notarize -description: 'Signs and notarizes a macOS application with Apple certificates' -author: moersoy +name: "macOS Sign and Notarize" +description: "Signs and notarizes macOS applications with Developer ID certificate" +author: moersoy" inputs: app-path: - description: 'Path to the .app bundle to sign' + description: "Path to the app bundle (.app)" required: true entitlements-file: - description: 'Path to entitlements file to use for signing' - required: false - default: '' + description: "Path to the entitlements file (.entitlements)" + required: true team-id: - description: 'Apple Developer Team ID' + description: "Apple Developer Team ID" required: true certificate-base64: - description: 'Base64-encoded certificate (P12 file)' + description: "Base64-encoded Developer ID Application certificate (.p12)" required: true certificate-password: - description: 'Certificate password' + description: "Password for the Developer ID Application certificate" required: true notarization-method: - description: 'Method to use for notarization (api-key or app-password)' + description: "Method to use for notarization: 'api-key' or 'app-password'" required: false - default: 'api-key' - app-password: - description: 'App-specific password for Apple ID (required if using app-password method)' + default: "api-key" + notary-user: + description: "Apple ID for notarization (for app-password method)" required: false - default: '' - apple-id: - description: 'Apple ID email (required if using app-password method)' + notary-password: + description: "App-specific password for notarization (for app-password method)" required: false - default: '' notary-api-key-id: - description: 'App Store Connect API Key ID (required if using api-key method)' + description: "API Key ID for notarization (for api-key method)" required: false - default: '' notary-api-key-issuer-id: - description: 'App Store Connect API Key Issuer ID (required if using api-key method)' + description: "API Issuer ID for notarization (for api-key method)" required: false - default: '' notary-api-key-path: - description: 'App Store Connect API Key file content (base64 encoded) (required if using api-key method)' + description: "Path to or content of the API Key .p8 file (for api-key method)" required: false - default: '' bundle-id: - description: 'Bundle ID of the app' + description: "App bundle identifier (com.example.app)" required: false - default: '' fallback-to-adhoc: - description: 'Fallback to ad-hoc signing if no certificate is available' + description: "Whether to fall back to ad-hoc signing if certificate is invalid" required: false - default: 'true' + default: "true" outputs: signed: - description: 'Signing status (true, ad-hoc, none)' - value: ${{ steps.set-outputs.outputs.signed }} + description: "Whether the app was signed (identity, adhoc, or none)" + value: ${{ steps.sign.outputs.signed }} notarized: - description: 'Notarization status (true, false)' - value: ${{ steps.set-outputs.outputs.notarized }} - app-path: - description: 'Path to the signed app bundle' - value: ${{ steps.set-outputs.outputs.app-path }} - zip-path: - description: 'Path to the packaged .ZIP file' - value: ${{ steps.set-outputs.outputs.zip-path }} + description: "Whether the app was notarized (true or false)" + value: ${{ steps.notarize.outputs.notarized }} package-path: - description: 'Path to the packaged .DMG file' - value: ${{ steps.set-outputs.outputs.package-path }} + description: "Path to the final package" + value: ${{ steps.package.outputs.package-path }} runs: using: "composite" steps: - - name: Setup debug environment + - name: Setup Certificate + id: setup-cert + shell: bash + env: + CERTIFICATE_BASE64: ${{ inputs.certificate-base64 }} + CERTIFICATE_PASSWORD: ${{ inputs.certificate-password }} + APPLE_TEAM_ID: ${{ inputs.team-id }} run: | - # Create debug directory if env variable is set - if [[ -n "$DEBUG_LOG_PATH" ]]; then - mkdir -p "$(dirname "$DEBUG_LOG_PATH")" - touch "$DEBUG_LOG_PATH" - echo "Debug logging enabled to: $DEBUG_LOG_PATH" | tee -a "$DEBUG_LOG_PATH" + echo "๐Ÿ” Setting up certificate..." + + # Create a temporary directory for certificates + CERT_DIR="$HOME/certificates" + mkdir -p "$CERT_DIR" + + # Decode the certificate to a p12 file + echo "$CERTIFICATE_BASE64" | base64 --decode > "$CERT_DIR/certificate.p12" + + # Create keychain + KEYCHAIN_PATH="$CERT_DIR/app-signing.keychain-db" + KEYCHAIN_PASSWORD="temppassword123" + + # Delete existing keychain if it exists + security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true + + # Create new keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -t 3600 -u -l "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + # Add to search list and make default + security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"') + security default-keychain -s "$KEYCHAIN_PATH" + + # Import certificate + echo "๐Ÿ”‘ Importing developer certificate..." + security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign + + # Try with additional parameters if needed + security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12 || true + + # Set partition list for codesign to access keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + # Verify certificate + echo "๐Ÿ” Verifying code signing identities..." + security find-identity -v -p codesigning "$KEYCHAIN_PATH" + + # Try to use the System keychain as a fallback + echo "๐Ÿ” Checking system keychain for code signing identities..." + SYSTEM_IDENTITIES=$(security find-identity -v -p codesigning) + echo "$SYSTEM_IDENTITIES" + + if echo "$SYSTEM_IDENTITIES" | grep -q "Developer ID Application"; then + echo "โœ… Found Developer ID Application certificate in system keychain" + echo "::set-output name=use_system_cert::true" + else + echo "::set-output name=use_system_cert::false" fi - # Define a debug function - debug_log() { - echo "DEBUG: $1" - if [[ -n "$DEBUG_LOG_PATH" ]]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$DEBUG_LOG_PATH" - fi - } - # Export the function for use in subsequent steps - export -f debug_log - - debug_log "Starting macOS notarize action" - debug_log "App path: ${{ inputs.app-path }}" - debug_log "Team ID: ${{ inputs.team-id }}" - debug_log "Notarization method: ${{ inputs.notarization-method }}" - debug_log "Bundle ID: ${{ inputs.bundle-id }}" - shell: bash - - - name: Set up variables - id: setup - run: | - # Debugging info - debug_log "Setting up variables" - - # Generate unique name for keychain - KEYCHAIN_NAME="build-keychain-$(uuidgen)" - KEYCHAIN_PASSWORD="$(uuidgen)" - echo "KEYCHAIN_NAME=$KEYCHAIN_NAME" >> $GITHUB_ENV + # Store keychain variables for later steps + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV + echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" >> $GITHUB_ENV - # Set paths - echo "APP_PATH=${{ inputs.app-path }}" >> $GITHUB_ENV + # Clean up + rm -f "$CERT_DIR/certificate.p12" - # Generate working directory for temp files - WORK_DIR="$(mktemp -d)" - echo "WORK_DIR=$WORK_DIR" >> $GITHUB_ENV - - # Set bundle id (from input or extract from app) - if [[ -n "${{ inputs.bundle-id }}" ]]; then - BUNDLE_ID="${{ inputs.bundle-id }}" - else - BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist") - fi - echo "BUNDLE_ID=$BUNDLE_ID" >> $GITHUB_ENV - - # Get app name from bundle path - APP_NAME=$(basename "${{ inputs.app-path }}" .app) - echo "APP_NAME=$APP_NAME" >> $GITHUB_ENV - - # Set output directory - OUTPUT_DIR="$(pwd)/PackagedReleases" - mkdir -p "$OUTPUT_DIR" - echo "OUTPUT_DIR=$OUTPUT_DIR" >> $GITHUB_ENV - - # Set package paths - ZIP_PATH="$OUTPUT_DIR/${APP_NAME}.zip" - DMG_PATH="$OUTPUT_DIR/${APP_NAME}.dmg" - echo "ZIP_PATH=$ZIP_PATH" >> $GITHUB_ENV - echo "DMG_PATH=$DMG_PATH" >> $GITHUB_ENV - - # Set notarization variables based on method - if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then - echo "Using API key method for notarization" - - # Create API key file - API_KEY_FILE="$WORK_DIR/api_key.p8" - echo "${{ inputs.notary-api-key-path }}" | base64 --decode > "$API_KEY_FILE" - echo "API_KEY_FILE=$API_KEY_FILE" >> $GITHUB_ENV - - # Verify API key file exists - if [[ ! -f "$API_KEY_FILE" ]]; then - debug_log "ERROR: API key file could not be created" - exit 1 - fi - - debug_log "API key file created at: $API_KEY_FILE" - debug_log "API key ID: ${{ inputs.notary-api-key-id }}" - debug_log "API key issuer ID: ${{ inputs.notary-api-key-issuer-id }}" - else - echo "Using app-specific password method for notarization" - debug_log "Apple ID: ${{ inputs.apple-id }}" - fi + - name: Sign App + id: sign shell: bash - - - name: Setup keychain - id: setup-keychain run: | - debug_log "Setting up keychain" + echo "๐Ÿ” Signing app with Developer ID certificate..." - # Create temporary keychain - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" - security default-keychain -s "$KEYCHAIN_NAME" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" - security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME" - - # Create certificate file - CERTIFICATE_PATH="$WORK_DIR/certificate.p12" - echo "${{ inputs.certificate-base64 }}" | base64 --decode > "$CERTIFICATE_PATH" - - # Add to keychain - debug_log "Importing certificate into keychain" - security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_NAME" -P "${{ inputs.certificate-password }}" -T /usr/bin/codesign - - # Allow codesign to access keychain items - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" - - # Verify certificate was imported - security find-identity -v "$KEYCHAIN_NAME" | grep "Developer ID Application" - IDENTITY_RESULT=$? - - if [ $IDENTITY_RESULT -eq 0 ]; then - debug_log "Certificate imported successfully" - SIGNING_IDENTITY="Developer ID Application: ${{ inputs.team-id }}" - echo "SIGNING_IDENTITY=$SIGNING_IDENTITY" >> $GITHUB_ENV - echo "CERTIFICATE_AVAILABLE=true" >> $GITHUB_ENV - else - debug_log "WARNING: No Developer ID Application certificate found" - if [[ "${{ inputs.fallback-to-adhoc }}" == "true" ]]; then - debug_log "Falling back to ad-hoc signing" - echo "CERTIFICATE_AVAILABLE=adhoc" >> $GITHUB_ENV - else - debug_log "Not falling back to ad-hoc signing as specified" - echo "CERTIFICATE_AVAILABLE=false" >> $GITHUB_ENV - fi - fi - shell: bash - - - name: Sign application - id: sign-app - run: | - debug_log "Starting application signing process" - - # Check if certificate is available - if [[ "$CERTIFICATE_AVAILABLE" == "false" ]]; then - debug_log "No certificate available and fallback disabled. Skipping signing." - echo "SIGNING_RESULT=none" >> $GITHUB_ENV - exit 0 - fi - - # Sign the app - if [[ "$CERTIFICATE_AVAILABLE" == "true" ]]; then - debug_log "Signing with Developer ID certificate" - - # First remove existing signatures - debug_log "Removing existing signatures..." - codesign --remove-signature "$APP_PATH" || true - - # Sign all dynamic libraries and frameworks - debug_log "Signing embedded binaries and frameworks..." - find "$APP_PATH/Contents/MacOS" -type f -name "*.dylib" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; - find "$APP_PATH/Contents/Frameworks" -type f -depth 1 -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; - find "$APP_PATH/Contents/Frameworks" -name "*.framework" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; - - # Sign all executables - debug_log "Signing executables..." - find "$APP_PATH/Contents/MacOS" -type f -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; - - # Sign app bundle - debug_log "Signing main app bundle..." - codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH" - - SIGN_RESULT=$? - if [ $SIGN_RESULT -eq 0 ]; then - debug_log "App signed successfully with Developer ID" - echo "SIGNING_RESULT=true" >> $GITHUB_ENV - else - debug_log "App signing failed with Developer ID" - echo "SIGNING_RESULT=false" >> $GITHUB_ENV - exit 1 - fi - - elif [[ "$CERTIFICATE_AVAILABLE" == "adhoc" ]]; then - debug_log "Signing with ad-hoc identity (not suitable for distribution)" - - # Remove existing signatures - codesign --remove-signature "$APP_PATH" || true - - # Sign with ad-hoc identity - codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign - "$APP_PATH" - - SIGN_RESULT=$? - if [ $SIGN_RESULT -eq 0 ]; then - debug_log "App signed successfully with ad-hoc identity" - echo "SIGNING_RESULT=ad-hoc" >> $GITHUB_ENV - else - debug_log "App signing failed with ad-hoc identity" - echo "SIGNING_RESULT=false" >> $GITHUB_ENV - exit 1 - fi - else - debug_log "Unexpected certificate state. Skipping signing." - echo "SIGNING_RESULT=none" >> $GITHUB_ENV - fi - - # Verify signing - debug_log "Verifying app signature..." - codesign -dvv "$APP_PATH" - shell: bash - - - name: Verify notarization and stapling - id: verify-notarization - if: env.SIGNING_RESULT == 'true' - run: | - debug_log "Verifying app signature and code requirements before notarization" - - # Verify code signature - codesign --verify --verbose "$APP_PATH" - if [ $? -ne 0 ]; then - debug_log "Error: App signature verification failed" - # Don't exit, just log the error - else - debug_log "App signature verification passed" - fi - - # Check app for code requirements - codesign --display --requirements "$APP_PATH" - if [ $? -ne 0 ]; then - debug_log "Error: App doesn't meet requirements" - # Don't exit, just log the error - else - debug_log "App meets code requirements" - fi - shell: bash - - - name: Notarize application - id: notarize-app - if: env.SIGNING_RESULT == 'true' - run: | - debug_log "Starting notarization process" - - # Create ZIP for notarization - debug_log "Creating ZIP archive for notarization" - ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH" - if [ $? -ne 0 ]; then - debug_log "Error creating ZIP archive" - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + # Check if app path exists + if [ ! -d "${{ inputs.app-path }}" ]; then + echo "โŒ App bundle not found at ${{ inputs.app-path }}" + echo "::set-output name=signed::none" exit 1 fi - # Notarize the app - if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then - debug_log "Notarizing with API key method" - - # Submit for notarization - debug_log "Submitting app for notarization..." - xcrun notarytool submit "$ZIP_PATH" \ - --key "$API_KEY_FILE" \ - --key-id "${{ inputs.notary-api-key-id }}" \ - --issuer "${{ inputs.notary-api-key-issuer-id }}" \ - --wait > "$WORK_DIR/notarization_output.txt" 2>&1 - - cat "$WORK_DIR/notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" - - REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/notarization_output.txt" | cut -d ' ' -f2) - - if [[ "$REQUEST_STATUS" == "Accepted" ]]; then - debug_log "Notarization successful" - echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV - else - debug_log "Notarization failed or timed out" - cat "$WORK_DIR/notarization_output.txt" - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV - fi - + # Check if entitlements file exists + if [ ! -f "${{ inputs.entitlements-file }}" ]; then + echo "โŒ Entitlements file not found at ${{ inputs.entitlements-file }}" + echo "::set-output name=signed::none" + exit 1 + fi + + # Decide which keychain to use + if [ "${{ steps.setup-cert.outputs.use_system_cert }}" = "true" ]; then + echo "Using system keychain identity" + # Get certificate hash instead of name to avoid ambiguity + IDENTITY_HASH=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | awk '{print $2}') + echo "Using certificate hash: $IDENTITY_HASH" else - debug_log "Notarizing with app-specific password method" + # Make sure keychain is unlocked + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + echo "Using custom keychain identity" + # Get certificate hash instead of name to avoid ambiguity + IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}') + echo "Using certificate hash: $IDENTITY_HASH" + fi + + if [ -z "$IDENTITY_HASH" ]; then + echo "โŒ No valid Developer ID Application certificate found" - # Submit for notarization - debug_log "Submitting app for notarization..." - xcrun altool --notarize-app \ - --primary-bundle-id "$BUNDLE_ID" \ - --username "${{ inputs.apple-id }}" \ - --password "${{ inputs.app-password }}" \ - --file "$ZIP_PATH" \ - > "$WORK_DIR/notarization_output.txt" 2>&1 - - cat "$WORK_DIR/notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" - - REQUEST_UUID=$(grep -o "RequestUUID = .*" "$WORK_DIR/notarization_output.txt" | cut -d ' ' -f3) + if [ "${{ inputs.fallback-to-adhoc }}" = "true" ]; then + echo "Falling back to ad-hoc signing for testing..." + # Use ad-hoc identity as fallback + codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign - --timestamp "${{ inputs.app-path }}" + echo "::set-output name=signed::adhoc" + else + echo "Skipping signing. Set fallback-to-adhoc=true to use ad-hoc signing instead." + echo "::set-output name=signed::none" + exit 1 + fi + else + echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH" - if [[ -n "$REQUEST_UUID" ]]; then - debug_log "Notarization request submitted, UUID: $REQUEST_UUID" - - # Wait for notarization to complete - debug_log "Waiting for notarization to complete..." - TIMEOUT=30 # 30 minutes timeout - COUNT=0 - NOTARIZATION_STATUS="in progress" - - while [[ "$NOTARIZATION_STATUS" == "in progress" && $COUNT -lt $TIMEOUT ]]; do - sleep 60 - - xcrun altool --notarization-info "$REQUEST_UUID" \ - --username "${{ inputs.apple-id }}" \ - --password "${{ inputs.app-password }}" \ - > "$WORK_DIR/notarization_info.txt" 2>&1 - - cat "$WORK_DIR/notarization_info.txt" | tee -a "$DEBUG_LOG_PATH" - - NOTARIZATION_STATUS=$(grep -o "Status: .*" "$WORK_DIR/notarization_info.txt" | cut -d ':' -f2 | xargs) - - debug_log "Notarization status: $NOTARIZATION_STATUS" - COUNT=$((COUNT+1)) + # Enhanced deep recursive signing for all binaries + echo "๐Ÿ” Performing deep recursive signing of all components..." + + # First, find all .dylib files and sign them individually + echo "Signing all dynamic libraries (.dylib files)..." + find "${{ inputs.app-path }}" -name "*.dylib" | while read -r dylib; do + echo "Signing: $dylib" + codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$dylib" || echo "โš ๏ธ Failed to sign: $dylib" + done + + # Sign all .so files + echo "Signing all shared objects (.so files)..." + find "${{ inputs.app-path }}" -name "*.so" | while read -r so; do + echo "Signing: $so" + codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$so" || echo "โš ๏ธ Failed to sign: $so" + done + + # Sign all executable files (files with execute permission) + echo "Signing all executable files..." + find "${{ inputs.app-path }}" -type f -perm +111 -not -path "*.framework/*" -not -name "*.dylib" -not -name "*.so" | while read -r exe; do + echo "Signing executable: $exe" + codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$exe" || echo "โš ๏ธ Failed to sign: $exe" + done + + # Sign all frameworks + echo "Signing frameworks..." + find "${{ inputs.app-path }}" -path "*.framework" -type d | while read -r framework; do + echo "Signing framework: $framework" + codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$framework" || echo "โš ๏ธ Failed to sign: $framework" + done + + # Special handling for CrashReportClient.app + CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1) + if [ -n "$CRASH_REPORTER" ]; then + echo "๐Ÿ” Special handling for CrashReportClient.app: $CRASH_REPORTER" + # Sign CrashReportClient.app specifically with focus on hardened runtime + find "$CRASH_REPORTER" -type f -perm +111 | while read -r crash_bin; do + echo "Signing CrashReportClient binary: $crash_bin" + codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$crash_bin" || echo "โš ๏ธ Failed to sign: $crash_bin" done - if [[ "$NOTARIZATION_STATUS" == "success" ]]; then - debug_log "Notarization successful" - echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV - else - debug_log "Notarization failed or timed out: $NOTARIZATION_STATUS" - if [[ -f "$WORK_DIR/notarization_info.txt" ]]; then - cat "$WORK_DIR/notarization_info.txt" - fi - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + echo "Signing the CrashReportClient.app bundle itself..." + codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$CRASH_REPORTER" || echo "โš ๏ธ Failed to sign CrashReportClient.app" + fi + + # Sign any other nested app bundles + find "${{ inputs.app-path }}" -path "*.app" -type d | grep -v CrashReportClient | while read -r nested_app; do + if [ "$nested_app" != "${{ inputs.app-path }}" ]; then + echo "Signing nested app: $nested_app" + codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$nested_app" || echo "โš ๏ธ Failed to sign: $nested_app" fi + done + + # Final signing of the main bundle + echo "๐Ÿ” Performing final signing of the main app bundle..." + codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "${{ inputs.app-path }}" + echo "::set-output name=signed::identity" + fi + + # Verify signing + echo "๐Ÿ” Verifying signature..." + codesign -vvv --deep --strict "${{ inputs.app-path }}" + + # Check entitlements + echo "๐Ÿ” Checking entitlements..." + codesign -d --entitlements - "${{ inputs.app-path }}" + + # Verify CrashReportClient + CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1) + if [ -n "$CRASH_REPORTER" ]; then + echo "๐Ÿ” Verifying CrashReportClient signature..." + codesign -vvv --deep --strict "$CRASH_REPORTER" || echo "โš ๏ธ CrashReportClient may have verification issues" + + echo "CrashReportClient entitlements:" + codesign -d --entitlements - "$CRASH_REPORTER" || echo "โš ๏ธ Could not display CrashReportClient entitlements" + fi + + - name: Notarize App + id: notarize + if: steps.sign.outputs.signed != 'none' + shell: bash + env: + APPLE_ID: ${{ inputs.notary-user }} + APP_PASSWORD: ${{ inputs.notary-password }} + API_KEY_ID: ${{ inputs.notary-api-key-id }} + API_ISSUER_ID: ${{ inputs.notary-api-key-issuer-id }} + API_KEY_PATH: ${{ inputs.notary-api-key-path }} + run: | + echo "๐Ÿ“ค Notarizing app..." + + # Set default output + echo "::set-output name=notarized::false" + + # Get app name for zip file naming + APP_NAME=$(basename "${{ inputs.app-path }}" .app) + BUNDLE_ID="${{ inputs.bundle-id }}" + + # If bundle ID is not provided, try to extract from Info.plist + if [ -z "$BUNDLE_ID" ]; then + if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then + BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist") + echo "Extracted bundle ID: $BUNDLE_ID" else - debug_log "Notarization submission failed, no UUID returned" - cat "$WORK_DIR/notarization_output.txt" - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + BUNDLE_ID="com.luckyrobots.app" + echo "Using default bundle ID: $BUNDLE_ID" fi fi - shell: bash - - - name: Staple notarization ticket - id: staple-ticket - if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' - run: | - debug_log "Stapling notarization ticket to app" - # Staple the ticket - xcrun stapler staple "$APP_PATH" - STAPLE_RESULT=$? - - if [ $STAPLE_RESULT -eq 0 ]; then - debug_log "Notarization ticket stapled successfully" - echo "STAPLING_RESULT=true" >> $GITHUB_ENV + # Check if we're using API key notarization method + if [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then + echo "Using App Store Connect API key for notarization..." - # Verify stapling - debug_log "Verifying notarization stapling" - xcrun stapler validate "$APP_PATH" - if [ $? -eq 0 ]; then - debug_log "Stapling validation successful" + # Create directory for API key if API_KEY_PATH contains content + mkdir -p ~/private_keys + + # Check if API_KEY_PATH is a path or content + if [[ "$API_KEY_PATH" == /* ]] && [ -f "$API_KEY_PATH" ]; then + # It's a path to a file + echo "Using API key from path: $API_KEY_PATH" + cp "$API_KEY_PATH" ~/private_keys/AuthKey_${API_KEY_ID}.p8 else - debug_log "Stapling validation failed, but continuing" + # It contains the key content + echo "Using API key from content" + echo "$API_KEY_PATH" > ~/private_keys/AuthKey_${API_KEY_ID}.p8 fi - else - debug_log "Stapling failed" - echo "STAPLING_RESULT=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Remove quarantine attribute - id: remove-quarantine - if: env.SIGNING_RESULT != 'none' - run: | - debug_log "Removing quarantine attribute from app" - - # Create helper script - QUARANTINE_SCRIPT="$WORK_DIR/remove_quarantine.sh" - cat > "$QUARANTINE_SCRIPT" << 'EOF' -#!/bin/bash -# Removes the quarantine attribute from app and all its contents -echo "Removing quarantine attribute from all files..." -find "$1" -exec xattr -d com.apple.quarantine {} \; 2>/dev/null || true -echo "Quarantine attributes removed" -EOF - chmod +x "$QUARANTINE_SCRIPT" - - # Remove quarantine attribute - "$QUARANTINE_SCRIPT" "$APP_PATH" - - debug_log "Quarantine attribute removal completed" - shell: bash - - - name: Package signed app - id: package-app - run: | - debug_log "Packaging the signed app" - - # Check if we should use create-dmg if available - if command -v create-dmg &> /dev/null; then - debug_log "Using create-dmg for DMG creation" - # Create a temporary directory for DMG contents - DMG_TEMP_DIR="$WORK_DIR/dmg-contents" - mkdir -p "$DMG_TEMP_DIR" + # Create zip for notarization + ZIP_PATH="${APP_NAME}-notarize.zip" + ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_PATH" - # Copy the app to the temporary directory - cp -R "$APP_PATH" "$DMG_TEMP_DIR/" + echo "Submitting for notarization with API key..." - # Create instructions text file - echo "Drag the application to the Applications folder to install it." > "$DMG_TEMP_DIR/README.txt" + # First, submit without waiting for completion + SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \ + --key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" 2>&1) + SUBMIT_STATUS=$? - # Create symlink to Applications folder - ln -s /Applications "$DMG_TEMP_DIR/Applications" + # Display output for debugging + echo "Notarization submission output:" + echo "$SUBMIT_OUTPUT" + echo "Submission exit status: $SUBMIT_STATUS" - # Use create-dmg to create a more beautiful DMG - create-dmg \ - --volname "$APP_NAME" \ - --window-pos 200 120 \ - --window-size 800 400 \ - --icon-size 100 \ - --app-drop-link 600 185 \ - --icon "$APP_NAME.app" 200 185 \ - --hide-extension "$APP_NAME.app" \ - --add-file "README.txt" 400 185 \ - --no-internet-enable \ - "$DMG_PATH" \ - "$DMG_TEMP_DIR" + # Check if submission was successful + if [ $SUBMIT_STATUS -ne 0 ]; then + echo "โŒ Failed to submit for notarization. Exit code: $SUBMIT_STATUS" + exit 1 + fi + + # Extract submission ID for log retrieval + SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2) + + if [ -z "$SUBMISSION_ID" ]; then + echo "โŒ Could not extract submission ID from output. Notarization failed." + exit 1 + fi + + echo "Submission ID: $SUBMISSION_ID" + echo "Waiting for notarization to complete..." + + # Now wait for the processing to complete + COMPLETE=false + MAX_ATTEMPTS=60 # Maximum number of attempts (60 * 30 seconds = 30 minutes max) + ATTEMPT=1 + + while [ "$COMPLETE" = "false" ] && [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Checking notarization status (attempt $ATTEMPT of $MAX_ATTEMPTS)..." - DMG_CREATE_RESULT=$? + INFO_OUTPUT=$(xcrun notarytool info "$SUBMISSION_ID" \ + --key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" 2>&1) + INFO_STATUS=$? + + echo "Status check output:" + echo "$INFO_OUTPUT" + + # Check if the notarization is complete + if echo "$INFO_OUTPUT" | grep -q "status: Accepted"; then + echo "โœ… Notarization completed successfully!" + COMPLETE=true + FINAL_STATUS="Accepted" + elif echo "$INFO_OUTPUT" | grep -q "status: Invalid"; then + echo "โŒ Notarization failed with status: Invalid" + COMPLETE=true + FINAL_STATUS="Invalid" + elif echo "$INFO_OUTPUT" | grep -q "status: Rejected"; then + echo "โŒ Notarization failed with status: Rejected" + COMPLETE=true + FINAL_STATUS="Rejected" + else + echo "Notarization still in progress. Waiting 30 seconds before checking again..." + sleep 30 + ATTEMPT=$((ATTEMPT + 1)) + fi + done - elif command -v hdiutil &> /dev/null; then - debug_log "Using hdiutil for DMG creation" - - # Create DMG using hdiutil - hdiutil create -volname "$APP_NAME" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_PATH" - DMG_CREATE_RESULT=$? - - else - debug_log "Neither create-dmg nor hdiutil available. Cannot create DMG." - DMG_CREATE_RESULT=1 - fi - - # Check DMG creation result - if [ $DMG_CREATE_RESULT -eq 0 ]; then - debug_log "DMG package created successfully at: $DMG_PATH" - echo "DMG_CREATED=true" >> $GITHUB_ENV - else - debug_log "DMG creation failed" - echo "DMG_CREATED=false" >> $GITHUB_ENV - fi - - # If we have a properly signed app, sign the DMG as well - if [[ "$SIGNING_RESULT" == "true" && "$DMG_CREATED" == "true" ]]; then - debug_log "Signing DMG with Developer ID certificate" - codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG_PATH" - - if [ $? -eq 0 ]; then - debug_log "DMG signed successfully" - else - debug_log "DMG signing failed" + # Handle timeout + if [ "$COMPLETE" = "false" ]; then + echo "โŒ Notarization timed out after $MAX_ATTEMPTS attempts." + exit 1 fi - # If app was notarized, also notarize the DMG - if [[ "$NOTARIZATION_RESULT" == "true" ]]; then - debug_log "Notarizing DMG..." + # Handle completed notarization + if [ "$FINAL_STATUS" = "Accepted" ]; then + # Get logs for information (even though successful) + echo "๐Ÿ“‹ Getting notarization logs for information..." + LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \ + --key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" 2>&1) - # Notarize the DMG - if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then - debug_log "Notarizing DMG with API key method" + echo "==== NOTARIZATION LOG SUMMARY ====" + echo "$LOGS_OUTPUT" | head -20 + echo "==================================" + + # Staple the notarization ticket + echo "Stapling notarization ticket..." + xcrun stapler staple -v "${{ inputs.app-path }}" + STAPLE_STATUS=$? + + if [ $STAPLE_STATUS -eq 0 ]; then + echo "โœ… Stapling completed successfully!" - xcrun notarytool submit "$DMG_PATH" \ - --key "$API_KEY_FILE" \ - --key-id "${{ inputs.notary-api-key-id }}" \ - --issuer "${{ inputs.notary-api-key-issuer-id }}" \ - --wait > "$WORK_DIR/dmg_notarization_output.txt" 2>&1 - - cat "$WORK_DIR/dmg_notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" - - DMG_REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/dmg_notarization_output.txt" | cut -d ' ' -f2) + # Verify the stapling worked properly + echo "Verifying stapled ticket is properly attached..." + xcrun stapler validate -v "${{ inputs.app-path }}" - if [[ "$DMG_REQUEST_STATUS" == "Accepted" ]]; then - debug_log "DMG notarization successful" - - # Staple DMG - debug_log "Stapling notarization ticket to DMG" - xcrun stapler staple "$DMG_PATH" - if [ $? -eq 0 ]; then - debug_log "DMG stapling successful" - else - debug_log "DMG stapling failed" - fi - else - debug_log "DMG notarization failed or timed out" - cat "$WORK_DIR/dmg_notarization_output.txt" + # Check if stapling metadata is correctly stored in xattr + echo "Checking app extended attributes..." + if command -v xattr &> /dev/null; then + xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "โš ๏ธ Warning: com.apple.provenance attribute not found" fi + # Add special instructions for distribution + echo "๐Ÿ“‹ IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings." + echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files." + echo "For proper distribution, consider the following options:" + echo "1. Use a DMG installer with a signed, notarized app inside" + echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)" + echo "3. If distributing directly, use a distribution platform that preserves notarization tickets" else - debug_log "Notarizing DMG with app-specific password method" + echo "โš ๏ธ Stapling completed with status $STAPLE_STATUS (may still be valid)" + fi + + # Verify notarization + echo "๐Ÿ” Verifying notarization..." + spctl --assess --verbose --type exec "${{ inputs.app-path }}" + + echo "::set-output name=notarized::true" + else + # Get detailed logs for failed notarization + echo "๐Ÿ“‹ Fetching detailed logs for submission ID: $SUBMISSION_ID" + LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \ + --key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" 2>&1) + + echo "==== DETAILED NOTARIZATION LOGS ====" + echo "$LOGS_OUTPUT" + echo "==================================" + + # Extract specific issues for easier debugging + echo "๐Ÿ” Extracting specific issues from logs..." + echo "$LOGS_OUTPUT" | grep -A 3 "issues" + + # Show current bundle ID in Info.plist + echo "๐Ÿ“‹ Current bundle ID information:" + if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then + echo "Info.plist content for bundle ID:" + /usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist" + echo "Full Info.plist excerpt:" + plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle + else + echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist" + fi + + # Check for mismatched bundle ID + if [ "$BUNDLE_ID" != "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null)" ]; then + echo "โš ๏ธ WARNING: Bundle ID mismatch detected between workflow and app!" + echo " - Workflow/input bundle ID: $BUNDLE_ID" + echo " - Actual app bundle ID: $(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null || echo "Could not read")" + echo "This mismatch could cause notarization problems." + fi + + # Check for code signature issues in internal components + echo "๐Ÿ” Checking for code signature issues in app components..." + find "${{ inputs.app-path }}" -type f -name "*.dylib" -o -name "*.so" | head -5 | while read -r lib; do + echo "Checking signature on: $lib" + codesign -vvv "$lib" || echo "โš ๏ธ Signature issue with: $lib" + done + + echo "โŒ Notarization failed with status: $FINAL_STATUS" + exit 1 + fi + + # Clean up + rm -rf ~/private_keys + + # Fall back to App-specific password if requested + elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then + echo "Using App-specific password for notarization..." + + # Create zip for notarization + ZIP_PATH="${APP_NAME}-notarize.zip" + ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_PATH" + + echo "Submitting for notarization..." + + # First, submit without waiting for completion + SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \ + --apple-id "$APPLE_ID" \ + --password "$APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" 2>&1) + SUBMIT_STATUS=$? + + # Display output for debugging + echo "Notarization submission output:" + echo "$SUBMIT_OUTPUT" + echo "Submission exit status: $SUBMIT_STATUS" + + # Check if submission was successful + if [ $SUBMIT_STATUS -ne 0 ]; then + echo "โŒ Failed to submit for notarization. Exit code: $SUBMIT_STATUS" + exit 1 + fi + + # Extract submission ID for log retrieval + SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2) + + if [ -z "$SUBMISSION_ID" ]; then + echo "โŒ Could not extract submission ID from output. Notarization failed." + exit 1 + fi + + echo "Submission ID: $SUBMISSION_ID" + echo "Waiting for notarization to complete..." + + # Now wait for the processing to complete + COMPLETE=false + MAX_ATTEMPTS=60 # Maximum number of attempts (60 * 30 seconds = 30 minutes max) + ATTEMPT=1 + + while [ "$COMPLETE" = "false" ] && [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Checking notarization status (attempt $ATTEMPT of $MAX_ATTEMPTS)..." + + INFO_OUTPUT=$(xcrun notarytool info "$SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --password "$APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" 2>&1) + INFO_STATUS=$? + + echo "Status check output:" + echo "$INFO_OUTPUT" + + # Check if the notarization is complete + if echo "$INFO_OUTPUT" | grep -q "status: Accepted"; then + echo "โœ… Notarization completed successfully!" + COMPLETE=true + FINAL_STATUS="Accepted" + elif echo "$INFO_OUTPUT" | grep -q "status: Invalid"; then + echo "โŒ Notarization failed with status: Invalid" + COMPLETE=true + FINAL_STATUS="Invalid" + elif echo "$INFO_OUTPUT" | grep -q "status: Rejected"; then + echo "โŒ Notarization failed with status: Rejected" + COMPLETE=true + FINAL_STATUS="Rejected" + else + echo "Notarization still in progress. Waiting 30 seconds before checking again..." + sleep 30 + ATTEMPT=$((ATTEMPT + 1)) + fi + done + + # Handle timeout + if [ "$COMPLETE" = "false" ]; then + echo "โŒ Notarization timed out after $MAX_ATTEMPTS attempts." + exit 1 + fi + + # Handle completed notarization + if [ "$FINAL_STATUS" = "Accepted" ]; then + # Get logs for information (even though successful) + echo "๐Ÿ“‹ Getting notarization logs for information..." + LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --password "$APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" 2>&1) + + echo "==== NOTARIZATION LOG SUMMARY ====" + echo "$LOGS_OUTPUT" | head -20 + echo "==================================" + + # Staple the notarization ticket + echo "Stapling notarization ticket..." + xcrun stapler staple -v "${{ inputs.app-path }}" + STAPLE_STATUS=$? + + if [ $STAPLE_STATUS -eq 0 ]; then + echo "โœ… Stapling completed successfully!" - xcrun altool --notarize-app \ - --primary-bundle-id "$BUNDLE_ID.dmg" \ - --username "${{ inputs.apple-id }}" \ - --password "${{ inputs.app-password }}" \ - --file "$DMG_PATH" \ - > "$WORK_DIR/dmg_notarization_output.txt" 2>&1 - - cat "$WORK_DIR/dmg_notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" - - DMG_REQUEST_UUID=$(grep -o "RequestUUID = .*" "$WORK_DIR/dmg_notarization_output.txt" | cut -d ' ' -f3) + # Verify the stapling worked properly + echo "Verifying stapled ticket is properly attached..." + xcrun stapler validate -v "${{ inputs.app-path }}" - if [[ -n "$DMG_REQUEST_UUID" ]]; then - debug_log "DMG notarization request submitted, UUID: $DMG_REQUEST_UUID" + # Check if stapling metadata is correctly stored in xattr + echo "Checking app extended attributes..." + if command -v xattr &> /dev/null; then + xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "โš ๏ธ Warning: com.apple.provenance attribute not found" + fi + + # Add special instructions for distribution + echo "๐Ÿ“‹ IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings." + echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files." + echo "For proper distribution, consider the following options:" + echo "1. Use a DMG installer with a signed, notarized app inside" + echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)" + echo "3. If distributing directly, use a distribution platform that preserves notarization tickets" + else + echo "โš ๏ธ Stapling completed with status $STAPLE_STATUS (may still be valid)" + fi + + # Verify notarization + echo "๐Ÿ” Verifying notarization..." + spctl --assess --verbose --type exec "${{ inputs.app-path }}" + + echo "::set-output name=notarized::true" + else + # Get detailed logs for failed notarization + echo "๐Ÿ“‹ Fetching detailed logs for submission ID: $SUBMISSION_ID" + LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --password "$APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" 2>&1) + + echo "==== DETAILED NOTARIZATION LOGS ====" + echo "$LOGS_OUTPUT" + echo "==================================" + + # Extract specific issues for easier debugging + echo "๐Ÿ” Extracting specific issues from logs..." + echo "$LOGS_OUTPUT" | grep -A 3 "issues" + + # Show current bundle ID in Info.plist + echo "๐Ÿ“‹ Current bundle ID information:" + if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then + echo "Info.plist content for bundle ID:" + /usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist" + echo "Full Info.plist excerpt:" + plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle + else + echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist" + fi + + echo "โŒ Notarization failed with status: $FINAL_STATUS" + exit 1 + fi + else + echo "โš ๏ธ Missing notarization credentials. Skipping notarization." + echo "For App Store Connect API key method, set these inputs:" + echo " - notarization-method: api-key" + echo " - notary-api-key-id: Your API key ID" + echo " - notary-api-key-issuer-id: Your API issuer ID" + echo " - notary-api-key-path: Path to or content of your p8 file" + echo "" + echo "For App-specific password method, set these inputs:" + echo " - notarization-method: app-password" + echo " - notary-user: Your Apple ID (email)" + echo " - notary-password: Your app-specific password" + echo " - team-id: Your Apple Developer team ID" + fi + + - name: Package App + id: package + if: steps.sign.outputs.signed != 'none' + shell: bash + run: | + echo "๐Ÿ“ฆ Packaging signed app..." + + # Get app name for zip file naming + APP_NAME=$(basename "${{ inputs.app-path }}" .app) + + if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then + PACKAGE_SUFFIX="Signed-Notarized" + echo "Creating distribution package with notarized app..." + else + PACKAGE_SUFFIX="Signed" + echo "Creating distribution package with signed app..." + fi + + # Create zip package + ZIP_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.zip" + ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_FILE" + echo "โœ… Created ZIP package: $ZIP_FILE" + + # Check if we can create DMG (hdiutil is available) + if command -v hdiutil &> /dev/null; then + # Create DMG package (much better for distribution) + DMG_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.dmg" + echo "Creating DMG distribution package..." + + # Create temporary folder for DMG contents + DMG_TMP_DIR=$(mktemp -d) + cp -R "${{ inputs.app-path }}" "$DMG_TMP_DIR/" + + # Optional: Add README or instructions + echo "# Installation Instructions\n\nDrag the application to your Applications folder to install." > "$DMG_TMP_DIR/README.txt" + + # Create DMG file with the app + hdiutil create -volname "${APP_NAME}" -srcfolder "$DMG_TMP_DIR" -ov -format UDZO "$DMG_FILE" + + if [ -f "$DMG_FILE" ]; then + echo "โœ… Created DMG package: $DMG_FILE" + + # Sign the DMG with the same certificate used for the app + echo "Signing DMG file..." + + # Decide which keychain to use + if [ "${{ steps.setup-cert.outputs.use_system_cert }}" = "true" ]; then + echo "Using system keychain identity" + # Get certificate hash instead of name to avoid ambiguity + IDENTITY_HASH=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | awk '{print $2}') + echo "Using certificate hash: $IDENTITY_HASH" + else + # Make sure keychain is unlocked + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + echo "Using custom keychain identity" + # Get certificate hash instead of name to avoid ambiguity + IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}') + echo "Using certificate hash: $IDENTITY_HASH" + fi + + if [ -n "$IDENTITY_HASH" ]; then + # Sign the DMG + codesign --force --sign "$IDENTITY_HASH" --options runtime --timestamp "$DMG_FILE" + + # Verify DMG signature + echo "Verifying DMG signature..." + codesign -vvv "$DMG_FILE" + + # Only notarize DMG if the app was successfully notarized + if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then + echo "Notarizing DMG file..." - # Wait for notarization to complete - debug_log "Waiting for DMG notarization to complete..." - TIMEOUT=10 # 10 minutes timeout for DMG - COUNT=0 - DMG_NOTARIZATION_STATUS="in progress" - - while [[ "$DMG_NOTARIZATION_STATUS" == "in progress" && $COUNT -lt $TIMEOUT ]]; do - sleep 60 + # Check if we're using API key notarization method + if [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then + # Use the same API key setup from the notarize step + echo "Using App Store Connect API key for DMG notarization..." - xcrun altool --notarization-info "$DMG_REQUEST_UUID" \ - --username "${{ inputs.apple-id }}" \ - --password "${{ inputs.app-password }}" \ - > "$WORK_DIR/dmg_notarization_info.txt" 2>&1 + echo "Submitting DMG for notarization..." + DMG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \ + --key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" --wait 2>&1) + + echo "DMG notarization submission output:" + echo "$DMG_SUBMIT_OUTPUT" + + # Extract DMG submission ID + DMG_SUBMISSION_ID=$(echo "$DMG_SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2) + + if [ -n "$DMG_SUBMISSION_ID" ] && echo "$DMG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then + echo "โœ… DMG notarization completed successfully!" - cat "$WORK_DIR/dmg_notarization_info.txt" | tee -a "$DEBUG_LOG_PATH" + # Staple the DMG + echo "Stapling notarization ticket to DMG..." + xcrun stapler staple "$DMG_FILE" - DMG_NOTARIZATION_STATUS=$(grep -o "Status: .*" "$WORK_DIR/dmg_notarization_info.txt" | cut -d ':' -f2 | xargs) - - debug_log "DMG notarization status: $DMG_NOTARIZATION_STATUS" - COUNT=$((COUNT+1)) - done - - if [[ "$DMG_NOTARIZATION_STATUS" == "success" ]]; then - debug_log "DMG notarization successful" - - # Staple DMG - debug_log "Stapling notarization ticket to DMG" - xcrun stapler staple "$DMG_PATH" - if [ $? -eq 0 ]; then - debug_log "DMG stapling successful" + # Verify DMG stapling + echo "Verifying DMG stapling..." + xcrun stapler validate "$DMG_FILE" + + echo "DMG is now fully signed, notarized, and stapled!" else - debug_log "DMG stapling failed" + echo "โš ๏ธ DMG notarization may have failed or is still in progress." + echo "The app itself is still properly notarized, but the DMG may need manual verification." + fi + elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then + # Use App-specific password for DMG notarization + echo "Using App-specific password for DMG notarization..." + + echo "Submitting DMG for notarization..." + DMG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \ + --apple-id "$APPLE_ID" \ + --password "$APP_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" --wait 2>&1) + + echo "DMG notarization submission output:" + echo "$DMG_SUBMIT_OUTPUT" + + # Extract DMG submission ID + DMG_SUBMISSION_ID=$(echo "$DMG_SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2) + + if [ -n "$DMG_SUBMISSION_ID" ] && echo "$DMG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then + echo "โœ… DMG notarization completed successfully!" + + # Staple the DMG + echo "Stapling notarization ticket to DMG..." + xcrun stapler staple "$DMG_FILE" + + # Verify DMG stapling + echo "Verifying DMG stapling..." + xcrun stapler validate "$DMG_FILE" + + echo "DMG is now fully signed, notarized, and stapled!" + else + echo "โš ๏ธ DMG notarization may have failed or is still in progress." + echo "The app itself is still properly notarized, but the DMG may need manual verification." fi else - debug_log "DMG notarization failed or timed out: $DMG_NOTARIZATION_STATUS" - if [[ -f "$WORK_DIR/dmg_notarization_info.txt" ]]; then - cat "$WORK_DIR/dmg_notarization_info.txt" - fi + echo "โš ๏ธ DMG not notarized due to missing credentials." + echo "The app itself is properly notarized, but the DMG is only signed." fi else - debug_log "DMG notarization submission failed, no UUID returned" - cat "$WORK_DIR/dmg_notarization_output.txt" + echo "App was not notarized, skipping DMG notarization." fi + else + echo "โš ๏ธ No valid identity found for DMG signing. DMG will be created but not signed." fi - fi - fi - - # Final verification of all distribution artifacts - debug_log "Verifying final distribution artifacts" - - # Check ZIP file - if [[ -f "$ZIP_PATH" ]]; then - ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) - debug_log "ZIP package size: $ZIP_SIZE" - - # Verify ZIP integrity - unzip -t "$ZIP_PATH" > /dev/null - if [ $? -eq 0 ]; then - debug_log "ZIP package integrity verified" + + # Use DMG as the primary package if available + echo "::set-output name=package-path::$DMG_FILE" + echo "::set-output name=zip-package-path::$ZIP_FILE" else - debug_log "ZIP package may be corrupted" + echo "โš ๏ธ Failed to create DMG, falling back to ZIP package" + echo "::set-output name=package-path::$ZIP_FILE" fi - fi - - # Check DMG file - if [[ -f "$DMG_PATH" ]]; then - DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1) - debug_log "DMG package size: $DMG_SIZE" - # Verify DMG signature if signed - if [[ "$SIGNING_RESULT" == "true" ]]; then - codesign -vvv "$DMG_PATH" 2>&1 | tee -a "$DEBUG_LOG_PATH" || debug_log "DMG signature verification failed" - fi - fi - shell: bash - - - name: Set outputs - id: set-outputs - run: | - debug_log "Setting action outputs" - - # Pass through environment variables to outputs - if [[ "$SIGNING_RESULT" == "true" ]]; then - echo "signed=true" >> $GITHUB_OUTPUT - elif [[ "$SIGNING_RESULT" == "ad-hoc" ]]; then - echo "signed=ad-hoc" >> $GITHUB_OUTPUT + # Clean up temp directory + rm -rf "$DMG_TMP_DIR" else - echo "signed=none" >> $GITHUB_OUTPUT + echo "hdiutil not available, skipping DMG creation" + echo "::set-output name=package-path::$ZIP_FILE" fi - - if [[ "$NOTARIZATION_RESULT" == "true" ]]; then - echo "notarized=true" >> $GITHUB_OUTPUT - else - echo "notarized=false" >> $GITHUB_OUTPUT - fi - - echo "app-path=$APP_PATH" >> $GITHUB_OUTPUT - echo "zip-path=$ZIP_PATH" >> $GITHUB_OUTPUT - - if [[ "$DMG_CREATED" == "true" ]]; then - echo "package-path=$DMG_PATH" >> $GITHUB_OUTPUT - else - echo "package-path=$ZIP_PATH" >> $GITHUB_OUTPUT - fi - - debug_log "Action completed" - shell: bash - - - name: Clean up + + - name: Cleanup if: always() + shell: bash run: | - debug_log "Cleaning up" - - # Clean up keychain - if [[ -n "$KEYCHAIN_NAME" ]]; then - security delete-keychain "$KEYCHAIN_NAME" || true - debug_log "Keychain deleted" - fi - - # Clean up temporary files - if [[ -d "$WORK_DIR" ]]; then - rm -rf "$WORK_DIR" || true - debug_log "Temporary files deleted" - fi - - debug_log "Cleanup completed" - shell: bash \ No newline at end of file + echo "๐Ÿงน Cleaning up..." + security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true + rm -f *-notarize.zip || true + echo "โœ… Cleanup complete" + \ No newline at end of file diff --git a/.gitea/workflows/test-macos-build.yml b/.gitea/workflows/test-macos-build.yml index 4e785bca..a853419c 100644 --- a/.gitea/workflows/test-macos-build.yml +++ b/.gitea/workflows/test-macos-build.yml @@ -561,15 +561,14 @@ jobs: ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) debug_log "ZIP archive size: $ZIP_SIZE" + # Save UUID to a file to ensure it persists across steps + UUID_FILE="$WORK_DIR/notarization_uuid.txt" + # Submit for notarization - use separate submission and polling debug_log "Submitting app for notarization..." - # Create submission file command from parts to avoid shell re-interpretation - SUBMIT_CMD="xcrun notarytool submit \"$ZIP_PATH\" --key \"$API_KEY_FILE\" --key-id \"${{ secrets.NOTARY_API_KEY_ID }}\" --issuer \"${{ secrets.NOTARY_API_KEY_ISSUER_ID }}\"" - debug_log "Running command: $SUBMIT_CMD" - # Submit with error capture - SUBMIT_OUTPUT=$(eval "$SUBMIT_CMD" 2>&1) + SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" --key "$API_KEY_FILE" --key-id "${{ secrets.NOTARY_API_KEY_ID }}" --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" 2>&1) SUBMIT_STATUS=$? # Save output for detailed analysis @@ -582,12 +581,12 @@ jobs: exit 1 fi - # Extract the request UUID (handle different output formats) - REQUEST_UUID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-z0-9-]*" | cut -d' ' -f2) + # Extract the request UUID - ensuring we get just one match + REQUEST_UUID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-z0-9-]*" | head -1 | cut -d' ' -f2) if [ -z "$REQUEST_UUID" ]; then # Alternative grep pattern - REQUEST_UUID=$(echo "$SUBMIT_OUTPUT" | grep -o "[a-f0-9]\{8\}-[a-f0-9]\{4\}-[a-f0-9]\{4\}-[a-f0-9]\{4\}-[a-f0-9]\{12\}") + REQUEST_UUID=$(echo "$SUBMIT_OUTPUT" | grep -o "[a-f0-9]\{8\}-[a-f0-9]\{4\}-[a-f0-9]\{4\}-[a-f0-9]\{4\}-[a-f0-9]\{12\}" | head -1) fi if [ -z "$REQUEST_UUID" ]; then @@ -601,11 +600,16 @@ jobs: debug_log "Notarization submission UUID: $REQUEST_UUID" echo "NOTARIZATION_UUID=$REQUEST_UUID" >> $GITHUB_ENV + # Also save UUID to file so it's available in cleanup step + echo "$REQUEST_UUID" > "$UUID_FILE" + debug_log "Saved UUID to file: $UUID_FILE" + # Wait for notarization to complete with polling debug_log "Waiting for notarization to complete (this may take several minutes)..." WAIT_COUNTER=1 TOTAL_WAIT=60 # 60 minutes maximum + NOTARIZATION_COMPLETED=false while [ $WAIT_COUNTER -le $TOTAL_WAIT ]; do if [ $WAIT_COUNTER -gt 1 ]; then @@ -613,10 +617,8 @@ jobs: sleep 60 fi - # Check status - STATUS_CMD="xcrun notarytool info \"$REQUEST_UUID\" --key \"$API_KEY_FILE\" --key-id \"${{ secrets.NOTARY_API_KEY_ID }}\" --issuer \"${{ secrets.NOTARY_API_KEY_ISSUER_ID }}\"" - debug_log "Checking status with: $STATUS_CMD" - STATUS_OUTPUT=$(eval "$STATUS_CMD" 2>&1) + # Check status - make sure UUID is clean and not duplicated + STATUS_OUTPUT=$(xcrun notarytool info "$REQUEST_UUID" --key "$API_KEY_FILE" --key-id "${{ secrets.NOTARY_API_KEY_ID }}" --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" 2>&1) STATUS_CODE=$? echo "$STATUS_OUTPUT" > "$WORK_DIR/status_output_$WAIT_COUNTER.txt" @@ -636,11 +638,21 @@ jobs: if [ "$REQUEST_STATUS" = "Accepted" ]; then debug_log "Notarization successful!" + NOTARIZATION_COMPLETED=true echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV + echo "NOTARIZATION_COMPLETED=true" >> $GITHUB_ENV break elif [ "$REQUEST_STATUS" = "Invalid" ] || [ "$REQUEST_STATUS" = "Rejected" ]; then debug_log "ERROR: Notarization failed with status: $REQUEST_STATUS" + NOTARIZATION_COMPLETED=true echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + echo "NOTARIZATION_COMPLETED=true" >> $GITHUB_ENV + + # Get the log URL to diagnose the issue + LOG_URL_OUTPUT=$(xcrun notarytool log "$REQUEST_UUID" --key "$API_KEY_FILE" --key-id "${{ secrets.NOTARY_API_KEY_ID }}" --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" 2>&1) + echo "Notarization log:" | tee -a "$DEBUG_LOG_PATH" + echo "$LOG_URL_OUTPUT" | tee -a "$DEBUG_LOG_PATH" + exit 1 fi # In progress - continue waiting @@ -650,15 +662,16 @@ jobs: WAIT_COUNTER=$((WAIT_COUNTER+1)) done - # Check final status - if [ $WAIT_COUNTER -gt $TOTAL_WAIT ]; then - debug_log "ERROR: Notarization wait timeout after $TOTAL_WAIT minutes" - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + # Check if we timed out + if [ "$NOTARIZATION_COMPLETED" != "true" ]; then + debug_log "ERROR: Notarization timed out after $TOTAL_WAIT attempts" + echo "NOTARIZATION_RESULT=timeout" >> $GITHUB_ENV + echo "NOTARIZATION_COMPLETED=true" >> $GITHUB_ENV exit 1 fi if [ "$REQUEST_STATUS" != "Accepted" ]; then - debug_log "ERROR: Notarization failed or timed out" + debug_log "ERROR: Notarization failed with status: $REQUEST_STATUS" echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV exit 1 fi @@ -688,128 +701,9 @@ jobs: echo "STAPLING_RESULT=false" >> $GITHUB_ENV exit 1 fi - shell: bash - - - name: Staple notarization ticket - id: staple-ticket - if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' - run: | - # Debug log helper - function debug_log() { - echo "DEBUG: $1" - if [[ -n "$DEBUG_LOG_PATH" ]]; then - echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$DEBUG_LOG_PATH" - fi - } - debug_log "Stapling notarization ticket to app" - - # Staple the ticket - xcrun stapler staple "$APP_PATH" - STAPLE_RESULT=$? - - if [ $STAPLE_RESULT -eq 0 ]; then - debug_log "Notarization ticket stapled successfully" - echo "STAPLING_RESULT=true" >> $GITHUB_ENV - else - debug_log "ERROR: Stapling failed with exit code: $STAPLE_RESULT" - echo "STAPLING_RESULT=false" >> $GITHUB_ENV - exit 1 - fi - shell: bash - - - name: Verify notarization and stapling - id: verify-notarization - if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' && env.STAPLING_RESULT == 'true' - run: | - # Debug log helper - function debug_log() { - echo "DEBUG: $1" - if [[ -n "$DEBUG_LOG_PATH" ]]; then - echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$DEBUG_LOG_PATH" - fi - } - - debug_log "Verifying notarization and stapling" - - # Verify code signature with staple - debug_log "Verifying code signature..." - codesign --verify --deep --strict --verbose=2 "$APP_PATH" - VERIFY_RESULT=$? - - if [ $VERIFY_RESULT -eq 0 ]; then - debug_log "Code signature verification successful" - else - debug_log "ERROR: Code signature verification failed with exit code: $VERIFY_RESULT" - exit 1 - fi - - # Verify stapling - debug_log "Verifying stapling..." - xcrun stapler validate "$APP_PATH" - STAPLE_VERIFY_RESULT=$? - - if [ $STAPLE_VERIFY_RESULT -eq 0 ]; then - debug_log "Stapling verification successful" - echo "VERIFY_RESULT=true" >> $GITHUB_ENV - else - debug_log "ERROR: Stapling verification failed with exit code: $STAPLE_VERIFY_RESULT" - echo "VERIFY_RESULT=false" >> $GITHUB_ENV - exit 1 - fi - shell: bash - - - name: Remove quarantine attribute - id: remove-quarantine - if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' && env.STAPLING_RESULT == 'true' - run: | - # Debug log helper - function debug_log() { - echo "DEBUG: $1" - if [[ -n "$DEBUG_LOG_PATH" ]]; then - echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$DEBUG_LOG_PATH" - fi - } - - debug_log "Removing quarantine attribute from app" - - # Directly run the command without creating a script file - debug_log "Removing quarantine attribute from all files..." - find "$APP_PATH" -exec xattr -d com.apple.quarantine {} \; 2>/dev/null || true - debug_log "Quarantine attributes removed" - shell: bash - - - name: Package signed app - id: package-app - if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' && env.STAPLING_RESULT == 'true' - run: | - # Debug log helper - function debug_log() { - echo "DEBUG: $1" - if [[ -n "$DEBUG_LOG_PATH" ]]; then - echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$DEBUG_LOG_PATH" - fi - } - - debug_log "Packaging the signed and notarized app" - - # Create DMG using hdiutil - debug_log "Creating DMG package..." - hdiutil create -volname "$APP_NAME" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_PATH" - DMG_CREATE_RESULT=$? - - # Check DMG creation result - if [ $DMG_CREATE_RESULT -eq 0 ]; then - debug_log "DMG package created successfully at: $DMG_PATH" - DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1) - debug_log "DMG size: $DMG_SIZE" - echo "DMG_CREATED=true" >> $GITHUB_ENV - echo "PACKAGE_PATH=$DMG_PATH" >> $GITHUB_ENV - else - debug_log "WARNING: DMG creation failed, using ZIP as distribution package" - echo "DMG_CREATED=false" >> $GITHUB_ENV - echo "PACKAGE_PATH=$ZIP_PATH" >> $GITHUB_ENV - fi + # Mark this step as having completed successfully for the cleanup step + echo "NOTARIZE_STEP_COMPLETED=true" >> $GITHUB_ENV shell: bash - name: Clean up @@ -824,36 +718,57 @@ jobs: fi } - debug_log "Cleaning up resources" + debug_log "Starting cleanup process..." - # Save state message for better debugging - if [[ "$NOTARIZATION_RESULT" == "true" ]]; then - debug_log "Cleanup after successful notarization" + # Check if notarization is still in progress + if [[ "$NOTARIZE_STEP_COMPLETED" != "true" && -n "$WORK_DIR" && -f "$WORK_DIR/notarization_uuid.txt" ]]; then + debug_log "WARNING: Notarization step did not complete properly. Checking status before cleanup." + + # Read UUID from file + REQUEST_UUID=$(cat "$WORK_DIR/notarization_uuid.txt") + + if [[ -n "$REQUEST_UUID" ]]; then + debug_log "Found notarization UUID: $REQUEST_UUID" + debug_log "Will check status one more time before cleanup..." + + if [[ -f "$API_KEY_FILE" ]]; then + STATUS_OUTPUT=$(xcrun notarytool info "$REQUEST_UUID" --key "$API_KEY_FILE" --key-id "${{ secrets.NOTARY_API_KEY_ID }}" --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" 2>&1 || echo "Status check failed") + debug_log "Final notarization status check output:" + debug_log "$STATUS_OUTPUT" + else + debug_log "API key file not available for final status check" + fi + else + debug_log "No UUID file found, skipping final status check" + fi else - debug_log "Cleanup after notarization state: $NOTARIZATION_RESULT" + debug_log "Notarization process already completed or not started" + fi + + # Save notarization logs if available + if [[ -d "$WORK_DIR" ]]; then + debug_log "Saving notarization logs from work directory" + mkdir -p "$DEBUG_LOG_PATH/../notarization_details" 2>/dev/null || true + cp "$WORK_DIR/"*.txt "$DEBUG_LOG_PATH/../notarization_details/" 2>/dev/null || true fi # Clean up keychain if [[ -n "$KEYCHAIN_NAME" ]]; then - security delete-keychain "$KEYCHAIN_NAME" || true + debug_log "Deleting keychain: $KEYCHAIN_NAME" + security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true debug_log "Keychain deleted" fi # Clean up temporary files if [[ -d "$WORK_DIR" ]]; then - # Save notarization logs first - if [[ -d "$DEBUG_LOG_PATH" ]] && [[ -d "$WORK_DIR" ]]; then - debug_log "Saving work directory files to debug logs" - cp -R "$WORK_DIR/"*.txt "$DEBUG_LOG_PATH/" 2>/dev/null || true - fi - - rm -rf "$WORK_DIR" || true + debug_log "Removing temporary work directory: $WORK_DIR" + rm -rf "$WORK_DIR" 2>/dev/null || true debug_log "Temporary files deleted" fi debug_log "Cleanup completed" shell: bash - + # Upload debug logs if available - name: Upload Debug Logs uses: actions/upload-artifact@v3