WIP: feat(workflows): add new build workflows for Windows, Linux, and macOS, and remove obsolete build scripts #17

Draft
m wants to merge 141 commits from ozgur/build into main
24 changed files with 3213 additions and 507 deletions

View File

@ -0,0 +1,46 @@
name: 'Linux Build Steps'
description: 'Build Linux application'
runs:
using: "composite"
steps:
- name: Setup environment
run: |
# Set environment variables for Unreal Engine
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
# Set environment variables for Linux toolchain (needed for cross-compilation)
$env:LINUX_MULTIARCH_ROOT="C:/UnrealToolchains/v23_clang-18.1.0-rockylinux8"
echo "LINUX_MULTIARCH_ROOT=${LINUX_MULTIARCH_ROOT}" >> $GITHUB_ENV
# Create directories for builds
if (!(Test-Path "Builds/Linux")) { New-Item -ItemType Directory -Path "Builds/Linux" -Force }
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
shell: pwsh
- name: Build for Linux
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./scripts/linux_build.sh"
shell: pwsh
- name: Package Linux build
run: |
echo "Packaging Linux build..."
if [ -d "Builds/Linux" ]; then
cd Builds/Linux
zip -r ../../PackagedReleases/LuckyRobots-Linux.zip .
cd ../..
fi
echo "=== Packaged Linux release ==="
ls -la PackagedReleases/
shell: bash
- name: Upload Linux Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Linux.zip') != ''
with:
name: LuckyRobots-Linux
path: PackagedReleases/LuckyRobots-Linux.zip
retention-days: 365

View File

@ -0,0 +1,132 @@
name: 'macOS Build Steps'
description: 'Build, sign and notarize macOS application'
inputs:
apple_team_id:
description: 'Apple Team ID for signing'
required: true
apple_certificate_base64:
description: 'Base64-encoded certificate file'
required: true
apple_certificate_password:
description: 'Password for certificate file'
required: true
api_key_path:
description: 'Base64-encoded API key file'
required: true
api_key_id:
description: 'API Key ID'
required: true
api_key_issuer_id:
description: 'API Key Issuer ID'
required: true
runs:
using: "composite"
steps:
- name: Setup environment
run: |
# Use the correct path where Unreal Engine is installed
UE_PATH="/Users/Shared/Epic Games/UE_5.5"
if [ ! -d "$UE_PATH" ]; then
echo "Error: Unreal Engine is not installed in the expected location"
echo "Please ensure Unreal Engine is installed at $UE_PATH"
exit 1
fi
# Create directories for builds
mkdir -p Builds/Mac
mkdir -p PackagedReleases
echo "Using Unreal Engine 5.5"
shell: bash
- name: Build for macOS
run: |
chmod +x ./scripts/mac_build.sh
./scripts/mac_build.sh
shell: bash
- name: Setup for Signing
id: setup-signing
if: ${{ success() }}
env:
API_KEY_PATH: ${{ inputs.api_key_path }}
run: |
# Create output directory
mkdir -p PackagedReleases
# Decode the API key from Base64 secret
echo "$API_KEY_PATH" | base64 --decode > api_key.p8
echo "api_key_file=$(pwd)/api_key.p8" >> $GITHUB_OUTPUT
# Find app bundle
APP_PATH=$(find Builds -type d -name "*.app" | head -1)
if [ -z "$APP_PATH" ]; then
# Look for a directory that might be a bundle but not named .app
APP_PATH=$(find Builds -mindepth 1 -maxdepth 1 -type d | head -1)
if [ -z "$APP_PATH" ]; then
echo "No build directory found, cannot continue"
exit 1
fi
fi
echo "Found app path: $APP_PATH"
echo "app_path=$APP_PATH" >> $GITHUB_OUTPUT
shell: bash
- name: Sign macOS App
uses: lando/code-sign-action@v3
id: sign-app
with:
file: ${{ steps.setup-signing.outputs.app_path }}
certificate-data: ${{ inputs.apple_certificate_base64 }}
certificate-password: ${{ inputs.apple_certificate_password }}
certificate-id: ${{ inputs.apple_team_id }}
options: --force --options runtime --deep --timestamp --entitlements ./LuckyRobots.entitlements
- name: Notarize macOS App
run: |
# Create a temporary file for notarization
APP_PATH="${{ steps.setup-signing.outputs.app_path }}"
NOTARIZE_APP_PATH="./LuckyRobots-notarize.zip"
ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_APP_PATH"
API_KEY_FILE="${{ steps.setup-signing.outputs.api_key_file }}"
# Submit for notarization using API key
echo "Submitting for notarization with API key..."
xcrun notarytool submit "$NOTARIZE_APP_PATH" --key "$API_KEY_FILE" --key-id "${{ inputs.api_key_id }}" --issuer "${{ inputs.api_key_issuer_id }}" --wait
# Staple the ticket to the application
xcrun stapler staple "$APP_PATH"
# Clean up the API key file
rm -f "$API_KEY_FILE"
rm -f "$NOTARIZE_APP_PATH"
shell: bash
- name: Package macOS App
run: |
# Package the signed and notarized app
APP_PATH="${{ steps.setup-signing.outputs.app_path }}"
APP_NAME=$(basename "$APP_PATH")
DIR_PATH=$(dirname "$APP_PATH")
echo "Creating final package..."
(cd "$DIR_PATH" && zip -r "../../PackagedReleases/LuckyRobots-macOS.zip" "$APP_NAME")
echo "Created packaged release: PackagedReleases/LuckyRobots-macOS.zip"
echo "Packaged releases:"
ls -la PackagedReleases/
shell: bash
- name: Upload macOS Build Artifact
uses: actions/upload-artifact@v3
if: success()
with:
name: LuckyRobots-macOS
path: PackagedReleases/LuckyRobots-macOS.zip
retention-days: 365

View File

