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 --omit=optional - 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"