diff --git a/.gitea/actions/linux-build/action.yml b/.gitea/actions/linux-build/action.yml new file mode 100644 index 00000000..f9cf4604 --- /dev/null +++ b/.gitea/actions/linux-build/action.yml @@ -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 \ No newline at end of file diff --git a/.gitea/actions/macos-build/action.yml b/.gitea/actions/macos-build/action.yml new file mode 100644 index 00000000..62c94c09 --- /dev/null +++ b/.gitea/actions/macos-build/action.yml @@ -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 \ No newline at end of file diff --git a/.gitea/actions/macos-notarize/action.yml b/.gitea/actions/macos-notarize/action.yml new file mode 100644 index 00000000..e77e590b --- /dev/null +++ b/.gitea/actions/macos-notarize/action.yml @@ -0,0 +1,704 @@ +name: macOS Notarize +description: 'Signs and notarizes a macOS application with Apple certificates' +author: moersoy + +inputs: + app-path: + description: 'Path to the .app bundle to sign' + required: true + entitlements-file: + description: 'Path to entitlements file to use for signing' + required: false + default: '' + team-id: + description: 'Apple Developer Team ID' + required: true + certificate-base64: + description: 'Base64-encoded certificate (P12 file)' + required: true + certificate-password: + description: 'Certificate password' + required: true + notarization-method: + description: 'Method to use for notarization (api-key or app-password)' + required: false + default: 'api-key' + app-password: + description: 'App-specific password for Apple ID (required if using app-password method)' + required: false + default: '' + apple-id: + description: 'Apple ID email (required if using app-password method)' + required: false + default: '' + notary-api-key-id: + description: 'App Store Connect API Key ID (required if using api-key method)' + required: false + default: '' + notary-api-key-issuer-id: + description: 'App Store Connect API Key Issuer ID (required if using api-key method)' + required: false + default: '' + notary-api-key-path: + description: 'App Store Connect API Key file content (base64 encoded) (required if using api-key method)' + required: false + default: '' + bundle-id: + description: 'Bundle ID of the app' + required: false + default: '' + fallback-to-adhoc: + description: 'Fallback to ad-hoc signing if no certificate is available' + required: false + default: 'true' + +outputs: + signed: + description: 'Signing status (true, ad-hoc, none)' + value: ${{ steps.set-outputs.outputs.signed }} + notarized: + description: 'Notarization status (true, false)' + value: ${{ steps.set-outputs.outputs.notarized }} + app-path: + description: 'Path to the signed app bundle' + value: ${{ steps.set-outputs.outputs.app-path }} + zip-path: + description: 'Path to the packaged .ZIP file' + value: ${{ steps.set-outputs.outputs.zip-path }} + package-path: + description: 'Path to the packaged .DMG file' + value: ${{ steps.set-outputs.outputs.package-path }} + +runs: + using: "composite" + steps: + - name: Setup debug environment + run: | + # Create debug directory if env variable is set + if [[ -n "$DEBUG_LOG_PATH" ]]; then + mkdir -p "$(dirname "$DEBUG_LOG_PATH")" + touch "$DEBUG_LOG_PATH" + echo "Debug logging enabled to: $DEBUG_LOG_PATH" | tee -a "$DEBUG_LOG_PATH" + fi + + # Define a debug function + debug_log() { + echo "DEBUG: $1" + if [[ -n "$DEBUG_LOG_PATH" ]]; then + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$DEBUG_LOG_PATH" + fi + } + # Export the function for use in subsequent steps + export -f debug_log + + debug_log "Starting macOS notarize action" + debug_log "App path: ${{ inputs.app-path }}" + debug_log "Team ID: ${{ inputs.team-id }}" + debug_log "Notarization method: ${{ inputs.notarization-method }}" + debug_log "Bundle ID: ${{ inputs.bundle-id }}" + shell: bash + + - name: Set up variables + id: setup + run: | + # Debugging info + debug_log "Setting up variables" + + # Generate unique name for keychain + KEYCHAIN_NAME="build-keychain-$(uuidgen)" + KEYCHAIN_PASSWORD="$(uuidgen)" + echo "KEYCHAIN_NAME=$KEYCHAIN_NAME" >> $GITHUB_ENV + echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV + + # Set paths + echo "APP_PATH=${{ inputs.app-path }}" >> $GITHUB_ENV + + # Generate working directory for temp files + WORK_DIR="$(mktemp -d)" + echo "WORK_DIR=$WORK_DIR" >> $GITHUB_ENV + + # Set bundle id (from input or extract from app) + if [[ -n "${{ inputs.bundle-id }}" ]]; then + BUNDLE_ID="${{ inputs.bundle-id }}" + else + BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist") + fi + echo "BUNDLE_ID=$BUNDLE_ID" >> $GITHUB_ENV + + # Get app name from bundle path + APP_NAME=$(basename "${{ inputs.app-path }}" .app) + echo "APP_NAME=$APP_NAME" >> $GITHUB_ENV + + # Set output directory + OUTPUT_DIR="$(pwd)/PackagedReleases" + mkdir -p "$OUTPUT_DIR" + echo "OUTPUT_DIR=$OUTPUT_DIR" >> $GITHUB_ENV + + # Set package paths + ZIP_PATH="$OUTPUT_DIR/${APP_NAME}.zip" + DMG_PATH="$OUTPUT_DIR/${APP_NAME}.dmg" + echo "ZIP_PATH=$ZIP_PATH" >> $GITHUB_ENV + echo "DMG_PATH=$DMG_PATH" >> $GITHUB_ENV + + # Set notarization variables based on method + if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then + echo "Using API key method for notarization" + + # Create API key file + API_KEY_FILE="$WORK_DIR/api_key.p8" + echo "${{ inputs.notary-api-key-path }}" | base64 --decode > "$API_KEY_FILE" + echo "API_KEY_FILE=$API_KEY_FILE" >> $GITHUB_ENV + + # Verify API key file exists + if [[ ! -f "$API_KEY_FILE" ]]; then + debug_log "ERROR: API key file could not be created" + exit 1 + fi + + debug_log "API key file created at: $API_KEY_FILE" + debug_log "API key ID: ${{ inputs.notary-api-key-id }}" + debug_log "API key issuer ID: ${{ inputs.notary-api-key-issuer-id }}" + else + echo "Using app-specific password method for notarization" + debug_log "Apple ID: ${{ inputs.apple-id }}" + fi + shell: bash + + - name: Setup keychain + id: setup-keychain + run: | + debug_log "Setting up keychain" + + # Create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" + security default-keychain -s "$KEYCHAIN_NAME" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" + security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME" + + # Create certificate file + CERTIFICATE_PATH="$WORK_DIR/certificate.p12" + echo "${{ inputs.certificate-base64 }}" | base64 --decode > "$CERTIFICATE_PATH" + + # Add to keychain + debug_log "Importing certificate into keychain" + security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_NAME" -P "${{ inputs.certificate-password }}" -T /usr/bin/codesign + + # Allow codesign to access keychain items + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" + + # Verify certificate was imported + security find-identity -v "$KEYCHAIN_NAME" | grep "Developer ID Application" + IDENTITY_RESULT=$? + + if [ $IDENTITY_RESULT -eq 0 ]; then + debug_log "Certificate imported successfully" + SIGNING_IDENTITY="Developer ID Application: ${{ inputs.team-id }}" + echo "SIGNING_IDENTITY=$SIGNING_IDENTITY" >> $GITHUB_ENV + echo "CERTIFICATE_AVAILABLE=true" >> $GITHUB_ENV + else + debug_log "WARNING: No Developer ID Application certificate found" + if [[ "${{ inputs.fallback-to-adhoc }}" == "true" ]]; then + debug_log "Falling back to ad-hoc signing" + echo "CERTIFICATE_AVAILABLE=adhoc" >> $GITHUB_ENV + else + debug_log "Not falling back to ad-hoc signing as specified" + echo "CERTIFICATE_AVAILABLE=false" >> $GITHUB_ENV + fi + fi + shell: bash + + - name: Sign application + id: sign-app + run: | + debug_log "Starting application signing process" + + # Check if certificate is available + if [[ "$CERTIFICATE_AVAILABLE" == "false" ]]; then + debug_log "No certificate available and fallback disabled. Skipping signing." + echo "SIGNING_RESULT=none" >> $GITHUB_ENV + exit 0 + fi + + # Sign the app + if [[ "$CERTIFICATE_AVAILABLE" == "true" ]]; then + debug_log "Signing with Developer ID certificate" + + # First remove existing signatures + debug_log "Removing existing signatures..." + codesign --remove-signature "$APP_PATH" || true + + # Sign all dynamic libraries and frameworks + debug_log "Signing embedded binaries and frameworks..." + find "$APP_PATH/Contents/MacOS" -type f -name "*.dylib" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + find "$APP_PATH/Contents/Frameworks" -type f -depth 1 -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + find "$APP_PATH/Contents/Frameworks" -name "*.framework" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + + # Sign all executables + debug_log "Signing executables..." + find "$APP_PATH/Contents/MacOS" -type f -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + + # Sign app bundle + debug_log "Signing main app bundle..." + codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH" + + SIGN_RESULT=$? + if [ $SIGN_RESULT -eq 0 ]; then + debug_log "App signed successfully with Developer ID" + echo "SIGNING_RESULT=true" >> $GITHUB_ENV + else + debug_log "App signing failed with Developer ID" + echo "SIGNING_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + + elif [[ "$CERTIFICATE_AVAILABLE" == "adhoc" ]]; then + debug_log "Signing with ad-hoc identity (not suitable for distribution)" + + # Remove existing signatures + codesign --remove-signature "$APP_PATH" || true + + # Sign with ad-hoc identity + codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign - "$APP_PATH" + + SIGN_RESULT=$? + if [ $SIGN_RESULT -eq 0 ]; then + debug_log "App signed successfully with ad-hoc identity" + echo "SIGNING_RESULT=ad-hoc" >> $GITHUB_ENV + else + debug_log "App signing failed with ad-hoc identity" + echo "SIGNING_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + else + debug_log "Unexpected certificate state. Skipping signing." + echo "SIGNING_RESULT=none" >> $GITHUB_ENV + fi + + # Verify signing + debug_log "Verifying app signature..." + codesign -dvv "$APP_PATH" + shell: bash + + - name: Verify notarization and stapling + id: verify-notarization + if: env.SIGNING_RESULT == 'true' + run: | + debug_log "Verifying app signature and code requirements before notarization" + + # Verify code signature + codesign --verify --verbose "$APP_PATH" + if [ $? -ne 0 ]; then + debug_log "Error: App signature verification failed" + # Don't exit, just log the error + else + debug_log "App signature verification passed" + fi + + # Check app for code requirements + codesign --display --requirements "$APP_PATH" + if [ $? -ne 0 ]; then + debug_log "Error: App doesn't meet requirements" + # Don't exit, just log the error + else + debug_log "App meets code requirements" + fi + shell: bash + + - name: Notarize application + id: notarize-app + if: env.SIGNING_RESULT == 'true' + run: | + debug_log "Starting notarization process" + + # Create ZIP for notarization + debug_log "Creating ZIP archive for notarization" + ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH" + if [ $? -ne 0 ]; then + debug_log "Error creating ZIP archive" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + + # Notarize the app + if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then + debug_log "Notarizing with API key method" + + # Submit for notarization + debug_log "Submitting app for notarization..." + xcrun notarytool submit "$ZIP_PATH" \ + --key "$API_KEY_FILE" \ + --key-id "${{ inputs.notary-api-key-id }}" \ + --issuer "${{ inputs.notary-api-key-issuer-id }}" \ + --wait > "$WORK_DIR/notarization_output.txt" 2>&1 + + cat "$WORK_DIR/notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + + REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/notarization_output.txt" | cut -d ' ' -f2) + + if [[ "$REQUEST_STATUS" == "Accepted" ]]; then + debug_log "Notarization successful" + echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV + else + debug_log "Notarization failed or timed out" + cat "$WORK_DIR/notarization_output.txt" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + fi + + else + debug_log "Notarizing with app-specific password method" + + # Submit for notarization + debug_log "Submitting app for notarization..." + xcrun altool --notarize-app \ + --primary-bundle-id "$BUNDLE_ID" \ + --username "${{ inputs.apple-id }}" \ + --password "${{ inputs.app-password }}" \ + --file "$ZIP_PATH" \ + > "$WORK_DIR/notarization_output.txt" 2>&1 + + cat "$WORK_DIR/notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + + REQUEST_UUID=$(grep -o "RequestUUID = .*" "$WORK_DIR/notarization_output.txt" | cut -d ' ' -f3) + + if [[ -n "$REQUEST_UUID" ]]; then + debug_log "Notarization request submitted, UUID: $REQUEST_UUID" + + # Wait for notarization to complete + debug_log "Waiting for notarization to complete..." + TIMEOUT=30 # 30 minutes timeout + COUNT=0 + NOTARIZATION_STATUS="in progress" + + while [[ "$NOTARIZATION_STATUS" == "in progress" && $COUNT -lt $TIMEOUT ]]; do + sleep 60 + + xcrun altool --notarization-info "$REQUEST_UUID" \ + --username "${{ inputs.apple-id }}" \ + --password "${{ inputs.app-password }}" \ + > "$WORK_DIR/notarization_info.txt" 2>&1 + + cat "$WORK_DIR/notarization_info.txt" | tee -a "$DEBUG_LOG_PATH" + + NOTARIZATION_STATUS=$(grep -o "Status: .*" "$WORK_DIR/notarization_info.txt" | cut -d ':' -f2 | xargs) + + debug_log "Notarization status: $NOTARIZATION_STATUS" + COUNT=$((COUNT+1)) + done + + if [[ "$NOTARIZATION_STATUS" == "success" ]]; then + debug_log "Notarization successful" + echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV + else + debug_log "Notarization failed or timed out: $NOTARIZATION_STATUS" + if [[ -f "$WORK_DIR/notarization_info.txt" ]]; then + cat "$WORK_DIR/notarization_info.txt" + fi + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + fi + else + debug_log "Notarization submission failed, no UUID returned" + cat "$WORK_DIR/notarization_output.txt" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + fi + fi + shell: bash + + - name: Staple notarization ticket + id: staple-ticket + if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' + run: | + debug_log "Stapling notarization ticket to app" + + # Staple the ticket + xcrun stapler staple "$APP_PATH" + STAPLE_RESULT=$? + + if [ $STAPLE_RESULT -eq 0 ]; then + debug_log "Notarization ticket stapled successfully" + echo "STAPLING_RESULT=true" >> $GITHUB_ENV + + # Verify stapling + debug_log "Verifying notarization stapling" + xcrun stapler validate "$APP_PATH" + if [ $? -eq 0 ]; then + debug_log "Stapling validation successful" + else + debug_log "Stapling validation failed, but continuing" + fi + else + debug_log "Stapling failed" + echo "STAPLING_RESULT=false" >> $GITHUB_ENV + fi + shell: bash + + - name: Remove quarantine attribute + id: remove-quarantine + if: env.SIGNING_RESULT != 'none' + run: | + debug_log "Removing quarantine attribute from app" + + # Create helper script + QUARANTINE_SCRIPT="$WORK_DIR/remove_quarantine.sh" + cat > "$QUARANTINE_SCRIPT" << 'EOF' +#!/bin/bash +# Removes the quarantine attribute from app and all its contents +echo "Removing quarantine attribute from all files..." +find "$1" -exec xattr -d com.apple.quarantine {} \; 2>/dev/null || true +echo "Quarantine attributes removed" +EOF + chmod +x "$QUARANTINE_SCRIPT" + + # Remove quarantine attribute + "$QUARANTINE_SCRIPT" "$APP_PATH" + + debug_log "Quarantine attribute removal completed" + shell: bash + + - name: Package signed app + id: package-app + run: | + debug_log "Packaging the signed app" + + # Check if we should use create-dmg if available + if command -v create-dmg &> /dev/null; then + debug_log "Using create-dmg for DMG creation" + + # Create a temporary directory for DMG contents + DMG_TEMP_DIR="$WORK_DIR/dmg-contents" + mkdir -p "$DMG_TEMP_DIR" + + # Copy the app to the temporary directory + cp -R "$APP_PATH" "$DMG_TEMP_DIR/" + + # Create instructions text file + echo "Drag the application to the Applications folder to install it." > "$DMG_TEMP_DIR/README.txt" + + # Create symlink to Applications folder + ln -s /Applications "$DMG_TEMP_DIR/Applications" + + # Use create-dmg to create a more beautiful DMG + create-dmg \ + --volname "$APP_NAME" \ + --window-pos 200 120 \ + --window-size 800 400 \ + --icon-size 100 \ + --app-drop-link 600 185 \ + --icon "$APP_NAME.app" 200 185 \ + --hide-extension "$APP_NAME.app" \ + --add-file "README.txt" 400 185 \ + --no-internet-enable \ + "$DMG_PATH" \ + "$DMG_TEMP_DIR" + + DMG_CREATE_RESULT=$? + + elif command -v hdiutil &> /dev/null; then + debug_log "Using hdiutil for DMG creation" + + # Create DMG using hdiutil + hdiutil create -volname "$APP_NAME" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_PATH" + DMG_CREATE_RESULT=$? + + else + debug_log "Neither create-dmg nor hdiutil available. Cannot create DMG." + DMG_CREATE_RESULT=1 + fi + + # Check DMG creation result + if [ $DMG_CREATE_RESULT -eq 0 ]; then + debug_log "DMG package created successfully at: $DMG_PATH" + echo "DMG_CREATED=true" >> $GITHUB_ENV + else + debug_log "DMG creation failed" + echo "DMG_CREATED=false" >> $GITHUB_ENV + fi + + # If we have a properly signed app, sign the DMG as well + if [[ "$SIGNING_RESULT" == "true" && "$DMG_CREATED" == "true" ]]; then + debug_log "Signing DMG with Developer ID certificate" + codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG_PATH" + + if [ $? -eq 0 ]; then + debug_log "DMG signed successfully" + else + debug_log "DMG signing failed" + fi + + # If app was notarized, also notarize the DMG + if [[ "$NOTARIZATION_RESULT" == "true" ]]; then + debug_log "Notarizing DMG..." + + # Notarize the DMG + if [[ "${{ inputs.notarization-method }}" == "api-key" ]]; then + debug_log "Notarizing DMG with API key method" + + xcrun notarytool submit "$DMG_PATH" \ + --key "$API_KEY_FILE" \ + --key-id "${{ inputs.notary-api-key-id }}" \ + --issuer "${{ inputs.notary-api-key-issuer-id }}" \ + --wait > "$WORK_DIR/dmg_notarization_output.txt" 2>&1 + + cat "$WORK_DIR/dmg_notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + + DMG_REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/dmg_notarization_output.txt" | cut -d ' ' -f2) + + if [[ "$DMG_REQUEST_STATUS" == "Accepted" ]]; then + debug_log "DMG notarization successful" + + # Staple DMG + debug_log "Stapling notarization ticket to DMG" + xcrun stapler staple "$DMG_PATH" + if [ $? -eq 0 ]; then + debug_log "DMG stapling successful" + else + debug_log "DMG stapling failed" + fi + else + debug_log "DMG notarization failed or timed out" + cat "$WORK_DIR/dmg_notarization_output.txt" + fi + + else + debug_log "Notarizing DMG with app-specific password method" + + xcrun altool --notarize-app \ + --primary-bundle-id "$BUNDLE_ID.dmg" \ + --username "${{ inputs.apple-id }}" \ + --password "${{ inputs.app-password }}" \ + --file "$DMG_PATH" \ + > "$WORK_DIR/dmg_notarization_output.txt" 2>&1 + + cat "$WORK_DIR/dmg_notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + + DMG_REQUEST_UUID=$(grep -o "RequestUUID = .*" "$WORK_DIR/dmg_notarization_output.txt" | cut -d ' ' -f3) + + if [[ -n "$DMG_REQUEST_UUID" ]]; then + debug_log "DMG notarization request submitted, UUID: $DMG_REQUEST_UUID" + + # Wait for notarization to complete + debug_log "Waiting for DMG notarization to complete..." + TIMEOUT=10 # 10 minutes timeout for DMG + COUNT=0 + DMG_NOTARIZATION_STATUS="in progress" + + while [[ "$DMG_NOTARIZATION_STATUS" == "in progress" && $COUNT -lt $TIMEOUT ]]; do + sleep 60 + + xcrun altool --notarization-info "$DMG_REQUEST_UUID" \ + --username "${{ inputs.apple-id }}" \ + --password "${{ inputs.app-password }}" \ + > "$WORK_DIR/dmg_notarization_info.txt" 2>&1 + + cat "$WORK_DIR/dmg_notarization_info.txt" | tee -a "$DEBUG_LOG_PATH" + + DMG_NOTARIZATION_STATUS=$(grep -o "Status: .*" "$WORK_DIR/dmg_notarization_info.txt" | cut -d ':' -f2 | xargs) + + debug_log "DMG notarization status: $DMG_NOTARIZATION_STATUS" + COUNT=$((COUNT+1)) + done + + if [[ "$DMG_NOTARIZATION_STATUS" == "success" ]]; then + debug_log "DMG notarization successful" + + # Staple DMG + debug_log "Stapling notarization ticket to DMG" + xcrun stapler staple "$DMG_PATH" + if [ $? -eq 0 ]; then + debug_log "DMG stapling successful" + else + debug_log "DMG stapling failed" + fi + else + debug_log "DMG notarization failed or timed out: $DMG_NOTARIZATION_STATUS" + if [[ -f "$WORK_DIR/dmg_notarization_info.txt" ]]; then + cat "$WORK_DIR/dmg_notarization_info.txt" + fi + fi + else + debug_log "DMG notarization submission failed, no UUID returned" + cat "$WORK_DIR/dmg_notarization_output.txt" + fi + fi + fi + fi + + # Final verification of all distribution artifacts + debug_log "Verifying final distribution artifacts" + + # Check ZIP file + if [[ -f "$ZIP_PATH" ]]; then + ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) + debug_log "ZIP package size: $ZIP_SIZE" + + # Verify ZIP integrity + unzip -t "$ZIP_PATH" > /dev/null + if [ $? -eq 0 ]; then + debug_log "ZIP package integrity verified" + else + debug_log "ZIP package may be corrupted" + fi + fi + + # Check DMG file + if [[ -f "$DMG_PATH" ]]; then + DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1) + debug_log "DMG package size: $DMG_SIZE" + + # Verify DMG signature if signed + if [[ "$SIGNING_RESULT" == "true" ]]; then + codesign -vvv "$DMG_PATH" 2>&1 | tee -a "$DEBUG_LOG_PATH" || debug_log "DMG signature verification failed" + fi + fi + shell: bash + + - name: Set outputs + id: set-outputs + run: | + debug_log "Setting action outputs" + + # Pass through environment variables to outputs + if [[ "$SIGNING_RESULT" == "true" ]]; then + echo "signed=true" >> $GITHUB_OUTPUT + elif [[ "$SIGNING_RESULT" == "ad-hoc" ]]; then + echo "signed=ad-hoc" >> $GITHUB_OUTPUT + else + echo "signed=none" >> $GITHUB_OUTPUT + fi + + if [[ "$NOTARIZATION_RESULT" == "true" ]]; then + echo "notarized=true" >> $GITHUB_OUTPUT + else + echo "notarized=false" >> $GITHUB_OUTPUT + fi + + echo "app-path=$APP_PATH" >> $GITHUB_OUTPUT + echo "zip-path=$ZIP_PATH" >> $GITHUB_OUTPUT + + if [[ "$DMG_CREATED" == "true" ]]; then + echo "package-path=$DMG_PATH" >> $GITHUB_OUTPUT + else + echo "package-path=$ZIP_PATH" >> $GITHUB_OUTPUT + fi + + debug_log "Action completed" + shell: bash + + - name: Clean up + if: always() + run: | + debug_log "Cleaning up" + + # Clean up keychain + if [[ -n "$KEYCHAIN_NAME" ]]; then + security delete-keychain "$KEYCHAIN_NAME" || true + debug_log "Keychain deleted" + fi + + # Clean up temporary files + if [[ -d "$WORK_DIR" ]]; then + rm -rf "$WORK_DIR" || true + debug_log "Temporary files deleted" + fi + + debug_log "Cleanup completed" + shell: bash \ No newline at end of file diff --git a/.gitea/actions/windows-build/action.yml b/.gitea/actions/windows-build/action.yml new file mode 100644 index 00000000..1518429e --- /dev/null +++ b/.gitea/actions/windows-build/action.yml @@ -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 \ No newline at end of file diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 00000000..28027bfd --- /dev/null +++ b/.gitea/workflows/build.yml @@ -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 ' + + + + + LuckyRobots ${{ env.RELEASE_TAG }} Downloads + + + +