@ -0,0 +1,824 @@
name: "macOS Sign and Notarize"
description: "Signs and notarizes macOS applications with Developer ID certificate"
author: moersoy
inputs:
app-path:
description: "Path to the app bundle (.app)"
required: true
entitlements-file:
description: "Path to the entitlements file (.entitlements)"
required: true
team-id:
description: "Apple Developer Team ID"
required: true
certificate-base64:
description: "Base64-encoded Developer ID Application certificate (.p12)"
required: true
certificate-password:
description: "Password for the Developer ID Application certificate"
required: true
notarization-method:
description: "Method to use for notarization: 'api-key' or 'app-password'"
required: false
default: "api-key"
notary-user:
description: "Apple ID for notarization (for app-password method)"
required: false
notary-password:
description: "App-specific password for notarization (for app-password method)"
required: false
notary-api-key-id:
description: "API Key ID for notarization (for api-key method)"
required: false
notary-api-key-issuer-id:
description: "API Issuer ID for notarization (for api-key method)"
required: false
notary-api-key-path:
description: "Path to or content of the API Key .p8 file (for api-key method)"
required: false
bundle-id:
description: "App bundle identifier (com.example.app)"
required: false
fallback-to-adhoc:
description: "Whether to fall back to ad-hoc signing if certificate is invalid"
required: false
default: "true"
outputs:
signed:
description: "Whether the app was signed (identity, adhoc, or none)"
value: ${{ steps.sign.outputs.signed }}
notarized:
description: "Whether the app was notarized (true or false)"
value: ${{ steps.notarize.outputs.notarized }}
package-path:
description: "Path to the final package"
value: ${{ steps.package.outputs.package-path }}
runs:
using: "composite"
steps:
- name: Setup Certificate
id: setup-cert
shell: bash
env:
CERTIFICATE_BASE64: ${{ inputs.certificate-base64 }}
CERTIFICATE_PASSWORD: ${{ inputs.certificate-password }}
APPLE_TEAM_ID: ${{ inputs.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: Sign App
id: sign
shell: bash
run: |
echo "🔏 Signing app with Developer ID certificate..."
# Check if app path exists
if [ ! -d "${{ inputs.app-path }}" ]; then
echo "❌ App bundle not found at ${{ inputs.app-path }}"
echo "::set-output name=signed::none"
exit 1
fi
# Check if entitlements file exists
if [ ! -f "${{ inputs.entitlements-file }}" ]; then
echo "❌ Entitlements file not found at ${{ inputs.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"
if [ "${{ inputs.fallback-to-adhoc }}" = "true" ]; then
echo "Falling back to ad-hoc signing for testing..."
# Use ad-hoc identity as fallback
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign - --timestamp "${{ inputs.app-path }}"
echo "::set-output name=signed::adhoc"
else
echo "Skipping signing. Set fallback-to-adhoc=true to use ad-hoc signing instead."
echo "::set-output name=signed::none"
exit 1
fi
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 "${{ inputs.app-path }}" -name "*.dylib" | while read -r dylib; do
echo "Signing: $dylib"
codesign --force --verbose --options runtime --entitlements "${{ inputs.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 "${{ inputs.app-path }}" -name "*.so" | while read -r so; do
echo "Signing: $so"
codesign --force --verbose --options runtime --entitlements "${{ inputs.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 "${{ inputs.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 "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$exe" || echo "⚠️ Failed to sign: $exe"
done
# Sign all frameworks
echo "Signing frameworks..."
find "${{ inputs.app-path }}" -path "*.framework" -type d | while read -r framework; do
echo "Signing framework: $framework"
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$framework" || echo "⚠️ Failed to sign: $framework"
done
# Special handling for CrashReportClient.app
CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1)
if [ -n "$CRASH_REPORTER" ]; then
echo "🔍 Special handling for CrashReportClient.app: $CRASH_REPORTER"
# Sign CrashReportClient.app specifically with focus on hardened runtime
find "$CRASH_REPORTER" -type f -perm +111 | while read -r crash_bin; do
echo "Signing CrashReportClient binary: $crash_bin"
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$crash_bin" || echo "⚠️ Failed to sign: $crash_bin"
done
echo "Signing the CrashReportClient.app bundle itself..."
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$CRASH_REPORTER" || echo "⚠️ Failed to sign CrashReportClient.app"
fi
# Sign any other nested app bundles
find "${{ inputs.app-path }}" -path "*.app" -type d | grep -v CrashReportClient | while read -r nested_app; do
if [ "$nested_app" != "${{ inputs.app-path }}" ]; then
echo "Signing nested app: $nested_app"
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$nested_app" || echo "⚠️ Failed to sign: $nested_app"
fi
done
# Final signing of the main bundle
echo "🔐 Performing final signing of the main app bundle..."
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "${{ inputs.app-path }}"
echo "::set-output name=signed::identity"
fi
# Verify signing
echo "🔍 Verifying signature..."
codesign -vvv --deep --strict "${{ inputs.app-path }}"
# Check entitlements
echo "🔍 Checking entitlements..."
codesign -d --entitlements - "${{ inputs.app-path }}"
# Verify CrashReportClient
CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1)
if [ -n "$CRASH_REPORTER" ]; then
echo "🔍 Verifying CrashReportClient signature..."
codesign -vvv --deep --strict "$CRASH_REPORTER" || echo "⚠️ CrashReportClient may have verification issues"
echo "CrashReportClient entitlements:"
codesign -d --entitlements - "$CRASH_REPORTER" || echo "⚠️ Could not display CrashReportClient entitlements"
fi
- name: Notarize App
id: notarize
if: steps.sign.outputs.signed != 'none'
shell: bash
env:
APPLE_ID: ${{ inputs.notary-user }}
APP_PASSWORD: ${{ inputs.notary-password }}
API_KEY_ID: ${{ inputs.notary-api-key-id }}
API_ISSUER_ID: ${{ inputs.notary-api-key-issuer-id }}
API_KEY_PATH: ${{ inputs.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 "${{ inputs.app-path }}" .app)
BUNDLE_ID="${{ inputs.bundle-id }}"
# If bundle ID is not provided, try to extract from Info.plist
if [ -z "$BUNDLE_ID" ]; then
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.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 [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -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 "${{ inputs.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 "${{ inputs.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 "${{ inputs.app-path }}"
# Check if stapling metadata is correctly stored in xattr
echo "Checking app extended attributes..."
if command -v xattr &> /dev/null; then
xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "⚠️ Warning: com.apple.provenance attribute not found"
fi
# Add special instructions for distribution
echo "📋 IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings."
echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files."
echo "For proper distribution, consider the following options:"
echo "1. Use a DMG installer with a signed, notarized app inside"
echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)"
echo "3. If distributing directly, use a distribution platform that preserves notarization tickets"
else
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
fi
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "${{ inputs.app-path }}"
echo "::set-output name=notarized::true"
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 "=================================="
# Extract specific issues for easier debugging
echo "🔍 Extracting specific issues from logs..."
echo "$LOGS_OUTPUT" | grep -A 3 "issues"
# Show current bundle ID in Info.plist
echo "📋 Current bundle ID information:"
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
echo "Info.plist content for bundle ID:"
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist"
echo "Full Info.plist excerpt:"
plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle
else
echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist"
fi
# Check for mismatched bundle ID
if [ "$BUNDLE_ID" != "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null)" ]; then
echo "⚠️ WARNING: Bundle ID mismatch detected between workflow and app!"
echo " - Workflow/input bundle ID: $BUNDLE_ID"
echo " - Actual app bundle ID: $(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null || echo "Could not read")"
echo "This mismatch could cause notarization problems."
fi
# Check for code signature issues in internal components
echo "🔍 Checking for code signature issues in app components..."
find "${{ inputs.app-path }}" -type f -name "*.dylib" -o -name "*.so" | head -5 | while read -r lib; do
echo "Checking signature on: $lib"
codesign -vvv "$lib" || echo "⚠️ Signature issue with: $lib"
done
echo "❌ Notarization failed with status: $FINAL_STATUS"
exit 1
fi
# Clean up
rm -rf ~/private_keys
# Fall back to App-specific password if requested
elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
echo "Using App-specific password for notarization..."
# Create zip for notarization
ZIP_PATH="${APP_NAME}-notarize.zip"
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_PATH"
echo "Submitting for notarization..."
# First, submit without waiting for completion
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_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" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_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" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_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 "${{ inputs.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 "${{ inputs.app-path }}"
# Check if stapling metadata is correctly stored in xattr
echo "Checking app extended attributes..."
if command -v xattr &> /dev/null; then
xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "⚠️ Warning: com.apple.provenance attribute not found"
fi
# Add special instructions for distribution
echo "📋 IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings."
echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files."
echo "For proper distribution, consider the following options:"
echo "1. Use a DMG installer with a signed, notarized app inside"
echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)"
echo "3. If distributing directly, use a distribution platform that preserves notarization tickets"
else
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
fi
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "${{ inputs.app-path }}"
echo "::set-output name=notarized::true"
else
# Get detailed logs for failed notarization
echo "📋 Fetching detailed logs for submission ID: $SUBMISSION_ID"
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" 2>&1)
echo "==== DETAILED NOTARIZATION LOGS ===="
echo "$LOGS_OUTPUT"
echo "=================================="
# Extract specific issues for easier debugging
echo "🔍 Extracting specific issues from logs..."
echo "$LOGS_OUTPUT" | grep -A 3 "issues"
# Show current bundle ID in Info.plist
echo "📋 Current bundle ID information:"
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
echo "Info.plist content for bundle ID:"
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist"
echo "Full Info.plist excerpt:"
plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle
else
echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist"
fi
echo "❌ Notarization failed with status: $FINAL_STATUS"
exit 1
fi
else
echo "⚠️ Missing notarization credentials. Skipping notarization."
echo "For App Store Connect API key method, set these inputs:"
echo " - notarization-method: api-key"
echo " - notary-api-key-id: Your API key ID"
echo " - notary-api-key-issuer-id: Your API issuer ID"
echo " - notary-api-key-path: Path to or content of your p8 file"
echo ""
echo "For App-specific password method, set these inputs:"
echo " - notarization-method: app-password"
echo " - notary-user: Your Apple ID (email)"
echo " - notary-password: Your app-specific password"
echo " - team-id: Your Apple Developer team ID"
fi
- name: Package App
id: package
if: steps.sign.outputs.signed != 'none'
shell: bash
run: |
echo "📦 Packaging signed app..."
# Get app name for zip file naming
APP_NAME=$(basename "${{ inputs.app-path }}" .app)
if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then
PACKAGE_SUFFIX="Signed-Notarized"
echo "Creating distribution package with notarized app..."
else
PACKAGE_SUFFIX="Signed"
echo "Creating distribution package with signed app..."
fi
# Create zip package
ZIP_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.zip"
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_FILE"
echo "✅ Created ZIP package: $ZIP_FILE"
# Check if we can create DMG (hdiutil is available)
if command -v hdiutil &> /dev/null; then
# Create DMG package (much better for distribution)
DMG_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.dmg"
echo "Creating DMG distribution package..."
# Create temporary folder for DMG contents
DMG_TMP_DIR=$(mktemp -d)
cp -R "${{ inputs.app-path }}" "$DMG_TMP_DIR/"
# Optional: Add README or instructions
echo "# Installation Instructions\n\nDrag the application to your Applications folder to install." > "$DMG_TMP_DIR/README.txt"
# 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"
# Sign the DMG with the same certificate used for the app
echo "Signing DMG file..."
# 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 [ -n "$IDENTITY_HASH" ]; then
# Sign the DMG
codesign --force --sign "$IDENTITY_HASH" --options runtime --timestamp "$DMG_FILE"
# Verify DMG signature
echo "Verifying DMG signature..."
codesign -vvv "$DMG_FILE"
# Only notarize DMG if the app was successfully notarized
if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then
echo "Notarizing DMG file..."
# Check if we're using API key notarization method
if [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then
# Use the same API key setup from the notarize step
echo "Using App Store Connect API key for DMG notarization..."
echo "Submitting DMG for notarization..."
DMG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" --wait 2>&1)
echo "DMG notarization submission output:"
echo "$DMG_SUBMIT_OUTPUT"
# Extract DMG submission ID
DMG_SUBMISSION_ID=$(echo "$DMG_SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
if [ -n "$DMG_SUBMISSION_ID" ] && echo "$DMG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then
echo "✅ DMG notarization completed successfully!"
# Staple the DMG
echo "Stapling notarization ticket to DMG..."
xcrun stapler staple "$DMG_FILE"
# Verify DMG stapling
echo "Verifying DMG stapling..."
xcrun stapler validate "$DMG_FILE"
echo "DMG is now fully signed, notarized, and stapled!"
else
echo "⚠️ DMG notarization may have failed or is still in progress."
echo "The app itself is still properly notarized, but the DMG may need manual verification."
fi
elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
# Use App-specific password for DMG notarization
echo "Using App-specific password for DMG notarization..."
echo "Submitting DMG for notarization..."
DMG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" --wait 2>&1)
echo "DMG notarization submission output:"
echo "$DMG_SUBMIT_OUTPUT"
# Extract DMG submission ID
DMG_SUBMISSION_ID=$(echo "$DMG_SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
if [ -n "$DMG_SUBMISSION_ID" ] && echo "$DMG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then
echo "✅ DMG notarization completed successfully!"
# Staple the DMG
echo "Stapling notarization ticket to DMG..."
xcrun stapler staple "$DMG_FILE"
# Verify DMG stapling
echo "Verifying DMG stapling..."
xcrun stapler validate "$DMG_FILE"
echo "DMG is now fully signed, notarized, and stapled!"
else
echo "⚠️ DMG notarization may have failed or is still in progress."
echo "The app itself is still properly notarized, but the DMG may need manual verification."
fi
else
echo "⚠️ DMG not notarized due to missing credentials."
echo "The app itself is properly notarized, but the DMG is only signed."
fi
else
echo "App was not notarized, skipping DMG notarization."
fi
else
echo "⚠️ No valid identity found for DMG signing. DMG will be created but not signed."
fi
# Use DMG as the primary package if available
echo "::set-output name=package-path::$DMG_FILE"
echo "::set-output name=zip-package-path::$ZIP_FILE"
else
echo "⚠️ Failed to create DMG, falling back to ZIP package"
echo "::set-output name=package-path::$ZIP_FILE"
fi
# Clean up temp directory
rm -rf "$DMG_TMP_DIR"
else
echo "hdiutil not available, skipping DMG creation"
echo "::set-output name=package-path::$ZIP_FILE"
fi
- 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"

View File

@ -0,0 +1,42 @@
name: 'Windows Build Steps'
description: 'Build Windows application'
runs:
using: "composite"
steps:
- name: Setup environment
run: |
# Set environment variables for Unreal Engine
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
# Create directories for builds
if (!(Test-Path "Builds/Windows")) { New-Item -ItemType Directory -Path "Builds/Windows" -Force }
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
shell: pwsh
- name: Build for Windows
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./scripts/win_build.sh"
shell: pwsh
- name: Package Windows build
run: |
echo "Packaging Windows build..."
if [ -d "Builds/Windows" ]; then
cd Builds/Windows
zip -r ../../PackagedReleases/LuckyRobots-Windows.zip .
cd ../..
fi
echo "=== Packaged Windows release ==="
ls -la PackagedReleases/
shell: bash
- name: Upload Windows Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Windows.zip') != ''
with:
name: LuckyRobots-Windows
path: PackagedReleases/LuckyRobots-Windows.zip
retention-days: 365

209
.gitea/workflows/build.yml Normal file
View File

@ -0,0 +1,209 @@
name: Unreal Engine Build
on:
workflow_dispatch:
# push:
# branches: [ozgur/build]
jobs:
windows-build:
runs-on: windows
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Build Windows
uses: ./.gitea/actions/windows-build
linux-build:
runs-on: windows
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Build Linux
uses: ./.gitea/actions/linux-build
macos-build:
runs-on: macos
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Build macOS
uses: ./.gitea/actions/macos-build
with:
apple_team_id: ${{ secrets.APPLE_TEAM_ID }}
apple_certificate_base64: ${{ secrets.MACOS_CERTIFICATE }}
apple_certificate_password: ${{ secrets.MACOS_CERTIFICATE_PWD }}
api_key_path: ${{ secrets.NOTARY_API_KEY_PATH }}
api_key_id: ${{ secrets.NOTARY_API_KEY_ID }}
api_key_issuer_id: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
create-release:
needs: [windows-build, linux-build, macos-build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Create Tag
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
# No previous version tag, start with 1.0.0
NEW_VERSION="1.0.0"
echo "No previous version tags found, starting with 1.0.0"
else
# Strip 'v' prefix if it exists
VERSION=${LATEST_TAG#v}
# Split version into parts
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# Auto-increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
fi
# Final tag with v prefix
TAG="v${NEW_VERSION}"
echo "Creating git tag: $TAG"
# Configure git with token authentication
git config --global user.email "actions@gitea.com"
git config --global user.name "Gitea Actions"
# Direct token approach
git remote set-url origin "https://runner:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
# Check if tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
# Create tag
git tag -a "$TAG" -m "Release $TAG"
# Push tag
git push origin "$TAG"
echo "Successfully created and pushed tag: $TAG"
else
echo "Tag $TAG already exists, skipping tag creation"
fi
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: releases
- name: Create Build Info
run: |
# Create a build info JSON file
echo '{
"version": "${{ env.RELEASE_TAG }}",
"buildNumber": "${{ github.run_number }}",
"commit": "${{ github.sha }}",
"branch": "${{ github.ref_name }}",
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
"artifacts": {
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux",
"macos": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS"
}
}' > build-info.json
# Create a simple HTML download page
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.download-btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin: 10px 5px;
}
.download-btn:hover { background-color: #45a049; }
.platform { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
<div class="platform">
<h2>Windows</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
</div>
<div class="platform">
<h2>Linux</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
</div>
<div class="platform">
<h2>macOS</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS" class="download-btn">Download macOS Build</a></p>
</div>
<footer>
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
</footer>
</body>
</html>' > downloads.html
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: |-
build-info.json
downloads.html
token: '${{ secrets.GITEATOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
### Download Links
Download builds from our CI artifacts:
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
- [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'

View File

@ -1,103 +0,0 @@
name: Unreal Release
on:
workflow_dispatch:
inputs:
windows_build_path:
description: 'Absolute path to the Windows build zip file'
required: true
default: 'E:\LuckyWorld\LuckyRobots\UNREAL_PROJECTS\Luckyrobots\Builds\Windows\LuckyRobots-Windows.zip'
linux_build_path:
description: 'Absolute path to the Linux build zip file'
required: true
default: 'E:\LuckyWorld\LuckyRobots\UNREAL_PROJECTS\Luckyrobots\Builds\Linux\LuckyRobots-Linux.zip'
mac_build_path:
description: 'Absolute path to the Mac build zip file'
required: true
default: 'E:\LuckyWorld\LuckyRobots\UNREAL_PROJECTS\Luckyrobots\Builds\Mac\LuckyRobots-Mac.zip'
jobs:
build:
runs-on: windows
steps:
- name: Upload Linux Build Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyRobots-Linux
path: ${{ github.event.inputs.linux_build_path }}
retention-days: 365
- name: Upload Windows Build Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyRobots-Windows
path: ${{ github.event.inputs.windows_build_path }}
retention-days: 365
- name: Upload Mac Build Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyRobots-Mac
path: ${{ github.event.inputs.mac_build_path }}
retention-days: 365
- name: Get Release Tag
shell: pwsh
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
# Uses Sort-Object with a version comparison scriptblock
$latestTag = git tag -l "v[0-9]*.[0-9]*.[0-9]*" | Sort-Object -Property @{Expression={[version]($_ -replace 'v')}} | Select-Object -Last 1
$newVersion = "1.0.0" # Default start version
if ($null -ne $latestTag -and $latestTag -ne '') {
Write-Host "Latest tag found: $latestTag"
# Strip 'v' prefix
$versionString = $latestTag -replace '^v'
# Split version into parts
$versionParts = $versionString.Split('.')
if ($versionParts.Length -eq 3) {
$major = [int]$versionParts[0]
$minor = [int]$versionParts[1]
$patch = [int]$versionParts[2]
# Auto-increment patch version
$patch++
$newVersion = "$major.$minor.$patch"
Write-Host "Auto-incremented patch version from $versionString to $newVersion"
} else {
Write-Host "Could not parse version from tag: $latestTag. Defaulting to 1.0.0"
}
} else {
Write-Host "No previous version tags found, starting with 1.0.0"
}
# Final tag with v prefix
$tag = "v$newVersion"
# Set environment variable for subsequent steps
echo "RELEASE_TAG=$tag" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Using release tag: $tag"
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
token: '${{ secrets.GITEA_TOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
Windows, Linux and Mac builds are attached below.
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'

View File

@ -0,0 +1,519 @@
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"

View File

@ -0,0 +1,163 @@
name: Create Release
on:
workflow_dispatch:
workflow_call:
jobs:
create-release:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Create Tag
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
# No previous version tag, start with 1.0.0
NEW_VERSION="1.0.0"
echo "No previous version tags found, starting with 1.0.0"
else
# Strip 'v' prefix if it exists
VERSION=${LATEST_TAG#v}
# Split version into parts
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# Auto-increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
fi
# Final tag with v prefix
TAG="v${NEW_VERSION}"
echo "Creating git tag: $TAG"
# Configure git with token authentication
git config --global user.email "actions@gitea.com"
git config --global user.name "Gitea Actions"
# Direct token approach
git remote set-url origin "https://runner:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
# Check if tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
# Create tag
git tag -a "$TAG" -m "Release $TAG"
# Push tag
git push origin "$TAG"
echo "Successfully created and pushed tag: $TAG"
else
echo "Tag $TAG already exists, skipping tag creation"
fi
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: releases
- name: Create Build Info
run: |
# Create a build info JSON file
echo '{
"version": "${{ env.RELEASE_TAG }}",
"buildNumber": "${{ github.run_number }}",
"commit": "${{ github.sha }}",
"branch": "${{ github.ref_name }}",
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
"artifacts": {
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux",
"macos": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS"
}
}' > build-info.json
# Create a simple HTML download page
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.download-btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin: 10px 5px;
}
.download-btn:hover { background-color: #45a049; }
.platform { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
<div class="platform">
<h2>Windows</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
</div>
<div class="platform">
<h2>Linux</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
</div>
<div class="platform">
<h2>macOS</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS" class="download-btn">Download macOS Build</a></p>
</div>
<footer>
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
</footer>
</body>
</html>' > downloads.html
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: |-
build-info.json
downloads.html
token: '${{ secrets.GITEATOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
### Download Links
Download builds from our CI artifacts:
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
- [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'

View File

@ -0,0 +1,392 @@
name: Test Local Signing
on:
workflow_dispatch: # Manual trigger
# push:
# branches: [ozgur/build]
jobs:
test-local-signing:
runs-on: macos
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Create Test Entitlements
run: |
echo "📝 Creating entitlements file..."
cat > LuckyWorld.entitlements << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
EOF
echo "✅ Created entitlements file"
cat LuckyWorld.entitlements
shell: bash
- name: Create Test App Bundle
run: |
echo "📦 Creating test app bundle..."
# Create test app bundle structure
TEST_APP_DIR="TestApp.app"
mkdir -p "$TEST_APP_DIR/Contents/MacOS"
# Create a simple test executable
echo '#!/bin/bash
echo "Hello from TestApp!"' > "$TEST_APP_DIR/Contents/MacOS/TestApp"
chmod +x "$TEST_APP_DIR/Contents/MacOS/TestApp"
# Create Info.plist
cat > "$TEST_APP_DIR/Contents/Info.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>TestApp</string>
<key>CFBundleIdentifier</key>
<string>com.luckyrobots.luckyworld.testapp</string>
<key>CFBundleName</key>
<string>TestApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
</dict>
</plist>
EOF
echo "✅ Created test app bundle"
# Verify app bundle exists
if [ ! -d "$TEST_APP_DIR" ]; then
echo "❌ Error: App bundle not found at $TEST_APP_DIR"
exit 1
fi
echo "🔍 App bundle contents:"
ls -la "$TEST_APP_DIR"
# Store app path as environment variable
echo "APP_PATH=$(pwd)/TestApp.app" >> "$GITHUB_ENV"
shell: bash
- name: Setup Certificate
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"
# Check certificate format and details
echo "📑 Certificate format check:"
file "$CERT_DIR/certificate.p12"
# Try to get certificate info with openssl
echo "📑 Certificate info with OpenSSL:"
openssl pkcs12 -info -in "$CERT_DIR/certificate.p12" -nokeys -passin pass:"$CERTIFICATE_PASSWORD" || echo "Failed to read certificate with OpenSSL"
# 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"
# Try multiple import approaches
echo "🔑 Importing developer certificate - attempt 1 (standard)..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
echo "🔑 Importing developer certificate - attempt 2 (with flags)..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -x -A
echo "🔑 Importing developer certificate - attempt 3 (with format)..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12
# Set partition list for codesign to access keychain
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Check all certificates in keychain
echo "🔍 Listing all certificates in keychain..."
security find-certificate -a "$KEYCHAIN_PATH"
# Verify certificate
echo "🔍 Verifying code signing identities..."
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
# Alternative check for identities
echo "🔍 Listing identities with code signing usage..."
security find-certificate -a -c "Developer ID Application" -p "$KEYCHAIN_PATH" | grep -q "Code Signing" && echo "✅ Certificate has code signing usage" || echo "❌ Certificate does NOT have code signing usage"
# 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 "USE_SYSTEM_CERT=true" >> "$GITHUB_ENV"
else
echo "❌ No Developer ID Application certificate found in system keychain"
echo "USE_SYSTEM_CERT=false" >> "$GITHUB_ENV"
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"
# Debug: keep p12 file for inspection
echo "💾 Keeping certificate.p12 for debugging"
shell: bash
- name: Debug Certificate Content
if: always()
env:
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
run: |
echo "🔎 Debugging certificate content..."
CERT_DIR="$HOME/certificates"
# Check if p12 file exists
if [ ! -f "$CERT_DIR/certificate.p12" ]; then
echo "❌ Certificate file not found"
exit 0
fi
# Try with OpenSSL to extract certificate info
echo "Attempting to extract certificate info..."
openssl pkcs12 -in "$CERT_DIR/certificate.p12" -info -nokeys -passin pass:"$CERTIFICATE_PASSWORD" > cert_info.txt || echo "Failed to extract info"
# Check certificate contents
echo "Certificate subject information:"
grep "subject" cert_info.txt || echo "No subject information found"
echo "Certificate issuer information:"
grep "issuer" cert_info.txt || echo "No issuer information found"
# Check if it's a Developer ID certificate
if grep -q "Developer ID" cert_info.txt; then
echo "✅ This appears to be a Developer ID certificate"
else
echo "❌ This does NOT appear to be a Developer ID certificate"
fi
# Check if it has a private key
echo "Checking for private key..."
if openssl pkcs12 -in "$CERT_DIR/certificate.p12" -nocerts -passin pass:"$CERTIFICATE_PASSWORD" -passout pass:temp 2>/dev/null; then
echo "✅ Certificate contains a private key"
else
echo "❌ Certificate does NOT contain a private key or wrong password"
fi
shell: bash
- name: Sign with Developer ID
run: |
echo "🔏 Signing app with Developer ID certificate..."
# Decide which keychain to use
if [ "${USE_SYSTEM_CERT:-false}" = "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 LuckyWorld.entitlements --sign - --timestamp "$APP_PATH"
echo "SIGNED=adhoc" >> "$GITHUB_ENV"
else
echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH"
# Sign the app bundle using the hash
codesign --force --deep --verbose --options runtime --entitlements LuckyWorld.entitlements --sign "$IDENTITY_HASH" --timestamp "$APP_PATH"
echo "SIGNED=identity" >> "$GITHUB_ENV"
fi
# Verify signing
echo "🔍 Verifying signature..."
codesign -vvv --deep --strict "$APP_PATH"
# Check entitlements
echo "🔍 Checking entitlements..."
codesign -d --entitlements - "$APP_PATH"
shell: bash
- name: Notarize App
if: success()
env:
APPLE_ID: ${{ secrets.NOTARY_USER }}
APP_PASSWORD: ${{ secrets.NOTARY_PASSWORD }}
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..."
# Check if we have API key credentials
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="TestApp-notarize.zip"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
echo "Submitting for notarization with API key..."
xcrun notarytool submit "$ZIP_PATH" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" \
--wait
# Staple the notarization ticket
echo "Stapling notarization ticket..."
xcrun stapler staple "$APP_PATH"
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "$APP_PATH"
echo "NOTARIZED=true" >> "$GITHUB_ENV"
# Clean up
rm -rf ~/private_keys
# Fall back to App-specific password if API key not available
elif [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
echo "Using App-specific password for notarization..."
# Create zip for notarization
ZIP_PATH="TestApp-notarize.zip"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
echo "Submitting for notarization..."
xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
# Staple the notarization ticket
echo "Stapling notarization ticket..."
xcrun stapler staple "$APP_PATH"
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "$APP_PATH"
echo "NOTARIZED=true" >> "$GITHUB_ENV"
else
echo "⚠️ Missing notarization credentials. Skipping notarization."
echo "For App Store Connect API key method, set these secrets:"
echo " - NOTARY_API_KEY_ID: Your API key ID"
echo " - NOTARY_API_KEY_ISSUER_ID: Your API issuer ID"
echo " - NOTARY_API_KEY_PATH: Path to or content of your p8 file"
echo ""
echo "For App-specific password method, set these secrets:"
echo " - NOTARY_USER: Your Apple ID (email)"
echo " - NOTARY_PASSWORD: Your app-specific password"
echo " - APPLE_TEAM_ID: Your Apple Developer team ID"
echo "NOTARIZED=false" >> "$GITHUB_ENV"
exit 0
fi
shell: bash
- name: Package Signed App
run: |
echo "📦 Packaging signed app..."
if [ "${NOTARIZED:-false}" == "true" ]; then
ZIP_FILE="TestApp-Signed-Notarized.zip"
echo "Creating distribution package with notarized app..."
else
ZIP_FILE="TestApp-Signed.zip"
echo "Creating distribution package with signed app..."
fi
# Create zip package
ditto -c -k --keepParent "$APP_PATH" "$ZIP_FILE"
echo "✅ Created package: $ZIP_FILE"
shell: bash
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyWorld-Signed-App
path: TestApp-*.zip
retention-days: 7
- name: Cleanup
if: always()
run: |
echo "🧹 Cleaning up..."
rm -rf TestApp.app TestApp-*.zip || true
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
echo "✅ Cleanup complete"
shell: bash

View File

@ -0,0 +1,271 @@
name: Test macOS Build Action
on:
workflow_dispatch: # Manual trigger only for testing
push:
branches: [ozgur/build]
jobs:
test-macos-build:
runs-on: macos
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
# Setup environment for build
- name: Setup environment
run: |
# Get the working directory path for absolute paths
WORKSPACE_DIR="$(pwd)"
echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> "$GITHUB_ENV"
echo "ENTITLEMENTS_FILE=LuckyWorld.entitlements" >> "$GITHUB_ENV"
# Set CI environment variable to true for build script
echo "CI=true" >> "$GITHUB_ENV"
# Create directories for builds
mkdir -p Builds/Mac
mkdir -p PackagedReleases
mkdir -p ArchivedApps
echo "Environment setup complete"
shell: bash
# Build for macOS - use your own build script
- name: Build for macOS
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
# Find the app bundle
- name: Find app bundle
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"
echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV"
# 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
# Basic pre-notarization checks
- name: Check for notarization issues
run: |
echo "🔍 Checking app for potential notarization issues..."
APP_PATH="${{ env.APP_PATH }}"
# Verify code signature already exists (from Unreal build)
echo "Checking existing signature..."
codesign -vvv "$APP_PATH" || echo "⚠️ App may not be properly signed by Unreal Engine"
# Check for any ad-hoc signatures that would cause issues
if codesign -dvv "$APP_PATH" 2>&1 | grep -q "adhoc"; then
echo "⚠️ Warning: Ad-hoc signature detected. This will be replaced with a proper signature."
fi
# Verify entitlements file exists
if [ ! -f "${{ env.ENTITLEMENTS_FILE }}" ]; then
echo "⚠️ Entitlements file not found. Will use default entitlements."
else
echo "Found entitlements file: ${{ env.ENTITLEMENTS_FILE }}"
fi
# Make install scripts executable
chmod +x ./scripts/install_luckyworld.sh
chmod +x ./scripts/create_dmg.sh
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'
# Upload only the DMG file from the macos-notarize action
- name: Upload Standard DMG
uses: actions/upload-artifact@v3
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none'
with:
name: LuckyWorld-macOS-Signed-Notarized-DMG
path: ${{ steps.sign-and-notarize.outputs.package-path }}
retention-days: 30
# Install create-dmg tool
- name: Install create-dmg tool
run: |
if ! command -v brew &> /dev/null; then
echo "Homebrew is not installed. Installing now..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
if [[ -d "/opt/homebrew/bin/" ]]; then
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
fi
if [[ -d "/usr/local/bin/" ]]; then
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/usr/local/bin/brew shellenv)"
fi
echo "Homebrew installed successfully!"
fi
brew install create-dmg
shell: bash
- name: Setup CI Variables
run: echo "CI=true" >> $GITHUB_ENV
shell: bash
# Create custom DMG with installer script
- name: Create Custom DMG with Installer
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none'
run: |
echo "Creating custom DMG with installer script..."
APP_PATH="${{ env.APP_PATH }}"
mkdir -p "./PackagedReleases/Install"
rm -rf "./PackagedReleases/Install/*" 2>/dev/null || true
# Copy the app and installer to package directory
cp -R "$APP_PATH" "./PackagedReleases/Install/"
cp "./scripts/install_luckyworld.sh" "./PackagedReleases/Install/"
chmod +x "./PackagedReleases/Install/install_luckyworld.sh"
# Create a simple README
echo "Writing README file"
printf "LuckyWorld Installer\n===================\n\n1. Double-click install_luckyworld.sh to install\n2. Follow on-screen instructions\n\nFor help: https://luckyrobots.io\n" > "./PackagedReleases/Install/README.txt"
# Create DMG using direct hdiutil approach (native macOS command)
CUSTOM_DMG_PATH="./PackagedReleases/LuckyWorld-Installer.dmg"
rm -f "$CUSTOM_DMG_PATH" 2>/dev/null || true
hdiutil create -volname "LuckyWorld Installer" -srcfolder "./PackagedReleases/Install" -ov -format UDZO "$CUSTOM_DMG_PATH"
if [ $? -ne 0 ]; then
echo "❌ DMG creation failed! Creating a ZIP file as fallback..."
( cd "./PackagedReleases" && zip -r "LuckyWorld-Installer.zip" "Install" )
CUSTOM_DMG_PATH="./PackagedReleases/LuckyWorld-Installer.zip"
fi
if [ -f "$CUSTOM_DMG_PATH" ]; then
echo "✅ Package created successfully: $CUSTOM_DMG_PATH"
echo "Size: $(du -h "$CUSTOM_DMG_PATH" | cut -f1)"
echo "CUSTOM_DMG_PATH=$CUSTOM_DMG_PATH" >> $GITHUB_ENV
else
echo "❌ Failed to create installation package"
exit 1
fi
# Clean up temporary directory
rm -rf "./PackagedReleases/Install"
shell: bash
# Upload the custom DMG with installer script
- name: Upload Custom DMG with Installer
uses: actions/upload-artifact@v3
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' && env.CUSTOM_DMG_PATH != ''
with:
name: LuckyWorld-macOS-Installer-DMG
path: ${{ env.CUSTOM_DMG_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 }}"
if [ "${{ steps.sign-and-notarize.outputs.signed }}" != "none" ]; then
echo "✅ Packaging completed successfully!"
echo "Final standard package: ${{ steps.sign-and-notarize.outputs.package-path }}"
if [ -n "$CUSTOM_DMG_PATH" ] && [ -f "$CUSTOM_DMG_PATH" ]; then
echo "Final installer package: $CUSTOM_DMG_PATH"
fi
else
echo "⚠️ App was not signed - check the logs for details"
fi
shell: bash

View File

@ -1,347 +0,0 @@
name: Unreal Engine Build
on:
workflow_dispatch:
jobs:
build-and-release:
runs-on: windows
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Setup environment
run: |
# Set environment variables for Unreal Engine
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
# Set environment variables for Linux toolchain
$env:LINUX_MULTIARCH_ROOT="C:/UnrealToolchains/v23_clang-18.1.0-rockylinux8"
echo "LINUX_MULTIARCH_ROOT=${LINUX_MULTIARCH_ROOT}" >> $GITHUB_ENV
# Create directories for builds (with error handling)
if (!(Test-Path "Builds/Windows")) { New-Item -ItemType Directory -Path "Builds/Windows" -Force }
if (!(Test-Path "Builds/Linux")) { New-Item -ItemType Directory -Path "Builds/Linux" -Force }
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
- name: Build for Windows
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./win_build.sh"
- name: Build for Linux
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./linux_build.sh"
- name: Package builds
run: |
echo "Packaging Windows build..."
if [ -d "Builds/Windows" ]; then
cd Builds/Windows
zip -r ../../PackagedReleases/LuckyRobots-Windows.zip .
cd ../..
fi
echo "Packaging Linux build..."
if [ -d "Builds/Linux" ]; then
cd Builds/Linux
zip -r ../../PackagedReleases/LuckyRobots-Linux.zip .
cd ../..
fi
echo "=== Packaged releases ==="
ls -la PackagedReleases/
- name: Upload Windows Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Windows.zip') != ''
with:
name: LuckyRobots-Windows
path: PackagedReleases/LuckyRobots-Windows.zip
retention-days: 365
- name: Upload Linux Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Linux.zip') != ''
with:
name: LuckyRobots-Linux
path: PackagedReleases/LuckyRobots-Linux.zip
retention-days: 365
- name: Create Tag
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
# No previous version tag, start with 1.0.0
NEW_VERSION="1.0.0"
echo "No previous version tags found, starting with 1.0.0"
else
# Strip 'v' prefix if it exists
VERSION=${LATEST_TAG#v}
# Split version into parts
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# Auto-increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
fi
# Final tag with v prefix
TAG="v${NEW_VERSION}"
echo "Creating git tag: $TAG"
# Configure git with token authentication
git config --global user.email "actions@gitea.com"
git config --global user.name "Gitea Actions"
# Direct token approach - simplest method
git remote set-url origin "https://goran:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
# Set git to not prompt for input
$env:GIT_TERMINAL_PROMPT=0
# Check if tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
# Create tag without opening editor (-m flag)
git tag -a "$TAG" -m "Release $TAG"
# Push with timeout and debug
echo "Pushing tag $TAG to origin..."
git push --verbose origin "$TAG" || {
echo "Error: Failed to push tag. Check your token permissions."
exit 1
}
echo "Successfully created and pushed tag: $TAG"
else
echo "Tag $TAG already exists, skipping tag creation"
fi
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Create Build Info
run: |
# Create a build info JSON file
echo '{
"version": "${{ env.RELEASE_TAG }}",
"buildNumber": "${{ github.run_number }}",
"commit": "${{ github.sha }}",
"branch": "${{ github.ref_name }}",
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
"artifacts": {
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux"
}
}' > PackagedReleases/build-info.json
# Create a simple HTML download page
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.download-btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin: 10px 5px;
}
.download-btn:hover { background-color: #45a049; }
.platform { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
<div class="platform">
<h2>Windows</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
</div>
<div class="platform">
<h2>Linux</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
</div>
<footer>
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
</footer>
</body>
</html>' > PackagedReleases/downloads.html
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: |-
PackagedReleases/build-info.json
PackagedReleases/downloads.html
token: '${{ secrets.GITEA_TOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
### Download Links
Download builds from our CI artifacts:
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'
macos-build:
runs-on: macos
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Get Release Tag
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
NEW_VERSION="1.0.0"
else
VERSION=${LATEST_TAG#v}
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
fi
TAG="v${NEW_VERSION}"
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
echo "Using release tag: $TAG"
- name: Setup Unreal Engine
run: |
# Use the correct path where Unreal Engine is installed
UE_PATH="/Users/Shared/Epic Games/UE_5.5"
if [ ! -d "$UE_PATH" ]; then
echo "Error: Unreal Engine is not installed in the expected location"
echo "Please ensure Unreal Engine is installed at $UE_PATH"
exit 1
fi
# Set environment variable with the correct Engine path
echo "UE_ROOT=$UE_PATH/Engine" >> $GITHUB_ENV
echo "Using Unreal Engine 5.5"
- name: Build Unreal Project
run: |
chmod +x ./mac_build.sh
./mac_build.sh
- name: Prepare Mac release
run: |
echo "Preparing packaged files for release..."
# Create a directory for release files
mkdir -p PackagedReleases
# Debug: Show what we're packaging
echo "=== Packaging for Release ==="
echo "Build directory contents:"
ls -la Builds/
# Find the app bundle in the Builds directory
APP_PATH=$(find Builds -type d -name "*.app" | head -1)
if [ -n "$APP_PATH" ]; then
echo "Found app bundle: $APP_PATH"
# Get the app name
APP_NAME=$(basename "$APP_PATH")
# Create zip file of the app bundle
(cd $(dirname "$APP_PATH") && zip -r "../../PackagedReleases/${APP_NAME%.app}-macOS.zip" "$APP_NAME")
echo "Created packaged release: PackagedReleases/${APP_NAME%.app}-macOS.zip"
else
echo "No .app bundle found in Builds directory"
# Look for a directory that might be a bundle but not named .app
MAIN_BUILD_DIR=$(find Builds -mindepth 1 -maxdepth 1 -type d | head -1)
if [ -n "$MAIN_BUILD_DIR" ]; then
echo "Found main build directory: $MAIN_BUILD_DIR"
DIR_NAME=$(basename "$MAIN_BUILD_DIR")
# Package this directory as if it were the app
(cd $(dirname "$MAIN_BUILD_DIR") && zip -r "../../PackagedReleases/${DIR_NAME}-macOS.zip" "$DIR_NAME")
echo "Created packaged release from main directory: PackagedReleases/${DIR_NAME}-macOS.zip"
else
# Package the entire Builds directory as a fallback
echo "No main directory found, packaging everything"
zip -r "PackagedReleases/LuckyRobots-macOS.zip" Builds
echo "Created fallback package: PackagedReleases/LuckyRobots-macOS.zip"
fi
fi
echo "Packaged releases:"
ls -la PackagedReleases/
- name: Upload macOS Build Artifact
uses: actions/upload-artifact@v3
if: success()
with:
name: LuckyRobots-macOS
path: PackagedReleases/*-macOS.zip
retention-days: 365
- name: Create Release Note
run: |
echo "## macOS Build Completed" > release-note.md
echo "" >> release-note.md
echo "macOS build is available as an artifact." >> release-note.md
echo "" >> release-note.md
echo "Download from: [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)" >> release-note.md
- name: Create Gitea Release
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
uses: https://gitea.com/actions/gitea-release-action@main
with:
token: ${{ secrets.GITEATOKEN }}
tag_name: ${{ env.RELEASE_TAG }}
title: "Release ${{ env.RELEASE_TAG }} - macOS"
body: |
## macOS Build Available as Artifact
The macOS build is available as an artifact due to its large file size.
[Download macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
Built from commit: ${{ github.sha }}
files: release-note.md

1
.gitignore vendored
View File

@ -41,3 +41,4 @@ DerivedDataCache/*
#this only is the Binaries folder on the root, not the Binaries folder in the plugin folders #this only is the Binaries folder on the root, not the Binaries folder in the plugin folders
Binaries/** Binaries/**
*.app/ *.app/
.cursorrules

View File

@ -338,3 +338,36 @@ NearClipPlane=0.100000
bFinalUsesRDO=True bFinalUsesRDO=True
FinalRDOLambda=100 FinalRDOLambda=100
[/Script/MacTargetPlatform.MacTargetSettings]
TargetedRHIs=SF_METAL_SM5
MetalLanguageVersion=5
MaxShaderLanguageVersion=4
MinimumOSVersion=11
BundleName=LuckyWorld
BundleDisplayName=LuckyWorld
bEnableMathOptimizations=True
UseFastIntrinsics=True
EnableMipGenOption=Default
FrameRateLock=PUFRL_None
AudioSampleRate=48000
AudioMaxChannels=32
bUseCustomIcon=False
bUseMiniUPnP=False
MetalDynamicLibraries=()
MetalRuntimeLibrary=1
OutputRealFPS=False
bBuildEmbeddedFrameworksForGame=False
EnableCodeCoverage=False
EnableCodeCoveragePath=(Path="")
ForwardShading=False
UseFastCopyToResolve=True
bAutomaticallySignBuilds=False
bUseSIPSafeRunloop=True
CodeSigningIdentity=""
[/Script/MacTargetPlatform.XcodeProjectSettings]
CodeSigningPrefix=com.luckyrobots
ApplicationDisplayName=LuckyWorld
ShippingSpecificMacEntitlements=(FilePath="../LuckyWorld.entitlements")

View File

@ -7,6 +7,9 @@ ProjectVersion=0.1
;bAddPacks=True ;bAddPacks=True
;InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent") ;InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent")
[/Script/MacTargetPlatform.MacTargetSettings]
BundleIdentifier=com.luckyrobots.luckyworld
[/Script/UnrealEd.ProjectPackagingSettings] [/Script/UnrealEd.ProjectPackagingSettings]
Build=IfProjectHasCode Build=IfProjectHasCode
BuildConfiguration=PPBC_Shipping BuildConfiguration=PPBC_Shipping

28
LuckyWorld.entitlements Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>

View File

@ -19,5 +19,6 @@ public class LuckyWorld : ModuleRules
// PrivateDependencyModuleNames.Add("OnlineSubsystem"); // PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
} }
} }

View File

@ -1,55 +0,0 @@
#!/bin/bash
# Get the user's home directory
USER_HOME="$HOME"
# Set up Unreal Engine paths
UE_ROOT="/Users/Shared/Epic Games/UE_5.5"
UE_EDITOR="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor"
UE_UAT="$UE_ROOT/Engine/Build/BatchFiles/RunUAT.command"
# Set up project paths
PROJECT_ROOT="$(pwd)"
PROJECT_FILE="$PROJECT_ROOT/LuckyWorld.uproject"
ARCHIVE_DIR="$PROJECT_ROOT/Builds"
rm -rf DerivedDataCache Intermediate Binaries Saved
"$UE_ROOT/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" -project="$PROJECT_FILE" -game -engine
# Run the build command
"$UE_UAT" -ScriptsForProject="$PROJECT_FILE" Turnkey \
-command=VerifySdk \
-platform=Mac \
-UpdateIfNeeded \
-EditorIO \
-EditorIOPort=59484 \
-project="$PROJECT_FILE" \
BuildCookRun \
-nop4 \
-utf8output \
-cook \
-project="$PROJECT_FILE" \
-target=LuckyWorld \
-unrealexe="$UE_EDITOR" \
-platform=Mac \
-installed \
-stage \
-archive \
-package \
-build \
-iterativecooking \
-pak \
-iostore \
-compressed \
-prereqs \
-archivedirectory="$ARCHIVE_DIR" \
-CrashReporter \
-clientconfig=Shipping \
# -nocompile \
# -nocompileuat \
# -nocompileeditor \
# -skipbuildeditor \
# enable these if you want to test build without pak and iostore (you're just testing the build)
# -skipiostore \
# -skippak \ (disable -pak and -iostore)

298
scripts/create_dmg.sh Normal file
View File

@ -0,0 +1,298 @@
#!/bin/bash
# LuckyWorld DMG Creation Script
# This script creates a DMG file for macOS containing the application and installation script
# Terminal colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check for create-dmg command
if ! command -v create-dmg &> /dev/null; then
echo -e "${RED}Error: 'create-dmg' command not found.${NC}"
echo -e "${YELLOW}Please install it with: 'brew install create-dmg'${NC}"
exit 1
fi
# Function to handle cleanup on exit
cleanup() {
echo -e "${BLUE}Cleaning up temporary files...${NC}"
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
fi
}
# Set up trap for cleanup
trap cleanup EXIT
# Usage information
if [ "$#" -lt 2 ]; then
echo -e "${YELLOW}Usage: $0 <application_dir> <output_directory> [version]${NC}"
echo -e "${BLUE}Example: $0 ./build/LuckyWorld.app ./dist 1.0.0${NC}"
exit 1
fi
# Parse parameters
APPLICATION_DIR="$1"
OUTPUT_DIR="$2"
VERSION="${3:-1.0.0}"
# Validate parameters
if [ ! -d "$APPLICATION_DIR" ]; then
echo -e "${RED}Error: Application directory does not exist: $APPLICATION_DIR${NC}"
exit 1
fi
# Application name
APP_NAME=$(basename "$APPLICATION_DIR")
DMG_NAME="LuckyWorld-${VERSION}"
# Create output directory if it doesn't exist
if [ ! -d "$OUTPUT_DIR" ]; then
echo -e "${BLUE}Creating output directory: $OUTPUT_DIR${NC}"
mkdir -p "$OUTPUT_DIR"
fi
# Create temporary directory for DMG contents
TEMP_DIR=$(mktemp -d)
echo -e "${BLUE}Creating temporary directory: $TEMP_DIR${NC}"
# Copy application to temp directory
echo -e "${BLUE}Copying application to temporary directory...${NC}"
cp -R "$APPLICATION_DIR" "$TEMP_DIR/"
# Create installation script in temp directory
INSTALL_SCRIPT="$TEMP_DIR/install_luckyworld.sh"
echo -e "${BLUE}Creating installation script...${NC}"
cat > "$INSTALL_SCRIPT" << 'EOF'
#!/bin/bash
# LuckyWorld macOS Installation Script
# This script installs the LuckyWorld app and removes macOS Gatekeeper quarantine flags
# Terminal colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# App paths
APP_NAME="LuckyWorld.app"
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/$APP_NAME"
APP_DEST="/Applications/$APP_NAME"
# Checking script permissions
if [[ $EUID -ne 0 ]]; then
echo -e "${YELLOW}This script requires administrator permissions to run.${NC}"
echo -e "${BLUE}Requesting administrator access...${NC}"
# Get the script path and re-run with sudo
SCRIPT_PATH=$(readlink -f "$0")
exec sudo "$SCRIPT_PATH"
exit 0
fi
# Header
echo -e "${BLUE}============================================${NC}"
echo -e "${GREEN}LuckyWorld macOS Installation Tool${NC}"
echo -e "${BLUE}============================================${NC}"
echo ""
# Check if source app exists
if [ ! -d "$APP_SOURCE" ]; then
# Check if source app exists in the same directory as the script
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/../$APP_NAME"
if [ ! -d "$APP_SOURCE" ]; then
echo -e "${RED}ERROR: Could not find $APP_NAME${NC}"
echo -e "${YELLOW}Please make sure the application is located in the same directory as this script.${NC}"
exit 1
fi
fi
echo -e "${BLUE}Source application: ${NC}${YELLOW}$APP_SOURCE${NC}"
echo -e "${BLUE}Destination: ${NC}${YELLOW}$APP_DEST${NC}"
echo ""
# Check if application already exists and is running
if [ -d "$APP_DEST" ]; then
echo -e "${YELLOW}Application $APP_NAME already exists in Applications folder.${NC}"
# Check if app is running
if pgrep -x "LuckyWorld" > /dev/null; then
echo -e "${RED}LuckyWorld is currently running. Please close the application before continuing.${NC}"
read -p "Press Enter after closing the application to continue..."
# Check again
if pgrep -x "LuckyWorld" > /dev/null; then
echo -e "${RED}LuckyWorld is still running. Installation aborted.${NC}"
exit 1
fi
fi
echo -e "${BLUE}Removing existing version...${NC}"
rm -rf "$APP_DEST"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Could not remove existing application. Possible permission issue.${NC}"
exit 1
fi
echo -e "${GREEN}Existing application removed successfully.${NC}"
fi
# Install the application
echo -e "${BLUE}Installing $APP_NAME to Applications folder...${NC}"
cp -R "$APP_SOURCE" /Applications/
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Installation failed. Could not copy the application to /Applications.${NC}"
exit 1
fi
# Clear macOS Gatekeeper quarantine flag
echo -e "${BLUE}Removing Gatekeeper quarantine flag...${NC}"
xattr -rd com.apple.quarantine "$APP_DEST"
if [ $? -ne 0 ]; then
echo -e "${YELLOW}WARNING: Could not remove Gatekeeper quarantine flag.${NC}"
echo -e "${YELLOW}You may need to manually allow the application in System Preferences > Security & Privacy.${NC}"
else
echo -e "${GREEN}Gatekeeper restrictions removed successfully.${NC}"
fi
# Set proper permissions
echo -e "${BLUE}Setting permissions...${NC}"
chmod -R 755 "$APP_DEST"
# Successful installation message
echo -e "${GREEN}✅ LuckyWorld has been successfully installed!${NC}"
echo -e "${BLUE}============================================${NC}"
echo -e "${YELLOW}To start the application, open /Applications/$APP_NAME${NC}"
echo -e "${BLUE}============================================${NC}"
# Ask if user wants to launch the app
echo ""
read -p "Would you like to launch LuckyWorld now? (y/n): " LAUNCH_APP
if [[ "$LAUNCH_APP" =~ ^[Yy]$ ]]; then
echo -e "${BLUE}Launching LuckyWorld...${NC}"
open "$APP_DEST"
echo -e "${GREEN}Done!${NC}"
fi
exit 0
EOF
# Make the installation script executable
chmod +x "$INSTALL_SCRIPT"
# Create a README file
README_FILE="$TEMP_DIR/README.txt"
echo -e "${BLUE}Creating README file...${NC}"
cat > "$README_FILE" << EOF
LuckyWorld ${VERSION}
===================
Installation Instructions:
1. Double-click the 'install_luckyworld.sh' script to install LuckyWorld
2. If prompted, enter your administrator password
3. The application will be installed to your Applications folder
If you encounter any issues with installation:
- Make sure you have administrator privileges
- Check System Preferences > Security & Privacy if macOS blocks the application
For support, please visit: https://luckyrobots.com
EOF
# Create DMG file
echo -e "${BLUE}Creating DMG file...${NC}"
OUTPUT_DMG="$OUTPUT_DIR/${DMG_NAME}.dmg"
# Remove existing DMG if it exists
if [ -f "$OUTPUT_DMG" ]; then
echo -e "${YELLOW}Removing existing DMG file: $OUTPUT_DMG${NC}"
rm -f "$OUTPUT_DMG"
fi
# Check if icon file exists
ICON_PATH="$(dirname "$0")/assets/LuckyWorld.icns"
VOLICON_PARAM=""
if [ -f "$ICON_PATH" ]; then
echo -e "${GREEN}Using volume icon: $ICON_PATH${NC}"
VOLICON_PARAM="--volicon \"$ICON_PATH\""
else
echo -e "${YELLOW}Volume icon not found at $ICON_PATH. Creating DMG without custom icon.${NC}"
fi
# CI ENVIRONMENT DETECTION - Use different approach if in CI
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] || [ -n "$GITEA_ACTIONS" ]; then
echo -e "${BLUE}CI environment detected! Using hdiutil directly instead of create-dmg${NC}"
# Create DMG using hdiutil directly (much more reliable in CI)
hdiutil create -volname "LuckyWorld ${VERSION}" -srcfolder "$TEMP_DIR" -ov -format UDZO "$OUTPUT_DMG"
# Check if DMG creation was successful
if [ $? -ne 0 ]; then
echo -e "${RED}Error: hdiutil DMG creation failed, attempting ZIP fallback...${NC}"
# Create a ZIP file as fallback
(cd "$(dirname "$TEMP_DIR")" && zip -r "$OUTPUT_DMG.zip" "$(basename "$TEMP_DIR")")
# If ZIP succeeded, use it instead
if [ $? -eq 0 ]; then
echo -e "${YELLOW}Created ZIP fallback: $OUTPUT_DMG.zip${NC}"
OUTPUT_DMG="$OUTPUT_DMG.zip"
else
echo -e "${RED}Error: Both DMG and ZIP creation failed.${NC}"
exit 1
fi
fi
else
# Non-CI environment: Use create-dmg
# Create DMG using create-dmg with dynamic volicon parameter
CMD_CREATE_DMG="create-dmg \
--volname \"LuckyWorld ${VERSION}\" \
$VOLICON_PARAM \
--window-pos 200 120 \
--window-size 800 500 \
--icon-size 128 \
--icon \"LuckyWorld.app\" 200 250 \
--icon \"install_luckyworld.sh\" 400 250 \
--icon \"README.txt\" 600 250 \
--hide-extension \"LuckyWorld.app\" \
--hide-extension \"install_luckyworld.sh\" \
--app-drop-link 400 120 \
--no-internet-enable \
--no-finder \
\"$OUTPUT_DMG\" \
\"$TEMP_DIR\""
# Execute the command
echo -e "${BLUE}Executing: $CMD_CREATE_DMG${NC}"
eval $CMD_CREATE_DMG
# Check if DMG creation was successful
if [ $? -ne 0 ]; then
echo -e "${RED}Error: Failed to create DMG with create-dmg, trying hdiutil...${NC}"
# Fallback to hdiutil
hdiutil create -volname "LuckyWorld ${VERSION}" -srcfolder "$TEMP_DIR" -ov -format UDZO "$OUTPUT_DMG"
if [ $? -ne 0 ]; then
echo -e "${RED}Error: All DMG creation methods failed.${NC}"
exit 1
fi
fi
fi
echo -e "${GREEN}✅ DMG file created successfully: $OUTPUT_DMG${NC}"
echo -e "${BLUE}DMG size: $(du -h "$OUTPUT_DMG" | cut -f1)${NC}"
exit 0

View File

@ -0,0 +1,118 @@
#!/bin/bash
# LuckyWorld macOS Installation Script
# This script installs the LuckyWorld app and removes macOS Gatekeeper quarantine flags
# Terminal colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# App paths
APP_NAME="LuckyWorld.app"
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/$APP_NAME"
APP_DEST="/Applications/$APP_NAME"
# Checking script permissions
if [[ $EUID -ne 0 ]]; then
echo -e "${YELLOW}This script requires administrator permissions to run.${NC}"
echo -e "${BLUE}Requesting administrator access...${NC}"
# Get the script path and re-run with sudo
SCRIPT_PATH=$(readlink -f "$0")
exec sudo "$SCRIPT_PATH"
exit 0
fi
# Header
echo -e "${BLUE}============================================${NC}"
echo -e "${GREEN}LuckyWorld macOS Installation Tool${NC}"
echo -e "${BLUE}============================================${NC}"
echo ""
# Check if source app exists
if [ ! -d "$APP_SOURCE" ]; then
# Check if source app exists in the same directory as the script
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/../$APP_NAME"
if [ ! -d "$APP_SOURCE" ]; then
echo -e "${RED}ERROR: Could not find $APP_NAME${NC}"
echo -e "${YELLOW}Please make sure the application is located in the same directory as this script.${NC}"
exit 1
fi
fi
echo -e "${BLUE}Source application: ${NC}${YELLOW}$APP_SOURCE${NC}"
echo -e "${BLUE}Destination: ${NC}${YELLOW}$APP_DEST${NC}"
echo ""
# Check if application already exists and is running
if [ -d "$APP_DEST" ]; then
echo -e "${YELLOW}Application $APP_NAME already exists in Applications folder.${NC}"
# Check if app is running
if pgrep -x "LuckyWorld" > /dev/null; then
echo -e "${RED}LuckyWorld is currently running. Please close the application before continuing.${NC}"
read -p "Press Enter after closing the application to continue..."
# Check again
if pgrep -x "LuckyWorld" > /dev/null; then
echo -e "${RED}LuckyWorld is still running. Installation aborted.${NC}"
exit 1
fi
fi
echo -e "${BLUE}Removing existing version...${NC}"
rm -rf "$APP_DEST"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Could not remove existing application. Possible permission issue.${NC}"
exit 1
fi
echo -e "${GREEN}Existing application removed successfully.${NC}"
fi
# Install the application
echo -e "${BLUE}Installing $APP_NAME to Applications folder...${NC}"
cp -R "$APP_SOURCE" /Applications/
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Installation failed. Could not copy the application to /Applications.${NC}"
exit 1
fi
# Clear macOS Gatekeeper quarantine flag
echo -e "${BLUE}Removing Gatekeeper quarantine flag...${NC}"
xattr -rd com.apple.quarantine "$APP_DEST"
if [ $? -ne 0 ]; then
echo -e "${YELLOW}WARNING: Could not remove Gatekeeper quarantine flag.${NC}"
echo -e "${YELLOW}You may need to manually allow the application in System Preferences > Security & Privacy.${NC}"
else
echo -e "${GREEN}Gatekeeper restrictions removed successfully.${NC}"
fi
# Set proper permissions
echo -e "${BLUE}Setting permissions...${NC}"
chmod -R 755 "$APP_DEST"
# Successful installation message
echo -e "${GREEN}✅ LuckyWorld has been successfully installed!${NC}"
echo -e "${BLUE}============================================${NC}"
echo -e "${YELLOW}To start the application, open /Applications/$APP_NAME${NC}"
echo -e "${BLUE}============================================${NC}"
# Ask if user wants to launch the app
echo ""
read -p "Would you like to launch LuckyWorld now? (y/n): " LAUNCH_APP
if [[ "$LAUNCH_APP" =~ ^[Yy]$ ]]; then
echo -e "${BLUE}Launching LuckyWorld...${NC}"
open "$APP_DEST"
echo -e "${GREEN}Done!${NC}"
fi
exit 0

131
scripts/mac_build.sh Executable file
View File

@ -0,0 +1,131 @@
#!/bin/bash
set -e # Exit on any error
# Get the user's home directory
USER_HOME="$HOME"
# Set up Unreal Engine paths
UE_ROOT="/Users/Shared/Epic Games/UE_5.5"
UE_EDITOR="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor"
UE_UAT="$UE_ROOT/Engine/Build/BatchFiles/RunUAT.command"
# Set up project paths
PROJECT_ROOT="$(pwd)"
PROJECT_FILE="$PROJECT_ROOT/LuckyWorld.uproject"
ARCHIVE_DIR="$PROJECT_ROOT/Builds"
# Check for entitlements file
if [ -f "$PROJECT_ROOT/LuckyWorld.entitlements" ]; then
ENTITLEMENTS_FILE="$PROJECT_ROOT/LuckyWorld.entitlements"
echo "✅ Using entitlements file: $ENTITLEMENTS_FILE"
else
echo "⚠️ Warning: No entitlements file found. This might affect notarization."
ENTITLEMENTS_FILE=""
fi
# Print paths and config for debugging
echo "Project root: $PROJECT_ROOT"
echo "Project file: $PROJECT_FILE"
echo "Archive directory: $ARCHIVE_DIR"
# More selective cleanup - don't remove DerivedDataCache
echo "🧹 Cleaning build artifacts..."
rm -rf DerivedDataCache Intermediate Binaries Saved
mkdir -p "$ARCHIVE_DIR"
# Generate project files
echo "📝 Generating project files..."
"$UE_ROOT/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" -project="$PROJECT_FILE" -game -engine
# Run the build command with simplified parameters and more diagnostics
echo "🔨 Starting build process..."
"$UE_UAT" -ScriptsForProject="$PROJECT_FILE" Turnkey \
-command=VerifySdk \
-platform=Mac \
-UpdateIfNeeded \
-EditorIO \
-EditorIOPort=59484 \
-project="$PROJECT_FILE" \
BuildCookRun \
-nop4 \
-utf8output \
-cook \
-project="$PROJECT_FILE" \
-target=LuckyWorld \
-unrealexe="$UE_EDITOR" \
-platform=Mac \
-installed \
-stage \
-archive \
-package \
-build \
-iterativecooking \
-pak \
-iostore \
-compressed \
-prereqs \
-archivedirectory="$ARCHIVE_DIR" \
-CrashReporter \
-clientconfig=Shipping \
# -nocompile \
# -nocompileuat \
# -nocompileeditor \
# -skipbuildeditor \
# enable these if you want to test build without pak and iostore (you're just testing the build)
# -skipiostore \
# -skippak \ (disable -pak and -iostore)
#!/bin/bash
# Check for errors in the build process
BUILD_STATUS=$?
if [ $BUILD_STATUS -ne 0 ]; then
echo "❌ ERROR: Build command failed with exit code $BUILD_STATUS"
exit $BUILD_STATUS
fi
echo ""
echo "🔍 Looking for built application..."
APP_PATH=$(find "$ARCHIVE_DIR" -name "*.app" -type d | head -n 1)
# Check if the build actually succeeded by verifying the app exists
if [ -z "$APP_PATH" ] || [ ! -d "$APP_PATH" ]; then
echo "❌ ERROR: Build failed or did not produce an app bundle!"
echo "Check the logs above for build errors."
# List all files in the archive directory to help debug
echo "Contents of archive directory:"
find "$ARCHIVE_DIR" -type f -o -type d | sort
exit 1
fi
echo "✅ Build completed successfully! Application path:"
echo "$APP_PATH"
if [ -n "$APP_PATH" ]; then
echo ""
echo "🔍 Binary files summary:"
DYLIB_COUNT=$(find "$APP_PATH" -name "*.dylib" | wc -l)
SO_COUNT=$(find "$APP_PATH" -name "*.so" | wc -l)
FRAMEWORKS=$(find "$APP_PATH" -path "*.framework/*" -type f -perm +111 | wc -l)
EXECUTABLES=$(find "$APP_PATH" -type f -perm +111 -not -path "*.framework/*" -not -name "*.dylib" -not -name "*.so" | wc -l)
echo "- $DYLIB_COUNT .dylib libraries"
echo "- $SO_COUNT .so libraries"
echo "- $FRAMEWORKS framework executables"
echo "- $EXECUTABLES other executables"
echo "Total binary files: $((DYLIB_COUNT + SO_COUNT + FRAMEWORKS + EXECUTABLES))"
# Check bundle ID (for information only, no modifications)
INFO_PLIST="$APP_PATH/Contents/Info.plist"
if [ -f "$INFO_PLIST" ]; then
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO_PLIST")
echo ""
echo "📦 App Bundle ID: $BUNDLE_ID"
fi
fi
echo ""
echo "✅ Build complete!"
echo "App location: $APP_PATH"