From 79ddd3ff1242594cb73e2241d34a05291cd8d2b5 Mon Sep 17 00:00:00 2001 From: Ozgur Ersoy Date: Wed, 16 Apr 2025 15:43:41 +0200 Subject: [PATCH] fix(actions): enhance macOS notarization workflow by adding comprehensive error handling, improved API key management, and detailed logging for notarization status checks --- .gitea/workflows/test-macos-build.yml | 291 ++++++++++++++------------ 1 file changed, 161 insertions(+), 130 deletions(-) diff --git a/.gitea/workflows/test-macos-build.yml b/.gitea/workflows/test-macos-build.yml index abd9ca86..e05d52ef 100644 --- a/.gitea/workflows/test-macos-build.yml +++ b/.gitea/workflows/test-macos-build.yml @@ -246,19 +246,49 @@ jobs: echo "DMG_PATH=$DMG_PATH" >> $GITHUB_ENV # Set notarization variables based on method - echo "Using API key method for notarization" + debug_log "Using API key method for notarization" - # Create API key file + # Create API key file - properly decode from base64 API_KEY_FILE="$WORK_DIR/api_key.p8" - echo "${{ secrets.NOTARY_API_KEY_PATH }}" | base64 --decode > "$API_KEY_FILE" - echo "API_KEY_FILE=$API_KEY_FILE" >> $GITHUB_ENV + debug_log "Decoding API key from base64 to: $API_KEY_FILE" - # Verify API key file exists + # Check if NOTARY_API_KEY_PATH is provided + if [[ -z "${{ secrets.NOTARY_API_KEY_PATH }}" ]]; then + debug_log "ERROR: NOTARY_API_KEY_PATH secret is empty" + exit 1 + fi + + # Explicitly decode from base64 as instructed + echo "${{ secrets.NOTARY_API_KEY_PATH }}" | base64 -d > "$API_KEY_FILE" 2>/dev/null + + # Verify API key file exists and has content if [[ ! -f "$API_KEY_FILE" ]]; then debug_log "ERROR: API key file could not be created" exit 1 fi + if [[ ! -s "$API_KEY_FILE" ]]; then + debug_log "ERROR: API key file is empty after base64 decoding" + exit 1 + fi + + # Verify key format + if ! grep -q "BEGIN PRIVATE KEY" "$API_KEY_FILE"; then + debug_log "ERROR: API key is not in PEM format (missing BEGIN PRIVATE KEY)" + debug_log "First 10 bytes of API key file: $(hexdump -n 10 -ve '1/1 "%.2x"' "$API_KEY_FILE")" + exit 1 + fi + + # Get file permissions and size for debugging + FILE_PERMS=$(ls -la "$API_KEY_FILE") + FILE_SIZE=$(wc -c < "$API_KEY_FILE") + debug_log "API key file ($FILE_SIZE bytes): $FILE_PERMS" + + # Set proper permissions + chmod 600 "$API_KEY_FILE" + + echo "API_KEY_FILE=$API_KEY_FILE" >> $GITHUB_ENV + debug_log "API key file created at: $API_KEY_FILE" debug_log "API key ID: ${{ secrets.NOTARY_API_KEY_ID }}" debug_log "API key issuer ID: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" @@ -513,97 +543,136 @@ jobs: fi debug_log "ZIP archive created successfully at: $ZIP_PATH" + ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) + debug_log "ZIP archive size: $ZIP_SIZE" - # Submit for notarization + # Submit for notarization - use separate submission and polling debug_log "Submitting app for notarization..." - # First submit the app to get the request UUID - 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 }}") - - echo "$SUBMIT_OUTPUT" | tee -a "$DEBUG_LOG_PATH" + # 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" - # Extract the request UUID - REQUEST_UUID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-z0-9-]*" | cut -d' ' -f2) + # Submit with error capture + SUBMIT_OUTPUT=$(eval "$SUBMIT_CMD" 2>&1) + SUBMIT_STATUS=$? - if [ -z "$REQUEST_UUID" ]; then - debug_log "ERROR: Failed to extract request UUID" + # Save output for detailed analysis + echo "$SUBMIT_OUTPUT" > "$WORK_DIR/submit_output.txt" + cat "$WORK_DIR/submit_output.txt" | tee -a "$DEBUG_LOG_PATH" + + if [ $SUBMIT_STATUS -ne 0 ]; then + debug_log "ERROR: Failed to submit for notarization, exit code: $SUBMIT_STATUS" echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV exit 1 fi - debug_log "Notarization request submitted with UUID: $REQUEST_UUID" + # Extract the request UUID (handle different output formats) + REQUEST_UUID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-z0-9-]*" | 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\}") + fi + + if [ -z "$REQUEST_UUID" ]; then + debug_log "ERROR: Failed to extract UUID from notarization submission" + debug_log "Full submission output:" + cat "$WORK_DIR/submit_output.txt" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + + debug_log "Notarization submission UUID: $REQUEST_UUID" + echo "NOTARIZATION_UUID=$REQUEST_UUID" >> $GITHUB_ENV + + # Wait for notarization to complete with polling debug_log "Waiting for notarization to complete (this may take several minutes)..." - # Wait for notarization to complete with verbose output WAIT_COUNTER=1 - while true; do - if [ $WAIT_COUNTER -gt 60 ]; then - debug_log "ERROR: Notarization wait timeout after 60 minutes" - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV - exit 1 - fi - - # Sleep for 60 seconds between checks + TOTAL_WAIT=60 # 60 minutes maximum + + while [ $WAIT_COUNTER -le $TOTAL_WAIT ]; do if [ $WAIT_COUNTER -gt 1 ]; then - debug_log "Waiting 60 seconds before checking again (attempt $WAIT_COUNTER)..." + debug_log "Waiting 60 seconds before checking again (attempt $WAIT_COUNTER/$TOTAL_WAIT)..." sleep 60 fi - INFO_OUTPUT=$(xcrun notarytool info "$REQUEST_UUID" \ - --key "$API_KEY_FILE" \ - --key-id "${{ secrets.NOTARY_API_KEY_ID }}" \ - --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}") + # 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) + STATUS_CODE=$? + + echo "$STATUS_OUTPUT" > "$WORK_DIR/status_output_$WAIT_COUNTER.txt" + cat "$WORK_DIR/status_output_$WAIT_COUNTER.txt" | tee -a "$DEBUG_LOG_PATH" + + if [ $STATUS_CODE -ne 0 ]; then + debug_log "WARNING: Status check failed, exit code: $STATUS_CODE" + # Continue anyway to retry + else + # Extract status + REQUEST_STATUS=$(echo "$STATUS_OUTPUT" | grep -o "status: [A-Za-z]*" | cut -d' ' -f2) - echo "$INFO_OUTPUT" | tee -a "$DEBUG_LOG_PATH" - - # Extract status - REQUEST_STATUS=$(echo "$INFO_OUTPUT" | grep -o "status: [A-Za-z]*" | cut -d' ' -f2) - - debug_log "Current notarization status: $REQUEST_STATUS" - - if [ "$REQUEST_STATUS" == "Accepted" ]; then - debug_log "Notarization completed successfully!" - break - elif [ "$REQUEST_STATUS" == "Invalid" ] || [ "$REQUEST_STATUS" == "Rejected" ]; then - debug_log "ERROR: Notarization failed with status: $REQUEST_STATUS" - - # Get log URL if available - LOG_URL=$(echo "$INFO_OUTPUT" | grep -o "LogFileURL: [^ ]*" | cut -d' ' -f2) - if [ -n "$LOG_URL" ]; then - debug_log "Downloading log file from: $LOG_URL" - curl -s "$LOG_URL" | tee "$WORK_DIR/notarization_log.json" | tee -a "$DEBUG_LOG_PATH" + if [ -z "$REQUEST_STATUS" ]; then + debug_log "WARNING: Could not extract status from output" + else + debug_log "Notarization status: $REQUEST_STATUS" + + if [ "$REQUEST_STATUS" = "Accepted" ]; then + debug_log "Notarization successful!" + echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV + break + elif [ "$REQUEST_STATUS" = "Invalid" ] || [ "$REQUEST_STATUS" = "Rejected" ]; then + debug_log "ERROR: Notarization failed with status: $REQUEST_STATUS" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + # In progress - continue waiting fi - - echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV - exit 1 fi WAIT_COUNTER=$((WAIT_COUNTER+1)) done - # Get detailed logs at the end - xcrun notarytool log "$REQUEST_UUID" \ - --key "$API_KEY_FILE" \ - --key-id "${{ secrets.NOTARY_API_KEY_ID }}" \ - --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" \ - "$WORK_DIR/notarization_details.json" - - debug_log "Detailed notarization log saved to $WORK_DIR/notarization_details.json" - cat "$WORK_DIR/notarization_details.json" | tee -a "$DEBUG_LOG_PATH" - - # Check if notarization was successful - if [[ "$REQUEST_STATUS" == "Accepted" ]]; then - debug_log "Notarization successful" - echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV - else - debug_log "ERROR: Notarization failed or timed out" - debug_log "Notarization status: $REQUEST_STATUS" + # 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 exit 1 fi + + if [ "$REQUEST_STATUS" != "Accepted" ]; then + debug_log "ERROR: Notarization failed or timed out" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + + # Success - now staple the ticket + debug_log "Notarization complete, proceeding to 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 + + # Verify stapling + xcrun stapler validate "$APP_PATH" + VALIDATE_RESULT=$? + + if [ $VALIDATE_RESULT -eq 0 ]; then + debug_log "Stapling validation successful" + echo "VERIFY_RESULT=true" >> $GITHUB_ENV + else + debug_log "WARNING: Stapling validation failed but continuing" + echo "VERIFY_RESULT=false" >> $GITHUB_ENV + fi + else + debug_log "ERROR: Failed to staple notarization ticket" + echo "STAPLING_RESULT=false" >> $GITHUB_ENV + exit 1 + fi shell: bash - name: Staple notarization ticket @@ -709,78 +778,27 @@ jobs: debug_log "Packaging the signed and notarized 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" - - # Copy the app to the temporary directory - cp -R "$APP_PATH" "$DMG_TEMP_DIR/" - - # Create instructions text file - echo "Drag the application to the Applications folder to install it." > "$DMG_TEMP_DIR/README.txt" - - # Create symlink to Applications folder - ln -s /Applications "$DMG_TEMP_DIR/Applications" - - # 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" - - DMG_CREATE_RESULT=$? - - 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 + # 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 "ERROR: DMG creation failed with exit code: $DMG_CREATE_RESULT" - debug_log "Falling back to ZIP package" + 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 - - # Final verification of distribution artifacts - debug_log "Verifying final distribution package" - - FINAL_PACKAGE="${PACKAGE_PATH:-$ZIP_PATH}" - if [[ -f "$FINAL_PACKAGE" ]]; then - PACKAGE_SIZE=$(du -h "$FINAL_PACKAGE" | cut -f1) - debug_log "Distribution package size: $PACKAGE_SIZE" - debug_log "Distribution package ready at: $FINAL_PACKAGE" - else - debug_log "ERROR: Distribution package not found" - exit 1 - fi shell: bash - name: Clean up + id: cleanup if: always() run: | # Debug log helper @@ -791,7 +809,14 @@ jobs: fi } - debug_log "Cleaning up" + debug_log "Cleaning up resources" + + # Save state message for better debugging + if [[ "$NOTARIZATION_RESULT" == "true" ]]; then + debug_log "Cleanup after successful notarization" + else + debug_log "Cleanup after notarization state: $NOTARIZATION_RESULT" + fi # Clean up keychain if [[ -n "$KEYCHAIN_NAME" ]]; then @@ -801,6 +826,12 @@ jobs: # 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 "Temporary files deleted" fi