diff --git a/.github/workflows/ios-release.yml b/.github/workflows/ios-release.yml new file mode 100644 index 0000000..fcb4ace --- /dev/null +++ b/.github/workflows/ios-release.yml @@ -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 diff --git a/scripts/build-ios-ipa.sh b/scripts/build-ios-ipa.sh index f35a50b..30c6b9a 100755 --- a/scripts/build-ios-ipa.sh +++ b/scripts/build-ios-ipa.sh @@ -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" \ @@ -61,6 +71,7 @@ xcodebuild archive \ -destination 'generic/platform=iOS' \ -archivePath "$ARCHIVE" \ -allowProvisioningUpdates \ + ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | tail -2 echo "▸ Exporting development-signed .ipa…" @@ -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.