WIP: feat(workflows): add new build workflows for Windows, Linux, and macOS, and remove obsolete build scripts #17
46
.gitea/actions/linux-build/action.yml
Normal file
46
.gitea/actions/linux-build/action.yml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: 'Linux Build Steps'
|
||||||
|
description: 'Build Linux application'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
# Set environment variables for Unreal Engine
|
||||||
|
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Set environment variables for Linux toolchain (needed for cross-compilation)
|
||||||
|
$env:LINUX_MULTIARCH_ROOT="C:/UnrealToolchains/v23_clang-18.1.0-rockylinux8"
|
||||||
|
echo "LINUX_MULTIARCH_ROOT=${LINUX_MULTIARCH_ROOT}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Create directories for builds
|
||||||
|
if (!(Test-Path "Builds/Linux")) { New-Item -ItemType Directory -Path "Builds/Linux" -Force }
|
||||||
|
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Build for Linux
|
||||||
|
run: |
|
||||||
|
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
|
||||||
|
& 'C:\Program Files\Git\bin\bash.exe' -c "./scripts/linux_build.sh"
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Package Linux build
|
||||||
|
run: |
|
||||||
|
echo "Packaging Linux build..."
|
||||||
|
if [ -d "Builds/Linux" ]; then
|
||||||
|
cd Builds/Linux
|
||||||
|
zip -r ../../PackagedReleases/LuckyRobots-Linux.zip .
|
||||||
|
cd ../..
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Packaged Linux release ==="
|
||||||
|
ls -la PackagedReleases/
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Linux Build Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: success() && hashFiles('PackagedReleases/LuckyRobots-Linux.zip') != ''
|
||||||
|
with:
|
||||||
|
name: LuckyRobots-Linux
|
||||||
|
path: PackagedReleases/LuckyRobots-Linux.zip
|
||||||
|
retention-days: 365
|
132
.gitea/actions/macos-build/action.yml
Normal file
132
.gitea/actions/macos-build/action.yml
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
name: 'macOS Build Steps'
|
||||||
|
description: 'Build, sign and notarize macOS application'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
apple_team_id:
|
||||||
|
description: 'Apple Team ID for signing'
|
||||||
|
required: true
|
||||||
|
apple_certificate_base64:
|
||||||
|
description: 'Base64-encoded certificate file'
|
||||||
|
required: true
|
||||||
|
apple_certificate_password:
|
||||||
|
description: 'Password for certificate file'
|
||||||
|
required: true
|
||||||
|
api_key_path:
|
||||||
|
description: 'Base64-encoded API key file'
|
||||||
|
required: true
|
||||||
|
api_key_id:
|
||||||
|
description: 'API Key ID'
|
||||||
|
required: true
|
||||||
|
api_key_issuer_id:
|
||||||
|
description: 'API Key Issuer ID'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
# Use the correct path where Unreal Engine is installed
|
||||||
|
UE_PATH="/Users/Shared/Epic Games/UE_5.5"
|
||||||
|
|
||||||
|
if [ ! -d "$UE_PATH" ]; then
|
||||||
|
echo "Error: Unreal Engine is not installed in the expected location"
|
||||||
|
echo "Please ensure Unreal Engine is installed at $UE_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directories for builds
|
||||||
|
mkdir -p Builds/Mac
|
||||||
|
mkdir -p PackagedReleases
|
||||||
|
|
||||||
|
echo "Using Unreal Engine 5.5"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build for macOS
|
||||||
|
run: |
|
||||||
|
chmod +x ./scripts/mac_build.sh
|
||||||
|
./scripts/mac_build.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup for Signing
|
||||||
|
id: setup-signing
|
||||||
|
if: ${{ success() }}
|
||||||
|
env:
|
||||||
|
API_KEY_PATH: ${{ inputs.api_key_path }}
|
||||||
|
run: |
|
||||||
|
# Create output directory
|
||||||
|
mkdir -p PackagedReleases
|
||||||
|
|
||||||
|
# Decode the API key from Base64 secret
|
||||||
|
echo "$API_KEY_PATH" | base64 --decode > api_key.p8
|
||||||
|
echo "api_key_file=$(pwd)/api_key.p8" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Find app bundle
|
||||||
|
APP_PATH=$(find Builds -type d -name "*.app" | head -1)
|
||||||
|
|
||||||
|
if [ -z "$APP_PATH" ]; then
|
||||||
|
# Look for a directory that might be a bundle but not named .app
|
||||||
|
APP_PATH=$(find Builds -mindepth 1 -maxdepth 1 -type d | head -1)
|
||||||
|
if [ -z "$APP_PATH" ]; then
|
||||||
|
echo "No build directory found, cannot continue"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found app path: $APP_PATH"
|
||||||
|
echo "app_path=$APP_PATH" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Sign macOS App
|
||||||
|
uses: lando/code-sign-action@v3
|
||||||
|
id: sign-app
|
||||||
|
with:
|
||||||
|
file: ${{ steps.setup-signing.outputs.app_path }}
|
||||||
|
certificate-data: ${{ inputs.apple_certificate_base64 }}
|
||||||
|
certificate-password: ${{ inputs.apple_certificate_password }}
|
||||||
|
certificate-id: ${{ inputs.apple_team_id }}
|
||||||
|
options: --force --options runtime --deep --timestamp --entitlements ./LuckyRobots.entitlements
|
||||||
|
|
||||||
|
- name: Notarize macOS App
|
||||||
|
run: |
|
||||||
|
# Create a temporary file for notarization
|
||||||
|
APP_PATH="${{ steps.setup-signing.outputs.app_path }}"
|
||||||
|
NOTARIZE_APP_PATH="./LuckyRobots-notarize.zip"
|
||||||
|
ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_APP_PATH"
|
||||||
|
|
||||||
|
API_KEY_FILE="${{ steps.setup-signing.outputs.api_key_file }}"
|
||||||
|
|
||||||
|
# Submit for notarization using API key
|
||||||
|
echo "Submitting for notarization with API key..."
|
||||||
|
xcrun notarytool submit "$NOTARIZE_APP_PATH" --key "$API_KEY_FILE" --key-id "${{ inputs.api_key_id }}" --issuer "${{ inputs.api_key_issuer_id }}" --wait
|
||||||
|
|
||||||
|
# Staple the ticket to the application
|
||||||
|
xcrun stapler staple "$APP_PATH"
|
||||||
|
|
||||||
|
# Clean up the API key file
|
||||||
|
rm -f "$API_KEY_FILE"
|
||||||
|
rm -f "$NOTARIZE_APP_PATH"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Package macOS App
|
||||||
|
run: |
|
||||||
|
# Package the signed and notarized app
|
||||||
|
APP_PATH="${{ steps.setup-signing.outputs.app_path }}"
|
||||||
|
APP_NAME=$(basename "$APP_PATH")
|
||||||
|
DIR_PATH=$(dirname "$APP_PATH")
|
||||||
|
|
||||||
|
echo "Creating final package..."
|
||||||
|
(cd "$DIR_PATH" && zip -r "../../PackagedReleases/LuckyRobots-macOS.zip" "$APP_NAME")
|
||||||
|
echo "Created packaged release: PackagedReleases/LuckyRobots-macOS.zip"
|
||||||
|
|
||||||
|
echo "Packaged releases:"
|
||||||
|
ls -la PackagedReleases/
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload macOS Build Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: success()
|
||||||
|
with:
|
||||||
|
name: LuckyRobots-macOS
|
||||||
|
path: PackagedReleases/LuckyRobots-macOS.zip
|
||||||
|
retention-days: 365
|
824
.gitea/actions/macos-notarize/action.yml
Normal file
824
.gitea/actions/macos-notarize/action.yml
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
name: "macOS Sign and Notarize"
|
||||||
|
description: "Signs and notarizes macOS applications with Developer ID certificate"
|
||||||
|
author: moersoy
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
app-path:
|
||||||
|
description: "Path to the app bundle (.app)"
|
||||||
|
required: true
|
||||||
|
entitlements-file:
|
||||||
|
description: "Path to the entitlements file (.entitlements)"
|
||||||
|
required: true
|
||||||
|
team-id:
|
||||||
|
description: "Apple Developer Team ID"
|
||||||
|
required: true
|
||||||
|
certificate-base64:
|
||||||
|
description: "Base64-encoded Developer ID Application certificate (.p12)"
|
||||||
|
required: true
|
||||||
|
certificate-password:
|
||||||
|
description: "Password for the Developer ID Application certificate"
|
||||||
|
required: true
|
||||||
|
notarization-method:
|
||||||
|
description: "Method to use for notarization: 'api-key' or 'app-password'"
|
||||||
|
required: false
|
||||||
|
default: "api-key"
|
||||||
|
notary-user:
|
||||||
|
description: "Apple ID for notarization (for app-password method)"
|
||||||
|
required: false
|
||||||
|
notary-password:
|
||||||
|
description: "App-specific password for notarization (for app-password method)"
|
||||||
|
required: false
|
||||||
|
notary-api-key-id:
|
||||||
|
description: "API Key ID for notarization (for api-key method)"
|
||||||
|
required: false
|
||||||
|
notary-api-key-issuer-id:
|
||||||
|
description: "API Issuer ID for notarization (for api-key method)"
|
||||||
|
required: false
|
||||||
|
notary-api-key-path:
|
||||||
|
description: "Path to or content of the API Key .p8 file (for api-key method)"
|
||||||
|
required: false
|
||||||
|
bundle-id:
|
||||||
|
description: "App bundle identifier (com.example.app)"
|
||||||
|
required: false
|
||||||
|
fallback-to-adhoc:
|
||||||
|
description: "Whether to fall back to ad-hoc signing if certificate is invalid"
|
||||||
|
required: false
|
||||||
|
default: "true"
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
signed:
|
||||||
|
description: "Whether the app was signed (identity, adhoc, or none)"
|
||||||
|
value: ${{ steps.sign.outputs.signed }}
|
||||||
|
notarized:
|
||||||
|
description: "Whether the app was notarized (true or false)"
|
||||||
|
value: ${{ steps.notarize.outputs.notarized }}
|
||||||
|
package-path:
|
||||||
|
description: "Path to the final package"
|
||||||
|
value: ${{ steps.package.outputs.package-path }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Setup Certificate
|
||||||
|
id: setup-cert
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CERTIFICATE_BASE64: ${{ inputs.certificate-base64 }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ inputs.certificate-password }}
|
||||||
|
APPLE_TEAM_ID: ${{ inputs.team-id }}
|
||||||
|
run: |
|
||||||
|
echo "🔐 Setting up certificate..."
|
||||||
|
|
||||||
|
# Create a temporary directory for certificates
|
||||||
|
CERT_DIR="$HOME/certificates"
|
||||||
|
mkdir -p "$CERT_DIR"
|
||||||
|
|
||||||
|
# Decode the certificate to a p12 file
|
||||||
|
echo "$CERTIFICATE_BASE64" | base64 --decode > "$CERT_DIR/certificate.p12"
|
||||||
|
|
||||||
|
# Create keychain
|
||||||
|
KEYCHAIN_PATH="$CERT_DIR/app-signing.keychain-db"
|
||||||
|
KEYCHAIN_PASSWORD="temppassword123"
|
||||||
|
|
||||||
|
# Delete existing keychain if it exists
|
||||||
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create new keychain
|
||||||
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
security set-keychain-settings -t 3600 -u -l "$KEYCHAIN_PATH"
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Add to search list and make default
|
||||||
|
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
||||||
|
security default-keychain -s "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Import certificate
|
||||||
|
echo "🔑 Importing developer certificate..."
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||||
|
|
||||||
|
# Try with additional parameters if needed
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12 || true
|
||||||
|
|
||||||
|
# Set partition list for codesign to access keychain
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Verify certificate
|
||||||
|
echo "🔍 Verifying code signing identities..."
|
||||||
|
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Try to use the System keychain as a fallback
|
||||||
|
echo "🔍 Checking system keychain for code signing identities..."
|
||||||
|
SYSTEM_IDENTITIES=$(security find-identity -v -p codesigning)
|
||||||
|
echo "$SYSTEM_IDENTITIES"
|
||||||
|
|
||||||
|
if echo "$SYSTEM_IDENTITIES" | grep -q "Developer ID Application"; then
|
||||||
|
echo "✅ Found Developer ID Application certificate in system keychain"
|
||||||
|
echo "::set-output name=use_system_cert::true"
|
||||||
|
else
|
||||||
|
echo "::set-output name=use_system_cert::false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store keychain variables for later steps
|
||||||
|
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
|
||||||
|
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
|
||||||
|
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -f "$CERT_DIR/certificate.p12"
|
||||||
|
|
||||||
|
- name: Sign App
|
||||||
|
id: sign
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "🔏 Signing app with Developer ID certificate..."
|
||||||
|
|
||||||
|
# Check if app path exists
|
||||||
|
if [ ! -d "${{ inputs.app-path }}" ]; then
|
||||||
|
echo "❌ App bundle not found at ${{ inputs.app-path }}"
|
||||||
|
echo "::set-output name=signed::none"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if entitlements file exists
|
||||||
|
if [ ! -f "${{ inputs.entitlements-file }}" ]; then
|
||||||
|
echo "❌ Entitlements file not found at ${{ inputs.entitlements-file }}"
|
||||||
|
echo "::set-output name=signed::none"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Decide which keychain to use
|
||||||
|
if [ "${{ steps.setup-cert.outputs.use_system_cert }}" = "true" ]; then
|
||||||
|
echo "Using system keychain identity"
|
||||||
|
# Get certificate hash instead of name to avoid ambiguity
|
||||||
|
IDENTITY_HASH=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | awk '{print $2}')
|
||||||
|
echo "Using certificate hash: $IDENTITY_HASH"
|
||||||
|
else
|
||||||
|
# Make sure keychain is unlocked
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
echo "Using custom keychain identity"
|
||||||
|
# Get certificate hash instead of name to avoid ambiguity
|
||||||
|
IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}')
|
||||||
|
echo "Using certificate hash: $IDENTITY_HASH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$IDENTITY_HASH" ]; then
|
||||||
|
echo "❌ No valid Developer ID Application certificate found"
|
||||||
|
|
||||||
|
if [ "${{ inputs.fallback-to-adhoc }}" = "true" ]; then
|
||||||
|
echo "Falling back to ad-hoc signing for testing..."
|
||||||
|
# Use ad-hoc identity as fallback
|
||||||
|
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign - --timestamp "${{ inputs.app-path }}"
|
||||||
|
echo "::set-output name=signed::adhoc"
|
||||||
|
else
|
||||||
|
echo "Skipping signing. Set fallback-to-adhoc=true to use ad-hoc signing instead."
|
||||||
|
echo "::set-output name=signed::none"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH"
|
||||||
|
|
||||||
|
# Enhanced deep recursive signing for all binaries
|
||||||
|
echo "🔍 Performing deep recursive signing of all components..."
|
||||||
|
|
||||||
|
# First, find all .dylib files and sign them individually
|
||||||
|
echo "Signing all dynamic libraries (.dylib files)..."
|
||||||
|
find "${{ inputs.app-path }}" -name "*.dylib" | while read -r dylib; do
|
||||||
|
echo "Signing: $dylib"
|
||||||
|
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$dylib" || echo "⚠️ Failed to sign: $dylib"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Sign all .so files
|
||||||
|
echo "Signing all shared objects (.so files)..."
|
||||||
|
find "${{ inputs.app-path }}" -name "*.so" | while read -r so; do
|
||||||
|
echo "Signing: $so"
|
||||||
|
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$so" || echo "⚠️ Failed to sign: $so"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Sign all executable files (files with execute permission)
|
||||||
|
echo "Signing all executable files..."
|
||||||
|
find "${{ inputs.app-path }}" -type f -perm +111 -not -path "*.framework/*" -not -name "*.dylib" -not -name "*.so" | while read -r exe; do
|
||||||
|
echo "Signing executable: $exe"
|
||||||
|
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$exe" || echo "⚠️ Failed to sign: $exe"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Sign all frameworks
|
||||||
|
echo "Signing frameworks..."
|
||||||
|
find "${{ inputs.app-path }}" -path "*.framework" -type d | while read -r framework; do
|
||||||
|
echo "Signing framework: $framework"
|
||||||
|
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$framework" || echo "⚠️ Failed to sign: $framework"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Special handling for CrashReportClient.app
|
||||||
|
CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1)
|
||||||
|
if [ -n "$CRASH_REPORTER" ]; then
|
||||||
|
echo "🔍 Special handling for CrashReportClient.app: $CRASH_REPORTER"
|
||||||
|
# Sign CrashReportClient.app specifically with focus on hardened runtime
|
||||||
|
find "$CRASH_REPORTER" -type f -perm +111 | while read -r crash_bin; do
|
||||||
|
echo "Signing CrashReportClient binary: $crash_bin"
|
||||||
|
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$crash_bin" || echo "⚠️ Failed to sign: $crash_bin"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Signing the CrashReportClient.app bundle itself..."
|
||||||
|
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$CRASH_REPORTER" || echo "⚠️ Failed to sign CrashReportClient.app"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sign any other nested app bundles
|
||||||
|
find "${{ inputs.app-path }}" -path "*.app" -type d | grep -v CrashReportClient | while read -r nested_app; do
|
||||||
|
if [ "$nested_app" != "${{ inputs.app-path }}" ]; then
|
||||||
|
echo "Signing nested app: $nested_app"
|
||||||
|
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$nested_app" || echo "⚠️ Failed to sign: $nested_app"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Final signing of the main bundle
|
||||||
|
echo "🔐 Performing final signing of the main app bundle..."
|
||||||
|
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "${{ inputs.app-path }}"
|
||||||
|
echo "::set-output name=signed::identity"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify signing
|
||||||
|
echo "🔍 Verifying signature..."
|
||||||
|
codesign -vvv --deep --strict "${{ inputs.app-path }}"
|
||||||
|
|
||||||
|
# Check entitlements
|
||||||
|
echo "🔍 Checking entitlements..."
|
||||||
|
codesign -d --entitlements - "${{ inputs.app-path }}"
|
||||||
|
|
||||||
|
# Verify CrashReportClient
|
||||||
|
CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1)
|
||||||
|
if [ -n "$CRASH_REPORTER" ]; then
|
||||||
|
echo "🔍 Verifying CrashReportClient signature..."
|
||||||
|
codesign -vvv --deep --strict "$CRASH_REPORTER" || echo "⚠️ CrashReportClient may have verification issues"
|
||||||
|
|
||||||
|
echo "CrashReportClient entitlements:"
|
||||||
|
codesign -d --entitlements - "$CRASH_REPORTER" || echo "⚠️ Could not display CrashReportClient entitlements"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Notarize App
|
||||||
|
id: notarize
|
||||||
|
if: steps.sign.outputs.signed != 'none'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
APPLE_ID: ${{ inputs.notary-user }}
|
||||||
|
APP_PASSWORD: ${{ inputs.notary-password }}
|
||||||
|
API_KEY_ID: ${{ inputs.notary-api-key-id }}
|
||||||
|
API_ISSUER_ID: ${{ inputs.notary-api-key-issuer-id }}
|
||||||
|
API_KEY_PATH: ${{ inputs.notary-api-key-path }}
|
||||||
|
run: |
|
||||||
|
echo "📤 Notarizing app..."
|
||||||
|
|
||||||
|
# Set default output
|
||||||
|
echo "::set-output name=notarized::false"
|
||||||
|
|
||||||
|
# Get app name for zip file naming
|
||||||
|
APP_NAME=$(basename "${{ inputs.app-path }}" .app)
|
||||||
|
BUNDLE_ID="${{ inputs.bundle-id }}"
|
||||||
|
|
||||||
|
# If bundle ID is not provided, try to extract from Info.plist
|
||||||
|
if [ -z "$BUNDLE_ID" ]; then
|
||||||
|
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
|
||||||
|
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist")
|
||||||
|
echo "Extracted bundle ID: $BUNDLE_ID"
|
||||||
|
else
|
||||||
|
BUNDLE_ID="com.luckyrobots.app"
|
||||||
|
echo "Using default bundle ID: $BUNDLE_ID"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we're using API key notarization method
|
||||||
|
if [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then
|
||||||
|
echo "Using App Store Connect API key for notarization..."
|
||||||
|
|
||||||
|
# Create directory for API key if API_KEY_PATH contains content
|
||||||
|
mkdir -p ~/private_keys
|
||||||
|
|
||||||
|
# Check if API_KEY_PATH is a path or content
|
||||||
|
if [[ "$API_KEY_PATH" == /* ]] && [ -f "$API_KEY_PATH" ]; then
|
||||||
|
# It's a path to a file
|
||||||
|
echo "Using API key from path: $API_KEY_PATH"
|
||||||
|
cp "$API_KEY_PATH" ~/private_keys/AuthKey_${API_KEY_ID}.p8
|
||||||
|
else
|
||||||
|
# It contains the key content
|
||||||
|
echo "Using API key from content"
|
||||||
|
echo "$API_KEY_PATH" > ~/private_keys/AuthKey_${API_KEY_ID}.p8
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create zip for notarization
|
||||||
|
ZIP_PATH="${APP_NAME}-notarize.zip"
|
||||||
|
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_PATH"
|
||||||
|
|
||||||
|
echo "Submitting for notarization with API key..."
|
||||||
|
|
||||||
|
# First, submit without waiting for completion
|
||||||
|
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
|
||||||
|
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||||||
|
--key-id "$API_KEY_ID" \
|
||||||
|
--issuer "$API_ISSUER_ID" 2>&1)
|
||||||
|
SUBMIT_STATUS=$?
|
||||||
|
|
||||||
|
# Display output for debugging
|
||||||
|
echo "Notarization submission output:"
|
||||||
|
echo "$SUBMIT_OUTPUT"
|
||||||
|
echo "Submission exit status: $SUBMIT_STATUS"
|
||||||
|
|
||||||
|
# Check if submission was successful
|
||||||
|
if [ $SUBMIT_STATUS -ne 0 ]; then
|
||||||
|
echo "❌ Failed to submit for notarization. Exit code: $SUBMIT_STATUS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract submission ID for log retrieval
|
||||||
|
SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
if [ -z "$SUBMISSION_ID" ]; then
|
||||||
|
echo "❌ Could not extract submission ID from output. Notarization failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Submission ID: $SUBMISSION_ID"
|
||||||
|
echo "Waiting for notarization to complete..."
|
||||||
|
|
||||||
|
# Now wait for the processing to complete
|
||||||
|
COMPLETE=false
|
||||||
|
MAX_ATTEMPTS=60 # Maximum number of attempts (60 * 30 seconds = 30 minutes max)
|
||||||
|
ATTEMPT=1
|
||||||
|
|
||||||
|
while [ "$COMPLETE" = "false" ] && [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
||||||
|
echo "Checking notarization status (attempt $ATTEMPT of $MAX_ATTEMPTS)..."
|
||||||
|
|
||||||
|
INFO_OUTPUT=$(xcrun notarytool info "$SUBMISSION_ID" \
|
||||||
|
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||||||
|
--key-id "$API_KEY_ID" \
|
||||||
|
--issuer "$API_ISSUER_ID" 2>&1)
|
||||||
|
INFO_STATUS=$?
|
||||||
|
|
||||||
|
echo "Status check output:"
|
||||||
|
echo "$INFO_OUTPUT"
|
||||||
|
|
||||||
|
# Check if the notarization is complete
|
||||||
|
if echo "$INFO_OUTPUT" | grep -q "status: Accepted"; then
|
||||||
|
echo "✅ Notarization completed successfully!"
|
||||||
|
COMPLETE=true
|
||||||
|
FINAL_STATUS="Accepted"
|
||||||
|
elif echo "$INFO_OUTPUT" | grep -q "status: Invalid"; then
|
||||||
|
echo "❌ Notarization failed with status: Invalid"
|
||||||
|
COMPLETE=true
|
||||||
|
FINAL_STATUS="Invalid"
|
||||||
|
elif echo "$INFO_OUTPUT" | grep -q "status: Rejected"; then
|
||||||
|
echo "❌ Notarization failed with status: Rejected"
|
||||||
|
COMPLETE=true
|
||||||
|
FINAL_STATUS="Rejected"
|
||||||
|
else
|
||||||
|
echo "Notarization still in progress. Waiting 30 seconds before checking again..."
|
||||||
|
sleep 30
|
||||||
|
ATTEMPT=$((ATTEMPT + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Handle timeout
|
||||||
|
if [ "$COMPLETE" = "false" ]; then
|
||||||
|
echo "❌ Notarization timed out after $MAX_ATTEMPTS attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle completed notarization
|
||||||
|
if [ "$FINAL_STATUS" = "Accepted" ]; then
|
||||||
|
# Get logs for information (even though successful)
|
||||||
|
echo "📋 Getting notarization logs for information..."
|
||||||
|
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
|
||||||
|
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||||||
|
--key-id "$API_KEY_ID" \
|
||||||
|
--issuer "$API_ISSUER_ID" 2>&1)
|
||||||
|
|
||||||
|
echo "==== NOTARIZATION LOG SUMMARY ===="
|
||||||
|
echo "$LOGS_OUTPUT" | head -20
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Staple the notarization ticket
|
||||||
|
echo "Stapling notarization ticket..."
|
||||||
|
xcrun stapler staple -v "${{ inputs.app-path }}"
|
||||||
|
STAPLE_STATUS=$?
|
||||||
|
|
||||||
|
if [ $STAPLE_STATUS -eq 0 ]; then
|
||||||
|
echo "✅ Stapling completed successfully!"
|
||||||
|
|
||||||
|
# Verify the stapling worked properly
|
||||||
|
echo "Verifying stapled ticket is properly attached..."
|
||||||
|
xcrun stapler validate -v "${{ inputs.app-path }}"
|
||||||
|
|
||||||
|
# Check if stapling metadata is correctly stored in xattr
|
||||||
|
echo "Checking app extended attributes..."
|
||||||
|
if command -v xattr &> /dev/null; then
|
||||||
|
xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "⚠️ Warning: com.apple.provenance attribute not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add special instructions for distribution
|
||||||
|
echo "📋 IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings."
|
||||||
|
echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files."
|
||||||
|
echo "For proper distribution, consider the following options:"
|
||||||
|
echo "1. Use a DMG installer with a signed, notarized app inside"
|
||||||
|
echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)"
|
||||||
|
echo "3. If distributing directly, use a distribution platform that preserves notarization tickets"
|
||||||
|
else
|
||||||
|
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify notarization
|
||||||
|
echo "🔍 Verifying notarization..."
|
||||||
|
spctl --assess --verbose --type exec "${{ inputs.app-path }}"
|
||||||
|
|
||||||
|
echo "::set-output name=notarized::true"
|
||||||
|
else
|
||||||
|
# Get detailed logs for failed notarization
|
||||||
|
echo "📋 Fetching detailed logs for submission ID: $SUBMISSION_ID"
|
||||||
|
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
|
||||||
|
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||||||
|
--key-id "$API_KEY_ID" \
|
||||||
|
--issuer "$API_ISSUER_ID" 2>&1)
|
||||||
|
|
||||||
|
echo "==== DETAILED NOTARIZATION LOGS ===="
|
||||||
|
echo "$LOGS_OUTPUT"
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Extract specific issues for easier debugging
|
||||||
|
echo "🔍 Extracting specific issues from logs..."
|
||||||
|
echo "$LOGS_OUTPUT" | grep -A 3 "issues"
|
||||||
|
|
||||||
|
# Show current bundle ID in Info.plist
|
||||||
|
echo "📋 Current bundle ID information:"
|
||||||
|
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
|
||||||
|
echo "Info.plist content for bundle ID:"
|
||||||
|
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist"
|
||||||
|
echo "Full Info.plist excerpt:"
|
||||||
|
plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle
|
||||||
|
else
|
||||||
|
echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for mismatched bundle ID
|
||||||
|
if [ "$BUNDLE_ID" != "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null)" ]; then
|
||||||
|
echo "⚠️ WARNING: Bundle ID mismatch detected between workflow and app!"
|
||||||
|
echo " - Workflow/input bundle ID: $BUNDLE_ID"
|
||||||
|
echo " - Actual app bundle ID: $(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null || echo "Could not read")"
|
||||||
|
echo "This mismatch could cause notarization problems."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for code signature issues in internal components
|
||||||
|
echo "🔍 Checking for code signature issues in app components..."
|
||||||
|
find "${{ inputs.app-path }}" -type f -name "*.dylib" -o -name "*.so" | head -5 | while read -r lib; do
|
||||||
|
echo "Checking signature on: $lib"
|
||||||
|
codesign -vvv "$lib" || echo "⚠️ Signature issue with: $lib"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "❌ Notarization failed with status: $FINAL_STATUS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -rf ~/private_keys
|
||||||
|
|
||||||
|
# Fall back to App-specific password if requested
|
||||||
|
elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
|
||||||
|
echo "Using App-specific password for notarization..."
|
||||||
|
|
||||||
|
# Create zip for notarization
|
||||||
|
ZIP_PATH="${APP_NAME}-notarize.zip"
|
||||||
|
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_PATH"
|
||||||
|
|
||||||
|
echo "Submitting for notarization..."
|
||||||
|
|
||||||
|
# First, submit without waiting for completion
|
||||||
|
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
|
||||||
|
--apple-id "$APPLE_ID" \
|
||||||
|
--password "$APP_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" 2>&1)
|
||||||
|
SUBMIT_STATUS=$?
|
||||||
|
|
||||||
|
# Display output for debugging
|
||||||
|
echo "Notarization submission output:"
|
||||||
|
echo "$SUBMIT_OUTPUT"
|
||||||
|
echo "Submission exit status: $SUBMIT_STATUS"
|
||||||
|
|
||||||
|
# Check if submission was successful
|
||||||
|
if [ $SUBMIT_STATUS -ne 0 ]; then
|
||||||
|
echo "❌ Failed to submit for notarization. Exit code: $SUBMIT_STATUS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract submission ID for log retrieval
|
||||||
|
SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
if [ -z "$SUBMISSION_ID" ]; then
|
||||||
|
echo "❌ Could not extract submission ID from output. Notarization failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Submission ID: $SUBMISSION_ID"
|
||||||
|
echo "Waiting for notarization to complete..."
|
||||||
|
|
||||||
|
# Now wait for the processing to complete
|
||||||
|
COMPLETE=false
|
||||||
|
MAX_ATTEMPTS=60 # Maximum number of attempts (60 * 30 seconds = 30 minutes max)
|
||||||
|
ATTEMPT=1
|
||||||
|
|
||||||
|
while [ "$COMPLETE" = "false" ] && [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
||||||
|
echo "Checking notarization status (attempt $ATTEMPT of $MAX_ATTEMPTS)..."
|
||||||
|
|
||||||
|
INFO_OUTPUT=$(xcrun notarytool info "$SUBMISSION_ID" \
|
||||||
|
--apple-id "$APPLE_ID" \
|
||||||
|
--password "$APP_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" 2>&1)
|
||||||
|
INFO_STATUS=$?
|
||||||
|
|
||||||
|
echo "Status check output:"
|
||||||
|
echo "$INFO_OUTPUT"
|
||||||
|
|
||||||
|
# Check if the notarization is complete
|
||||||
|
if echo "$INFO_OUTPUT" | grep -q "status: Accepted"; then
|
||||||
|
echo "✅ Notarization completed successfully!"
|
||||||
|
COMPLETE=true
|
||||||
|
FINAL_STATUS="Accepted"
|
||||||
|
elif echo "$INFO_OUTPUT" | grep -q "status: Invalid"; then
|
||||||
|
echo "❌ Notarization failed with status: Invalid"
|
||||||
|
COMPLETE=true
|
||||||
|
FINAL_STATUS="Invalid"
|
||||||
|
elif echo "$INFO_OUTPUT" | grep -q "status: Rejected"; then
|
||||||
|
echo "❌ Notarization failed with status: Rejected"
|
||||||
|
COMPLETE=true
|
||||||
|
FINAL_STATUS="Rejected"
|
||||||
|
else
|
||||||
|
echo "Notarization still in progress. Waiting 30 seconds before checking again..."
|
||||||
|
sleep 30
|
||||||
|
ATTEMPT=$((ATTEMPT + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Handle timeout
|
||||||
|
if [ "$COMPLETE" = "false" ]; then
|
||||||
|
echo "❌ Notarization timed out after $MAX_ATTEMPTS attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle completed notarization
|
||||||
|
if [ "$FINAL_STATUS" = "Accepted" ]; then
|
||||||
|
# Get logs for information (even though successful)
|
||||||
|
echo "📋 Getting notarization logs for information..."
|
||||||
|
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
|
||||||
|
--apple-id "$APPLE_ID" \
|
||||||
|
--password "$APP_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" 2>&1)
|
||||||
|
|
||||||
|
echo "==== NOTARIZATION LOG SUMMARY ===="
|
||||||
|
echo "$LOGS_OUTPUT" | head -20
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Staple the notarization ticket
|
||||||
|
echo "Stapling notarization ticket..."
|
||||||
|
xcrun stapler staple -v "${{ inputs.app-path }}"
|
||||||
|
STAPLE_STATUS=$?
|
||||||
|
|
||||||
|
if [ $STAPLE_STATUS -eq 0 ]; then
|
||||||
|
echo "✅ Stapling completed successfully!"
|
||||||
|
|
||||||
|
# Verify the stapling worked properly
|
||||||
|
echo "Verifying stapled ticket is properly attached..."
|
||||||
|
xcrun stapler validate -v "${{ inputs.app-path }}"
|
||||||
|
|
||||||
|
# Check if stapling metadata is correctly stored in xattr
|
||||||
|
echo "Checking app extended attributes..."
|
||||||
|
if command -v xattr &> /dev/null; then
|
||||||
|
xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "⚠️ Warning: com.apple.provenance attribute not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add special instructions for distribution
|
||||||
|
echo "📋 IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings."
|
||||||
|
echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files."
|
||||||
|
echo "For proper distribution, consider the following options:"
|
||||||
|
echo "1. Use a DMG installer with a signed, notarized app inside"
|
||||||
|
echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)"
|
||||||
|
echo "3. If distributing directly, use a distribution platform that preserves notarization tickets"
|
||||||
|
else
|
||||||
|
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify notarization
|
||||||
|
echo "🔍 Verifying notarization..."
|
||||||
|
spctl --assess --verbose --type exec "${{ inputs.app-path }}"
|
||||||
|
|
||||||
|
echo "::set-output name=notarized::true"
|
||||||
|
else
|
||||||
|
# Get detailed logs for failed notarization
|
||||||
|
echo "📋 Fetching detailed logs for submission ID: $SUBMISSION_ID"
|
||||||
|
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
|
||||||
|
--apple-id "$APPLE_ID" \
|
||||||
|
--password "$APP_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" 2>&1)
|
||||||
|
|
||||||
|
echo "==== DETAILED NOTARIZATION LOGS ===="
|
||||||
|
echo "$LOGS_OUTPUT"
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Extract specific issues for easier debugging
|
||||||
|
echo "🔍 Extracting specific issues from logs..."
|
||||||
|
echo "$LOGS_OUTPUT" | grep -A 3 "issues"
|
||||||
|
|
||||||
|
# Show current bundle ID in Info.plist
|
||||||
|
echo "📋 Current bundle ID information:"
|
||||||
|
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
|
||||||
|
echo "Info.plist content for bundle ID:"
|
||||||
|
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist"
|
||||||
|
echo "Full Info.plist excerpt:"
|
||||||
|
plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle
|
||||||
|
else
|
||||||
|
echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "❌ Notarization failed with status: $FINAL_STATUS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ Missing notarization credentials. Skipping notarization."
|
||||||
|
echo "For App Store Connect API key method, set these inputs:"
|
||||||
|
echo " - notarization-method: api-key"
|
||||||
|
echo " - notary-api-key-id: Your API key ID"
|
||||||
|
echo " - notary-api-key-issuer-id: Your API issuer ID"
|
||||||
|
echo " - notary-api-key-path: Path to or content of your p8 file"
|
||||||
|
echo ""
|
||||||
|
echo "For App-specific password method, set these inputs:"
|
||||||
|
echo " - notarization-method: app-password"
|
||||||
|
echo " - notary-user: Your Apple ID (email)"
|
||||||
|
echo " - notary-password: Your app-specific password"
|
||||||
|
echo " - team-id: Your Apple Developer team ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Package App
|
||||||
|
id: package
|
||||||
|
if: steps.sign.outputs.signed != 'none'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "📦 Packaging signed app..."
|
||||||
|
|
||||||
|
# Get app name for zip file naming
|
||||||
|
APP_NAME=$(basename "${{ inputs.app-path }}" .app)
|
||||||
|
|
||||||
|
if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then
|
||||||
|
PACKAGE_SUFFIX="Signed-Notarized"
|
||||||
|
echo "Creating distribution package with notarized app..."
|
||||||
|
else
|
||||||
|
PACKAGE_SUFFIX="Signed"
|
||||||
|
echo "Creating distribution package with signed app..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create zip package
|
||||||
|
ZIP_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.zip"
|
||||||
|
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_FILE"
|
||||||
|
echo "✅ Created ZIP package: $ZIP_FILE"
|
||||||
|
|
||||||
|
# Check if we can create DMG (hdiutil is available)
|
||||||
|
if command -v hdiutil &> /dev/null; then
|
||||||
|
# Create DMG package (much better for distribution)
|
||||||
|
DMG_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.dmg"
|
||||||
|
echo "Creating DMG distribution package..."
|
||||||
|
|
||||||
|
# Create temporary folder for DMG contents
|
||||||
|
DMG_TMP_DIR=$(mktemp -d)
|
||||||
|
cp -R "${{ inputs.app-path }}" "$DMG_TMP_DIR/"
|
||||||
|
|
||||||
|
# Optional: Add README or instructions
|
||||||
|
echo "# Installation Instructions\n\nDrag the application to your Applications folder to install." > "$DMG_TMP_DIR/README.txt"
|
||||||
|
|
||||||
|
# Create DMG file with the app
|
||||||
|
hdiutil create -volname "${APP_NAME}" -srcfolder "$DMG_TMP_DIR" -ov -format UDZO "$DMG_FILE"
|
||||||
|
|
||||||
|
if [ -f "$DMG_FILE" ]; then
|
||||||
|
echo "✅ Created DMG package: $DMG_FILE"
|
||||||
|
|
||||||
|
# Sign the DMG with the same certificate used for the app
|
||||||
|
echo "Signing DMG file..."
|
||||||
|
|
||||||
|
# Decide which keychain to use
|
||||||
|
if [ "${{ steps.setup-cert.outputs.use_system_cert }}" = "true" ]; then
|
||||||
|
echo "Using system keychain identity"
|
||||||
|
# Get certificate hash instead of name to avoid ambiguity
|
||||||
|
IDENTITY_HASH=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | awk '{print $2}')
|
||||||
|
echo "Using certificate hash: $IDENTITY_HASH"
|
||||||
|
else
|
||||||
|
# Make sure keychain is unlocked
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
echo "Using custom keychain identity"
|
||||||
|
# Get certificate hash instead of name to avoid ambiguity
|
||||||
|
IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}')
|
||||||
|
echo "Using certificate hash: $IDENTITY_HASH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$IDENTITY_HASH" ]; then
|
||||||
|
# Sign the DMG
|
||||||
|
codesign --force --sign "$IDENTITY_HASH" --options runtime --timestamp "$DMG_FILE"
|
||||||
|
|
||||||
|
# Verify DMG signature
|
||||||
|
echo "Verifying DMG signature..."
|
||||||
|
codesign -vvv "$DMG_FILE"
|
||||||
|
|
||||||
|
# Only notarize DMG if the app was successfully notarized
|
||||||
|
if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then
|
||||||
|
echo "Notarizing DMG file..."
|
||||||
|
|
||||||
|
# Check if we're using API key notarization method
|
||||||
|
if [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then
|
||||||
|
# Use the same API key setup from the notarize step
|
||||||
|
echo "Using App Store Connect API key for DMG notarization..."
|
||||||
|
|
||||||
|
echo "Submitting DMG for notarization..."
|
||||||
|
DMG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \
|
||||||
|
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||||||
|
--key-id "$API_KEY_ID" \
|
||||||
|
--issuer "$API_ISSUER_ID" --wait 2>&1)
|
||||||
|
|
||||||
|
echo "DMG notarization submission output:"
|
||||||
|
echo "$DMG_SUBMIT_OUTPUT"
|
||||||
|
|
||||||
|
# Extract DMG submission ID
|
||||||
|
DMG_SUBMISSION_ID=$(echo "$DMG_SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
if [ -n "$DMG_SUBMISSION_ID" ] && echo "$DMG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then
|
||||||
|
echo "✅ DMG notarization completed successfully!"
|
||||||
|
|
||||||
|
# Staple the DMG
|
||||||
|
echo "Stapling notarization ticket to DMG..."
|
||||||
|
xcrun stapler staple "$DMG_FILE"
|
||||||
|
|
||||||
|
# Verify DMG stapling
|
||||||
|
echo "Verifying DMG stapling..."
|
||||||
|
xcrun stapler validate "$DMG_FILE"
|
||||||
|
|
||||||
|
echo "DMG is now fully signed, notarized, and stapled!"
|
||||||
|
else
|
||||||
|
echo "⚠️ DMG notarization may have failed or is still in progress."
|
||||||
|
echo "The app itself is still properly notarized, but the DMG may need manual verification."
|
||||||
|
fi
|
||||||
|
elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
|
||||||
|
# Use App-specific password for DMG notarization
|
||||||
|
echo "Using App-specific password for DMG notarization..."
|
||||||
|
|
||||||
|
echo "Submitting DMG for notarization..."
|
||||||
|
DMG_SUBMIT_OUTPUT=$(xcrun notarytool submit "$DMG_FILE" \
|
||||||
|
--apple-id "$APPLE_ID" \
|
||||||
|
--password "$APP_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" --wait 2>&1)
|
||||||
|
|
||||||
|
echo "DMG notarization submission output:"
|
||||||
|
echo "$DMG_SUBMIT_OUTPUT"
|
||||||
|
|
||||||
|
# Extract DMG submission ID
|
||||||
|
DMG_SUBMISSION_ID=$(echo "$DMG_SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
if [ -n "$DMG_SUBMISSION_ID" ] && echo "$DMG_SUBMIT_OUTPUT" | grep -q "status: Accepted"; then
|
||||||
|
echo "✅ DMG notarization completed successfully!"
|
||||||
|
|
||||||
|
# Staple the DMG
|
||||||
|
echo "Stapling notarization ticket to DMG..."
|
||||||
|
xcrun stapler staple "$DMG_FILE"
|
||||||
|
|
||||||
|
# Verify DMG stapling
|
||||||
|
echo "Verifying DMG stapling..."
|
||||||
|
xcrun stapler validate "$DMG_FILE"
|
||||||
|
|
||||||
|
echo "DMG is now fully signed, notarized, and stapled!"
|
||||||
|
else
|
||||||
|
echo "⚠️ DMG notarization may have failed or is still in progress."
|
||||||
|
echo "The app itself is still properly notarized, but the DMG may need manual verification."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ DMG not notarized due to missing credentials."
|
||||||
|
echo "The app itself is properly notarized, but the DMG is only signed."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "App was not notarized, skipping DMG notarization."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ No valid identity found for DMG signing. DMG will be created but not signed."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use DMG as the primary package if available
|
||||||
|
echo "::set-output name=package-path::$DMG_FILE"
|
||||||
|
echo "::set-output name=zip-package-path::$ZIP_FILE"
|
||||||
|
else
|
||||||
|
echo "⚠️ Failed to create DMG, falling back to ZIP package"
|
||||||
|
echo "::set-output name=package-path::$ZIP_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up temp directory
|
||||||
|
rm -rf "$DMG_TMP_DIR"
|
||||||
|
else
|
||||||
|
echo "hdiutil not available, skipping DMG creation"
|
||||||
|
echo "::set-output name=package-path::$ZIP_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "🧹 Cleaning up..."
|
||||||
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||||||
|
rm -f *-notarize.zip || true
|
||||||
|
echo "✅ Cleanup complete"
|
42
.gitea/actions/windows-build/action.yml
Normal file
42
.gitea/actions/windows-build/action.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: 'Windows Build Steps'
|
||||||
|
description: 'Build Windows application'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
# Set environment variables for Unreal Engine
|
||||||
|
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Create directories for builds
|
||||||
|
if (!(Test-Path "Builds/Windows")) { New-Item -ItemType Directory -Path "Builds/Windows" -Force }
|
||||||
|
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Build for Windows
|
||||||
|
run: |
|
||||||
|
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
|
||||||
|
& 'C:\Program Files\Git\bin\bash.exe' -c "./scripts/win_build.sh"
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Package Windows build
|
||||||
|
run: |
|
||||||
|
echo "Packaging Windows build..."
|
||||||
|
if [ -d "Builds/Windows" ]; then
|
||||||
|
cd Builds/Windows
|
||||||
|
zip -r ../../PackagedReleases/LuckyRobots-Windows.zip .
|
||||||
|
cd ../..
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Packaged Windows release ==="
|
||||||
|
ls -la PackagedReleases/
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Windows Build Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: success() && hashFiles('PackagedReleases/LuckyRobots-Windows.zip') != ''
|
||||||
|
with:
|
||||||
|
name: LuckyRobots-Windows
|
||||||
|
path: PackagedReleases/LuckyRobots-Windows.zip
|
||||||
|
retention-days: 365
|
209
.gitea/workflows/build.yml
Normal file
209
.gitea/workflows/build.yml
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
name: Unreal Engine Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# push:
|
||||||
|
# branches: [ozgur/build]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows-build:
|
||||||
|
runs-on: windows
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build Windows
|
||||||
|
uses: ./.gitea/actions/windows-build
|
||||||
|
|
||||||
|
linux-build:
|
||||||
|
runs-on: windows
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build Linux
|
||||||
|
uses: ./.gitea/actions/linux-build
|
||||||
|
|
||||||
|
macos-build:
|
||||||
|
runs-on: macos
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build macOS
|
||||||
|
uses: ./.gitea/actions/macos-build
|
||||||
|
with:
|
||||||
|
apple_team_id: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
apple_certificate_base64: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
|
apple_certificate_password: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||||
|
api_key_path: ${{ secrets.NOTARY_API_KEY_PATH }}
|
||||||
|
api_key_id: ${{ secrets.NOTARY_API_KEY_ID }}
|
||||||
|
api_key_issuer_id: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: [windows-build, linux-build, macos-build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Create Tag
|
||||||
|
run: |
|
||||||
|
# Fetch all tags
|
||||||
|
git fetch --tags
|
||||||
|
|
||||||
|
# Get the latest version tag, if any
|
||||||
|
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
|
||||||
|
|
||||||
|
if [ -z "$LATEST_TAG" ]; then
|
||||||
|
# No previous version tag, start with 1.0.0
|
||||||
|
NEW_VERSION="1.0.0"
|
||||||
|
echo "No previous version tags found, starting with 1.0.0"
|
||||||
|
else
|
||||||
|
# Strip 'v' prefix if it exists
|
||||||
|
VERSION=${LATEST_TAG#v}
|
||||||
|
|
||||||
|
# Split version into parts
|
||||||
|
MAJOR=$(echo $VERSION | cut -d. -f1)
|
||||||
|
MINOR=$(echo $VERSION | cut -d. -f2)
|
||||||
|
PATCH=$(echo $VERSION | cut -d. -f3)
|
||||||
|
|
||||||
|
# Auto-increment patch version
|
||||||
|
PATCH=$((PATCH + 1))
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||||
|
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final tag with v prefix
|
||||||
|
TAG="v${NEW_VERSION}"
|
||||||
|
echo "Creating git tag: $TAG"
|
||||||
|
|
||||||
|
# Configure git with token authentication
|
||||||
|
git config --global user.email "actions@gitea.com"
|
||||||
|
git config --global user.name "Gitea Actions"
|
||||||
|
|
||||||
|
# Direct token approach
|
||||||
|
git remote set-url origin "https://runner:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
|
||||||
|
|
||||||
|
# Check if tag exists
|
||||||
|
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||||
|
# Create tag
|
||||||
|
git tag -a "$TAG" -m "Release $TAG"
|
||||||
|
|
||||||
|
# Push tag
|
||||||
|
git push origin "$TAG"
|
||||||
|
echo "Successfully created and pushed tag: $TAG"
|
||||||
|
else
|
||||||
|
echo "Tag $TAG already exists, skipping tag creation"
|
||||||
|
fi
|
||||||
|
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: releases
|
||||||
|
|
||||||
|
- name: Create Build Info
|
||||||
|
run: |
|
||||||
|
# Create a build info JSON file
|
||||||
|
echo '{
|
||||||
|
"version": "${{ env.RELEASE_TAG }}",
|
||||||
|
"buildNumber": "${{ github.run_number }}",
|
||||||
|
"commit": "${{ github.sha }}",
|
||||||
|
"branch": "${{ github.ref_name }}",
|
||||||
|
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
|
||||||
|
"artifacts": {
|
||||||
|
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
|
||||||
|
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux",
|
||||||
|
"macos": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS"
|
||||||
|
}
|
||||||
|
}' > build-info.json
|
||||||
|
|
||||||
|
# Create a simple HTML download page
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
.download-btn {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 5px;
|
||||||
|
}
|
||||||
|
.download-btn:hover { background-color: #45a049; }
|
||||||
|
.platform { margin-bottom: 30px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
|
||||||
|
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
|
||||||
|
|
||||||
|
<div class="platform">
|
||||||
|
<h2>Windows</h2>
|
||||||
|
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="platform">
|
||||||
|
<h2>Linux</h2>
|
||||||
|
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="platform">
|
||||||
|
<h2>macOS</h2>
|
||||||
|
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS" class="download-btn">Download macOS Build</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>' > downloads.html
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: https://gitea.com/actions/gitea-release-action@main
|
||||||
|
with:
|
||||||
|
files: |-
|
||||||
|
build-info.json
|
||||||
|
downloads.html
|
||||||
|
token: '${{ secrets.GITEATOKEN }}'
|
||||||
|
title: 'Release ${{ env.RELEASE_TAG }}'
|
||||||
|
body: |
|
||||||
|
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
|
||||||
|
|
||||||
|
### Download Links
|
||||||
|
|
||||||
|
Download builds from our CI artifacts:
|
||||||
|
|
||||||
|
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
|
||||||
|
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
|
||||||
|
- [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
|
||||||
|
|
||||||
|
### Build Information
|
||||||
|
|
||||||
|
- Build Number: #${{ github.run_number }}
|
||||||
|
- Commit: ${{ github.sha }}
|
||||||
|
- Branch: ${{ github.ref_name }}
|
||||||
|
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
tag_name: '${{ env.RELEASE_TAG }}'
|
||||||
|
|
@ -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 }}'
|
|
519
.gitea/workflows/macos-build.yml
Normal file
519
.gitea/workflows/macos-build.yml
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
name: macOS Build, Sign and Notarize
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Manuel tetikleme
|
||||||
|
# push:
|
||||||
|
# branches: [ozgur/build]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-sign-notarize:
|
||||||
|
runs-on: macos
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
# Çalışma dizini yolunu al
|
||||||
|
WORKSPACE_DIR="$(pwd)"
|
||||||
|
echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> "$GITHUB_ENV"
|
||||||
|
echo "ENTITLEMENTS_FILE=LuckyWorld.entitlements" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# CI ortam değişkenini true olarak ayarla
|
||||||
|
echo "CI=true" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Build için gerekli dizinleri oluştur
|
||||||
|
mkdir -p Builds/Mac
|
||||||
|
mkdir -p PackagedReleases
|
||||||
|
mkdir -p ArchivedApps
|
||||||
|
|
||||||
|
echo "Environment setup complete"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup Certificate and Keychain
|
||||||
|
id: setup-cert
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
run: |
|
||||||
|
echo "🔐 Setting up certificate..."
|
||||||
|
|
||||||
|
# Create a temporary directory for certificates
|
||||||
|
CERT_DIR="$HOME/certificates"
|
||||||
|
mkdir -p "$CERT_DIR"
|
||||||
|
|
||||||
|
# Decode the certificate to a p12 file
|
||||||
|
echo "$CERTIFICATE_BASE64" | base64 --decode > "$CERT_DIR/certificate.p12"
|
||||||
|
|
||||||
|
# Create keychain
|
||||||
|
KEYCHAIN_PATH="$CERT_DIR/app-signing.keychain-db"
|
||||||
|
KEYCHAIN_PASSWORD="temppassword123"
|
||||||
|
|
||||||
|
# Delete existing keychain if it exists
|
||||||
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create new keychain
|
||||||
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
security set-keychain-settings -t 3600 -u -l "$KEYCHAIN_PATH"
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Add to search list and make default
|
||||||
|
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
||||||
|
security default-keychain -s "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Import certificate
|
||||||
|
echo "🔑 Importing developer certificate..."
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||||
|
|
||||||
|
# Try with additional parameters if needed
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12 || true
|
||||||
|
|
||||||
|
# Set partition list for codesign to access keychain
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Verify certificate
|
||||||
|
echo "🔍 Verifying code signing identities..."
|
||||||
|
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Try to use the System keychain as a fallback
|
||||||
|
echo "🔍 Checking system keychain for code signing identities..."
|
||||||
|
SYSTEM_IDENTITIES=$(security find-identity -v -p codesigning)
|
||||||
|
echo "$SYSTEM_IDENTITIES"
|
||||||
|
|
||||||
|
if echo "$SYSTEM_IDENTITIES" | grep -q "Developer ID Application"; then
|
||||||
|
echo "✅ Found Developer ID Application certificate in system keychain"
|
||||||
|
echo "::set-output name=use_system_cert::true"
|
||||||
|
else
|
||||||
|
echo "::set-output name=use_system_cert::false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store keychain variables for later steps
|
||||||
|
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
|
||||||
|
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
|
||||||
|
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -f "$CERT_DIR/certificate.p12"
|
||||||
|
|
||||||
|
- name: Build for macOS with UE5-Build-Project
|
||||||
|
id: build
|
||||||
|
uses: OrchidIsle/UE5-Build-Project@latest
|
||||||
|
with:
|
||||||
|
RUNUAT_PATH: ${{ github.workspace }}/Engine/Build/BatchFiles/RunUAT.sh
|
||||||
|
UPROJECT_PATH: ${{ github.workspace }}/LuckyWorld.uproject
|
||||||
|
BUILD_CONFIG: Development
|
||||||
|
PLATFORM: Mac
|
||||||
|
COOK: true
|
||||||
|
STAGE: true
|
||||||
|
PACKAGE: true
|
||||||
|
PAK: true
|
||||||
|
ARCHIVE: false
|
||||||
|
NULLRHI: true
|
||||||
|
|
||||||
|
- name: Find app bundle
|
||||||
|
id: find-app
|
||||||
|
run: |
|
||||||
|
# Add error handling
|
||||||
|
set +e # Don't exit immediately on error for this block
|
||||||
|
|
||||||
|
echo "Build status check..."
|
||||||
|
if [ ! -d "./Builds" ] && [ ! -d "./Saved/StagedBuilds" ]; then
|
||||||
|
echo "❌ ERROR: Build directories do not exist. Build likely failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# First check Saved/StagedBuilds directory - where Unreal often places built apps
|
||||||
|
echo "Checking Saved/StagedBuilds directory..."
|
||||||
|
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# If not found, check Builds directory
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "No app found in Saved/StagedBuilds, checking Builds directory..."
|
||||||
|
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If still not found, check the whole workspace
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "No app found in Builds, checking entire workspace..."
|
||||||
|
APP_PATHS=$(find . -type d -name "*.app" -not -path "*/\.*" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "❌ ERROR: Could not find any app bundles!"
|
||||||
|
echo "Listing all directories to help debug:"
|
||||||
|
find . -type d -maxdepth 3 | sort
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found potential app bundles:"
|
||||||
|
echo "$APP_PATHS"
|
||||||
|
|
||||||
|
# Use the first app path found (preferably the main app, not a child app)
|
||||||
|
MAIN_APP_PATH=$(echo "$APP_PATHS" | grep -v "CrashReportClient" | head -1 || echo "$APP_PATHS" | head -1)
|
||||||
|
|
||||||
|
echo "Using app bundle: $MAIN_APP_PATH"
|
||||||
|
|
||||||
|
# Make sure app exists - using local variable
|
||||||
|
if [ ! -d "$MAIN_APP_PATH" ]; then
|
||||||
|
echo "❌ ERROR: App bundle not found at $MAIN_APP_PATH!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export APP_PATH for next steps to use
|
||||||
|
echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Get bundle ID from Info.plist for reference (not modifying)
|
||||||
|
if [ -f "$MAIN_APP_PATH/Contents/Info.plist" ]; then
|
||||||
|
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$MAIN_APP_PATH/Contents/Info.plist")
|
||||||
|
echo "Detected bundle ID: $BUNDLE_ID"
|
||||||
|
echo "BUNDLE_ID=$BUNDLE_ID" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup App Store Connect API Key Directory
|
||||||
|
id: appstore-api-key
|
||||||
|
if: secrets.NOTARY_API_KEY_PATH != ''
|
||||||
|
run: |
|
||||||
|
echo "🔑 Setting up App Store Connect API Key..."
|
||||||
|
|
||||||
|
# Create directory for API key
|
||||||
|
mkdir -p ~/private_keys
|
||||||
|
|
||||||
|
# Write the API key to a file
|
||||||
|
echo "${{ secrets.NOTARY_API_KEY_PATH }}" > ~/private_keys/AuthKey_${{ secrets.NOTARY_API_KEY_ID }}.p8
|
||||||
|
|
||||||
|
echo "✅ API Key setup complete"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Sign and Notarize App
|
||||||
|
id: sign-notarize
|
||||||
|
uses: indygreg/apple-code-sign-action@v1
|
||||||
|
with:
|
||||||
|
input_path: ${{ env.APP_PATH }}
|
||||||
|
output_path: ${{ env.APP_PATH }}
|
||||||
|
p12_file: ${{ env.KEYCHAIN_PATH }}
|
||||||
|
p12_password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||||
|
notarize: true
|
||||||
|
staple: true
|
||||||
|
app_store_connect_api_issuer: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
|
||||||
|
app_store_connect_api_key: ${{ secrets.NOTARY_API_KEY_ID }}
|
||||||
|
entitlements_path: ${{ env.ENTITLEMENTS_FILE }}
|
||||||
|
|
||||||
|
- name: Update app path with signed version
|
||||||
|
if: steps.sign-notarize.outcome == 'success'
|
||||||
|
run: |
|
||||||
|
echo "APP_PATH=${{ steps.sign-notarize.outputs.output_path }}" >> $GITHUB_ENV
|
||||||
|
echo "✅ Updated APP_PATH to use signed app: ${{ steps.sign-notarize.outputs.output_path }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Create DMG Package
|
||||||
|
id: package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "📦 Creating DMG package..."
|
||||||
|
|
||||||
|
# Get app name for DMG file naming
|
||||||
|
APP_NAME=$(basename "${{ env.APP_PATH }}" .app)
|
||||||
|
|
||||||
|
# Create a DMG package
|
||||||
|
DMG_FILE="./PackagedReleases/${APP_NAME}-Signed-Notarized.dmg"
|
||||||
|
rm -f "$DMG_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create temporary folder for DMG contents
|
||||||
|
DMG_TMP_DIR=$(mktemp -d)
|
||||||
|
cp -R "${{ env.APP_PATH }}" "$DMG_TMP_DIR/"
|
||||||
|
|
||||||
|
# Create DMG file with the app
|
||||||
|
hdiutil create -volname "${APP_NAME}" -srcfolder "$DMG_TMP_DIR" -ov -format UDZO "$DMG_FILE"
|
||||||
|
|
||||||
|
if [ -f "$DMG_FILE" ]; then
|
||||||
|
echo "✅ Created DMG package: $DMG_FILE"
|
||||||
|
echo "Size: $(du -h "$DMG_FILE" | cut -f1)"
|
||||||
|
echo "DMG_PATH=$DMG_FILE" >> $GITHUB_ENV
|
||||||
|
echo "DMG_STATUS=success" >> $GITHUB_ENV
|
||||||
|
echo "DMG_SIZE=$(du -h "$DMG_FILE" | cut -f1)" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=dmg_created::true"
|
||||||
|
echo "::set-output name=dmg_path::$DMG_FILE"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to create DMG package"
|
||||||
|
echo "DMG_STATUS=failed" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=dmg_created::false"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up temporary directory
|
||||||
|
rm -rf "$DMG_TMP_DIR"
|
||||||
|
|
||||||
|
- name: Upload DMG Package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: env.DMG_PATH != ''
|
||||||
|
with:
|
||||||
|
name: LuckyWorld-macOS-Signed-Notarized
|
||||||
|
path: ${{ env.DMG_PATH }}
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Check Quarantine Attributes
|
||||||
|
id: quarantine
|
||||||
|
if: steps.sign-notarize.outcome == 'success'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "🔍 Checking quarantine attributes..."
|
||||||
|
|
||||||
|
# Check quarantine attributes on the app
|
||||||
|
if command -v xattr &> /dev/null; then
|
||||||
|
QUARANTINE=$(xattr -l "${{ env.APP_PATH }}" | grep -i "quarantine" || echo "None")
|
||||||
|
|
||||||
|
if [ "$QUARANTINE" == "None" ]; then
|
||||||
|
echo "✅ No quarantine attributes found (good)"
|
||||||
|
echo "QUARANTINE_STATUS=clean" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: Quarantine attributes found on the app:"
|
||||||
|
echo "$QUARANTINE"
|
||||||
|
echo "QUARANTINE_STATUS=present" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for provenance attribute (indicates successful notarization)
|
||||||
|
PROVENANCE=$(xattr -l "${{ env.APP_PATH }}" | grep -i "com.apple.provenance" || echo "None")
|
||||||
|
|
||||||
|
if [ "$PROVENANCE" != "None" ]; then
|
||||||
|
echo "✅ Provenance attribute found (indicates successful notarization)"
|
||||||
|
echo "PROVENANCE_STATUS=present" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: No provenance attribute found - notarization may not be properly attached"
|
||||||
|
echo "PROVENANCE_STATUS=missing" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ xattr command not available, can't check quarantine status"
|
||||||
|
echo "QUARANTINE_STATUS=unknown" >> $GITHUB_ENV
|
||||||
|
echo "PROVENANCE_STATUS=unknown" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If DMG exists, check it too
|
||||||
|
if [ -n "$DMG_PATH" ] && [ -f "$DMG_PATH" ]; then
|
||||||
|
if command -v xattr &> /dev/null; then
|
||||||
|
DMG_QUARANTINE=$(xattr -l "$DMG_PATH" | grep -i "quarantine" || echo "None")
|
||||||
|
|
||||||
|
if [ "$DMG_QUARANTINE" == "None" ]; then
|
||||||
|
echo "✅ No quarantine attributes found on DMG (good)"
|
||||||
|
echo "DMG_QUARANTINE_STATUS=clean" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: Quarantine attributes found on the DMG:"
|
||||||
|
echo "$DMG_QUARANTINE"
|
||||||
|
echo "DMG_QUARANTINE_STATUS=present" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Now mount the DMG and check the app inside
|
||||||
|
echo "🔍 Mounting DMG to check app inside..."
|
||||||
|
DMG_MOUNT_POINT=$(mktemp -d)
|
||||||
|
|
||||||
|
# Mount the DMG
|
||||||
|
hdiutil attach "$DMG_PATH" -mountpoint "$DMG_MOUNT_POINT" -nobrowse
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
# Find the app inside the DMG
|
||||||
|
DMG_APP_PATH=$(find "$DMG_MOUNT_POINT" -maxdepth 1 -name "*.app" | head -1)
|
||||||
|
|
||||||
|
if [ -n "$DMG_APP_PATH" ]; then
|
||||||
|
echo "Found app in DMG: $DMG_APP_PATH"
|
||||||
|
|
||||||
|
# Check quarantine attributes on the app inside DMG
|
||||||
|
DMG_APP_QUARANTINE=$(xattr -l "$DMG_APP_PATH" | grep -i "quarantine" || echo "None")
|
||||||
|
|
||||||
|
if [ "$DMG_APP_QUARANTINE" == "None" ]; then
|
||||||
|
echo "✅ No quarantine attributes found on app inside DMG (good)"
|
||||||
|
echo "DMG_APP_QUARANTINE_STATUS=clean" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: Quarantine attributes found on the app inside DMG:"
|
||||||
|
echo "$DMG_APP_QUARANTINE"
|
||||||
|
echo "DMG_APP_QUARANTINE_STATUS=present" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for provenance attribute on app inside DMG
|
||||||
|
DMG_APP_PROVENANCE=$(xattr -l "$DMG_APP_PATH" | grep -i "com.apple.provenance" || echo "None")
|
||||||
|
|
||||||
|
if [ "$DMG_APP_PROVENANCE" != "None" ]; then
|
||||||
|
echo "✅ Provenance attribute found on app inside DMG (indicates successful notarization)"
|
||||||
|
echo "DMG_APP_PROVENANCE_STATUS=present" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: No provenance attribute found on app inside DMG"
|
||||||
|
echo "DMG_APP_PROVENANCE_STATUS=missing" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run spctl to check Gatekeeper assessment on the app inside DMG
|
||||||
|
echo "🛡️ Checking Gatekeeper assessment on app inside DMG..."
|
||||||
|
SPCTL_RESULT=$(spctl --assess --verbose --type exec "$DMG_APP_PATH" 2>&1 || echo "Failed")
|
||||||
|
|
||||||
|
if echo "$SPCTL_RESULT" | grep -q "accepted"; then
|
||||||
|
echo "✅ App inside DMG passes Gatekeeper assessment"
|
||||||
|
echo "DMG_APP_GATEKEEPER_STATUS=accepted" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: App inside DMG may not pass Gatekeeper assessment:"
|
||||||
|
echo "$SPCTL_RESULT"
|
||||||
|
echo "DMG_APP_GATEKEEPER_STATUS=rejected" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ No app found inside DMG"
|
||||||
|
echo "DMG_APP_STATUS=missing" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unmount the DMG
|
||||||
|
hdiutil detach "$DMG_MOUNT_POINT" -force
|
||||||
|
rm -rf "$DMG_MOUNT_POINT"
|
||||||
|
else
|
||||||
|
echo "⚠️ Failed to mount DMG"
|
||||||
|
echo "DMG_MOUNT_STATUS=failed" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Status Report
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "📋 ========== macOS Build Status Report =========="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# App Info
|
||||||
|
if [ -n "${{ env.APP_PATH }}" ]; then
|
||||||
|
echo "🔍 Application Info:"
|
||||||
|
echo " Path: ${{ env.APP_PATH }}"
|
||||||
|
echo " Bundle ID: ${{ env.BUNDLE_ID || 'Unknown' }}"
|
||||||
|
|
||||||
|
if [ -f "${{ env.APP_PATH }}/Contents/Info.plist" ]; then
|
||||||
|
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "${{ env.APP_PATH }}/Contents/Info.plist" 2>/dev/null || echo "Unknown")
|
||||||
|
BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${{ env.APP_PATH }}/Contents/Info.plist" 2>/dev/null || echo "Unknown")
|
||||||
|
echo " Version: $VERSION (Build $BUILD)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "❌ No application found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Code Signing Status
|
||||||
|
echo "🔏 Code Signing Status:"
|
||||||
|
if [ "${{ steps.sign-notarize.outcome }}" == "success" ]; then
|
||||||
|
echo " ✅ Successfully signed with Developer ID"
|
||||||
|
elif [ "${{ steps.sign-notarize.outcome }}" == "failure" ]; then
|
||||||
|
echo " ❌ Signing failed"
|
||||||
|
else
|
||||||
|
echo " ❓ Signing status unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Notarization Status
|
||||||
|
echo "🔐 Notarization Status:"
|
||||||
|
if [ "${{ steps.sign-notarize.outcome }}" == "success" ]; then
|
||||||
|
echo " ✅ Successfully notarized and stapled"
|
||||||
|
elif [ "${{ steps.sign-notarize.outcome }}" == "failure" ]; then
|
||||||
|
echo " ❌ Notarization failed"
|
||||||
|
elif [ "${{ steps.sign-notarize.outcome }}" == "skipped" ]; then
|
||||||
|
echo " ⚠️ Notarization was skipped (likely missing credentials)"
|
||||||
|
else
|
||||||
|
echo " ❓ Notarization status unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Quarantine Status
|
||||||
|
if [ -n "${{ env.QUARANTINE_STATUS }}" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "🛡️ Original App Security Status:"
|
||||||
|
if [ "${{ env.QUARANTINE_STATUS }}" == "clean" ]; then
|
||||||
|
echo " ✅ No quarantine attributes (good)"
|
||||||
|
elif [ "${{ env.QUARANTINE_STATUS }}" == "present" ]; then
|
||||||
|
echo " ⚠️ Quarantine attributes present"
|
||||||
|
else
|
||||||
|
echo " ❓ Quarantine status unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${{ env.PROVENANCE_STATUS }}" == "present" ]; then
|
||||||
|
echo " ✅ Provenance attribute present (indicates successful notarization)"
|
||||||
|
elif [ "${{ env.PROVENANCE_STATUS }}" == "missing" ]; then
|
||||||
|
echo " ⚠️ No provenance attribute (might indicate notarization issues)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# DMG Package Status
|
||||||
|
echo ""
|
||||||
|
echo "📦 DMG Package Status:"
|
||||||
|
if [ "${{ env.DMG_STATUS }}" == "success" ]; then
|
||||||
|
echo " ✅ DMG created successfully"
|
||||||
|
echo " 📍 Path: ${{ env.DMG_PATH }}"
|
||||||
|
echo " 📏 Size: ${{ env.DMG_SIZE || 'Unknown' }}"
|
||||||
|
|
||||||
|
if [ "${{ env.DMG_QUARANTINE_STATUS }}" == "clean" ]; then
|
||||||
|
echo " ✅ DMG has no quarantine attributes (good)"
|
||||||
|
elif [ "${{ env.DMG_QUARANTINE_STATUS }}" == "present" ]; then
|
||||||
|
echo " ⚠️ DMG has quarantine attributes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# App inside DMG status
|
||||||
|
echo ""
|
||||||
|
echo "📱 App Inside DMG Status:"
|
||||||
|
|
||||||
|
if [ "${{ env.DMG_APP_STATUS }}" == "missing" ]; then
|
||||||
|
echo " ❌ No app found inside DMG"
|
||||||
|
elif [ "${{ env.DMG_MOUNT_STATUS }}" == "failed" ]; then
|
||||||
|
echo " ❌ Could not mount DMG to check app"
|
||||||
|
else
|
||||||
|
# Quarantine status of app inside DMG
|
||||||
|
if [ "${{ env.DMG_APP_QUARANTINE_STATUS }}" == "clean" ]; then
|
||||||
|
echo " ✅ App inside DMG has no quarantine attributes (good)"
|
||||||
|
elif [ "${{ env.DMG_APP_QUARANTINE_STATUS }}" == "present" ]; then
|
||||||
|
echo " ⚠️ App inside DMG has quarantine attributes"
|
||||||
|
else
|
||||||
|
echo " ❓ App inside DMG quarantine status unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Provenance status of app inside DMG
|
||||||
|
if [ "${{ env.DMG_APP_PROVENANCE_STATUS }}" == "present" ]; then
|
||||||
|
echo " ✅ App inside DMG has provenance attribute (good)"
|
||||||
|
elif [ "${{ env.DMG_APP_PROVENANCE_STATUS }}" == "missing" ]; then
|
||||||
|
echo " ⚠️ App inside DMG is missing provenance attribute"
|
||||||
|
else
|
||||||
|
echo " ❓ App inside DMG provenance status unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gatekeeper assessment
|
||||||
|
if [ "${{ env.DMG_APP_GATEKEEPER_STATUS }}" == "accepted" ]; then
|
||||||
|
echo " ✅ App inside DMG passes Gatekeeper assessment"
|
||||||
|
elif [ "${{ env.DMG_APP_GATEKEEPER_STATUS }}" == "rejected" ]; then
|
||||||
|
echo " ⚠️ App inside DMG fails Gatekeeper assessment"
|
||||||
|
else
|
||||||
|
echo " ❓ App inside DMG Gatekeeper status unknown"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ "${{ env.DMG_STATUS }}" == "failed" ]; then
|
||||||
|
echo " ❌ DMG creation failed"
|
||||||
|
elif [ "${{ steps.package.outputs.dmg_created }}" == "true" ]; then
|
||||||
|
echo " ✅ DMG created successfully"
|
||||||
|
echo " 📍 Path: ${{ steps.package.outputs.dmg_path }}"
|
||||||
|
else
|
||||||
|
echo " ❓ DMG was not created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Artifact Upload Status
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Artifact Upload Status:"
|
||||||
|
if [ -n "${{ env.DMG_PATH }}" ] && [ -f "${{ env.DMG_PATH }}" ]; then
|
||||||
|
echo " ✅ DMG artifact should be uploaded"
|
||||||
|
else
|
||||||
|
echo " ❌ DMG artifact not available for upload"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=================================================="
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "🧹 Cleaning up..."
|
||||||
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||||||
|
rm -f *-notarize.zip || true
|
||||||
|
echo "✅ Cleanup complete"
|
163
.gitea/workflows/release.yml
Normal file
163
.gitea/workflows/release.yml
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
name: Create Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Create Tag
|
||||||
|
run: |
|
||||||
|
# Fetch all tags
|
||||||
|
git fetch --tags
|
||||||
|
|
||||||
|
# Get the latest version tag, if any
|
||||||
|
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
|
||||||
|
|
||||||
|
if [ -z "$LATEST_TAG" ]; then
|
||||||
|
# No previous version tag, start with 1.0.0
|
||||||
|
NEW_VERSION="1.0.0"
|
||||||
|
echo "No previous version tags found, starting with 1.0.0"
|
||||||
|
else
|
||||||
|
# Strip 'v' prefix if it exists
|
||||||
|
VERSION=${LATEST_TAG#v}
|
||||||
|
|
||||||
|
# Split version into parts
|
||||||
|
MAJOR=$(echo $VERSION | cut -d. -f1)
|
||||||
|
MINOR=$(echo $VERSION | cut -d. -f2)
|
||||||
|
PATCH=$(echo $VERSION | cut -d. -f3)
|
||||||
|
|
||||||
|
# Auto-increment patch version
|
||||||
|
PATCH=$((PATCH + 1))
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||||
|
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final tag with v prefix
|
||||||
|
TAG="v${NEW_VERSION}"
|
||||||
|
echo "Creating git tag: $TAG"
|
||||||
|
|
||||||
|
# Configure git with token authentication
|
||||||
|
git config --global user.email "actions@gitea.com"
|
||||||
|
git config --global user.name "Gitea Actions"
|
||||||
|
|
||||||
|
# Direct token approach
|
||||||
|
git remote set-url origin "https://runner:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
|
||||||
|
|
||||||
|
# Check if tag exists
|
||||||
|
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||||
|
# Create tag
|
||||||
|
git tag -a "$TAG" -m "Release $TAG"
|
||||||
|
|
||||||
|
# Push tag
|
||||||
|
git push origin "$TAG"
|
||||||
|
echo "Successfully created and pushed tag: $TAG"
|
||||||
|
else
|
||||||
|
echo "Tag $TAG already exists, skipping tag creation"
|
||||||
|
fi
|
||||||
|
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: releases
|
||||||
|
|
||||||
|
- name: Create Build Info
|
||||||
|
run: |
|
||||||
|
# Create a build info JSON file
|
||||||
|
echo '{
|
||||||
|
"version": "${{ env.RELEASE_TAG }}",
|
||||||
|
"buildNumber": "${{ github.run_number }}",
|
||||||
|
"commit": "${{ github.sha }}",
|
||||||
|
"branch": "${{ github.ref_name }}",
|
||||||
|
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
|
||||||
|
"artifacts": {
|
||||||
|
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
|
||||||
|
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux",
|
||||||
|
"macos": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS"
|
||||||
|
}
|
||||||
|
}' > build-info.json
|
||||||
|
|
||||||
|
# Create a simple HTML download page
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
.download-btn {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 5px;
|
||||||
|
}
|
||||||
|
.download-btn:hover { background-color: #45a049; }
|
||||||
|
.platform { margin-bottom: 30px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
|
||||||
|
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
|
||||||
|
|
||||||
|
<div class="platform">
|
||||||
|
<h2>Windows</h2>
|
||||||
|
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="platform">
|
||||||
|
<h2>Linux</h2>
|
||||||
|
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="platform">
|
||||||
|
<h2>macOS</h2>
|
||||||
|
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS" class="download-btn">Download macOS Build</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>' > downloads.html
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: https://gitea.com/actions/gitea-release-action@main
|
||||||
|
with:
|
||||||
|
files: |-
|
||||||
|
build-info.json
|
||||||
|
downloads.html
|
||||||
|
token: '${{ secrets.GITEATOKEN }}'
|
||||||
|
title: 'Release ${{ env.RELEASE_TAG }}'
|
||||||
|
body: |
|
||||||
|
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
|
||||||
|
|
||||||
|
### Download Links
|
||||||
|
|
||||||
|
Download builds from our CI artifacts:
|
||||||
|
|
||||||
|
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
|
||||||
|
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
|
||||||
|
- [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
|
||||||
|
|
||||||
|
### Build Information
|
||||||
|
|
||||||
|
- Build Number: #${{ github.run_number }}
|
||||||
|
- Commit: ${{ github.sha }}
|
||||||
|
- Branch: ${{ github.ref_name }}
|
||||||
|
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
tag_name: '${{ env.RELEASE_TAG }}'
|
392
.gitea/workflows/test-local-signing.yml
Normal file
392
.gitea/workflows/test-local-signing.yml
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
name: Test Local Signing
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Manual trigger
|
||||||
|
# push:
|
||||||
|
# branches: [ozgur/build]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-local-signing:
|
||||||
|
runs-on: macos
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Create Test Entitlements
|
||||||
|
run: |
|
||||||
|
echo "📝 Creating entitlements file..."
|
||||||
|
cat > LuckyWorld.entitlements << EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.automation.apple-events</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Created entitlements file"
|
||||||
|
cat LuckyWorld.entitlements
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Create Test App Bundle
|
||||||
|
run: |
|
||||||
|
echo "📦 Creating test app bundle..."
|
||||||
|
|
||||||
|
# Create test app bundle structure
|
||||||
|
TEST_APP_DIR="TestApp.app"
|
||||||
|
mkdir -p "$TEST_APP_DIR/Contents/MacOS"
|
||||||
|
|
||||||
|
# Create a simple test executable
|
||||||
|
echo '#!/bin/bash
|
||||||
|
echo "Hello from TestApp!"' > "$TEST_APP_DIR/Contents/MacOS/TestApp"
|
||||||
|
chmod +x "$TEST_APP_DIR/Contents/MacOS/TestApp"
|
||||||
|
|
||||||
|
# Create Info.plist
|
||||||
|
cat > "$TEST_APP_DIR/Contents/Info.plist" << EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>TestApp</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.luckyrobots.luckyworld.testapp</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>TestApp</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.10</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Created test app bundle"
|
||||||
|
|
||||||
|
# Verify app bundle exists
|
||||||
|
if [ ! -d "$TEST_APP_DIR" ]; then
|
||||||
|
echo "❌ Error: App bundle not found at $TEST_APP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔍 App bundle contents:"
|
||||||
|
ls -la "$TEST_APP_DIR"
|
||||||
|
|
||||||
|
# Store app path as environment variable
|
||||||
|
echo "APP_PATH=$(pwd)/TestApp.app" >> "$GITHUB_ENV"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup Certificate
|
||||||
|
env:
|
||||||
|
CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
run: |
|
||||||
|
echo "🔐 Setting up certificate..."
|
||||||
|
|
||||||
|
# Create a temporary directory for certificates
|
||||||
|
CERT_DIR="$HOME/certificates"
|
||||||
|
mkdir -p "$CERT_DIR"
|
||||||
|
|
||||||
|
# Decode the certificate to a p12 file
|
||||||
|
echo "$CERTIFICATE_BASE64" | base64 --decode > "$CERT_DIR/certificate.p12"
|
||||||
|
|
||||||
|
# Check certificate format and details
|
||||||
|
echo "📑 Certificate format check:"
|
||||||
|
file "$CERT_DIR/certificate.p12"
|
||||||
|
|
||||||
|
# Try to get certificate info with openssl
|
||||||
|
echo "📑 Certificate info with OpenSSL:"
|
||||||
|
openssl pkcs12 -info -in "$CERT_DIR/certificate.p12" -nokeys -passin pass:"$CERTIFICATE_PASSWORD" || echo "Failed to read certificate with OpenSSL"
|
||||||
|
|
||||||
|
# Create keychain
|
||||||
|
KEYCHAIN_PATH="$CERT_DIR/app-signing.keychain-db"
|
||||||
|
KEYCHAIN_PASSWORD="temppassword123"
|
||||||
|
|
||||||
|
# Delete existing keychain if it exists
|
||||||
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create new keychain
|
||||||
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
security set-keychain-settings -t 3600 -u -l "$KEYCHAIN_PATH"
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Add to search list and make default
|
||||||
|
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
||||||
|
security default-keychain -s "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Try multiple import approaches
|
||||||
|
echo "🔑 Importing developer certificate - attempt 1 (standard)..."
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||||
|
|
||||||
|
echo "🔑 Importing developer certificate - attempt 2 (with flags)..."
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -x -A
|
||||||
|
|
||||||
|
echo "🔑 Importing developer certificate - attempt 3 (with format)..."
|
||||||
|
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12
|
||||||
|
|
||||||
|
# Set partition list for codesign to access keychain
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Check all certificates in keychain
|
||||||
|
echo "🔍 Listing all certificates in keychain..."
|
||||||
|
security find-certificate -a "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Verify certificate
|
||||||
|
echo "🔍 Verifying code signing identities..."
|
||||||
|
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
|
# Alternative check for identities
|
||||||
|
echo "🔍 Listing identities with code signing usage..."
|
||||||
|
security find-certificate -a -c "Developer ID Application" -p "$KEYCHAIN_PATH" | grep -q "Code Signing" && echo "✅ Certificate has code signing usage" || echo "❌ Certificate does NOT have code signing usage"
|
||||||
|
|
||||||
|
# Try to use the System keychain as a fallback
|
||||||
|
echo "🔍 Checking system keychain for code signing identities..."
|
||||||
|
SYSTEM_IDENTITIES=$(security find-identity -v -p codesigning)
|
||||||
|
echo "$SYSTEM_IDENTITIES"
|
||||||
|
|
||||||
|
if echo "$SYSTEM_IDENTITIES" | grep -q "Developer ID Application"; then
|
||||||
|
echo "✅ Found Developer ID Application certificate in system keychain"
|
||||||
|
echo "USE_SYSTEM_CERT=true" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "❌ No Developer ID Application certificate found in system keychain"
|
||||||
|
echo "USE_SYSTEM_CERT=false" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store keychain variables for later steps
|
||||||
|
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
|
||||||
|
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> "$GITHUB_ENV"
|
||||||
|
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Debug: keep p12 file for inspection
|
||||||
|
echo "💾 Keeping certificate.p12 for debugging"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Debug Certificate Content
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||||
|
run: |
|
||||||
|
echo "🔎 Debugging certificate content..."
|
||||||
|
CERT_DIR="$HOME/certificates"
|
||||||
|
|
||||||
|
# Check if p12 file exists
|
||||||
|
if [ ! -f "$CERT_DIR/certificate.p12" ]; then
|
||||||
|
echo "❌ Certificate file not found"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try with OpenSSL to extract certificate info
|
||||||
|
echo "Attempting to extract certificate info..."
|
||||||
|
openssl pkcs12 -in "$CERT_DIR/certificate.p12" -info -nokeys -passin pass:"$CERTIFICATE_PASSWORD" > cert_info.txt || echo "Failed to extract info"
|
||||||
|
|
||||||
|
# Check certificate contents
|
||||||
|
echo "Certificate subject information:"
|
||||||
|
grep "subject" cert_info.txt || echo "No subject information found"
|
||||||
|
|
||||||
|
echo "Certificate issuer information:"
|
||||||
|
grep "issuer" cert_info.txt || echo "No issuer information found"
|
||||||
|
|
||||||
|
# Check if it's a Developer ID certificate
|
||||||
|
if grep -q "Developer ID" cert_info.txt; then
|
||||||
|
echo "✅ This appears to be a Developer ID certificate"
|
||||||
|
else
|
||||||
|
echo "❌ This does NOT appear to be a Developer ID certificate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if it has a private key
|
||||||
|
echo "Checking for private key..."
|
||||||
|
if openssl pkcs12 -in "$CERT_DIR/certificate.p12" -nocerts -passin pass:"$CERTIFICATE_PASSWORD" -passout pass:temp 2>/dev/null; then
|
||||||
|
echo "✅ Certificate contains a private key"
|
||||||
|
else
|
||||||
|
echo "❌ Certificate does NOT contain a private key or wrong password"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Sign with Developer ID
|
||||||
|
run: |
|
||||||
|
echo "🔏 Signing app with Developer ID certificate..."
|
||||||
|
|
||||||
|
# Decide which keychain to use
|
||||||
|
if [ "${USE_SYSTEM_CERT:-false}" = "true" ]; then
|
||||||
|
echo "Using system keychain identity"
|
||||||
|
# Get certificate hash instead of name to avoid ambiguity
|
||||||
|
IDENTITY_HASH=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | awk '{print $2}')
|
||||||
|
echo "Using certificate hash: $IDENTITY_HASH"
|
||||||
|
else
|
||||||
|
# Make sure keychain is unlocked
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
echo "Using custom keychain identity"
|
||||||
|
# Get certificate hash instead of name to avoid ambiguity
|
||||||
|
IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}')
|
||||||
|
echo "Using certificate hash: $IDENTITY_HASH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$IDENTITY_HASH" ]; then
|
||||||
|
echo "❌ No valid Developer ID Application certificate found"
|
||||||
|
echo "Falling back to ad-hoc signing for testing..."
|
||||||
|
# Use ad-hoc identity as fallback
|
||||||
|
codesign --force --deep --verbose --options runtime --entitlements LuckyWorld.entitlements --sign - --timestamp "$APP_PATH"
|
||||||
|
echo "SIGNED=adhoc" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH"
|
||||||
|
|
||||||
|
# Sign the app bundle using the hash
|
||||||
|
codesign --force --deep --verbose --options runtime --entitlements LuckyWorld.entitlements --sign "$IDENTITY_HASH" --timestamp "$APP_PATH"
|
||||||
|
echo "SIGNED=identity" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify signing
|
||||||
|
echo "🔍 Verifying signature..."
|
||||||
|
codesign -vvv --deep --strict "$APP_PATH"
|
||||||
|
|
||||||
|
# Check entitlements
|
||||||
|
echo "🔍 Checking entitlements..."
|
||||||
|
codesign -d --entitlements - "$APP_PATH"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Notarize App
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
APPLE_ID: ${{ secrets.NOTARY_USER }}
|
||||||
|
APP_PASSWORD: ${{ secrets.NOTARY_PASSWORD }}
|
||||||
|
API_KEY_ID: ${{ secrets.NOTARY_API_KEY_ID }}
|
||||||
|
API_ISSUER_ID: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
|
||||||
|
API_KEY_PATH: ${{ secrets.NOTARY_API_KEY_PATH }}
|
||||||
|
run: |
|
||||||
|
echo "📤 Notarizing app..."
|
||||||
|
|
||||||
|
# Check if we have API key credentials
|
||||||
|
if [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then
|
||||||
|
echo "Using App Store Connect API key for notarization..."
|
||||||
|
|
||||||
|
# Create directory for API key if API_KEY_PATH contains content
|
||||||
|
mkdir -p ~/private_keys
|
||||||
|
|
||||||
|
# Check if API_KEY_PATH is a path or content
|
||||||
|
if [[ "$API_KEY_PATH" == /* ]] && [ -f "$API_KEY_PATH" ]; then
|
||||||
|
# It's a path to a file
|
||||||
|
echo "Using API key from path: $API_KEY_PATH"
|
||||||
|
cp "$API_KEY_PATH" ~/private_keys/AuthKey_${API_KEY_ID}.p8
|
||||||
|
else
|
||||||
|
# It contains the key content
|
||||||
|
echo "Using API key from content"
|
||||||
|
echo "$API_KEY_PATH" > ~/private_keys/AuthKey_${API_KEY_ID}.p8
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create zip for notarization
|
||||||
|
ZIP_PATH="TestApp-notarize.zip"
|
||||||
|
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
|
||||||
|
|
||||||
|
echo "Submitting for notarization with API key..."
|
||||||
|
xcrun notarytool submit "$ZIP_PATH" \
|
||||||
|
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
|
||||||
|
--key-id "$API_KEY_ID" \
|
||||||
|
--issuer "$API_ISSUER_ID" \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
# Staple the notarization ticket
|
||||||
|
echo "Stapling notarization ticket..."
|
||||||
|
xcrun stapler staple "$APP_PATH"
|
||||||
|
|
||||||
|
# Verify notarization
|
||||||
|
echo "🔍 Verifying notarization..."
|
||||||
|
spctl --assess --verbose --type exec "$APP_PATH"
|
||||||
|
|
||||||
|
echo "NOTARIZED=true" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -rf ~/private_keys
|
||||||
|
|
||||||
|
# Fall back to App-specific password if API key not available
|
||||||
|
elif [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
|
||||||
|
echo "Using App-specific password for notarization..."
|
||||||
|
|
||||||
|
# Create zip for notarization
|
||||||
|
ZIP_PATH="TestApp-notarize.zip"
|
||||||
|
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
|
||||||
|
|
||||||
|
echo "Submitting for notarization..."
|
||||||
|
xcrun notarytool submit "$ZIP_PATH" \
|
||||||
|
--apple-id "$APPLE_ID" \
|
||||||
|
--password "$APP_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
# Staple the notarization ticket
|
||||||
|
echo "Stapling notarization ticket..."
|
||||||
|
xcrun stapler staple "$APP_PATH"
|
||||||
|
|
||||||
|
# Verify notarization
|
||||||
|
echo "🔍 Verifying notarization..."
|
||||||
|
spctl --assess --verbose --type exec "$APP_PATH"
|
||||||
|
|
||||||
|
echo "NOTARIZED=true" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "⚠️ Missing notarization credentials. Skipping notarization."
|
||||||
|
echo "For App Store Connect API key method, set these secrets:"
|
||||||
|
echo " - NOTARY_API_KEY_ID: Your API key ID"
|
||||||
|
echo " - NOTARY_API_KEY_ISSUER_ID: Your API issuer ID"
|
||||||
|
echo " - NOTARY_API_KEY_PATH: Path to or content of your p8 file"
|
||||||
|
echo ""
|
||||||
|
echo "For App-specific password method, set these secrets:"
|
||||||
|
echo " - NOTARY_USER: Your Apple ID (email)"
|
||||||
|
echo " - NOTARY_PASSWORD: Your app-specific password"
|
||||||
|
echo " - APPLE_TEAM_ID: Your Apple Developer team ID"
|
||||||
|
|
||||||
|
echo "NOTARIZED=false" >> "$GITHUB_ENV"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Package Signed App
|
||||||
|
run: |
|
||||||
|
echo "📦 Packaging signed app..."
|
||||||
|
|
||||||
|
if [ "${NOTARIZED:-false}" == "true" ]; then
|
||||||
|
ZIP_FILE="TestApp-Signed-Notarized.zip"
|
||||||
|
echo "Creating distribution package with notarized app..."
|
||||||
|
else
|
||||||
|
ZIP_FILE="TestApp-Signed.zip"
|
||||||
|
echo "Creating distribution package with signed app..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create zip package
|
||||||
|
ditto -c -k --keepParent "$APP_PATH" "$ZIP_FILE"
|
||||||
|
|
||||||
|
echo "✅ Created package: $ZIP_FILE"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: LuckyWorld-Signed-App
|
||||||
|
path: TestApp-*.zip
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "🧹 Cleaning up..."
|
||||||
|
rm -rf TestApp.app TestApp-*.zip || true
|
||||||
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
||||||
|
echo "✅ Cleanup complete"
|
||||||
|
shell: bash
|
271
.gitea/workflows/test-macos-build.yml
Normal file
271
.gitea/workflows/test-macos-build.yml
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
name: Test macOS Build Action
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Manual trigger only for testing
|
||||||
|
push:
|
||||||
|
branches: [ozgur/build]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-macos-build:
|
||||||
|
runs-on: macos
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Setup environment for build
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
# Get the working directory path for absolute paths
|
||||||
|
WORKSPACE_DIR="$(pwd)"
|
||||||
|
echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> "$GITHUB_ENV"
|
||||||
|
echo "ENTITLEMENTS_FILE=LuckyWorld.entitlements" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Set CI environment variable to true for build script
|
||||||
|
echo "CI=true" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Create directories for builds
|
||||||
|
mkdir -p Builds/Mac
|
||||||
|
mkdir -p PackagedReleases
|
||||||
|
mkdir -p ArchivedApps
|
||||||
|
|
||||||
|
echo "Environment setup complete"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Build for macOS - use your own build script
|
||||||
|
- name: Build for macOS
|
||||||
|
run: |
|
||||||
|
if [ -f "./scripts/mac_build.sh" ]; then
|
||||||
|
chmod +x ./scripts/mac_build.sh
|
||||||
|
# Set CI environment variable explicitly before running
|
||||||
|
export CI=true
|
||||||
|
./scripts/mac_build.sh
|
||||||
|
|
||||||
|
# Check if the build succeeded by looking for the app
|
||||||
|
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "❌ ERROR: Build command appeared to succeed but no app bundle was found!"
|
||||||
|
echo "This usually means the build failed but didn't properly return an error code."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ERROR: Build script not found at ./scripts/mac_build.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Find the app bundle
|
||||||
|
- name: Find app bundle
|
||||||
|
run: |
|
||||||
|
# Add error handling
|
||||||
|
set +e # Don't exit immediately on error for this block
|
||||||
|
|
||||||
|
echo "Build status check..."
|
||||||
|
if [ ! -d "./Builds" ] && [ ! -d "./Saved/StagedBuilds" ]; then
|
||||||
|
echo "❌ ERROR: Build directories do not exist. Build likely failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# First check Saved/StagedBuilds directory - where Unreal often places built apps
|
||||||
|
echo "Checking Saved/StagedBuilds directory..."
|
||||||
|
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# If not found, check Builds directory
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "No app found in Saved/StagedBuilds, checking Builds directory..."
|
||||||
|
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If still not found, check the whole workspace
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "No app found in Builds, checking entire workspace..."
|
||||||
|
APP_PATHS=$(find . -type d -name "*.app" -not -path "*/\.*" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_PATHS" ]; then
|
||||||
|
echo "❌ ERROR: Could not find any app bundles!"
|
||||||
|
echo "Listing all directories to help debug:"
|
||||||
|
find . -type d -maxdepth 3 | sort
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found potential app bundles:"
|
||||||
|
echo "$APP_PATHS"
|
||||||
|
|
||||||
|
# Use the first app path found (preferably the main app, not a child app)
|
||||||
|
MAIN_APP_PATH=$(echo "$APP_PATHS" | grep -v "CrashReportClient" | head -1 || echo "$APP_PATHS" | head -1)
|
||||||
|
|
||||||
|
echo "Using app bundle: $MAIN_APP_PATH"
|
||||||
|
echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Make sure app exists - using local variable
|
||||||
|
if [ ! -d "$MAIN_APP_PATH" ]; then
|
||||||
|
echo "❌ ERROR: App bundle not found at $MAIN_APP_PATH!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export APP_PATH for next steps to use
|
||||||
|
echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Get bundle ID from Info.plist for reference (not modifying)
|
||||||
|
if [ -f "$MAIN_APP_PATH/Contents/Info.plist" ]; then
|
||||||
|
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$MAIN_APP_PATH/Contents/Info.plist")
|
||||||
|
echo "Detected bundle ID: $BUNDLE_ID"
|
||||||
|
echo "BUNDLE_ID=$BUNDLE_ID" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Basic pre-notarization checks
|
||||||
|
- name: Check for notarization issues
|
||||||
|
run: |
|
||||||
|
echo "🔍 Checking app for potential notarization issues..."
|
||||||
|
|
||||||
|
APP_PATH="${{ env.APP_PATH }}"
|
||||||
|
|
||||||
|
# Verify code signature already exists (from Unreal build)
|
||||||
|
echo "Checking existing signature..."
|
||||||
|
codesign -vvv "$APP_PATH" || echo "⚠️ App may not be properly signed by Unreal Engine"
|
||||||
|
|
||||||
|
# Check for any ad-hoc signatures that would cause issues
|
||||||
|
if codesign -dvv "$APP_PATH" 2>&1 | grep -q "adhoc"; then
|
||||||
|
echo "⚠️ Warning: Ad-hoc signature detected. This will be replaced with a proper signature."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify entitlements file exists
|
||||||
|
if [ ! -f "${{ env.ENTITLEMENTS_FILE }}" ]; then
|
||||||
|
echo "⚠️ Entitlements file not found. Will use default entitlements."
|
||||||
|
else
|
||||||
|
echo "Found entitlements file: ${{ env.ENTITLEMENTS_FILE }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make install scripts executable
|
||||||
|
chmod +x ./scripts/install_luckyworld.sh
|
||||||
|
chmod +x ./scripts/create_dmg.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Use the macos-notarize action to sign and notarize the app
|
||||||
|
- name: Sign and Notarize macOS App
|
||||||
|
uses: ./.gitea/actions/macos-notarize
|
||||||
|
id: sign-and-notarize
|
||||||
|
with:
|
||||||
|
app-path: ${{ env.APP_PATH }}
|
||||||
|
entitlements-file: ${{ env.ENTITLEMENTS_FILE }}
|
||||||
|
team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
certificate-base64: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
|
certificate-password: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||||
|
notarization-method: 'api-key'
|
||||||
|
notary-api-key-id: ${{ secrets.NOTARY_API_KEY_ID }}
|
||||||
|
notary-api-key-issuer-id: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
|
||||||
|
notary-api-key-path: ${{ secrets.NOTARY_API_KEY_PATH }}
|
||||||
|
bundle-id: ${{ env.BUNDLE_ID }}
|
||||||
|
fallback-to-adhoc: 'false'
|
||||||
|
|
||||||
|
# Upload only the DMG file from the macos-notarize action
|
||||||
|
- name: Upload Standard DMG
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none'
|
||||||
|
with:
|
||||||
|
name: LuckyWorld-macOS-Signed-Notarized-DMG
|
||||||
|
path: ${{ steps.sign-and-notarize.outputs.package-path }}
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
# Install create-dmg tool
|
||||||
|
- name: Install create-dmg tool
|
||||||
|
run: |
|
||||||
|
if ! command -v brew &> /dev/null; then
|
||||||
|
echo "Homebrew is not installed. Installing now..."
|
||||||
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
if [[ -d "/opt/homebrew/bin/" ]]; then
|
||||||
|
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
|
||||||
|
eval "$(/opt/homebrew/bin/brew shellenv)"
|
||||||
|
fi
|
||||||
|
if [[ -d "/usr/local/bin/" ]]; then
|
||||||
|
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zprofile
|
||||||
|
eval "$(/usr/local/bin/brew shellenv)"
|
||||||
|
fi
|
||||||
|
echo "Homebrew installed successfully!"
|
||||||
|
fi
|
||||||
|
brew install create-dmg
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup CI Variables
|
||||||
|
run: echo "CI=true" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Create custom DMG with installer script
|
||||||
|
- name: Create Custom DMG with Installer
|
||||||
|
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none'
|
||||||
|
run: |
|
||||||
|
echo "Creating custom DMG with installer script..."
|
||||||
|
APP_PATH="${{ env.APP_PATH }}"
|
||||||
|
mkdir -p "./PackagedReleases/Install"
|
||||||
|
rm -rf "./PackagedReleases/Install/*" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Copy the app and installer to package directory
|
||||||
|
cp -R "$APP_PATH" "./PackagedReleases/Install/"
|
||||||
|
cp "./scripts/install_luckyworld.sh" "./PackagedReleases/Install/"
|
||||||
|
chmod +x "./PackagedReleases/Install/install_luckyworld.sh"
|
||||||
|
|
||||||
|
# Create a simple README
|
||||||
|
echo "Writing README file"
|
||||||
|
printf "LuckyWorld Installer\n===================\n\n1. Double-click install_luckyworld.sh to install\n2. Follow on-screen instructions\n\nFor help: https://luckyrobots.io\n" > "./PackagedReleases/Install/README.txt"
|
||||||
|
|
||||||
|
# Create DMG using direct hdiutil approach (native macOS command)
|
||||||
|
CUSTOM_DMG_PATH="./PackagedReleases/LuckyWorld-Installer.dmg"
|
||||||
|
rm -f "$CUSTOM_DMG_PATH" 2>/dev/null || true
|
||||||
|
|
||||||
|
hdiutil create -volname "LuckyWorld Installer" -srcfolder "./PackagedReleases/Install" -ov -format UDZO "$CUSTOM_DMG_PATH"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ DMG creation failed! Creating a ZIP file as fallback..."
|
||||||
|
( cd "./PackagedReleases" && zip -r "LuckyWorld-Installer.zip" "Install" )
|
||||||
|
CUSTOM_DMG_PATH="./PackagedReleases/LuckyWorld-Installer.zip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$CUSTOM_DMG_PATH" ]; then
|
||||||
|
echo "✅ Package created successfully: $CUSTOM_DMG_PATH"
|
||||||
|
echo "Size: $(du -h "$CUSTOM_DMG_PATH" | cut -f1)"
|
||||||
|
echo "CUSTOM_DMG_PATH=$CUSTOM_DMG_PATH" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "❌ Failed to create installation package"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up temporary directory
|
||||||
|
rm -rf "./PackagedReleases/Install"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Upload the custom DMG with installer script
|
||||||
|
- name: Upload Custom DMG with Installer
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' && env.CUSTOM_DMG_PATH != ''
|
||||||
|
with:
|
||||||
|
name: LuckyWorld-macOS-Installer-DMG
|
||||||
|
path: ${{ env.CUSTOM_DMG_PATH }}
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
|
||||||
|
# Report results
|
||||||
|
- name: Report Results
|
||||||
|
run: |
|
||||||
|
echo "🔐 App signing: ${{ steps.sign-and-notarize.outputs.signed }}"
|
||||||
|
echo "🔏 App notarization: ${{ steps.sign-and-notarize.outputs.notarized }}"
|
||||||
|
|
||||||
|
if [ "${{ steps.sign-and-notarize.outputs.signed }}" != "none" ]; then
|
||||||
|
echo "✅ Packaging completed successfully!"
|
||||||
|
echo "Final standard package: ${{ steps.sign-and-notarize.outputs.package-path }}"
|
||||||
|
|
||||||
|
if [ -n "$CUSTOM_DMG_PATH" ] && [ -f "$CUSTOM_DMG_PATH" ]; then
|
||||||
|
echo "Final installer package: $CUSTOM_DMG_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ App was not signed - check the logs for details"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
@ -1,347 +0,0 @@
|
|||||||
name: Unreal Engine Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-release:
|
|
||||||
runs-on: windows
|
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup environment
|
|
||||||
run: |
|
|
||||||
# Set environment variables for Unreal Engine
|
|
||||||
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
|
|
||||||
# Set environment variables for Linux toolchain
|
|
||||||
$env:LINUX_MULTIARCH_ROOT="C:/UnrealToolchains/v23_clang-18.1.0-rockylinux8"
|
|
||||||
echo "LINUX_MULTIARCH_ROOT=${LINUX_MULTIARCH_ROOT}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# Create directories for builds (with error handling)
|
|
||||||
if (!(Test-Path "Builds/Windows")) { New-Item -ItemType Directory -Path "Builds/Windows" -Force }
|
|
||||||
if (!(Test-Path "Builds/Linux")) { New-Item -ItemType Directory -Path "Builds/Linux" -Force }
|
|
||||||
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
|
|
||||||
|
|
||||||
- name: Build for Windows
|
|
||||||
run: |
|
|
||||||
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
|
|
||||||
& 'C:\Program Files\Git\bin\bash.exe' -c "./win_build.sh"
|
|
||||||
|
|
||||||
- name: Build for Linux
|
|
||||||
run: |
|
|
||||||
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
|
|
||||||
& 'C:\Program Files\Git\bin\bash.exe' -c "./linux_build.sh"
|
|
||||||
|
|
||||||
- name: Package builds
|
|
||||||
run: |
|
|
||||||
echo "Packaging Windows build..."
|
|
||||||
if [ -d "Builds/Windows" ]; then
|
|
||||||
cd Builds/Windows
|
|
||||||
zip -r ../../PackagedReleases/LuckyRobots-Windows.zip .
|
|
||||||
cd ../..
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Packaging Linux build..."
|
|
||||||
if [ -d "Builds/Linux" ]; then
|
|
||||||
cd Builds/Linux
|
|
||||||
zip -r ../../PackagedReleases/LuckyRobots-Linux.zip .
|
|
||||||
cd ../..
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=== Packaged releases ==="
|
|
||||||
ls -la PackagedReleases/
|
|
||||||
|
|
||||||
- name: Upload Windows Build Artifact
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: success() && hashFiles('PackagedReleases/LuckyRobots-Windows.zip') != ''
|
|
||||||
with:
|
|
||||||
name: LuckyRobots-Windows
|
|
||||||
path: PackagedReleases/LuckyRobots-Windows.zip
|
|
||||||
retention-days: 365
|
|
||||||
|
|
||||||
- name: Upload Linux Build Artifact
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: success() && hashFiles('PackagedReleases/LuckyRobots-Linux.zip') != ''
|
|
||||||
with:
|
|
||||||
name: LuckyRobots-Linux
|
|
||||||
path: PackagedReleases/LuckyRobots-Linux.zip
|
|
||||||
retention-days: 365
|
|
||||||
|
|
||||||
- name: Create Tag
|
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
|
||||||
run: |
|
|
||||||
# Fetch all tags
|
|
||||||
git fetch --tags
|
|
||||||
|
|
||||||
# Get the latest version tag, if any
|
|
||||||
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
|
|
||||||
|
|
||||||
if [ -z "$LATEST_TAG" ]; then
|
|
||||||
# No previous version tag, start with 1.0.0
|
|
||||||
NEW_VERSION="1.0.0"
|
|
||||||
echo "No previous version tags found, starting with 1.0.0"
|
|
||||||
else
|
|
||||||
# Strip 'v' prefix if it exists
|
|
||||||
VERSION=${LATEST_TAG#v}
|
|
||||||
|
|
||||||
# Split version into parts
|
|
||||||
MAJOR=$(echo $VERSION | cut -d. -f1)
|
|
||||||
MINOR=$(echo $VERSION | cut -d. -f2)
|
|
||||||
PATCH=$(echo $VERSION | cut -d. -f3)
|
|
||||||
|
|
||||||
# Auto-increment patch version
|
|
||||||
PATCH=$((PATCH + 1))
|
|
||||||
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
|
||||||
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Final tag with v prefix
|
|
||||||
TAG="v${NEW_VERSION}"
|
|
||||||
echo "Creating git tag: $TAG"
|
|
||||||
|
|
||||||
# Configure git with token authentication
|
|
||||||
git config --global user.email "actions@gitea.com"
|
|
||||||
git config --global user.name "Gitea Actions"
|
|
||||||
|
|
||||||
# Direct token approach - simplest method
|
|
||||||
git remote set-url origin "https://goran:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
|
|
||||||
|
|
||||||
# Set git to not prompt for input
|
|
||||||
$env:GIT_TERMINAL_PROMPT=0
|
|
||||||
|
|
||||||
# Check if tag exists
|
|
||||||
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
||||||
# Create tag without opening editor (-m flag)
|
|
||||||
git tag -a "$TAG" -m "Release $TAG"
|
|
||||||
|
|
||||||
# Push with timeout and debug
|
|
||||||
echo "Pushing tag $TAG to origin..."
|
|
||||||
git push --verbose origin "$TAG" || {
|
|
||||||
echo "Error: Failed to push tag. Check your token permissions."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
echo "Successfully created and pushed tag: $TAG"
|
|
||||||
else
|
|
||||||
echo "Tag $TAG already exists, skipping tag creation"
|
|
||||||
fi
|
|
||||||
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Build Info
|
|
||||||
run: |
|
|
||||||
# Create a build info JSON file
|
|
||||||
echo '{
|
|
||||||
"version": "${{ env.RELEASE_TAG }}",
|
|
||||||
"buildNumber": "${{ github.run_number }}",
|
|
||||||
"commit": "${{ github.sha }}",
|
|
||||||
"branch": "${{ github.ref_name }}",
|
|
||||||
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
|
|
||||||
"artifacts": {
|
|
||||||
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
|
|
||||||
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux"
|
|
||||||
}
|
|
||||||
}' > PackagedReleases/build-info.json
|
|
||||||
|
|
||||||
# Create a simple HTML download page
|
|
||||||
echo '<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
||||||
h1 { color: #333; }
|
|
||||||
.download-btn {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 20px;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 10px 5px;
|
|
||||||
}
|
|
||||||
.download-btn:hover { background-color: #45a049; }
|
|
||||||
.platform { margin-bottom: 30px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
|
|
||||||
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
|
|
||||||
|
|
||||||
<div class="platform">
|
|
||||||
<h2>Windows</h2>
|
|
||||||
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="platform">
|
|
||||||
<h2>Linux</h2>
|
|
||||||
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>' > PackagedReleases/downloads.html
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
uses: https://gitea.com/actions/gitea-release-action@main
|
|
||||||
with:
|
|
||||||
files: |-
|
|
||||||
PackagedReleases/build-info.json
|
|
||||||
PackagedReleases/downloads.html
|
|
||||||
token: '${{ secrets.GITEA_TOKEN }}'
|
|
||||||
title: 'Release ${{ env.RELEASE_TAG }}'
|
|
||||||
body: |
|
|
||||||
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
|
|
||||||
|
|
||||||
### Download Links
|
|
||||||
|
|
||||||
Download builds from our CI artifacts:
|
|
||||||
|
|
||||||
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
|
|
||||||
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
|
|
||||||
|
|
||||||
### Build Information
|
|
||||||
|
|
||||||
- Build Number: #${{ github.run_number }}
|
|
||||||
- Commit: ${{ github.sha }}
|
|
||||||
- Branch: ${{ github.ref_name }}
|
|
||||||
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
|
||||||
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
|
||||||
tag_name: '${{ env.RELEASE_TAG }}'
|
|
||||||
|
|
||||||
macos-build:
|
|
||||||
runs-on: macos
|
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Get Release Tag
|
|
||||||
run: |
|
|
||||||
# Fetch all tags
|
|
||||||
git fetch --tags
|
|
||||||
|
|
||||||
# Get the latest version tag
|
|
||||||
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
|
|
||||||
|
|
||||||
if [ -z "$LATEST_TAG" ]; then
|
|
||||||
NEW_VERSION="1.0.0"
|
|
||||||
else
|
|
||||||
VERSION=${LATEST_TAG#v}
|
|
||||||
MAJOR=$(echo $VERSION | cut -d. -f1)
|
|
||||||
MINOR=$(echo $VERSION | cut -d. -f2)
|
|
||||||
PATCH=$(echo $VERSION | cut -d. -f3)
|
|
||||||
PATCH=$((PATCH + 1))
|
|
||||||
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
TAG="v${NEW_VERSION}"
|
|
||||||
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
|
|
||||||
echo "Using release tag: $TAG"
|
|
||||||
|
|
||||||
- name: Setup Unreal Engine
|
|
||||||
run: |
|
|
||||||
# Use the correct path where Unreal Engine is installed
|
|
||||||
UE_PATH="/Users/Shared/Epic Games/UE_5.5"
|
|
||||||
|
|
||||||
if [ ! -d "$UE_PATH" ]; then
|
|
||||||
echo "Error: Unreal Engine is not installed in the expected location"
|
|
||||||
echo "Please ensure Unreal Engine is installed at $UE_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set environment variable with the correct Engine path
|
|
||||||
echo "UE_ROOT=$UE_PATH/Engine" >> $GITHUB_ENV
|
|
||||||
echo "Using Unreal Engine 5.5"
|
|
||||||
|
|
||||||
- name: Build Unreal Project
|
|
||||||
run: |
|
|
||||||
chmod +x ./mac_build.sh
|
|
||||||
./mac_build.sh
|
|
||||||
|
|
||||||
- name: Prepare Mac release
|
|
||||||
run: |
|
|
||||||
echo "Preparing packaged files for release..."
|
|
||||||
|
|
||||||
# Create a directory for release files
|
|
||||||
mkdir -p PackagedReleases
|
|
||||||
|
|
||||||
# Debug: Show what we're packaging
|
|
||||||
echo "=== Packaging for Release ==="
|
|
||||||
echo "Build directory contents:"
|
|
||||||
ls -la Builds/
|
|
||||||
|
|
||||||
# Find the app bundle in the Builds directory
|
|
||||||
APP_PATH=$(find Builds -type d -name "*.app" | head -1)
|
|
||||||
|
|
||||||
if [ -n "$APP_PATH" ]; then
|
|
||||||
echo "Found app bundle: $APP_PATH"
|
|
||||||
# Get the app name
|
|
||||||
APP_NAME=$(basename "$APP_PATH")
|
|
||||||
# Create zip file of the app bundle
|
|
||||||
(cd $(dirname "$APP_PATH") && zip -r "../../PackagedReleases/${APP_NAME%.app}-macOS.zip" "$APP_NAME")
|
|
||||||
echo "Created packaged release: PackagedReleases/${APP_NAME%.app}-macOS.zip"
|
|
||||||
else
|
|
||||||
echo "No .app bundle found in Builds directory"
|
|
||||||
|
|
||||||
# Look for a directory that might be a bundle but not named .app
|
|
||||||
MAIN_BUILD_DIR=$(find Builds -mindepth 1 -maxdepth 1 -type d | head -1)
|
|
||||||
if [ -n "$MAIN_BUILD_DIR" ]; then
|
|
||||||
echo "Found main build directory: $MAIN_BUILD_DIR"
|
|
||||||
DIR_NAME=$(basename "$MAIN_BUILD_DIR")
|
|
||||||
# Package this directory as if it were the app
|
|
||||||
(cd $(dirname "$MAIN_BUILD_DIR") && zip -r "../../PackagedReleases/${DIR_NAME}-macOS.zip" "$DIR_NAME")
|
|
||||||
echo "Created packaged release from main directory: PackagedReleases/${DIR_NAME}-macOS.zip"
|
|
||||||
else
|
|
||||||
# Package the entire Builds directory as a fallback
|
|
||||||
echo "No main directory found, packaging everything"
|
|
||||||
zip -r "PackagedReleases/LuckyRobots-macOS.zip" Builds
|
|
||||||
echo "Created fallback package: PackagedReleases/LuckyRobots-macOS.zip"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Packaged releases:"
|
|
||||||
ls -la PackagedReleases/
|
|
||||||
|
|
||||||
- name: Upload macOS Build Artifact
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: success()
|
|
||||||
with:
|
|
||||||
name: LuckyRobots-macOS
|
|
||||||
path: PackagedReleases/*-macOS.zip
|
|
||||||
retention-days: 365
|
|
||||||
|
|
||||||
- name: Create Release Note
|
|
||||||
run: |
|
|
||||||
echo "## macOS Build Completed" > release-note.md
|
|
||||||
echo "" >> release-note.md
|
|
||||||
echo "macOS build is available as an artifact." >> release-note.md
|
|
||||||
echo "" >> release-note.md
|
|
||||||
echo "Download from: [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)" >> release-note.md
|
|
||||||
|
|
||||||
- name: Create Gitea Release
|
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
|
||||||
uses: https://gitea.com/actions/gitea-release-action@main
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITEATOKEN }}
|
|
||||||
tag_name: ${{ env.RELEASE_TAG }}
|
|
||||||
title: "Release ${{ env.RELEASE_TAG }} - macOS"
|
|
||||||
body: |
|
|
||||||
## macOS Build Available as Artifact
|
|
||||||
|
|
||||||
The macOS build is available as an artifact due to its large file size.
|
|
||||||
|
|
||||||
[Download macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
|
|
||||||
|
|
||||||
Built from commit: ${{ github.sha }}
|
|
||||||
files: release-note.md
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -41,3 +41,4 @@ DerivedDataCache/*
|
|||||||
#this only is the Binaries folder on the root, not the Binaries folder in the plugin folders
|
#this only is the Binaries folder on the root, not the Binaries folder in the plugin folders
|
||||||
Binaries/**
|
Binaries/**
|
||||||
*.app/
|
*.app/
|
||||||
|
.cursorrules
|
||||||
|
@ -338,3 +338,36 @@ NearClipPlane=0.100000
|
|||||||
bFinalUsesRDO=True
|
bFinalUsesRDO=True
|
||||||
FinalRDOLambda=100
|
FinalRDOLambda=100
|
||||||
|
|
||||||
|
[/Script/MacTargetPlatform.MacTargetSettings]
|
||||||
|
TargetedRHIs=SF_METAL_SM5
|
||||||
|
MetalLanguageVersion=5
|
||||||
|
MaxShaderLanguageVersion=4
|
||||||
|
MinimumOSVersion=11
|
||||||
|
BundleName=LuckyWorld
|
||||||
|
BundleDisplayName=LuckyWorld
|
||||||
|
bEnableMathOptimizations=True
|
||||||
|
UseFastIntrinsics=True
|
||||||
|
EnableMipGenOption=Default
|
||||||
|
FrameRateLock=PUFRL_None
|
||||||
|
AudioSampleRate=48000
|
||||||
|
AudioMaxChannels=32
|
||||||
|
bUseCustomIcon=False
|
||||||
|
bUseMiniUPnP=False
|
||||||
|
MetalDynamicLibraries=()
|
||||||
|
MetalRuntimeLibrary=1
|
||||||
|
OutputRealFPS=False
|
||||||
|
bBuildEmbeddedFrameworksForGame=False
|
||||||
|
EnableCodeCoverage=False
|
||||||
|
EnableCodeCoveragePath=(Path="")
|
||||||
|
ForwardShading=False
|
||||||
|
UseFastCopyToResolve=True
|
||||||
|
bAutomaticallySignBuilds=False
|
||||||
|
bUseSIPSafeRunloop=True
|
||||||
|
CodeSigningIdentity=""
|
||||||
|
|
||||||
|
[/Script/MacTargetPlatform.XcodeProjectSettings]
|
||||||
|
CodeSigningPrefix=com.luckyrobots
|
||||||
|
ApplicationDisplayName=LuckyWorld
|
||||||
|
ShippingSpecificMacEntitlements=(FilePath="../LuckyWorld.entitlements")
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ ProjectVersion=0.1
|
|||||||
;bAddPacks=True
|
;bAddPacks=True
|
||||||
;InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent")
|
;InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent")
|
||||||
|
|
||||||
|
[/Script/MacTargetPlatform.MacTargetSettings]
|
||||||
|
BundleIdentifier=com.luckyrobots.luckyworld
|
||||||
|
|
||||||
[/Script/UnrealEd.ProjectPackagingSettings]
|
[/Script/UnrealEd.ProjectPackagingSettings]
|
||||||
Build=IfProjectHasCode
|
Build=IfProjectHasCode
|
||||||
BuildConfiguration=PPBC_Shipping
|
BuildConfiguration=PPBC_Shipping
|
||||||
|
28
LuckyWorld.entitlements
Normal file
28
LuckyWorld.entitlements
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.automation.apple-events</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.debugger</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -19,5 +19,6 @@ public class LuckyWorld : ModuleRules
|
|||||||
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||||
|
|
||||||
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
55
mac_build.sh
55
mac_build.sh
@ -1,55 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Get the user's home directory
|
|
||||||
USER_HOME="$HOME"
|
|
||||||
|
|
||||||
# Set up Unreal Engine paths
|
|
||||||
UE_ROOT="/Users/Shared/Epic Games/UE_5.5"
|
|
||||||
UE_EDITOR="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor"
|
|
||||||
UE_UAT="$UE_ROOT/Engine/Build/BatchFiles/RunUAT.command"
|
|
||||||
|
|
||||||
# Set up project paths
|
|
||||||
PROJECT_ROOT="$(pwd)"
|
|
||||||
PROJECT_FILE="$PROJECT_ROOT/LuckyWorld.uproject"
|
|
||||||
ARCHIVE_DIR="$PROJECT_ROOT/Builds"
|
|
||||||
|
|
||||||
rm -rf DerivedDataCache Intermediate Binaries Saved
|
|
||||||
|
|
||||||
"$UE_ROOT/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" -project="$PROJECT_FILE" -game -engine
|
|
||||||
# Run the build command
|
|
||||||
"$UE_UAT" -ScriptsForProject="$PROJECT_FILE" Turnkey \
|
|
||||||
-command=VerifySdk \
|
|
||||||
-platform=Mac \
|
|
||||||
-UpdateIfNeeded \
|
|
||||||
-EditorIO \
|
|
||||||
-EditorIOPort=59484 \
|
|
||||||
-project="$PROJECT_FILE" \
|
|
||||||
BuildCookRun \
|
|
||||||
-nop4 \
|
|
||||||
-utf8output \
|
|
||||||
-cook \
|
|
||||||
-project="$PROJECT_FILE" \
|
|
||||||
-target=LuckyWorld \
|
|
||||||
-unrealexe="$UE_EDITOR" \
|
|
||||||
-platform=Mac \
|
|
||||||
-installed \
|
|
||||||
-stage \
|
|
||||||
-archive \
|
|
||||||
-package \
|
|
||||||
-build \
|
|
||||||
-iterativecooking \
|
|
||||||
-pak \
|
|
||||||
-iostore \
|
|
||||||
-compressed \
|
|
||||||
-prereqs \
|
|
||||||
-archivedirectory="$ARCHIVE_DIR" \
|
|
||||||
-CrashReporter \
|
|
||||||
-clientconfig=Shipping \
|
|
||||||
# -nocompile \
|
|
||||||
# -nocompileuat \
|
|
||||||
# -nocompileeditor \
|
|
||||||
# -skipbuildeditor \
|
|
||||||
|
|
||||||
# enable these if you want to test build without pak and iostore (you're just testing the build)
|
|
||||||
# -skipiostore \
|
|
||||||
# -skippak \ (disable -pak and -iostore)
|
|
298
scripts/create_dmg.sh
Normal file
298
scripts/create_dmg.sh
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LuckyWorld DMG Creation Script
|
||||||
|
# This script creates a DMG file for macOS containing the application and installation script
|
||||||
|
|
||||||
|
# Terminal colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Check for create-dmg command
|
||||||
|
if ! command -v create-dmg &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'create-dmg' command not found.${NC}"
|
||||||
|
echo -e "${YELLOW}Please install it with: 'brew install create-dmg'${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to handle cleanup on exit
|
||||||
|
cleanup() {
|
||||||
|
echo -e "${BLUE}Cleaning up temporary files...${NC}"
|
||||||
|
if [ -d "$TEMP_DIR" ]; then
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set up trap for cleanup
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Usage information
|
||||||
|
if [ "$#" -lt 2 ]; then
|
||||||
|
echo -e "${YELLOW}Usage: $0 <application_dir> <output_directory> [version]${NC}"
|
||||||
|
echo -e "${BLUE}Example: $0 ./build/LuckyWorld.app ./dist 1.0.0${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse parameters
|
||||||
|
APPLICATION_DIR="$1"
|
||||||
|
OUTPUT_DIR="$2"
|
||||||
|
VERSION="${3:-1.0.0}"
|
||||||
|
|
||||||
|
# Validate parameters
|
||||||
|
if [ ! -d "$APPLICATION_DIR" ]; then
|
||||||
|
echo -e "${RED}Error: Application directory does not exist: $APPLICATION_DIR${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Application name
|
||||||
|
APP_NAME=$(basename "$APPLICATION_DIR")
|
||||||
|
DMG_NAME="LuckyWorld-${VERSION}"
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
if [ ! -d "$OUTPUT_DIR" ]; then
|
||||||
|
echo -e "${BLUE}Creating output directory: $OUTPUT_DIR${NC}"
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create temporary directory for DMG contents
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
echo -e "${BLUE}Creating temporary directory: $TEMP_DIR${NC}"
|
||||||
|
|
||||||
|
# Copy application to temp directory
|
||||||
|
echo -e "${BLUE}Copying application to temporary directory...${NC}"
|
||||||
|
cp -R "$APPLICATION_DIR" "$TEMP_DIR/"
|
||||||
|
|
||||||
|
# Create installation script in temp directory
|
||||||
|
INSTALL_SCRIPT="$TEMP_DIR/install_luckyworld.sh"
|
||||||
|
echo -e "${BLUE}Creating installation script...${NC}"
|
||||||
|
|
||||||
|
cat > "$INSTALL_SCRIPT" << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LuckyWorld macOS Installation Script
|
||||||
|
# This script installs the LuckyWorld app and removes macOS Gatekeeper quarantine flags
|
||||||
|
|
||||||
|
# Terminal colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# App paths
|
||||||
|
APP_NAME="LuckyWorld.app"
|
||||||
|
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/$APP_NAME"
|
||||||
|
APP_DEST="/Applications/$APP_NAME"
|
||||||
|
|
||||||
|
# Checking script permissions
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo -e "${YELLOW}This script requires administrator permissions to run.${NC}"
|
||||||
|
echo -e "${BLUE}Requesting administrator access...${NC}"
|
||||||
|
|
||||||
|
# Get the script path and re-run with sudo
|
||||||
|
SCRIPT_PATH=$(readlink -f "$0")
|
||||||
|
exec sudo "$SCRIPT_PATH"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Header
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
echo -e "${GREEN}LuckyWorld macOS Installation Tool${NC}"
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if source app exists
|
||||||
|
if [ ! -d "$APP_SOURCE" ]; then
|
||||||
|
# Check if source app exists in the same directory as the script
|
||||||
|
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/../$APP_NAME"
|
||||||
|
|
||||||
|
if [ ! -d "$APP_SOURCE" ]; then
|
||||||
|
echo -e "${RED}ERROR: Could not find $APP_NAME${NC}"
|
||||||
|
echo -e "${YELLOW}Please make sure the application is located in the same directory as this script.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Source application: ${NC}${YELLOW}$APP_SOURCE${NC}"
|
||||||
|
echo -e "${BLUE}Destination: ${NC}${YELLOW}$APP_DEST${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if application already exists and is running
|
||||||
|
if [ -d "$APP_DEST" ]; then
|
||||||
|
echo -e "${YELLOW}Application $APP_NAME already exists in Applications folder.${NC}"
|
||||||
|
|
||||||
|
# Check if app is running
|
||||||
|
if pgrep -x "LuckyWorld" > /dev/null; then
|
||||||
|
echo -e "${RED}LuckyWorld is currently running. Please close the application before continuing.${NC}"
|
||||||
|
read -p "Press Enter after closing the application to continue..."
|
||||||
|
|
||||||
|
# Check again
|
||||||
|
if pgrep -x "LuckyWorld" > /dev/null; then
|
||||||
|
echo -e "${RED}LuckyWorld is still running. Installation aborted.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Removing existing version...${NC}"
|
||||||
|
rm -rf "$APP_DEST"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}ERROR: Could not remove existing application. Possible permission issue.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Existing application removed successfully.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install the application
|
||||||
|
echo -e "${BLUE}Installing $APP_NAME to Applications folder...${NC}"
|
||||||
|
cp -R "$APP_SOURCE" /Applications/
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}ERROR: Installation failed. Could not copy the application to /Applications.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear macOS Gatekeeper quarantine flag
|
||||||
|
echo -e "${BLUE}Removing Gatekeeper quarantine flag...${NC}"
|
||||||
|
xattr -rd com.apple.quarantine "$APP_DEST"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${YELLOW}WARNING: Could not remove Gatekeeper quarantine flag.${NC}"
|
||||||
|
echo -e "${YELLOW}You may need to manually allow the application in System Preferences > Security & Privacy.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Gatekeeper restrictions removed successfully.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
echo -e "${BLUE}Setting permissions...${NC}"
|
||||||
|
chmod -R 755 "$APP_DEST"
|
||||||
|
|
||||||
|
# Successful installation message
|
||||||
|
echo -e "${GREEN}✅ LuckyWorld has been successfully installed!${NC}"
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
echo -e "${YELLOW}To start the application, open /Applications/$APP_NAME${NC}"
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
|
||||||
|
# Ask if user wants to launch the app
|
||||||
|
echo ""
|
||||||
|
read -p "Would you like to launch LuckyWorld now? (y/n): " LAUNCH_APP
|
||||||
|
|
||||||
|
if [[ "$LAUNCH_APP" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${BLUE}Launching LuckyWorld...${NC}"
|
||||||
|
open "$APP_DEST"
|
||||||
|
echo -e "${GREEN}Done!${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Make the installation script executable
|
||||||
|
chmod +x "$INSTALL_SCRIPT"
|
||||||
|
|
||||||
|
# Create a README file
|
||||||
|
README_FILE="$TEMP_DIR/README.txt"
|
||||||
|
echo -e "${BLUE}Creating README file...${NC}"
|
||||||
|
|
||||||
|
cat > "$README_FILE" << EOF
|
||||||
|
LuckyWorld ${VERSION}
|
||||||
|
===================
|
||||||
|
|
||||||
|
Installation Instructions:
|
||||||
|
1. Double-click the 'install_luckyworld.sh' script to install LuckyWorld
|
||||||
|
2. If prompted, enter your administrator password
|
||||||
|
3. The application will be installed to your Applications folder
|
||||||
|
|
||||||
|
If you encounter any issues with installation:
|
||||||
|
- Make sure you have administrator privileges
|
||||||
|
- Check System Preferences > Security & Privacy if macOS blocks the application
|
||||||
|
|
||||||
|
For support, please visit: https://luckyrobots.com
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create DMG file
|
||||||
|
echo -e "${BLUE}Creating DMG file...${NC}"
|
||||||
|
OUTPUT_DMG="$OUTPUT_DIR/${DMG_NAME}.dmg"
|
||||||
|
|
||||||
|
# Remove existing DMG if it exists
|
||||||
|
if [ -f "$OUTPUT_DMG" ]; then
|
||||||
|
echo -e "${YELLOW}Removing existing DMG file: $OUTPUT_DMG${NC}"
|
||||||
|
rm -f "$OUTPUT_DMG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if icon file exists
|
||||||
|
ICON_PATH="$(dirname "$0")/assets/LuckyWorld.icns"
|
||||||
|
VOLICON_PARAM=""
|
||||||
|
if [ -f "$ICON_PATH" ]; then
|
||||||
|
echo -e "${GREEN}Using volume icon: $ICON_PATH${NC}"
|
||||||
|
VOLICON_PARAM="--volicon \"$ICON_PATH\""
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Volume icon not found at $ICON_PATH. Creating DMG without custom icon.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CI ENVIRONMENT DETECTION - Use different approach if in CI
|
||||||
|
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] || [ -n "$GITEA_ACTIONS" ]; then
|
||||||
|
echo -e "${BLUE}CI environment detected! Using hdiutil directly instead of create-dmg${NC}"
|
||||||
|
|
||||||
|
# Create DMG using hdiutil directly (much more reliable in CI)
|
||||||
|
hdiutil create -volname "LuckyWorld ${VERSION}" -srcfolder "$TEMP_DIR" -ov -format UDZO "$OUTPUT_DMG"
|
||||||
|
|
||||||
|
# Check if DMG creation was successful
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: hdiutil DMG creation failed, attempting ZIP fallback...${NC}"
|
||||||
|
# Create a ZIP file as fallback
|
||||||
|
(cd "$(dirname "$TEMP_DIR")" && zip -r "$OUTPUT_DMG.zip" "$(basename "$TEMP_DIR")")
|
||||||
|
|
||||||
|
# If ZIP succeeded, use it instead
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${YELLOW}Created ZIP fallback: $OUTPUT_DMG.zip${NC}"
|
||||||
|
OUTPUT_DMG="$OUTPUT_DMG.zip"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Error: Both DMG and ZIP creation failed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Non-CI environment: Use create-dmg
|
||||||
|
# Create DMG using create-dmg with dynamic volicon parameter
|
||||||
|
CMD_CREATE_DMG="create-dmg \
|
||||||
|
--volname \"LuckyWorld ${VERSION}\" \
|
||||||
|
$VOLICON_PARAM \
|
||||||
|
--window-pos 200 120 \
|
||||||
|
--window-size 800 500 \
|
||||||
|
--icon-size 128 \
|
||||||
|
--icon \"LuckyWorld.app\" 200 250 \
|
||||||
|
--icon \"install_luckyworld.sh\" 400 250 \
|
||||||
|
--icon \"README.txt\" 600 250 \
|
||||||
|
--hide-extension \"LuckyWorld.app\" \
|
||||||
|
--hide-extension \"install_luckyworld.sh\" \
|
||||||
|
--app-drop-link 400 120 \
|
||||||
|
--no-internet-enable \
|
||||||
|
--no-finder \
|
||||||
|
\"$OUTPUT_DMG\" \
|
||||||
|
\"$TEMP_DIR\""
|
||||||
|
|
||||||
|
# Execute the command
|
||||||
|
echo -e "${BLUE}Executing: $CMD_CREATE_DMG${NC}"
|
||||||
|
eval $CMD_CREATE_DMG
|
||||||
|
|
||||||
|
# Check if DMG creation was successful
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: Failed to create DMG with create-dmg, trying hdiutil...${NC}"
|
||||||
|
|
||||||
|
# Fallback to hdiutil
|
||||||
|
hdiutil create -volname "LuckyWorld ${VERSION}" -srcfolder "$TEMP_DIR" -ov -format UDZO "$OUTPUT_DMG"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: All DMG creation methods failed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ DMG file created successfully: $OUTPUT_DMG${NC}"
|
||||||
|
echo -e "${BLUE}DMG size: $(du -h "$OUTPUT_DMG" | cut -f1)${NC}"
|
||||||
|
|
||||||
|
exit 0
|
118
scripts/install_luckyworld.sh
Normal file
118
scripts/install_luckyworld.sh
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LuckyWorld macOS Installation Script
|
||||||
|
# This script installs the LuckyWorld app and removes macOS Gatekeeper quarantine flags
|
||||||
|
|
||||||
|
# Terminal colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# App paths
|
||||||
|
APP_NAME="LuckyWorld.app"
|
||||||
|
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/$APP_NAME"
|
||||||
|
APP_DEST="/Applications/$APP_NAME"
|
||||||
|
|
||||||
|
# Checking script permissions
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo -e "${YELLOW}This script requires administrator permissions to run.${NC}"
|
||||||
|
echo -e "${BLUE}Requesting administrator access...${NC}"
|
||||||
|
|
||||||
|
# Get the script path and re-run with sudo
|
||||||
|
SCRIPT_PATH=$(readlink -f "$0")
|
||||||
|
exec sudo "$SCRIPT_PATH"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Header
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
echo -e "${GREEN}LuckyWorld macOS Installation Tool${NC}"
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if source app exists
|
||||||
|
if [ ! -d "$APP_SOURCE" ]; then
|
||||||
|
# Check if source app exists in the same directory as the script
|
||||||
|
APP_SOURCE="$(cd "$(dirname "$0")" && pwd)/../$APP_NAME"
|
||||||
|
|
||||||
|
if [ ! -d "$APP_SOURCE" ]; then
|
||||||
|
echo -e "${RED}ERROR: Could not find $APP_NAME${NC}"
|
||||||
|
echo -e "${YELLOW}Please make sure the application is located in the same directory as this script.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Source application: ${NC}${YELLOW}$APP_SOURCE${NC}"
|
||||||
|
echo -e "${BLUE}Destination: ${NC}${YELLOW}$APP_DEST${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if application already exists and is running
|
||||||
|
if [ -d "$APP_DEST" ]; then
|
||||||
|
echo -e "${YELLOW}Application $APP_NAME already exists in Applications folder.${NC}"
|
||||||
|
|
||||||
|
# Check if app is running
|
||||||
|
if pgrep -x "LuckyWorld" > /dev/null; then
|
||||||
|
echo -e "${RED}LuckyWorld is currently running. Please close the application before continuing.${NC}"
|
||||||
|
read -p "Press Enter after closing the application to continue..."
|
||||||
|
|
||||||
|
# Check again
|
||||||
|
if pgrep -x "LuckyWorld" > /dev/null; then
|
||||||
|
echo -e "${RED}LuckyWorld is still running. Installation aborted.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Removing existing version...${NC}"
|
||||||
|
rm -rf "$APP_DEST"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}ERROR: Could not remove existing application. Possible permission issue.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Existing application removed successfully.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install the application
|
||||||
|
echo -e "${BLUE}Installing $APP_NAME to Applications folder...${NC}"
|
||||||
|
cp -R "$APP_SOURCE" /Applications/
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}ERROR: Installation failed. Could not copy the application to /Applications.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear macOS Gatekeeper quarantine flag
|
||||||
|
echo -e "${BLUE}Removing Gatekeeper quarantine flag...${NC}"
|
||||||
|
xattr -rd com.apple.quarantine "$APP_DEST"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${YELLOW}WARNING: Could not remove Gatekeeper quarantine flag.${NC}"
|
||||||
|
echo -e "${YELLOW}You may need to manually allow the application in System Preferences > Security & Privacy.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Gatekeeper restrictions removed successfully.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set proper permissions
|
||||||
|
echo -e "${BLUE}Setting permissions...${NC}"
|
||||||
|
chmod -R 755 "$APP_DEST"
|
||||||
|
|
||||||
|
# Successful installation message
|
||||||
|
echo -e "${GREEN}✅ LuckyWorld has been successfully installed!${NC}"
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
echo -e "${YELLOW}To start the application, open /Applications/$APP_NAME${NC}"
|
||||||
|
echo -e "${BLUE}============================================${NC}"
|
||||||
|
|
||||||
|
# Ask if user wants to launch the app
|
||||||
|
echo ""
|
||||||
|
read -p "Would you like to launch LuckyWorld now? (y/n): " LAUNCH_APP
|
||||||
|
|
||||||
|
if [[ "$LAUNCH_APP" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${BLUE}Launching LuckyWorld...${NC}"
|
||||||
|
open "$APP_DEST"
|
||||||
|
echo -e "${GREEN}Done!${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
131
scripts/mac_build.sh
Executable file
131
scripts/mac_build.sh
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
# Get the user's home directory
|
||||||
|
USER_HOME="$HOME"
|
||||||
|
|
||||||
|
# Set up Unreal Engine paths
|
||||||
|
UE_ROOT="/Users/Shared/Epic Games/UE_5.5"
|
||||||
|
UE_EDITOR="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor"
|
||||||
|
UE_UAT="$UE_ROOT/Engine/Build/BatchFiles/RunUAT.command"
|
||||||
|
|
||||||
|
# Set up project paths
|
||||||
|
PROJECT_ROOT="$(pwd)"
|
||||||
|
PROJECT_FILE="$PROJECT_ROOT/LuckyWorld.uproject"
|
||||||
|
ARCHIVE_DIR="$PROJECT_ROOT/Builds"
|
||||||
|
|
||||||
|
# Check for entitlements file
|
||||||
|
if [ -f "$PROJECT_ROOT/LuckyWorld.entitlements" ]; then
|
||||||
|
ENTITLEMENTS_FILE="$PROJECT_ROOT/LuckyWorld.entitlements"
|
||||||
|
echo "✅ Using entitlements file: $ENTITLEMENTS_FILE"
|
||||||
|
else
|
||||||
|
echo "⚠️ Warning: No entitlements file found. This might affect notarization."
|
||||||
|
ENTITLEMENTS_FILE=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print paths and config for debugging
|
||||||
|
echo "Project root: $PROJECT_ROOT"
|
||||||
|
echo "Project file: $PROJECT_FILE"
|
||||||
|
echo "Archive directory: $ARCHIVE_DIR"
|
||||||
|
|
||||||
|
# More selective cleanup - don't remove DerivedDataCache
|
||||||
|
echo "🧹 Cleaning build artifacts..."
|
||||||
|
rm -rf DerivedDataCache Intermediate Binaries Saved
|
||||||
|
mkdir -p "$ARCHIVE_DIR"
|
||||||
|
|
||||||
|
# Generate project files
|
||||||
|
echo "📝 Generating project files..."
|
||||||
|
"$UE_ROOT/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" -project="$PROJECT_FILE" -game -engine
|
||||||
|
|
||||||
|
# Run the build command with simplified parameters and more diagnostics
|
||||||
|
echo "🔨 Starting build process..."
|
||||||
|
"$UE_UAT" -ScriptsForProject="$PROJECT_FILE" Turnkey \
|
||||||
|
-command=VerifySdk \
|
||||||
|
-platform=Mac \
|
||||||
|
-UpdateIfNeeded \
|
||||||
|
-EditorIO \
|
||||||
|
-EditorIOPort=59484 \
|
||||||
|
-project="$PROJECT_FILE" \
|
||||||
|
BuildCookRun \
|
||||||
|
-nop4 \
|
||||||
|
-utf8output \
|
||||||
|
-cook \
|
||||||
|
-project="$PROJECT_FILE" \
|
||||||
|
-target=LuckyWorld \
|
||||||
|
-unrealexe="$UE_EDITOR" \
|
||||||
|
-platform=Mac \
|
||||||
|
-installed \
|
||||||
|
-stage \
|
||||||
|
-archive \
|
||||||
|
-package \
|
||||||
|
-build \
|
||||||
|
-iterativecooking \
|
||||||
|
-pak \
|
||||||
|
-iostore \
|
||||||
|
-compressed \
|
||||||
|
-prereqs \
|
||||||
|
-archivedirectory="$ARCHIVE_DIR" \
|
||||||
|
-CrashReporter \
|
||||||
|
-clientconfig=Shipping \
|
||||||
|
# -nocompile \
|
||||||
|
# -nocompileuat \
|
||||||
|
# -nocompileeditor \
|
||||||
|
# -skipbuildeditor \
|
||||||
|
|
||||||
|
# enable these if you want to test build without pak and iostore (you're just testing the build)
|
||||||
|
# -skipiostore \
|
||||||
|
# -skippak \ (disable -pak and -iostore)
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check for errors in the build process
|
||||||
|
BUILD_STATUS=$?
|
||||||
|
if [ $BUILD_STATUS -ne 0 ]; then
|
||||||
|
echo "❌ ERROR: Build command failed with exit code $BUILD_STATUS"
|
||||||
|
exit $BUILD_STATUS
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🔍 Looking for built application..."
|
||||||
|
APP_PATH=$(find "$ARCHIVE_DIR" -name "*.app" -type d | head -n 1)
|
||||||
|
|
||||||
|
# Check if the build actually succeeded by verifying the app exists
|
||||||
|
if [ -z "$APP_PATH" ] || [ ! -d "$APP_PATH" ]; then
|
||||||
|
echo "❌ ERROR: Build failed or did not produce an app bundle!"
|
||||||
|
echo "Check the logs above for build errors."
|
||||||
|
|
||||||
|
# List all files in the archive directory to help debug
|
||||||
|
echo "Contents of archive directory:"
|
||||||
|
find "$ARCHIVE_DIR" -type f -o -type d | sort
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Build completed successfully! Application path:"
|
||||||
|
echo "$APP_PATH"
|
||||||
|
|
||||||
|
if [ -n "$APP_PATH" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "🔍 Binary files summary:"
|
||||||
|
DYLIB_COUNT=$(find "$APP_PATH" -name "*.dylib" | wc -l)
|
||||||
|
SO_COUNT=$(find "$APP_PATH" -name "*.so" | wc -l)
|
||||||
|
FRAMEWORKS=$(find "$APP_PATH" -path "*.framework/*" -type f -perm +111 | wc -l)
|
||||||
|
EXECUTABLES=$(find "$APP_PATH" -type f -perm +111 -not -path "*.framework/*" -not -name "*.dylib" -not -name "*.so" | wc -l)
|
||||||
|
|
||||||
|
echo "- $DYLIB_COUNT .dylib libraries"
|
||||||
|
echo "- $SO_COUNT .so libraries"
|
||||||
|
echo "- $FRAMEWORKS framework executables"
|
||||||
|
echo "- $EXECUTABLES other executables"
|
||||||
|
echo "Total binary files: $((DYLIB_COUNT + SO_COUNT + FRAMEWORKS + EXECUTABLES))"
|
||||||
|
|
||||||
|
# Check bundle ID (for information only, no modifications)
|
||||||
|
INFO_PLIST="$APP_PATH/Contents/Info.plist"
|
||||||
|
if [ -f "$INFO_PLIST" ]; then
|
||||||
|
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO_PLIST")
|
||||||
|
echo ""
|
||||||
|
echo "📦 App Bundle ID: $BUNDLE_ID"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Build complete!"
|
||||||
|
echo "App location: $APP_PATH"
|
Loading…
x
Reference in New Issue
Block a user