chore(deps): bump llama-cpp-2 from 0.1.136 to 0.1.138 in /src-tauri #81
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] | |
| paths-ignore: | |
| - 'website/**' | |
| - '*.md' | |
| - 'branding/**' | |
| - 'LICENSE' | |
| - '.github/workflows/website-deploy.yml' | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - 'website/**' | |
| - '*.md' | |
| - 'branding/**' | |
| - 'LICENSE' | |
| - '.github/workflows/website-deploy.yml' | |
| 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 | |
| CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse | |
| 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-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - 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 | |
| - name: Frontend type-check & build | |
| run: bun run build | |
| # ─── Security audit (no compilation, ~30s) ─────────────────────── | |
| audit: | |
| name: Audit | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: taiki-e/install-action@cargo-audit | |
| - name: Security audit | |
| working-directory: src-tauri | |
| run: cargo audit | |
| # ─── Test + Clippy (heavy compilation, sccache + mold) ────────── | |
| test: | |
| name: Test & Clippy | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| RUSTFLAGS: "-C link-arg=-fuse-ld=mold" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -qq | |
| 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 | |
| # ── Cache Cargo registry for test job ── | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| key: cargo-registry-test-${{ hashFiles('src-tauri/Cargo.lock') }} | |
| restore-keys: cargo-registry-test- | |
| - 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@v6 | |
| 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 | |
| features: vulkan | |
| - label: macOS ARM64 | |
| runner: macos-latest | |
| target: aarch64-apple-darwin | |
| bundles: dmg | |
| features: metal,accelerate | |
| - label: macOS x64 | |
| runner: macos-15-large | |
| target: x86_64-apple-darwin | |
| bundles: dmg | |
| features: metal,accelerate | |
| - label: Linux x64 | |
| runner: ubuntu-24.04 | |
| target: x86_64-unknown-linux-gnu | |
| bundles: deb,appimage,rpm | |
| features: vulkan | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.release.outputs.tag }} | |
| - name: Install Linux dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update -qq | |
| 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 \ | |
| libvulkan-dev glslc | |
| - name: Set mold linker for Linux | |
| if: runner.os == 'Linux' | |
| run: echo "RUSTFLAGS=-C link-arg=-fuse-ld=mold" >> "$GITHUB_ENV" | |
| # ── Vulkan SDK (Windows): required at compile time for llama-cpp-sys-2. | |
| # Sets VULKAN_SDK env var and adds glslangValidator to PATH. | |
| - name: Install Vulkan SDK (Windows) | |
| if: runner.os == 'Windows' | |
| uses: jakoch/install-vulkan-sdk-action@v1 | |
| with: | |
| vulkan_version: latest | |
| install_runtime: true | |
| cache: true | |
| stripdown: true | |
| - 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 | |
| # ── Cache Cargo registry (avoids re-downloading crate metadata) ── | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| key: cargo-registry-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }} | |
| restore-keys: cargo-registry-${{ matrix.target }}- | |
| - name: Install frontend deps | |
| run: bun install | |
| # ── Windows: fix long-path C1041 PDB error + CRT linkage. | |
| # | |
| # ROOT CAUSE: llama-cpp-sys-2's cmake ExternalProject for | |
| # vulkan-shaders-gen creates deeply nested TryCompile paths. | |
| # The PDB path reaches ~267 chars, exceeding Windows MAX_PATH (260). | |
| # mspdbsrv.exe uses legacy Win32 APIs and cannot open the PDB, | |
| # producing the misleading C1041 "cannot open program database" | |
| # error. Neither /FS, /Z7, nor Ninja fix the path length issue. | |
| # | |
| # FIX: Set CARGO_TARGET_DIR to a short path (D:\b) to reduce | |
| # the nested build path from ~267 to ~238 chars (under MAX_PATH). | |
| # | |
| # CRT linkage: /MD forces dynamic CRT to match Rust's default. | |
| # CMAKE_MSVC_RUNTIME_LIBRARY is forwarded to cmake automatically. | |
| # /Z7 embeds debug info in .obj (no PDB) as defense-in-depth. | |
| # | |
| # Refs: sccache#1012 (strips /FS), Windows MAX_PATH limitation. | |
| - name: Configure Windows build environment | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| echo "CARGO_TARGET_DIR=D:\\b" >> "$GITHUB_ENV" | |
| echo "CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL" >> "$GITHUB_ENV" | |
| echo "CMAKE_GENERATOR=Ninja" >> "$GITHUB_ENV" | |
| echo "CFLAGS=/MD /Z7" >> "$GITHUB_ENV" | |
| echo "CXXFLAGS=/MD /Z7" >> "$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 }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| 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 }} --features ${{ matrix.features }} | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # ANDROID BUILD — signed release APK for aarch64 (covers 95%+ devices) | |
| # | |
| # Strategy: Gradle-integrated signing via keystore.properties. | |
| # build.gradle.kts is committed with conditional signing config. | |
| # When ANDROID_KEY_BASE64 secret exists → signed APK. | |
| # When no secret → unsigned APK (still uploaded as artifact). | |
| # | |
| # Required secrets (for signed builds): | |
| # ANDROID_KEY_BASE64 — base64-encoded .jks keystore file | |
| # ANDROID_KEY_PASSWORD — keystore + key password | |
| # ANDROID_KEY_ALIAS — key alias (e.g. "upload") | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| build-android: | |
| name: Android (aarch64) | |
| needs: release | |
| if: needs.release.outputs.released == 'true' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| RUSTC_WRAPPER: "sccache" | |
| # gemm-f16 (candle dependency) requires ARM fp16 instructions | |
| # Without this, compilation fails: "error: instruction requires: fullfp16" | |
| # See: https://github.com/sarah-quinones/gemm/issues/31 | |
| CARGO_TARGET_AARCH64_LINUX_ANDROID_RUSTFLAGS: "-Ctarget-feature=+fp16,+fhm" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.release.outputs.tag }} | |
| # ── Java 17 (required by Android Gradle Plugin) ── | |
| - uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: "21" | |
| # ── 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-36" \ | |
| "ndk;27.3.13750724" \ | |
| "build-tools;36.0.0" | |
| echo "NDK_HOME=$ANDROID_HOME/ndk/27.3.13750724" >> "$GITHUB_ENV" | |
| # ── Rust with Android target ── | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-linux-android | |
| - uses: Mozilla-Actions/sccache-action@v0.0.9 | |
| # ── Gradle + Cargo registry cache ── | |
| - name: Cache Gradle & Cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| key: android-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'src-tauri/Cargo.lock') }} | |
| restore-keys: android-${{ runner.os }}- | |
| # ── Bun + frontend ── | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install frontend deps | |
| run: bun install | |
| # ── Setup Android signing (Gradle-integrated) ── | |
| # Creates keystore.properties read by build.gradle.kts signingConfigs. | |
| # If secrets are not configured, build produces unsigned APK. | |
| - name: Setup Android signing | |
| env: | |
| ANDROID_KEY_BASE64: ${{ secrets.ANDROID_KEY_BASE64 }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| run: | | |
| if [ -n "$ANDROID_KEY_BASE64" ]; then | |
| echo "Setting up Gradle-integrated Android signing..." | |
| base64 -d <<< "$ANDROID_KEY_BASE64" > "$RUNNER_TEMP/upload-keystore.jks" | |
| cd src-tauri/gen/android | |
| echo "password=$ANDROID_KEY_PASSWORD" > keystore.properties | |
| echo "keyAlias=$ANDROID_KEY_ALIAS" >> keystore.properties | |
| echo "storeFile=$RUNNER_TEMP/upload-keystore.jks" >> keystore.properties | |
| cd "$GITHUB_WORKSPACE" | |
| echo "ANDROID_SIGNED=true" >> "$GITHUB_ENV" | |
| echo "Android signing configured (Gradle will sign release APK)" | |
| else | |
| echo "ANDROID_SIGNED=false" >> "$GITHUB_ENV" | |
| echo "::notice::No ANDROID_KEY_BASE64 secret found — building unsigned APK" | |
| fi | |
| # ── Build Android APK ── | |
| # gen/android/ is committed with build.gradle.kts signing config, | |
| # so we skip `tauri android init` to avoid overwriting it. | |
| - name: Build Android APK | |
| run: bun run tauri android build --target aarch64 | |
| # ── 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 }}" | |
| # Find the release APK (signed or unsigned depending on secrets) | |
| APK=$(find src-tauri/gen/android/app/build/outputs/apk -name "*.apk" -path "*/release/*" -type f | head -1) | |
| if [ -z "$APK" ]; then | |
| echo "::error::No release APK found!" | |
| exit 1 | |
| fi | |
| SUFFIX="" | |
| if [ "$ANDROID_SIGNED" != "true" ]; then | |
| SUFFIX="-unsigned" | |
| fi | |
| DEST="Ghost_${VERSION}_android-aarch64${SUFFIX}.apk" | |
| cp "$APK" "$DEST" | |
| echo "Uploading: $DEST ($(du -h "$DEST" | cut -f1))" | |
| 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 | |
| - name: Cleanup signing artifacts | |
| if: always() | |
| run: | | |
| rm -f "$RUNNER_TEMP/upload-keystore.jks" | |
| rm -f src-tauri/gen/android/keystore.properties | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # 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@v6 | |
| with: | |
| ref: ${{ needs.release.outputs.tag }} | |
| # ── 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 | |
| # 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 |