LuckyRobots Game - ${{ env.RELEASE_TAG }}

+

Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}

+ +
+

Windows

+

Download Windows Build

+
+ +
+

Linux

+

Download Linux Build

+
+ +
+

macOS

+

Download macOS Build

+
+ + + + ' > 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 }}' + \ No newline at end of file diff --git a/.gitea/workflows/create-release.yml b/.gitea/workflows/create-release.yml deleted file mode 100644 index 555b5af2..00000000 --- a/.gitea/workflows/create-release.yml +++ /dev/null @@ -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 }}' \ No newline at end of file diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 00000000..76e687ff --- /dev/null +++ b/.gitea/workflows/release.yml @@ -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 ' + + + + + LuckyRobots ${{ env.RELEASE_TAG }} Downloads + + + +

LuckyRobots Game - ${{ env.RELEASE_TAG }}

+

Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}

+ +
+

Windows

+

Download Windows Build

+
+ +
+

Linux

+

Download Linux Build

+
+ +
+

macOS

+

Download macOS Build

+
+ + + + ' > 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 }}' \ No newline at end of file diff --git a/.gitea/workflows/test-local-signing.yml b/.gitea/workflows/test-local-signing.yml new file mode 100644 index 00000000..5f13a79e --- /dev/null +++ b/.gitea/workflows/test-local-signing.yml @@ -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 + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.automation.apple-events + + com.apple.security.get-task-allow + + + + 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 + + + + + CFBundleExecutable + TestApp + CFBundleIdentifier + com.luckyrobots.luckyworld.testapp + CFBundleName + TestApp + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + LSMinimumSystemVersion + 10.10 + + + 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 \ No newline at end of file diff --git a/.gitea/workflows/test-macos-build.yml b/.gitea/workflows/test-macos-build.yml new file mode 100644 index 00000000..6380acd3 --- /dev/null +++ b/.gitea/workflows/test-macos-build.yml @@ -0,0 +1,709 @@ +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 + + # Enable debug logging + - name: Enable Debug Logging + run: | + echo "ACTIONS_RUNNER_DEBUG=true" >> $GITHUB_ENV + echo "ACTIONS_STEP_DEBUG=true" >> $GITHUB_ENV + shell: bash + + # 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 + + # Restore cache for build dependencies + - name: Restore Build Cache + id: build-cache + uses: actions/cache@v3 + with: + path: | + DerivedDataCache + Intermediate + Saved/Autosaves + Saved/Config + .unreal + key: ${{ runner.os }}-macbuild-${{ hashFiles('**/*.uproject') }}-${{ hashFiles('Config/**') }} + restore-keys: | + ${{ runner.os }}-macbuild-${{ hashFiles('**/*.uproject') }}- + ${{ runner.os }}-macbuild- + + # 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 + shell: bash + + # Save cache for next workflow run + - name: Save Build Cache + if: always() + uses: actions/cache/save@v3 + with: + path: | + DerivedDataCache + Intermediate + Saved/Autosaves + Saved/Config + .unreal + key: ${{ steps.build-cache.outputs.cache-primary-key }} + + # Create a debug log file for notarize action + - name: Create debug log directory + run: | + mkdir -p debug_logs + echo "DEBUG_LOG_PATH=$(pwd)/debug_logs/notarize_log.txt" >> $GITHUB_ENV + shell: bash + + # Beginning of macos-notarize steps + - name: Setup debug environment + id: setup-debug-env + run: | + # Create debug directory if env variable is set + if [[ -n "$DEBUG_LOG_PATH" ]]; then + mkdir -p "$(dirname "$DEBUG_LOG_PATH")" + touch "$DEBUG_LOG_PATH" + echo "Debug logging enabled to: $DEBUG_LOG_PATH" | tee -a "$DEBUG_LOG_PATH" + fi + + # Define a debug function + debug_log() { + echo "DEBUG: $1" + if [[ -n "$DEBUG_LOG_PATH" ]]; then + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$DEBUG_LOG_PATH" + fi + } + # Export the function for use in subsequent steps + export -f debug_log + + debug_log "Starting macOS notarize action" + debug_log "App path: ${{ env.APP_PATH }}" + debug_log "Team ID: ${{ secrets.APPLE_TEAM_ID }}" + debug_log "Notarization method: api-key" + debug_log "Bundle ID: ${{ env.BUNDLE_ID }}" + shell: bash + + - name: Set up variables + id: setup + run: | + # Debugging info + debug_log "Setting up variables" + + # Generate unique name for keychain + KEYCHAIN_NAME="build-keychain-$(uuidgen)" + KEYCHAIN_PASSWORD="$(uuidgen)" + echo "KEYCHAIN_NAME=$KEYCHAIN_NAME" >> $GITHUB_ENV + echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV + + # Set paths + echo "APP_PATH=${{ env.APP_PATH }}" >> $GITHUB_ENV + + # Generate working directory for temp files + WORK_DIR="$(mktemp -d)" + echo "WORK_DIR=$WORK_DIR" >> $GITHUB_ENV + + # Set bundle id (from input or extract from app) + if [[ -n "${{ env.BUNDLE_ID }}" ]]; then + BUNDLE_ID="${{ env.BUNDLE_ID }}" + else + BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ env.APP_PATH }}/Contents/Info.plist") + fi + echo "BUNDLE_ID=$BUNDLE_ID" >> $GITHUB_ENV + + # Get app name from bundle path + APP_NAME=$(basename "${{ env.APP_PATH }}" .app) + echo "APP_NAME=$APP_NAME" >> $GITHUB_ENV + + # Set output directory + OUTPUT_DIR="$(pwd)/PackagedReleases" + mkdir -p "$OUTPUT_DIR" + echo "OUTPUT_DIR=$OUTPUT_DIR" >> $GITHUB_ENV + + # Set package paths + ZIP_PATH="$OUTPUT_DIR/${APP_NAME}.zip" + DMG_PATH="$OUTPUT_DIR/${APP_NAME}.dmg" + echo "ZIP_PATH=$ZIP_PATH" >> $GITHUB_ENV + echo "DMG_PATH=$DMG_PATH" >> $GITHUB_ENV + + # Set notarization variables based on method + echo "Using API key method for notarization" + + # Create API key file + API_KEY_FILE="$WORK_DIR/api_key.p8" + echo "${{ secrets.NOTARY_API_KEY_PATH }}" | base64 --decode > "$API_KEY_FILE" + echo "API_KEY_FILE=$API_KEY_FILE" >> $GITHUB_ENV + + # Verify API key file exists + if [[ ! -f "$API_KEY_FILE" ]]; then + debug_log "ERROR: API key file could not be created" + exit 1 + fi + + debug_log "API key file created at: $API_KEY_FILE" + debug_log "API key ID: ${{ secrets.NOTARY_API_KEY_ID }}" + debug_log "API key issuer ID: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" + shell: bash + + - name: Setup keychain + id: setup-keychain + run: | + debug_log "Setting up keychain" + + # Create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" + security default-keychain -s "$KEYCHAIN_NAME" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" + security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME" + + # Create certificate file + CERTIFICATE_PATH="$WORK_DIR/certificate.p12" + echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > "$CERTIFICATE_PATH" + + # Add to keychain + debug_log "Importing certificate into keychain" + security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_NAME" -P "${{ secrets.MACOS_CERTIFICATE_PWD }}" -T /usr/bin/codesign + + # Allow codesign to access keychain items + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" + + # Verify certificate was imported + security find-identity -v "$KEYCHAIN_NAME" | grep "Developer ID Application" + IDENTITY_RESULT=$? + + if [ $IDENTITY_RESULT -eq 0 ]; then + debug_log "Certificate imported successfully" + SIGNING_IDENTITY="Developer ID Application: ${{ secrets.APPLE_TEAM_ID }}" + echo "SIGNING_IDENTITY=$SIGNING_IDENTITY" >> $GITHUB_ENV + echo "CERTIFICATE_AVAILABLE=true" >> $GITHUB_ENV + else + debug_log "WARNING: No Developer ID Application certificate found" + if [[ "false" == "true" ]]; then + debug_log "Falling back to ad-hoc signing" + echo "CERTIFICATE_AVAILABLE=adhoc" >> $GITHUB_ENV + else + debug_log "Not falling back to ad-hoc signing as specified" + echo "CERTIFICATE_AVAILABLE=false" >> $GITHUB_ENV + fi + fi + shell: bash + + - name: Sign application + id: sign-app + run: | + debug_log "Starting application signing process" + + # Check if certificate is available + if [[ "$CERTIFICATE_AVAILABLE" == "false" ]]; then + debug_log "No certificate available and fallback disabled. Skipping signing." + echo "SIGNING_RESULT=none" >> $GITHUB_ENV + exit 0 + fi + + # Make sure entitlements file is defined and reset variable name + ENTITLEMENTS_PATH="${{ env.ENTITLEMENTS_FILE }}" + + # Sign the app + if [[ "$CERTIFICATE_AVAILABLE" == "true" ]]; then + debug_log "Signing with Developer ID certificate" + + # First remove existing signatures + debug_log "Removing existing signatures..." + codesign --remove-signature "$APP_PATH" || true + + # Sign all dynamic libraries and frameworks + debug_log "Signing embedded binaries and frameworks..." + find "$APP_PATH/Contents/MacOS" -type f -name "*.dylib" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + find "$APP_PATH/Contents/Frameworks" -type f -depth 1 -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + find "$APP_PATH/Contents/Frameworks" -name "*.framework" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + + # Sign all executables + debug_log "Signing executables..." + find "$APP_PATH/Contents/MacOS" -type f -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; + + # Sign app bundle + debug_log "Signing main app bundle..." + codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH" + + SIGN_RESULT=$? + if [ $SIGN_RESULT -eq 0 ]; then + debug_log "App signed successfully with Developer ID" + echo "SIGNING_RESULT=true" >> $GITHUB_ENV + else + debug_log "App signing failed with Developer ID" + echo "SIGNING_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + + elif [[ "$CERTIFICATE_AVAILABLE" == "adhoc" ]]; then + debug_log "Signing with ad-hoc identity (not suitable for distribution)" + + # Remove existing signatures + codesign --remove-signature "$APP_PATH" || true + + # Sign with ad-hoc identity + codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign - "$APP_PATH" + + SIGN_RESULT=$? + if [ $SIGN_RESULT -eq 0 ]; then + debug_log "App signed successfully with ad-hoc identity" + echo "SIGNING_RESULT=ad-hoc" >> $GITHUB_ENV + else + debug_log "App signing failed with ad-hoc identity" + echo "SIGNING_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + else + debug_log "Unexpected certificate state. Skipping signing." + echo "SIGNING_RESULT=none" >> $GITHUB_ENV + fi + + # Verify signing + debug_log "Verifying app signature..." + codesign -dvv "$APP_PATH" + shell: bash + + - name: Verify notarization and stapling + id: verify-notarization + if: env.SIGNING_RESULT == 'true' + run: | + debug_log "Verifying app signature and code requirements before notarization" + + # Verify code signature + codesign --verify --verbose "$APP_PATH" + if [ $? -ne 0 ]; then + debug_log "Error: App signature verification failed" + # Don't exit, just log the error + else + debug_log "App signature verification passed" + fi + + # Check app for code requirements + codesign --display --requirements "$APP_PATH" + if [ $? -ne 0 ]; then + debug_log "Error: App doesn't meet requirements" + # Don't exit, just log the error + else + debug_log "App meets code requirements" + fi + shell: bash + + - name: Notarize application + id: notarize-app + if: env.SIGNING_RESULT == 'true' + run: | + debug_log "Starting notarization process" + + # Create ZIP for notarization + debug_log "Creating ZIP archive for notarization" + ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH" + if [ $? -ne 0 ]; then + debug_log "Error creating ZIP archive" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + + # Notarize the app using API key method + debug_log "Notarizing with API key method" + + # Submit for notarization + debug_log "Submitting app for notarization..." + xcrun notarytool submit "$ZIP_PATH" \ + --key "$API_KEY_FILE" \ + --key-id "${{ secrets.NOTARY_API_KEY_ID }}" \ + --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" \ + --wait > "$WORK_DIR/notarization_output.txt" 2>&1 + + cat "$WORK_DIR/notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + + REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/notarization_output.txt" | cut -d ' ' -f2) + + if [[ "$REQUEST_STATUS" == "Accepted" ]]; then + debug_log "Notarization successful" + echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV + else + debug_log "Notarization failed or timed out" + cat "$WORK_DIR/notarization_output.txt" + echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + fi + shell: bash + + - name: Staple notarization ticket + id: staple-ticket + if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' + run: | + debug_log "Stapling notarization ticket to app" + + # Staple the ticket + xcrun stapler staple "$APP_PATH" + STAPLE_RESULT=$? + + if [ $STAPLE_RESULT -eq 0 ]; then + debug_log "Notarization ticket stapled successfully" + echo "STAPLING_RESULT=true" >> $GITHUB_ENV + + # Verify stapling + debug_log "Verifying notarization stapling" + xcrun stapler validate "$APP_PATH" + if [ $? -eq 0 ]; then + debug_log "Stapling validation successful" + else + debug_log "Stapling validation failed, but continuing" + fi + else + debug_log "Stapling failed" + echo "STAPLING_RESULT=false" >> $GITHUB_ENV + fi + shell: bash + + - name: Remove quarantine attribute + id: remove-quarantine + if: env.SIGNING_RESULT != 'none' + run: | + debug_log "Removing quarantine attribute from app" + + # Directly run the command without creating a script file + debug_log "Removing quarantine attribute from all files..." + find "$APP_PATH" -exec xattr -d com.apple.quarantine {} \; 2>/dev/null || true + debug_log "Quarantine attributes removed" + shell: bash + + - name: Package signed app + id: package-app + run: | + debug_log "Packaging the signed app" + + # Check if we should use create-dmg if available + if command -v create-dmg &> /dev/null; then + debug_log "Using create-dmg for DMG creation" + + # Create a temporary directory for DMG contents + DMG_TEMP_DIR="$WORK_DIR/dmg-contents" + mkdir -p "$DMG_TEMP_DIR" + + # Copy the app to the temporary directory + cp -R "$APP_PATH" "$DMG_TEMP_DIR/" + + # Create instructions text file + echo "Drag the application to the Applications folder to install it." > "$DMG_TEMP_DIR/README.txt" + + # Create symlink to Applications folder + ln -s /Applications "$DMG_TEMP_DIR/Applications" + + # Use create-dmg to create a more beautiful DMG + create-dmg \ + --volname "$APP_NAME" \ + --window-pos 200 120 \ + --window-size 800 400 \ + --icon-size 100 \ + --app-drop-link 600 185 \ + --icon "$APP_NAME.app" 200 185 \ + --hide-extension "$APP_NAME.app" \ + --add-file "README.txt" 400 185 \ + --no-internet-enable \ + "$DMG_PATH" \ + "$DMG_TEMP_DIR" + + DMG_CREATE_RESULT=$? + + elif command -v hdiutil &> /dev/null; then + debug_log "Using hdiutil for DMG creation" + + # Create DMG using hdiutil + hdiutil create -volname "$APP_NAME" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_PATH" + DMG_CREATE_RESULT=$? + + else + debug_log "Neither create-dmg nor hdiutil available. Cannot create DMG." + DMG_CREATE_RESULT=1 + fi + + # Check DMG creation result + if [ $DMG_CREATE_RESULT -eq 0 ]; then + debug_log "DMG package created successfully at: $DMG_PATH" + echo "DMG_CREATED=true" >> $GITHUB_ENV + else + debug_log "DMG creation failed" + echo "DMG_CREATED=false" >> $GITHUB_ENV + fi + + # If we have a properly signed app, sign the DMG as well + if [[ "$SIGNING_RESULT" == "true" && "$DMG_CREATED" == "true" ]]; then + debug_log "Signing DMG with Developer ID certificate" + codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG_PATH" + + if [ $? -eq 0 ]; then + debug_log "DMG signed successfully" + else + debug_log "DMG signing failed" + fi + + # If app was notarized, also notarize the DMG + if [[ "$NOTARIZATION_RESULT" == "true" ]]; then + debug_log "Notarizing DMG..." + + # Notarize the DMG using API key method + debug_log "Notarizing DMG with API key method" + + xcrun notarytool submit "$DMG_PATH" \ + --key "$API_KEY_FILE" \ + --key-id "${{ secrets.NOTARY_API_KEY_ID }}" \ + --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" \ + --wait > "$WORK_DIR/dmg_notarization_output.txt" 2>&1 + + cat "$WORK_DIR/dmg_notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + + DMG_REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/dmg_notarization_output.txt" | cut -d ' ' -f2) + + if [[ "$DMG_REQUEST_STATUS" == "Accepted" ]]; then + debug_log "DMG notarization successful" + + # Staple DMG + debug_log "Stapling notarization ticket to DMG" + xcrun stapler staple "$DMG_PATH" + if [ $? -eq 0 ]; then + debug_log "DMG stapling successful" + else + debug_log "DMG stapling failed" + fi + else + debug_log "DMG notarization failed or timed out" + cat "$WORK_DIR/dmg_notarization_output.txt" + fi + fi + fi + + # Final verification of all distribution artifacts + debug_log "Verifying final distribution artifacts" + + # Check ZIP file + if [[ -f "$ZIP_PATH" ]]; then + ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) + debug_log "ZIP package size: $ZIP_SIZE" + + # Verify ZIP integrity + unzip -t "$ZIP_PATH" > /dev/null + if [ $? -eq 0 ]; then + debug_log "ZIP package integrity verified" + else + debug_log "ZIP package may be corrupted" + fi + fi + + # Check DMG file + if [[ -f "$DMG_PATH" ]]; then + DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1) + debug_log "DMG package size: $DMG_SIZE" + + # Verify DMG signature if signed + if [[ "$SIGNING_RESULT" == "true" ]]; then + codesign -vvv "$DMG_PATH" 2>&1 | tee -a "$DEBUG_LOG_PATH" || debug_log "DMG signature verification failed" + fi + fi + + # Set output variables + if [[ "$SIGNING_RESULT" == "true" ]]; then + echo "SIGNED_STATUS=true" >> $GITHUB_ENV + elif [[ "$SIGNING_RESULT" == "ad-hoc" ]]; then + echo "SIGNED_STATUS=ad-hoc" >> $GITHUB_ENV + else + echo "SIGNED_STATUS=none" >> $GITHUB_ENV + fi + + if [[ "$NOTARIZATION_RESULT" == "true" ]]; then + echo "NOTARIZED_STATUS=true" >> $GITHUB_ENV + else + echo "NOTARIZED_STATUS=false" >> $GITHUB_ENV + fi + + if [[ "$DMG_CREATED" == "true" ]]; then + echo "PACKAGE_PATH=$DMG_PATH" >> $GITHUB_ENV + else + echo "PACKAGE_PATH=$ZIP_PATH" >> $GITHUB_ENV + fi + shell: bash + + - name: Clean up + if: always() + run: | + debug_log "Cleaning up" + + # Clean up keychain + if [[ -n "$KEYCHAIN_NAME" ]]; then + security delete-keychain "$KEYCHAIN_NAME" || true + debug_log "Keychain deleted" + fi + + # Clean up temporary files + if [[ -d "$WORK_DIR" ]]; then + rm -rf "$WORK_DIR" || true + debug_log "Temporary files deleted" + fi + + debug_log "Cleanup completed" + shell: bash + + # Upload debug logs if available + - name: Upload Debug Logs + uses: actions/upload-artifact@v3 + if: always() + with: + name: notarize-debug-logs + path: debug_logs + retention-days: 7 + + # Upload only the DMG file as main distribution artifact + - name: Upload Mac Distribution DMG + uses: actions/upload-artifact@v3 + if: env.NOTARIZED_STATUS == 'true' && env.SIGNED_STATUS != 'none' + with: + name: LuckyWorld-Mac-Distribution + path: ${{ env.PACKAGE_PATH }} + retention-days: 30 + + # Report results + - name: Report Results + run: | + echo "๐Ÿ” App signing: ${{ env.SIGNED_STATUS }}" + echo "๐Ÿ” App notarization: ${{ env.NOTARIZED_STATUS }}" + + if [ "${{ env.SIGNED_STATUS }}" != "none" ]; then + echo "โœ… Packaging completed successfully!" + echo "Final package: ${{ env.PACKAGE_PATH }}" + else + echo "โš ๏ธ App was not signed - check the logs for details" + fi + shell: bash + \ No newline at end of file diff --git a/.gitea/workflows/unreal-build.yml b/.gitea/workflows/unreal-build.yml deleted file mode 100644 index b106bcdd..00000000 --- a/.gitea/workflows/unreal-build.yml +++ /dev/null @@ -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 ' - - - - - LuckyRobots ${{ env.RELEASE_TAG }} Downloads - - - -

