diff --git a/.github/workflows/build-ios-release.yml b/.github/workflows/build-ios-release.yml new file mode 100644 index 00000000..b88d1552 --- /dev/null +++ b/.github/workflows/build-ios-release.yml @@ -0,0 +1,142 @@ +name: Build and Publish iOS Release + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + build: + name: Build, attach, and upload signed IPA + runs-on: macos-latest + environment: testflight + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }} + PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_APPSTORE_PROVISIONING_PROFILE_BASE64 }} + APPSTORE_CONNECT_API_KEY_BASE64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }} + APPSTORE_CONNECT_API_KEY_ID: ${{ secrets.APPSTORE_CONNECT_API_KEY_ID }} + APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }} + steps: + - name: Check out release tag + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Install JavaScript dependencies + run: npm ci + + - name: Build shared web app + run: npm run generate + + - name: Sync Capacitor iOS project + run: npx cap sync ios + + - name: Install CocoaPods dependencies + working-directory: ios/App + run: pod install --deployment + + - name: Set unique App Store build number + run: echo "BUILD_NUMBER=$((1000 + GITHUB_RUN_NUMBER))" >> "$GITHUB_ENV" + + - name: Install signing certificate and provisioning profile + env: + KEYCHAIN_PASSWORD: ${{ github.run_id }}-${{ github.run_attempt }} + run: | + certificate_path="$RUNNER_TEMP/distribution.p12" + profile_path="$RUNNER_TEMP/app-store.mobileprovision" + keychain_path="$RUNNER_TEMP/app-signing.keychain-db" + + printf '%s' "$BUILD_CERTIFICATE_BASE64" | base64 -D > "$certificate_path" + printf '%s' "$PROVISIONING_PROFILE_BASE64" | base64 -D > "$profile_path" + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + security set-keychain-settings -lut 21600 "$keychain_path" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + security import "$certificate_path" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path" + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" + security list-keychain -d user -s "$keychain_path" + + profile_uuid="$(security cms -D -i "$profile_path" | plutil -extract UUID raw -)" + mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" + cp "$profile_path" "$HOME/Library/MobileDevice/Provisioning Profiles/$profile_uuid.mobileprovision" + echo "PROFILE_UUID=$profile_uuid" >> "$GITHUB_ENV" + + - name: Archive signed iOS app + working-directory: ios/App + run: | + xcodebuild -quiet \ + -workspace App.xcworkspace \ + -scheme App \ + -configuration Release \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath "$RUNNER_TEMP/Audiobookshelf.xcarchive" \ + CODE_SIGN_STYLE=Manual \ + CODE_SIGN_IDENTITY="Apple Distribution" \ + PROVISIONING_PROFILE_SPECIFIER="$PROFILE_UUID" \ + CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \ + archive + + echo "Signed iOS archive succeeded." + + - name: Export App Store IPA + run: | + cat > "$RUNNER_TEMP/ExportOptions.plist" < + + + + method + app-store-connect + signingStyle + manual + teamID + 7UFJ7D8V6A + provisioningProfiles + + com.audiobookshelf.app + $PROFILE_UUID + + + + PLIST + + xcodebuild -quiet -exportArchive \ + -archivePath "$RUNNER_TEMP/Audiobookshelf.xcarchive" \ + -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \ + -exportPath "$RUNNER_TEMP/export" + + echo "App Store IPA export succeeded." + + - name: Store IPA artifact + uses: actions/upload-artifact@v4 + with: + name: audiobookshelf-ios-release + path: ${{ runner.temp }}/export/Audiobookshelf.ipa + if-no-files-found: error + + - name: Attach IPA to GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$RELEASE_TAG" "$RUNNER_TEMP/export/Audiobookshelf.ipa" --clobber + + - name: Upload IPA to TestFlight + run: | + mkdir -p "$HOME/.appstoreconnect/private_keys" + printf '%s' "$APPSTORE_CONNECT_API_KEY_BASE64" | base64 -D > \ + "$HOME/.appstoreconnect/private_keys/AuthKey_$APPSTORE_CONNECT_API_KEY_ID.p8" + xcrun iTMSTransporter \ + -m upload \ + -assetFile "$RUNNER_TEMP/export/Audiobookshelf.ipa" \ + -apiKey "$APPSTORE_CONNECT_API_KEY_ID" \ + -apiIssuer "$APPSTORE_CONNECT_ISSUER_ID"