Skip to content

chore(deps): bump llama-cpp-2 from 0.1.136 to 0.1.138 in /src-tauri #81

chore(deps): bump llama-cpp-2 from 0.1.136 to 0.1.138 in /src-tauri

chore(deps): bump llama-cpp-2 from 0.1.136 to 0.1.138 in /src-tauri #81

Workflow file for this run

# 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