Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .github/workflows/ios-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Builds a development-signed .ipa and attaches it to a GitHub release.
#
# Fires when you publish a release (the same release that carries the Android
# .apk), or manually via the Actions tab against an existing tag. The .ipa is
# the sideload/direct-install artifact — see README "Installing on iOS".
#
# ── One-time setup: add these repo secrets (Settings ▸ Secrets and variables ▸ Actions) ──
#
# IOS_CERT_P12_BASE64 Your "Apple Development" cert + private key, exported
# from Keychain Access as a .p12, then base64'd:
# base64 -i Certificates.p12 | pbcopy
# IOS_CERT_PASSWORD The password you set when exporting that .p12.
#
# ASC_API_KEY_P8_BASE64 An App Store Connect API key (.p8). Create at
# App Store Connect ▸ Users and Access ▸ Integrations ▸
# App Store Connect API ▸ "+", role: Developer. Then:
# base64 -i AuthKey_XXXXXX.p8 | pbcopy
# ASC_KEY_ID The key's Key ID (10 chars, shown next to the key).
# ASC_ISSUER_ID The Issuer ID (UUID, shown above the keys table).
#
# The API key lets xcodebuild -allowProvisioningUpdates register/fetch the
# development profiles for both the app and the ClowderWidget extension
# (including the App Group) without an interactive Apple account on the runner.

name: iOS .ipa release

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to attach the .ipa to (e.g. v2.7.3)"
required: true

permissions:
contents: write # upload release assets

jobs:
build-ipa:
runs-on: macos-15
steps:
- uses: actions/checkout@v4

- name: Select latest stable Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Build web bundle and sync to iOS
run: |
npm ci
npm run build
npx cap sync ios

- name: Import signing certificate into a temporary keychain
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.IOS_CERT_P12_BASE64 }}
p12-password: ${{ secrets.IOS_CERT_PASSWORD }}

- name: Write App Store Connect API key
env:
ASC_KEY_B64: ${{ secrets.ASC_API_KEY_P8_BASE64 }}
run: |
mkdir -p "$RUNNER_TEMP/asc"
echo "$ASC_KEY_B64" | base64 --decode > "$RUNNER_TEMP/asc/AuthKey.p8"

- name: Resolve target tag
id: tag
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
fi

- name: Build .ipa and attach to release
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_PATH: ${{ runner.temp }}/asc/AuthKey.p8
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
chmod +x scripts/build-ios-ipa.sh
# Web is already built above, so skip it here.
scripts/build-ios-ipa.sh --skip-web --release "${{ steps.tag.outputs.tag }}"

- name: Upload .ipa as a workflow artifact (fallback)
if: always()
uses: actions/upload-artifact@v4
with:
name: ClowderAndCrest-ipa
path: build/ios/export/*.ipa
if-no-files-found: ignore
12 changes: 12 additions & 0 deletions scripts/build-ios-ipa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ fi
rm -rf "$ARCHIVE" "$EXPORT_DIR"
mkdir -p "$BUILD_DIR"

# On a dev machine, -allowProvisioningUpdates works because Xcode is signed into
# the Apple account. In CI there is no logged-in account, so pass an App Store
# Connect API key when these env vars are set. The `${ARR[@]+...}` guard keeps
# the empty-array expansion safe under `set -u` on macOS's bash 3.2.
AUTH_ARGS=()
if [ -n "${ASC_KEY_ID:-}" ] && [ -n "${ASC_ISSUER_ID:-}" ] && [ -n "${ASC_KEY_PATH:-}" ]; then
echo "▸ Using App Store Connect API key ${ASC_KEY_ID} for provisioning"
AUTH_ARGS=(-authenticationKeyID "$ASC_KEY_ID" -authenticationKeyIssuerID "$ASC_ISSUER_ID" -authenticationKeyPath "$ASC_KEY_PATH")
fi

echo "▸ Archiving (Release, generic iOS device)…"
xcodebuild archive \
-project "$PROJECT" \
Expand All @@ -61,6 +71,7 @@ xcodebuild archive \
-destination 'generic/platform=iOS' \
-archivePath "$ARCHIVE" \
-allowProvisioningUpdates \
${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \
| tail -2

echo "▸ Exporting development-signed .ipa…"
Expand All @@ -69,6 +80,7 @@ xcodebuild -exportArchive \
-exportOptionsPlist "$EXPORT_OPTS" \
-exportPath "$EXPORT_DIR" \
-allowProvisioningUpdates \
${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \
| tail -2

# Xcode names the export "App.ipa" (after the product); rename to a versioned file.
Expand Down