Some checks failed
Test macOS Build Action / test-macos-build (push) Failing after 2s
704 lines
27 KiB
YAML
704 lines
27 KiB
YAML
name: macOS Notarize
|
|
description: 'Signs and notarizes a macOS application with Apple certificates'
|
|
author: moersoy
|
|
|
|
inputs:
|
|
app-path:
|
|
description: 'Path to the .app bundle to sign'
|
|
required: true
|
|
entitlements-file:
|
|
description: 'Path to entitlements file to use for signing'
|
|
required: false
|
|
default: ''
|
|
team-id:
|
|
description: 'Apple Developer Team ID'
|
|
required: true
|
|
certificate-base64:
|
|
description: 'Base64-encoded certificate (P12 file)'
|
|
required: true
|
|
certificate-password:
|
|
description: 'Certificate password'
|
|
required: true
|
|
notarization-method:
|
|
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)'
|
|
required: false
|
|
default: ''
|
|
apple-id:
|
|
description: 'Apple ID email (required if using app-password method)'
|
|
required: false
|
|
default: ''
|
|
notary-api-key-id:
|
|
description: 'App Store Connect API Key ID (required if using 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)'
|
|
required: false
|
|
default: ''
|
|
notary-api-key-path:
|
|
description: 'App Store Connect API Key file content (base64 encoded) (required if using api-key method)'
|
|
required: false
|
|
default: ''
|
|
bundle-id:
|
|
description: 'Bundle ID of the app'
|
|
required: false
|
|
default: ''
|
|
fallback-to-adhoc:
|
|
description: 'Fallback to ad-hoc signing if no certificate is available'
|
|
required: false
|
|
default: 'true'
|
|
|
|
outputs:
|
|
signed:
|
|
description: 'Signing status (true, ad-hoc, none)'
|
|
value: ${{ steps.set-outputs.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 }}
|
|
package-path:
|
|
description: 'Path to the packaged .DMG file'
|
|
value: ${{ steps.set-outputs.outputs.package-path }}
|
|
|
|
runs:
|
|
using: "composite"
|
|
steps:
|
|
- name: Setup debug environment
|
|
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: ${{ 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
|
|
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
|
|
|
|
# Set paths
|
|
echo "APP_PATH=${{ inputs.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 "${{ 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
|
|
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 "${{ 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
|
|
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
|
|
|
|
else
|
|
debug_log "Notarizing with app-specific password method"
|
|
|
|
# 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 [[ -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))
|
|
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
|
|
fi
|
|
else
|
|
debug_log "Notarization submission failed, no UUID returned"
|
|
cat "$WORK_DIR/notarization_output.txt"
|
|
echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV
|
|
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
|
|
|
|
# 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
|
|
if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then
|
|
debug_log "Notarizing DMG with API key method"
|
|
|
|
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)
|
|
|
|
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
|
|
|
|
else
|
|
debug_log "Notarizing DMG with app-specific password method"
|
|
|
|
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)
|
|
|
|
if [[ -n "$DMG_REQUEST_UUID" ]]; then
|
|
debug_log "DMG notarization request submitted, UUID: $DMG_REQUEST_UUID"
|
|
|
|
# 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
|
|
|
|
xcrun altool --notarization-info "$DMG_REQUEST_UUID" \
|
|
--username "${{ inputs.apple-id }}" \
|
|
--password "${{ inputs.app-password }}" \
|
|
> "$WORK_DIR/dmg_notarization_info.txt" 2>&1
|
|
|
|
cat "$WORK_DIR/dmg_notarization_info.txt" | tee -a "$DEBUG_LOG_PATH"
|
|
|
|
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"
|
|
else
|
|
debug_log "DMG stapling failed"
|
|
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
|
|
fi
|
|
else
|
|
debug_log "DMG notarization submission failed, no UUID returned"
|
|
cat "$WORK_DIR/dmg_notarization_output.txt"
|
|
fi
|
|
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
|
|
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
|
|
else
|
|
echo "signed=none" >> $GITHUB_OUTPUT
|
|
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
|
|
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 |