506 lines
22 KiB
YAML
506 lines
22 KiB
YAML
name: macOS Build, Sign and Notarize
|
||
|
||
on:
|
||
workflow_dispatch: # Manuel tetikleme
|
||
push:
|
||
branches: [ozgur/build]
|
||
|
||
jobs:
|
||
build-sign-notarize:
|
||
runs-on: macos
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v3
|
||
with:
|
||
lfs: true
|
||
fetch-depth: 0
|
||
|
||
- name: Setup environment
|
||
run: |
|
||
# Çalışma dizini yolunu al
|
||
WORKSPACE_DIR="$(pwd)"
|
||
echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> "$GITHUB_ENV"
|
||
echo "ENTITLEMENTS_FILE=LuckyWorld.entitlements" >> "$GITHUB_ENV"
|
||
|
||
# CI ortam değişkenini true olarak ayarla
|
||
echo "CI=true" >> "$GITHUB_ENV"
|
||
|
||
# Build için gerekli dizinleri oluştur
|
||
mkdir -p Builds/Mac
|
||
mkdir -p PackagedReleases
|
||
mkdir -p ArchivedApps
|
||
|
||
echo "Environment setup complete"
|
||
shell: bash
|
||
|
||
- name: Setup Certificate and Keychain
|
||
id: setup-cert
|
||
shell: bash
|
||
env:
|
||
CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
|
||
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||
run: |
|
||
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
|
||
|
||
# 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
|
||
|
||
# Clean up
|
||
rm -f "$CERT_DIR/certificate.p12"
|
||
|
||
- name: Build for macOS
|
||
id: build
|
||
run: |
|
||
if [ -f "./scripts/mac_build.sh" ]; then
|
||
chmod +x ./scripts/mac_build.sh
|
||
# Set CI environment variable explicitly before running
|
||
export CI=true
|
||
./scripts/mac_build.sh
|
||
|
||
# Check if the build succeeded by looking for the app
|
||
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
|
||
if [ -z "$APP_PATHS" ]; then
|
||
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
|
||
fi
|
||
|
||
if [ -z "$APP_PATHS" ]; then
|
||
echo "❌ ERROR: Build command appeared to succeed but no app bundle was found!"
|
||
echo "This usually means the build failed but didn't properly return an error code."
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "ERROR: Build script not found at ./scripts/mac_build.sh"
|
||
exit 1
|
||
fi
|
||
shell: bash
|
||
|
||
- name: Find app bundle
|
||
id: find-app
|
||
run: |
|
||
# Add error handling
|
||
set +e # Don't exit immediately on error for this block
|
||
|
||
echo "Build status check..."
|
||
if [ ! -d "./Builds" ] && [ ! -d "./Saved/StagedBuilds" ]; then
|
||
echo "❌ ERROR: Build directories do not exist. Build likely failed."
|
||
exit 1
|
||
fi
|
||
|
||
# First check Saved/StagedBuilds directory - where Unreal often places built apps
|
||
echo "Checking Saved/StagedBuilds directory..."
|
||
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
|
||
|
||
# If not found, check Builds directory
|
||
if [ -z "$APP_PATHS" ]; then
|
||
echo "No app found in Saved/StagedBuilds, checking Builds directory..."
|
||
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
|
||
fi
|
||
|
||
# If still not found, check the whole workspace
|
||
if [ -z "$APP_PATHS" ]; then
|
||
echo "No app found in Builds, checking entire workspace..."
|
||
APP_PATHS=$(find . -type d -name "*.app" -not -path "*/\.*" 2>/dev/null || echo "")
|
||
fi
|
||
|
||
if [ -z "$APP_PATHS" ]; then
|
||
echo "❌ ERROR: Could not find any app bundles!"
|
||
echo "Listing all directories to help debug:"
|
||
find . -type d -maxdepth 3 | sort
|
||
exit 1
|
||
fi
|
||
|
||
echo "Found potential app bundles:"
|
||
echo "$APP_PATHS"
|
||
|
||
# Use the first app path found (preferably the main app, not a child app)
|
||
MAIN_APP_PATH=$(echo "$APP_PATHS" | grep -v "CrashReportClient" | head -1 || echo "$APP_PATHS" | head -1)
|
||
|
||
echo "Using app bundle: $MAIN_APP_PATH"
|
||
|
||
# Make sure app exists - using local variable
|
||
if [ ! -d "$MAIN_APP_PATH" ]; then
|
||
echo "❌ ERROR: App bundle not found at $MAIN_APP_PATH!"
|
||
exit 1
|
||
fi
|
||
|
||
# Export APP_PATH for next steps to use
|
||
echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV"
|
||
|
||
# Get bundle ID from Info.plist for reference (not modifying)
|
||
if [ -f "$MAIN_APP_PATH/Contents/Info.plist" ]; then
|
||
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$MAIN_APP_PATH/Contents/Info.plist")
|
||
echo "Detected bundle ID: $BUNDLE_ID"
|
||
echo "BUNDLE_ID=$BUNDLE_ID" >> "$GITHUB_ENV"
|
||
fi
|
||
shell: bash
|
||
|
||
- name: Sign App
|
||
id: sign
|
||
shell: bash
|
||
run: |
|
||
echo "🔏 Signing app with Developer ID certificate..."
|
||
|
||
# Check if app path exists
|
||
if [ ! -d "${{ env.APP_PATH }}" ]; then
|
||
echo "❌ App bundle not found at ${{ env.APP_PATH }}"
|
||
echo "::set-output name=signed::none"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if entitlements file exists
|
||
if [ ! -f "${{ env.ENTITLEMENTS_FILE }}" ]; then
|
||
echo "❌ Entitlements file not found at ${{ env.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
|
||
# 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"
|
||
echo "Falling back to ad-hoc signing for testing..."
|
||
# Use ad-hoc identity as fallback
|
||
codesign --force --deep --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign - --timestamp "${{ env.APP_PATH }}"
|
||
echo "::set-output name=signed::adhoc"
|
||
else
|
||
echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH"
|
||
|
||
# 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 "${{ env.APP_PATH }}" -name "*.dylib" | while read -r dylib; do
|
||
echo "Signing: $dylib"
|
||
codesign --force --verbose --options runtime --entitlements "${{ env.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 "${{ env.APP_PATH }}" -name "*.so" | while read -r so; do
|
||
echo "Signing: $so"
|
||
codesign --force --verbose --options runtime --entitlements "${{ env.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 "${{ env.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 "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "$exe" || echo "⚠️ Failed to sign: $exe"
|
||
done
|
||
|
||
# Sign all frameworks
|
||
echo "Signing frameworks..."
|
||
find "${{ env.APP_PATH }}" -path "*.framework" -type d | while read -r framework; do
|
||
echo "Signing framework: $framework"
|
||
codesign --force --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "$framework" || echo "⚠️ Failed to sign: $framework"
|
||
done
|
||
|
||
# Final signing of the main bundle
|
||
echo "🔐 Performing final signing of the main app bundle..."
|
||
codesign --force --deep --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "${{ env.APP_PATH }}"
|
||
echo "::set-output name=signed::identity"
|
||
fi
|
||
|
||
# Verify signing
|
||
echo "🔍 Verifying signature..."
|
||
codesign -vvv --deep --strict "${{ env.APP_PATH }}"
|
||
|
||
# Check entitlements
|
||
echo "🔍 Checking entitlements..."
|
||
codesign -d --entitlements - "${{ env.APP_PATH }}"
|
||
|
||
- name: Notarize App
|
||
id: notarize
|
||
if: steps.sign.outputs.signed != 'none'
|
||
shell: bash
|
||
env:
|
||
API_KEY_ID: ${{ secrets.NOTARY_API_KEY_ID }}
|
||
API_ISSUER_ID: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
|
||
API_KEY_PATH: ${{ secrets.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 "${{ env.APP_PATH }}" .app)
|
||
BUNDLE_ID="${{ env.BUNDLE_ID }}"
|
||
|
||
# If bundle ID is not provided, try to extract from Info.plist
|
||
if [ -z "$BUNDLE_ID" ]; then
|
||
if [ -f "${{ env.APP_PATH }}/Contents/Info.plist" ]; then
|
||
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ env.APP_PATH }}/Contents/Info.plist")
|
||
echo "Extracted bundle ID: $BUNDLE_ID"
|
||
else
|
||
BUNDLE_ID="com.luckyrobots.app"
|
||
echo "Using default bundle ID: $BUNDLE_ID"
|
||
fi
|
||
fi
|
||
|
||
# Check if we're using API key notarization method
|
||
if [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then
|
||
echo "Using App Store Connect API key for notarization..."
|
||
|
||
# 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
|
||
# It contains the key content
|
||
echo "Using API key from content"
|
||
echo "$API_KEY_PATH" > ~/private_keys/AuthKey_${API_KEY_ID}.p8
|
||
fi
|
||
|
||
# Create zip for notarization
|
||
ZIP_PATH="${APP_NAME}-notarize.zip"
|
||
ditto -c -k --keepParent "${{ env.APP_PATH }}" "$ZIP_PATH"
|
||
|
||
echo "Submitting for notarization with API key..."
|
||
|
||
# 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=$?
|
||
|
||
# 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" \
|
||
--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
|
||
|
||
# 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" \
|
||
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||
--key-id "$API_KEY_ID" \
|
||
--issuer "$API_ISSUER_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 "${{ env.APP_PATH }}"
|
||
STAPLE_STATUS=$?
|
||
|
||
if [ $STAPLE_STATUS -eq 0 ]; then
|
||
echo "✅ Stapling completed successfully!"
|
||
|
||
# Verify the stapling worked properly
|
||
echo "Verifying stapled ticket is properly attached..."
|
||
xcrun stapler validate -v "${{ env.APP_PATH }}"
|
||
|
||
echo "::set-output name=notarized::true"
|
||
else
|
||
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
|
||
fi
|
||
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 "=================================="
|
||
|
||
echo "❌ Notarization failed with status: $FINAL_STATUS"
|
||
exit 1
|
||
fi
|
||
|
||
# Clean up
|
||
rm -rf ~/private_keys
|
||
else
|
||
echo "⚠️ Missing notarization credentials. Skipping notarization."
|
||
echo "Set these secrets for notarization:"
|
||
echo " - NOTARY_API_KEY_ID: Your API key ID"
|
||
echo " - NOTARY_API_KEY_ISSUER_ID: Your API issuer ID"
|
||
echo " - NOTARY_API_KEY_PATH: Your p8 file content"
|
||
fi
|
||
|
||
- name: Create DMG Package
|
||
id: package
|
||
if: steps.notarize.outputs.notarized == 'true'
|
||
shell: bash
|
||
run: |
|
||
echo "📦 Creating DMG package..."
|
||
|
||
# Get app name for DMG file naming
|
||
APP_NAME=$(basename "${{ env.APP_PATH }}" .app)
|
||
|
||
# Create a DMG package
|
||
DMG_FILE="./PackagedReleases/${APP_NAME}-Signed-Notarized.dmg"
|
||
rm -f "$DMG_FILE" 2>/dev/null || true
|
||
|
||
# Create temporary folder for DMG contents
|
||
DMG_TMP_DIR=$(mktemp -d)
|
||
cp -R "${{ env.APP_PATH }}" "$DMG_TMP_DIR/"
|
||
|
||
# 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"
|
||
echo "Size: $(du -h "$DMG_FILE" | cut -f1)"
|
||
echo "DMG_PATH=$DMG_FILE" >> $GITHUB_ENV
|
||
else
|
||
echo "❌ Failed to create DMG package"
|
||
exit 1
|
||
fi
|
||
|
||
# Clean up temporary directory
|
||
rm -rf "$DMG_TMP_DIR"
|
||
|
||
- name: Upload DMG Package
|
||
uses: actions/upload-artifact@v3
|
||
if: steps.package.outputs.DMG_PATH != ''
|
||
with:
|
||
name: LuckyWorld-macOS-Signed-Notarized
|
||
path: ${{ env.DMG_PATH }}
|
||
retention-days: 30
|
||
|
||
- name: Cleanup
|
||
if: always()
|
||
shell: bash
|
||
run: |
|
||
echo "🧹 Cleaning up..."
|
||
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||
rm -f *-notarize.zip || true
|
||
echo "✅ Cleanup complete" |