From e83358054cb07f5ad61d155127659b647dd8f800 Mon Sep 17 00:00:00 2001 From: Ayushya Amitabh Date: Wed, 7 Oct 2026 19:04:39 -0400 Subject: [PATCH] CI: signed iOS build and TestFlight upload on Release tags Co-Authored-By: Claude Sonnet 5.5 --- .gitea/scripts/ios_ci_signing.rb | 26 +++++++++ .gitea/workflows/ios.yml | 94 ++++++++++++++++++++++++++++++++ ios/ExportOptions.plist | 23 ++++++++ ios/Runner/Info.plist | 2 + 4 files changed, 145 insertions(+) create mode 100644 .gitea/scripts/ios_ci_signing.rb create mode 100644 .gitea/workflows/ios.yml create mode 100644 ios/ExportOptions.plist diff --git a/.gitea/scripts/ios_ci_signing.rb b/.gitea/scripts/ios_ci_signing.rb new file mode 100644 index 0000000..f79606b --- /dev/null +++ b/.gitea/scripts/ios_ci_signing.rb @@ -0,0 +1,26 @@ +# CI-only: switches the Release configuration of the Runner and ShareExtension +# targets to manual signing with the App Store profiles. The committed project +# stays on Automatic signing so local development is unaffected; this edit +# happens only inside the CI checkout. Run from the repo root: +# ruby .gitea/scripts/ios_ci_signing.rb +require "xcodeproj" + +TEAM = "Q3JLTAG9PV" +PROFILES = { + "Runner" => "NooProfile", + "ShareExtension" => "NooShareSheetProfile", +}.freeze + +project = Xcodeproj::Project.open("ios/Runner.xcodeproj") +PROFILES.each do |target_name, profile| + target = project.targets.find { |t| t.name == target_name } or abort("no target #{target_name}") + target.build_configurations.select { |c| c.name == "Release" }.each do |config| + settings = config.build_settings + settings["CODE_SIGN_STYLE"] = "Manual" + settings["DEVELOPMENT_TEAM"] = TEAM + settings["CODE_SIGN_IDENTITY"] = "Apple Distribution" + settings["CODE_SIGN_IDENTITY[sdk=iphoneos*]"] = "Apple Distribution" + settings["PROVISIONING_PROFILE_SPECIFIER"] = profile + end +end +project.save diff --git a/.gitea/workflows/ios.yml b/.gitea/workflows/ios.yml new file mode 100644 index 0000000..3caed23 --- /dev/null +++ b/.gitea/workflows/ios.yml @@ -0,0 +1,94 @@ +name: Build iOS + +# Same trigger as the Play Store bundle (release.yml): a real release tag. +# git tag Release-1.0.0 && git push origin Release-1.0.0 +# Builds a signed .ipa, attaches it to the Gitea release, and uploads it to +# TestFlight. Needs a Mac runner registered with the `macos` label (act_runner +# in host mode) with Xcode installed; Linux runners cannot build iOS. +on: + push: + tags: + - "Release-*" + +jobs: + build: + runs-on: macos + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Check tag matches pubspec version + run: | + VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //' | cut -d'+' -f1) + if [ "${GITHUB_REF_NAME}" != "Release-${VERSION}" ]; then + echo "Tag ${GITHUB_REF_NAME} does not match pubspec version ${VERSION}" >&2 + exit 1 + fi + + - name: Install dependencies + run: flutter pub get + + - name: Analyze + run: flutter analyze + + - name: Install signing certificate and profiles + # A throwaway keychain keeps the distribution key off the runner's + # login keychain; it is deleted again in the cleanup step. + env: + CERT_P12_BASE64: ${{ secrets.IOS_DIST_CERT_P12_BASE64 }} + CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }} + APP_PROFILE_BASE64: ${{ secrets.IOS_APP_PROFILE_BASE64 }} + EXT_PROFILE_BASE64: ${{ secrets.IOS_EXT_PROFILE_BASE64 }} + run: | + KEYCHAIN="${{ runner.temp }}/ci-signing.keychain-db" + KEYCHAIN_PASSWORD=$(uuidgen) + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" + security set-keychain-settings -lut 21600 "$KEYCHAIN" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" + echo "$CERT_P12_BASE64" | base64 -d > "${{ runner.temp }}/dist.p12" + security import "${{ runner.temp }}/dist.p12" -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN" + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" + security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"') + rm -f "${{ runner.temp }}/dist.p12" + + PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles" + mkdir -p "$PROFILES_DIR" + echo "$APP_PROFILE_BASE64" | base64 -d > "$PROFILES_DIR/NooProfile.mobileprovision" + echo "$EXT_PROFILE_BASE64" | base64 -d > "$PROFILES_DIR/NooShareSheetProfile.mobileprovision" + + - name: Switch Release config to manual signing + run: ruby .gitea/scripts/ios_ci_signing.rb + + - name: Build signed IPA + run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist + + - name: Rename IPA to Noo-.ipa + run: | + VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //' | cut -d'+' -f1) + mkdir -p release + cp build/ios/ipa/*.ipa "release/Noo-${VERSION}.ipa" + echo "IPA_PATH=release/Noo-${VERSION}.ipa" >> "$GITHUB_ENV" + + - name: Upload to TestFlight + env: + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} + ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }} + run: | + mkdir -p "$HOME/.appstoreconnect/private_keys" + echo "$ASC_KEY_P8_BASE64" | base64 -d > "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8" + xcrun altool --upload-app -f "$IPA_PATH" -t ios \ + --apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID" + + - name: Cleanup signing material + if: always() + run: | + security delete-keychain "${{ runner.temp }}/ci-signing.keychain-db" || true + rm -f "$HOME/Library/MobileDevice/Provisioning Profiles/NooProfile.mobileprovision" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/NooShareSheetProfile.mobileprovision" + rm -rf "$HOME/.appstoreconnect/private_keys" diff --git a/ios/ExportOptions.plist b/ios/ExportOptions.plist new file mode 100644 index 0000000..c01f601 --- /dev/null +++ b/ios/ExportOptions.plist @@ -0,0 +1,23 @@ + + + + + method + app-store-connect + teamID + Q3JLTAG9PV + signingStyle + manual + signingCertificate + Apple Distribution + provisioningProfiles + + dev.ayushya.noo + NooProfile + dev.ayushya.noo.ShareExtension + NooShareSheetProfile + + uploadSymbols + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index eabd0ab..3d447d4 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -86,5 +86,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + ITSAppUsesNonExemptEncryption +