LuckyWorld/.gitea/workflows/macos-build.yml

519 lines
22 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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@v4
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 with UE5-Build-Project
id: build
uses: OrchidIsle/UE5-Build-Project@latest
with:
RUNUAT_PATH: ${{ github.workspace }}/Engine/Build/BatchFiles/RunUAT.sh
UPROJECT_PATH: ${{ github.workspace }}/LuckyWorld.uproject
BUILD_CONFIG: Development
PLATFORM: Mac
COOK: true
STAGE: true
PACKAGE: true
PAK: true
ARCHIVE: false
NULLRHI: true
- 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: Setup App Store Connect API Key Directory
id: appstore-api-key
if: secrets.NOTARY_API_KEY_PATH != ''
run: |
echo "🔑 Setting up App Store Connect API Key..."
# Create directory for API key
mkdir -p ~/private_keys
# Write the API key to a file
echo "${{ secrets.NOTARY_API_KEY_PATH }}" > ~/private_keys/AuthKey_${{ secrets.NOTARY_API_KEY_ID }}.p8
echo "✅ API Key setup complete"
shell: bash
- name: Sign and Notarize App
id: sign-notarize
uses: indygreg/apple-code-sign-action@v1
with:
input_path: ${{ env.APP_PATH }}
output_path: ${{ env.APP_PATH }}
p12_file: ${{ env.KEYCHAIN_PATH }}
p12_password: ${{ env.KEYCHAIN_PASSWORD }}
notarize: true
staple: true
app_store_connect_api_issuer: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
app_store_connect_api_key: ${{ secrets.NOTARY_API_KEY_ID }}
entitlements_path: ${{ env.ENTITLEMENTS_FILE }}
- name: Update app path with signed version
if: steps.sign-notarize.outcome == 'success'
run: |
echo "APP_PATH=${{ steps.sign-notarize.outputs.output_path }}" >> $GITHUB_ENV
echo "✅ Updated APP_PATH to use signed app: ${{ steps.sign-notarize.outputs.output_path }}"
shell: bash
- name: Create DMG Package
id: package
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
echo "DMG_STATUS=success" >> $GITHUB_ENV
echo "DMG_SIZE=$(du -h "$DMG_FILE" | cut -f1)" >> $GITHUB_ENV
echo "::set-output name=dmg_created::true"
echo "::set-output name=dmg_path::$DMG_FILE"
else
echo "❌ Failed to create DMG package"
echo "DMG_STATUS=failed" >> $GITHUB_ENV
echo "::set-output name=dmg_created::false"
exit 1
fi
# Clean up temporary directory
rm -rf "$DMG_TMP_DIR"
- name: Upload DMG Package
uses: actions/upload-artifact@v4
if: env.DMG_PATH != ''
with:
name: LuckyWorld-macOS-Signed-Notarized
path: ${{ env.DMG_PATH }}
retention-days: 30
- name: Check Quarantine Attributes
id: quarantine
if: steps.sign-notarize.outcome == 'success'
shell: bash
run: |
echo "🔍 Checking quarantine attributes..."
# Check quarantine attributes on the app
if command -v xattr &> /dev/null; then
QUARANTINE=$(xattr -l "${{ env.APP_PATH }}" | grep -i "quarantine" || echo "None")
if [ "$QUARANTINE" == "None" ]; then
echo "✅ No quarantine attributes found (good)"
echo "QUARANTINE_STATUS=clean" >> $GITHUB_ENV
else
echo "⚠️ Warning: Quarantine attributes found on the app:"
echo "$QUARANTINE"
echo "QUARANTINE_STATUS=present" >> $GITHUB_ENV
fi
# Check for provenance attribute (indicates successful notarization)
PROVENANCE=$(xattr -l "${{ env.APP_PATH }}" | grep -i "com.apple.provenance" || echo "None")
if [ "$PROVENANCE" != "None" ]; then
echo "✅ Provenance attribute found (indicates successful notarization)"
echo "PROVENANCE_STATUS=present" >> $GITHUB_ENV
else
echo "⚠️ Warning: No provenance attribute found - notarization may not be properly attached"
echo "PROVENANCE_STATUS=missing" >> $GITHUB_ENV
fi
else
echo "⚠️ xattr command not available, can't check quarantine status"
echo "QUARANTINE_STATUS=unknown" >> $GITHUB_ENV
echo "PROVENANCE_STATUS=unknown" >> $GITHUB_ENV
fi
# If DMG exists, check it too
if [ -n "$DMG_PATH" ] && [ -f "$DMG_PATH" ]; then
if command -v xattr &> /dev/null; then
DMG_QUARANTINE=$(xattr -l "$DMG_PATH" | grep -i "quarantine" || echo "None")
if [ "$DMG_QUARANTINE" == "None" ]; then
echo "✅ No quarantine attributes found on DMG (good)"
echo "DMG_QUARANTINE_STATUS=clean" >> $GITHUB_ENV
else
echo "⚠️ Warning: Quarantine attributes found on the DMG:"
echo "$DMG_QUARANTINE"
echo "DMG_QUARANTINE_STATUS=present" >> $GITHUB_ENV
fi
# Now mount the DMG and check the app inside
echo "🔍 Mounting DMG to check app inside..."
DMG_MOUNT_POINT=$(mktemp -d)
# Mount the DMG
hdiutil attach "$DMG_PATH" -mountpoint "$DMG_MOUNT_POINT" -nobrowse
if [ $? -eq 0 ]; then
# Find the app inside the DMG
DMG_APP_PATH=$(find "$DMG_MOUNT_POINT" -maxdepth 1 -name "*.app" | head -1)
if [ -n "$DMG_APP_PATH" ]; then
echo "Found app in DMG: $DMG_APP_PATH"
# Check quarantine attributes on the app inside DMG
DMG_APP_QUARANTINE=$(xattr -l "$DMG_APP_PATH" | grep -i "quarantine" || echo "None")
if [ "$DMG_APP_QUARANTINE" == "None" ]; then
echo "✅ No quarantine attributes found on app inside DMG (good)"
echo "DMG_APP_QUARANTINE_STATUS=clean" >> $GITHUB_ENV
else
echo "⚠️ Warning: Quarantine attributes found on the app inside DMG:"
echo "$DMG_APP_QUARANTINE"
echo "DMG_APP_QUARANTINE_STATUS=present" >> $GITHUB_ENV
fi
# Check for provenance attribute on app inside DMG
DMG_APP_PROVENANCE=$(xattr -l "$DMG_APP_PATH" | grep -i "com.apple.provenance" || echo "None")
if [ "$DMG_APP_PROVENANCE" != "None" ]; then
echo "✅ Provenance attribute found on app inside DMG (indicates successful notarization)"
echo "DMG_APP_PROVENANCE_STATUS=present" >> $GITHUB_ENV
else
echo "⚠️ Warning: No provenance attribute found on app inside DMG"
echo "DMG_APP_PROVENANCE_STATUS=missing" >> $GITHUB_ENV
fi
# Run spctl to check Gatekeeper assessment on the app inside DMG
echo "🛡️ Checking Gatekeeper assessment on app inside DMG..."
SPCTL_RESULT=$(spctl --assess --verbose --type exec "$DMG_APP_PATH" 2>&1 || echo "Failed")
if echo "$SPCTL_RESULT" | grep -q "accepted"; then
echo "✅ App inside DMG passes Gatekeeper assessment"
echo "DMG_APP_GATEKEEPER_STATUS=accepted" >> $GITHUB_ENV
else
echo "⚠️ Warning: App inside DMG may not pass Gatekeeper assessment:"
echo "$SPCTL_RESULT"
echo "DMG_APP_GATEKEEPER_STATUS=rejected" >> $GITHUB_ENV
fi
else
echo "⚠️ No app found inside DMG"
echo "DMG_APP_STATUS=missing" >> $GITHUB_ENV
fi
# Unmount the DMG
hdiutil detach "$DMG_MOUNT_POINT" -force
rm -rf "$DMG_MOUNT_POINT"
else
echo "⚠️ Failed to mount DMG"
echo "DMG_MOUNT_STATUS=failed" >> $GITHUB_ENV
fi
fi
fi
- name: Status Report
if: always()
shell: bash
run: |
echo "📋 ========== macOS Build Status Report =========="
echo ""
# App Info
if [ -n "${{ env.APP_PATH }}" ]; then
echo "🔍 Application Info:"
echo " Path: ${{ env.APP_PATH }}"
echo " Bundle ID: ${{ env.BUNDLE_ID || 'Unknown' }}"
if [ -f "${{ env.APP_PATH }}/Contents/Info.plist" ]; then
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "${{ env.APP_PATH }}/Contents/Info.plist" 2>/dev/null || echo "Unknown")
BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${{ env.APP_PATH }}/Contents/Info.plist" 2>/dev/null || echo "Unknown")
echo " Version: $VERSION (Build $BUILD)"
fi
else
echo "❌ No application found"
fi
echo ""
# Code Signing Status
echo "🔏 Code Signing Status:"
if [ "${{ steps.sign-notarize.outcome }}" == "success" ]; then
echo " ✅ Successfully signed with Developer ID"
elif [ "${{ steps.sign-notarize.outcome }}" == "failure" ]; then
echo " ❌ Signing failed"
else
echo " ❓ Signing status unknown"
fi
echo ""
# Notarization Status
echo "🔐 Notarization Status:"
if [ "${{ steps.sign-notarize.outcome }}" == "success" ]; then
echo " ✅ Successfully notarized and stapled"
elif [ "${{ steps.sign-notarize.outcome }}" == "failure" ]; then
echo " ❌ Notarization failed"
elif [ "${{ steps.sign-notarize.outcome }}" == "skipped" ]; then
echo " ⚠️ Notarization was skipped (likely missing credentials)"
else
echo " ❓ Notarization status unknown"
fi
# Quarantine Status
if [ -n "${{ env.QUARANTINE_STATUS }}" ]; then
echo ""
echo "🛡️ Original App Security Status:"
if [ "${{ env.QUARANTINE_STATUS }}" == "clean" ]; then
echo " ✅ No quarantine attributes (good)"
elif [ "${{ env.QUARANTINE_STATUS }}" == "present" ]; then
echo " ⚠️ Quarantine attributes present"
else
echo " ❓ Quarantine status unknown"
fi
if [ "${{ env.PROVENANCE_STATUS }}" == "present" ]; then
echo " ✅ Provenance attribute present (indicates successful notarization)"
elif [ "${{ env.PROVENANCE_STATUS }}" == "missing" ]; then
echo " ⚠️ No provenance attribute (might indicate notarization issues)"
fi
fi
# DMG Package Status
echo ""
echo "📦 DMG Package Status:"
if [ "${{ env.DMG_STATUS }}" == "success" ]; then
echo " ✅ DMG created successfully"
echo " 📍 Path: ${{ env.DMG_PATH }}"
echo " 📏 Size: ${{ env.DMG_SIZE || 'Unknown' }}"
if [ "${{ env.DMG_QUARANTINE_STATUS }}" == "clean" ]; then
echo " ✅ DMG has no quarantine attributes (good)"
elif [ "${{ env.DMG_QUARANTINE_STATUS }}" == "present" ]; then
echo " ⚠️ DMG has quarantine attributes"
fi
# App inside DMG status
echo ""
echo "📱 App Inside DMG Status:"
if [ "${{ env.DMG_APP_STATUS }}" == "missing" ]; then
echo " ❌ No app found inside DMG"
elif [ "${{ env.DMG_MOUNT_STATUS }}" == "failed" ]; then
echo " ❌ Could not mount DMG to check app"
else
# Quarantine status of app inside DMG
if [ "${{ env.DMG_APP_QUARANTINE_STATUS }}" == "clean" ]; then
echo " ✅ App inside DMG has no quarantine attributes (good)"
elif [ "${{ env.DMG_APP_QUARANTINE_STATUS }}" == "present" ]; then
echo " ⚠️ App inside DMG has quarantine attributes"
else
echo " ❓ App inside DMG quarantine status unknown"
fi
# Provenance status of app inside DMG
if [ "${{ env.DMG_APP_PROVENANCE_STATUS }}" == "present" ]; then
echo " ✅ App inside DMG has provenance attribute (good)"
elif [ "${{ env.DMG_APP_PROVENANCE_STATUS }}" == "missing" ]; then
echo " ⚠️ App inside DMG is missing provenance attribute"
else
echo " ❓ App inside DMG provenance status unknown"
fi
# Gatekeeper assessment
if [ "${{ env.DMG_APP_GATEKEEPER_STATUS }}" == "accepted" ]; then
echo " ✅ App inside DMG passes Gatekeeper assessment"
elif [ "${{ env.DMG_APP_GATEKEEPER_STATUS }}" == "rejected" ]; then
echo " ⚠️ App inside DMG fails Gatekeeper assessment"
else
echo " ❓ App inside DMG Gatekeeper status unknown"
fi
fi
elif [ "${{ env.DMG_STATUS }}" == "failed" ]; then
echo " ❌ DMG creation failed"
elif [ "${{ steps.package.outputs.dmg_created }}" == "true" ]; then
echo " ✅ DMG created successfully"
echo " 📍 Path: ${{ steps.package.outputs.dmg_path }}"
else
echo " ❓ DMG was not created"
fi
# Artifact Upload Status
echo ""
echo "🚀 Artifact Upload Status:"
if [ -n "${{ env.DMG_PATH }}" ] && [ -f "${{ env.DMG_PATH }}" ]; then
echo " ✅ DMG artifact should be uploaded"
else
echo " ❌ DMG artifact not available for upload"
fi
echo ""
echo "=================================================="
- 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"