fix(actions): enhance macOS notarization workflow with detailed debug logging, improved variable management, and streamlined artifact handling

This commit is contained in:
Ozgur 2025-04-16 01:41:36 +02:00
parent 1c7e12e279
commit 6c29eb93be
No known key found for this signature in database
GPG Key ID: 66CDF27505A35546

View File

@ -188,22 +188,503 @@ jobs:
echo "DEBUG_LOG_PATH=$(pwd)/debug_logs/notarize_log.txt" >> $GITHUB_ENV
shell: bash
# Use the macos-notarize action to sign and notarize the app
- name: Sign and Notarize macOS App
uses: ./.gitea/actions/macos-notarize
id: sign-and-notarize
with:
app-path: ${{ env.APP_PATH }}
entitlements-file: ${{ env.ENTITLEMENTS_FILE }}
team-id: ${{ secrets.APPLE_TEAM_ID }}
certificate-base64: ${{ secrets.MACOS_CERTIFICATE }}
certificate-password: ${{ secrets.MACOS_CERTIFICATE_PWD }}
notarization-method: 'api-key'
notary-api-key-id: ${{ secrets.NOTARY_API_KEY_ID }}
notary-api-key-issuer-id: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
notary-api-key-path: ${{ secrets.NOTARY_API_KEY_PATH }}
bundle-id: ${{ env.BUNDLE_ID }}
fallback-to-adhoc: 'false'
# Beginning of macos-notarize steps
- name: Setup debug environment
id: setup-debug-env
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"
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: ${{ env.APP_PATH }}"
debug_log "Team ID: ${{ secrets.APPLE_TEAM_ID }}"
debug_log "Notarization method: api-key"
debug_log "Bundle ID: ${{ env.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
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
# Set paths
echo "APP_PATH=${{ env.APP_PATH }}" >> $GITHUB_ENV
# 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 "${{ env.BUNDLE_ID }}" ]]; then
BUNDLE_ID="${{ env.BUNDLE_ID }}"
else
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ env.APP_PATH }}/Contents/Info.plist")
fi
echo "BUNDLE_ID=$BUNDLE_ID" >> $GITHUB_ENV
# Get app name from bundle path
APP_NAME=$(basename "${{ env.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
echo "Using API key method for notarization"
# Create API key file
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
# 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: ${{ secrets.NOTARY_API_KEY_ID }}"
debug_log "API key issuer ID: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}"
shell: bash
- name: Setup keychain
id: setup-keychain
run: |
debug_log "Setting up keychain"
# 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 "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > "$CERTIFICATE_PATH"
# Add to keychain
debug_log "Importing certificate into keychain"
security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_NAME" -P "${{ secrets.MACOS_CERTIFICATE_PWD }}" -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: ${{ secrets.APPLE_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 [[ "false" == "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
# Make sure entitlements file is defined and reset variable name
ENTITLEMENTS_PATH="${{ env.ENTITLEMENTS_FILE }}"
# 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
exit 1
fi
# Notarize the app using API key method
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 "${{ secrets.NOTARY_API_KEY_ID }}" \
--issuer "${{ secrets.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
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
# Verify stapling
debug_log "Verifying notarization stapling"
xcrun stapler validate "$APP_PATH"
if [ $? -eq 0 ]; then
debug_log "Stapling validation successful"
else
debug_log "Stapling validation failed, but continuing"
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"
# 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
# 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"
fi
# If app was notarized, also notarize the DMG
if [[ "$NOTARIZATION_RESULT" == "true" ]]; then
debug_log "Notarizing DMG..."
# Notarize the DMG using API key method
debug_log "Notarizing DMG with API key method"
xcrun notarytool submit "$DMG_PATH" \
--key "$API_KEY_FILE" \
--key-id "${{ secrets.NOTARY_API_KEY_ID }}" \
--issuer "${{ secrets.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)
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"
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"
else
debug_log "ZIP package may be corrupted"
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
# Set output variables
if [[ "$SIGNING_RESULT" == "true" ]]; then
echo "SIGNED_STATUS=true" >> $GITHUB_ENV
elif [[ "$SIGNING_RESULT" == "ad-hoc" ]]; then
echo "SIGNED_STATUS=ad-hoc" >> $GITHUB_ENV
else
echo "SIGNED_STATUS=none" >> $GITHUB_ENV
fi
if [[ "$NOTARIZATION_RESULT" == "true" ]]; then
echo "NOTARIZED_STATUS=true" >> $GITHUB_ENV
else
echo "NOTARIZED_STATUS=false" >> $GITHUB_ENV
fi
if [[ "$DMG_CREATED" == "true" ]]; then
echo "PACKAGE_PATH=$DMG_PATH" >> $GITHUB_ENV
else
echo "PACKAGE_PATH=$ZIP_PATH" >> $GITHUB_ENV
fi
shell: bash
- name: Clean up
if: always()
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
# Upload debug logs if available
- name: Upload Debug Logs
@ -217,21 +698,21 @@ jobs:
# Upload only the DMG file as main distribution artifact
- name: Upload Mac Distribution DMG
uses: actions/upload-artifact@v3
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none'
if: env.NOTARIZED_STATUS == 'true' && env.SIGNED_STATUS != 'none'
with:
name: LuckyWorld-Mac-Distribution
path: ${{ steps.sign-and-notarize.outputs.package-path }}
path: ${{ env.PACKAGE_PATH }}
retention-days: 30
# Report results
- name: Report Results
run: |
echo "🔐 App signing: ${{ steps.sign-and-notarize.outputs.signed }}"
echo "🔏 App notarization: ${{ steps.sign-and-notarize.outputs.notarized }}"
echo "🔐 App signing: ${{ env.SIGNED_STATUS }}"
echo "🔏 App notarization: ${{ env.NOTARIZED_STATUS }}"
if [ "${{ steps.sign-and-notarize.outputs.signed }}" != "none" ]; then
if [ "${{ env.SIGNED_STATUS }}" != "none" ]; then
echo "✅ Packaging completed successfully!"
echo "Final package: ${{ steps.sign-and-notarize.outputs.package-path }}"
echo "Final package: ${{ env.PACKAGE_PATH }}"
else
echo "⚠️ App was not signed - check the logs for details"
fi