LuckyRobots Game - ${{ env.RELEASE_TAG }}

-

Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}

- -
-

Windows

-

Download Windows Build

-
- -
-

Linux

-

Download Linux Build

-
- - - - ' > 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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 21abe81c..dceaf989 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ DerivedDataCache/* #this only is the Binaries folder on the root, not the Binaries folder in the plugin folders Binaries/** *.app/ +.cursorrules diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 761b6da7..e8b910a9 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -338,3 +338,35 @@ NearClipPlane=0.100000 bFinalUsesRDO=True 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") + diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 96915495..7cae8a2f 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -7,6 +7,9 @@ ProjectVersion=0.1 ;bAddPacks=True ;InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent") +[/Script/MacTargetPlatform.MacTargetSettings] +BundleIdentifier=com.luckyrobots.luckyworld + [/Script/UnrealEd.ProjectPackagingSettings] Build=IfProjectHasCode BuildConfiguration=PPBC_Shipping diff --git a/LuckyWorld.entitlements b/LuckyWorld.entitlements new file mode 100644 index 00000000..d8b1cb47 --- /dev/null +++ b/LuckyWorld.entitlements @@ -0,0 +1,30 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.automation.apple-events + + com.apple.security.cs.debugger + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.automation.apple-events + + com.apple.security.get-task-allow + + + \ No newline at end of file diff --git a/Source/LuckyWorld.Target.cs b/Source/LuckyWorld.Target.cs index 25223105..86aedaa3 100644 --- a/Source/LuckyWorld.Target.cs +++ b/Source/LuckyWorld.Target.cs @@ -9,7 +9,7 @@ public class LuckyWorldTarget : TargetRules { Type = TargetType.Game; DefaultBuildSettings = BuildSettingsVersion.V5; - + ExtraModuleNames.AddRange( new string[] { "LuckyWorld" } ); IncludeOrderVersion = EngineIncludeOrderVersion.Latest; diff --git a/Source/LuckyWorld/LuckyWorld.Build.cs b/Source/LuckyWorld/LuckyWorld.Build.cs index d6c887a7..9d80c354 100644 --- a/Source/LuckyWorld/LuckyWorld.Build.cs +++ b/Source/LuckyWorld/LuckyWorld.Build.cs @@ -19,5 +19,6 @@ public class LuckyWorld : ModuleRules // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } } diff --git a/Source/LuckyWorldEditor.Target.cs b/Source/LuckyWorldEditor.Target.cs index a698b47d..b1d60a9d 100644 --- a/Source/LuckyWorldEditor.Target.cs +++ b/Source/LuckyWorldEditor.Target.cs @@ -9,7 +9,7 @@ public class LuckyWorldEditorTarget : TargetRules { Type = TargetType.Editor; DefaultBuildSettings = BuildSettingsVersion.V5; - + ExtraModuleNames.AddRange( new string[] { "LuckyWorld" } ); IncludeOrderVersion = EngineIncludeOrderVersion.Latest; diff --git a/mac_build.sh b/mac_build.sh deleted file mode 100755 index fe68fdfa..00000000 --- a/mac_build.sh +++ /dev/null @@ -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) \ No newline at end of file diff --git a/linux_build.sh b/scripts/linux_build.sh similarity index 100% rename from linux_build.sh rename to scripts/linux_build.sh diff --git a/scripts/mac_build.sh b/scripts/mac_build.sh new file mode 100755 index 00000000..d07c69ab --- /dev/null +++ b/scripts/mac_build.sh @@ -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" diff --git a/win_build.sh b/scripts/win_build.sh similarity index 100% rename from win_build.sh rename to scripts/win_build.sh