Merge pull request #3 from ghostapp-ai/dependabot/github_actions/acti… #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Ghost CI/CD - Multiplatform Pipeline | |
| # | |
| # Jobs flow: | |
| # checks --\ | |
| # audit ----+--> release --+--> build-desktop (4 matrix) | |
| # test ----/ +--> build-android | |
| # \--> build-ios (conditional) | |
| name: Ghost CI/CD | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_NET_RETRY: 10 | |
| RUSTUP_MAX_RETRIES: 10 | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # QUALITY GATES — run on every push & PR | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| jobs: | |
| # ─── Fast checks (no heavy Rust compilation, ~1-2min) ──────────── | |
| checks: | |
| name: Checks | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/stub-pro | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - name: Rust format check | |
| working-directory: src-tauri | |
| run: cargo fmt --all -- --check | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install frontend deps | |
| run: bun install --frozen-lockfile | |
| - name: Frontend type-check & build | |
| run: bun run build | |
| # ─── Security audit (no compilation, ~30s) ─────────────────────── | |
| audit: | |
| name: Audit | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: taiki-e/install-action@cargo-audit | |
| - name: Security audit | |
| working-directory: src-tauri | |
| run: cargo audit || true | |
| # ─── Test + Clippy (heavy compilation, sccache + mold) ────────── | |
| test: | |
| name: Test & Clippy | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/stub-pro | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libwebkit2gtk-4.1-dev libappindicator3-dev \ | |
| librsvg2-dev patchelf libgtk-3-dev libsoup-3.0-dev \ | |
| libjavascriptcoregtk-4.1-dev mold | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: Mozilla-Actions/sccache-action@v0.0.9 | |
| - uses: taiki-e/install-action@cargo-nextest | |
| - name: Rust tests | |
| working-directory: src-tauri | |
| run: cargo nextest run | |
| - name: Clippy (reuses sccache artifacts) | |
| working-directory: src-tauri | |
| run: cargo clippy --all-targets -- -D warnings | |
| - name: sccache stats | |
| run: sccache --show-stats | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # RELEASE — semantic-release on main push only | |
| # Analyzes conventional commits → bumps version → CHANGELOG → tag | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| release: | |
| name: Release | |
| needs: [checks, audit, test] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| outputs: | |
| released: ${{ steps.semantic.outputs.new_release_published }} | |
| version: ${{ steps.semantic.outputs.new_release_version }} | |
| tag: ${{ steps.semantic.outputs.new_release_git_tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: true | |
| - name: Semantic Release | |
| uses: cycjimmy/semantic-release-action@v6 | |
| id: semantic | |
| with: | |
| extra_plugins: | | |
| @semantic-release/changelog | |
| @semantic-release/git | |
| @semantic-release/exec | |
| conventional-changelog-conventionalcommits | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # DESKTOP BUILDS — Windows, macOS (ARM+Intel), Linux (all bundles) | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| build-desktop: | |
| name: Desktop (${{ matrix.label }}) | |
| needs: release | |
| if: needs.release.outputs.released == 'true' | |
| permissions: | |
| contents: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: Windows x64 | |
| runner: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| bundles: nsis | |
| - label: macOS ARM64 | |
| runner: macos-latest | |
| target: aarch64-apple-darwin | |
| bundles: dmg | |
| - label: macOS x64 | |
| runner: macos-latest | |
| target: x86_64-apple-darwin | |
| bundles: dmg | |
| - label: Linux x64 | |
| runner: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| bundles: deb,appimage,rpm | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.release.outputs.tag }} | |
| - uses: ./.github/actions/stub-pro | |
| - name: Install Linux dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libwebkit2gtk-4.1-dev libappindicator3-dev \ | |
| librsvg2-dev patchelf libgtk-3-dev libsoup-3.0-dev \ | |
| libjavascriptcoregtk-4.1-dev mold | |
| - name: Set mold linker for Linux | |
| if: runner.os == 'Linux' | |
| run: echo "RUSTFLAGS=-C link-arg=-fuse-ld=mold" >> "$GITHUB_ENV" | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Mozilla-Actions/sccache-action@v0.0.9 | |
| - name: Install frontend deps | |
| run: bun install --frozen-lockfile | |
| # ── Windows: force dynamic CRT (/MD) for all C++ compiled by | |
| # llama-cpp-sys-2 (both CMake and cc::Build code paths). | |
| # Without this, CMake Release defaults to /MT (static CRT → | |
| # libcpmt.lib), which conflicts with Rust's /MD (dynamic CRT → | |
| # msvcprt.lib) and causes LNK2005 "multiply defined symbols". | |
| # CMAKE_MSVC_RUNTIME_LIBRARY is forwarded to CMake automatically | |
| # by the llama-cpp-sys-2 build script (it forwards all CMAKE_* vars). | |
| # CFLAGS/CXXFLAGS cover the cc::Build wrapper compilation step. | |
| - name: Configure Windows CRT linkage | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| echo "CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL" >> "$GITHUB_ENV" | |
| echo "CFLAGS=/MD" >> "$GITHUB_ENV" | |
| echo "CXXFLAGS=/MD" >> "$GITHUB_ENV" | |
| # ── macOS signing: use real Apple certificate if configured, | |
| # otherwise fall back to ad-hoc signing ("-"). | |
| # Ad-hoc is required on ARM (aarch64) or the app shows | |
| # "App is damaged" — no Apple Developer account needed. | |
| - name: Configure macOS signing | |
| if: runner.os == 'macOS' | |
| env: | |
| APPLE_CERT_RAW: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERT_PASSWORD_RAW: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_ID_RAW: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_ID_RAW: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD_RAW: ${{ secrets.APPLE_PASSWORD }} | |
| APPLE_TEAM_ID_RAW: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| if [ -n "$APPLE_CERT_RAW" ]; then | |
| echo "APPLE_CERTIFICATE=$APPLE_CERT_RAW" >> "$GITHUB_ENV" | |
| echo "APPLE_CERTIFICATE_PASSWORD=$APPLE_CERT_PASSWORD_RAW" >> "$GITHUB_ENV" | |
| echo "APPLE_SIGNING_IDENTITY=$APPLE_SIGNING_ID_RAW" >> "$GITHUB_ENV" | |
| echo "APPLE_ID=$APPLE_ID_RAW" >> "$GITHUB_ENV" | |
| echo "APPLE_PASSWORD=$APPLE_PASSWORD_RAW" >> "$GITHUB_ENV" | |
| echo "APPLE_TEAM_ID=$APPLE_TEAM_ID_RAW" >> "$GITHUB_ENV" | |
| echo "macOS signing: using Apple Developer certificate" | |
| else | |
| # Ad-hoc signing: codesign -s "-" | |
| # tauri.conf.json already sets signingIdentity:"-", | |
| # this env var takes precedence to be explicit. | |
| echo "APPLE_SIGNING_IDENTITY=-" >> "$GITHUB_ENV" | |
| echo "macOS signing: ad-hoc (no Apple Developer account configured)" | |
| fi | |
| - name: Build & upload artifacts | |
| uses: tauri-apps/tauri-action@v0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tagName: ${{ needs.release.outputs.tag }} | |
| releaseName: Ghost ${{ needs.release.outputs.tag }} | |
| releaseDraft: false | |
| prerelease: false | |
| tauriScript: bun run tauri | |
| args: --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # ANDROID BUILD — APK for aarch64 (covers 95%+ devices) | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| build-android: | |
| name: Android (aarch64) | |
| needs: release | |
| if: needs.release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: write | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.release.outputs.tag }} | |
| - uses: ./.github/actions/stub-pro | |
| # ── Java 17 (required by Android Gradle) ── | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| # ── Android SDK + NDK ── | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Install Android SDK packages | |
| run: | | |
| yes | sdkmanager --licenses > /dev/null 2>&1 || true | |
| sdkmanager --install \ | |
| "platforms;android-34" \ | |
| "ndk;27.0.12077973" \ | |
| "build-tools;34.0.0" | |
| echo "NDK_HOME=$ANDROID_HOME/ndk/27.0.12077973" >> "$GITHUB_ENV" | |
| # ── Rust with Android targets ── | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android | |
| - uses: Mozilla-Actions/sccache-action@v0.0.9 | |
| # ── Gradle cache ── | |
| - name: Cache Gradle | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: gradle-${{ runner.os }}- | |
| # ── Bun + frontend ── | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install frontend deps | |
| run: bun install --frozen-lockfile | |
| # ── Initialize & build ── | |
| - name: Initialize Android project | |
| run: bun run tauri android init | |
| - name: Build Android APK | |
| run: bun run tauri android build --target aarch64 --apk | |
| # ── Detect keystore availability ── | |
| - name: Detect Android keystore | |
| env: | |
| KEYSTORE_RAW: ${{ secrets.ANDROID_KEYSTORE }} | |
| run: | | |
| if [ -n "$KEYSTORE_RAW" ]; then | |
| echo "HAS_KEYSTORE=true" >> "$GITHUB_ENV" | |
| else | |
| echo "HAS_KEYSTORE=false" >> "$GITHUB_ENV" | |
| fi | |
| # ── Sign APK (if keystore secrets configured) ── | |
| - name: Sign APK | |
| if: env.HAS_KEYSTORE == 'true' | |
| env: | |
| ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }} | |
| KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| echo "$ANDROID_KEYSTORE" | base64 --decode > /tmp/ghost-release.keystore | |
| APK=$(find src-tauri/gen/android -name "*.apk" -type f | head -1) | |
| echo "Found APK: $APK" | |
| if [ -z "$APK" ]; then | |
| echo "::error::No APK found!" | |
| exit 1 | |
| fi | |
| "$ANDROID_HOME/build-tools/34.0.0/apksigner" sign \ | |
| --ks /tmp/ghost-release.keystore \ | |
| --ks-pass "env:KEYSTORE_PASSWORD" \ | |
| --ks-key-alias "$KEY_ALIAS" \ | |
| --key-pass "env:KEY_PASSWORD" \ | |
| "$APK" | |
| "$ANDROID_HOME/build-tools/34.0.0/apksigner" verify --verbose "$APK" | |
| rm -f /tmp/ghost-release.keystore | |
| # ── Upload to release ── | |
| - name: Upload APK to release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.release.outputs.version }}" | |
| TAG="${{ needs.release.outputs.tag }}" | |
| APK=$(find src-tauri/gen/android -name "*.apk" -type f | head -1) | |
| if [ -z "$APK" ]; then | |
| echo "::error::No APK found to upload!" | |
| exit 1 | |
| fi | |
| DEST="Ghost_${VERSION}_android-aarch64.apk" | |
| cp "$APK" "$DEST" | |
| gh release upload "$TAG" "$DEST" --clobber | |
| - name: Upload workflow artifact | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: ghost-android-aarch64 | |
| path: Ghost_*.apk | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # iOS BUILD — always produces an unsigned .xcarchive (no Apple | |
| # Developer account required). When Apple secrets ARE configured, | |
| # additionally exports a signed .ipa for direct distribution. | |
| # | |
| # Background: iOS has NO equivalent of macOS ad-hoc signing. | |
| # Every signed IPA needs a paid Apple Developer account ($99/yr). | |
| # However, an unsigned .xcarchive can be sideloaded by users via | |
| # AltStore / Sideloadly / their own free Apple ID in Xcode. | |
| # | |
| # Strategy: | |
| # 1. tauri ios init → generate Xcode project | |
| # 2. xcodebuild archive CODE_SIGNING_REQUIRED=NO → .xcarchive | |
| # 3. Zip and upload .xcarchive (usable by all users) | |
| # 4. IF Apple secrets configured → export signed .ipa + upload | |
| # | |
| # To enable signed IPA, configure these repository secrets: | |
| # APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, | |
| # APPLE_SIGNING_IDENTITY, APPLE_ID, APPLE_PASSWORD, | |
| # APPLE_TEAM_ID, APPLE_PROVISIONING_PROFILE | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| build-ios: | |
| name: iOS (aarch64) | |
| needs: release | |
| if: needs.release.outputs.released == 'true' | |
| runs-on: macos-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.release.outputs.tag }} | |
| - uses: ./.github/actions/stub-pro | |
| # ── Rust with iOS target ── | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-apple-ios | |
| - uses: Mozilla-Actions/sccache-action@v0.0.9 | |
| # ── Bun + frontend ── | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install frontend deps | |
| run: bun install --frozen-lockfile | |
| # Detect whether Apple signing secrets are configured. | |
| # secrets context is NOT allowed in step `if:` or `run:` expressions, | |
| # so we expose it as an env var and check via shell test. | |
| - name: Detect Apple certificate | |
| env: | |
| APPLE_CERT_RAW: ${{ secrets.APPLE_CERTIFICATE }} | |
| run: | | |
| if [ -n "$APPLE_CERT_RAW" ]; then | |
| echo "HAS_APPLE_CERT=true" >> "$GITHUB_ENV" | |
| else | |
| echo "HAS_APPLE_CERT=false" >> "$GITHUB_ENV" | |
| fi | |
| # ── Generate Xcode project ── | |
| - name: Initialize iOS project | |
| run: bun run tauri ios init | |
| # ── Build unsigned .xcarchive (no Apple account required) ── | |
| # tauri ios build requires signing; we go directly to xcodebuild | |
| # with CODE_SIGNING_REQUIRED=NO so CI passes without any cert. | |
| # Reference: https://github.com/tauri-apps/tauri/issues/14940 | |
| - name: Build unsigned xcarchive | |
| run: | | |
| APP_NAME="ghost" | |
| SCHEME="${APP_NAME}_iOS" | |
| PROJECT="src-tauri/gen/apple/${APP_NAME}.xcodeproj" | |
| ARCHIVE_PATH="src-tauri/gen/apple/build/App.xcarchive" | |
| xcodebuild archive \ | |
| -project "$PROJECT" \ | |
| -scheme "$SCHEME" \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -configuration Release \ | |
| -destination "generic/platform=iOS" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| | xcpretty || true | |
| ls -la "src-tauri/gen/apple/build/" || true | |
| # ── Upload unsigned .xcarchive (users can sideload via AltStore) ── | |
| - name: Upload xcarchive to release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.release.outputs.version }}" | |
| TAG="${{ needs.release.outputs.tag }}" | |
| ARCHIVE="src-tauri/gen/apple/build/App.xcarchive" | |
| if [ ! -d "$ARCHIVE" ]; then | |
| echo "::warning::xcarchive not found, skipping xcarchive upload" | |
| exit 0 | |
| fi | |
| DEST="Ghost_${VERSION}_ios-aarch64.xcarchive.zip" | |
| ditto -c -k --sequesterRsrc --keepParent "$ARCHIVE" "$DEST" | |
| gh release upload "$TAG" "$DEST" --clobber | |
| echo "Uploaded unsigned xcarchive: $DEST" | |
| echo "Users can sideload via AltStore/Sideloadly or sign with their own Apple Developer account." | |
| # ── Install Apple certs (only if configured) ── | |
| - name: Install Apple certificates | |
| if: env.HAS_APPLE_CERT == 'true' | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_PROVISIONING_PROFILE: ${{ secrets.APPLE_PROVISIONING_PROFILE }} | |
| run: | | |
| KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| CERT_PATH="$RUNNER_TEMP/certificate.p12" | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH" | |
| security import "$CERT_PATH" \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -A -t cert -f pkcs12 \ | |
| -k "$KEYCHAIN_PATH" | |
| security set-key-partition-list -S apple-tool:,apple: \ | |
| -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" | |
| if [ -n "$APPLE_PROVISIONING_PROFILE" ]; then | |
| PP_PATH="$RUNNER_TEMP/profile.mobileprovision" | |
| echo "$APPLE_PROVISIONING_PROFILE" | base64 --decode > "$PP_PATH" | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| PP_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin \ | |
| <<< "$(security cms -D -i "$PP_PATH")" 2>/dev/null || true) | |
| if [ -n "$PP_UUID" ]; then | |
| cp "$PP_PATH" \ | |
| ~/Library/MobileDevice/Provisioning\ Profiles/"$PP_UUID".mobileprovision | |
| fi | |
| fi | |
| rm -f "$CERT_PATH" | |
| # ── Export signed IPA from the .xcarchive (only if certs configured) ── | |
| - name: Export signed IPA | |
| if: env.HAS_APPLE_CERT == 'true' | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| ARCHIVE_PATH="src-tauri/gen/apple/build/App.xcarchive" | |
| EXPORT_PATH="src-tauri/gen/apple/build/ipa-export" | |
| # Write ExportOptions.plist for ad-hoc distribution | |
| cat > /tmp/ExportOptions.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>method</key> | |
| <string>ad-hoc</string> | |
| <key>teamID</key> | |
| <string>${APPLE_TEAM_ID}</string> | |
| <key>signingStyle</key> | |
| <string>automatic</string> | |
| <key>stripSwiftSymbols</key> | |
| <true/> | |
| <key>compileBitcode</key> | |
| <false/> | |
| </dict> | |
| </plist> | |
| EOF | |
| xcodebuild -exportArchive \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -exportPath "$EXPORT_PATH" \ | |
| -exportOptionsPlist /tmp/ExportOptions.plist \ | |
| | xcpretty || true | |
| # ── Upload signed IPA to release (only if signing succeeded) ── | |
| - name: Upload IPA to release | |
| if: env.HAS_APPLE_CERT == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.release.outputs.version }}" | |
| TAG="${{ needs.release.outputs.tag }}" | |
| IPA=$(find src-tauri/gen/apple/build/ipa-export -name "*.ipa" -type f 2>/dev/null | head -1) | |
| if [ -n "$IPA" ]; then | |
| DEST="Ghost_${VERSION}_ios-aarch64.ipa" | |
| cp "$IPA" "$DEST" | |
| gh release upload "$TAG" "$DEST" --clobber | |
| echo "Uploaded signed IPA: $DEST" | |
| else | |
| echo "::warning::IPA export failed or not found. xcarchive was already uploaded." | |
| fi | |
| # ── Upload xcarchive as workflow artifact too ── | |
| - name: Upload workflow artifact | |
| uses: actions/upload-artifact@v6 | |
| if: always() | |
| with: | |
| name: ghost-ios-aarch64-xcarchive | |
| path: Ghost_*.xcarchive.zip | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| - name: Cleanup keychain | |
| if: always() | |
| run: security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true |