2 Commits
Author SHA1 Message Date
Ayushya AmitabhandClaude Sonnet 5.5 0ea98363de CI: split release tags - RC-Android-* (APK), RC-iOS-* (iOS), Release-* (both)
Build iOS / build (push) Failing after 1m55s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 19:15:29 -04:00
Ayushya AmitabhandClaude Sonnet 5.5 ca4afb2ed7 CI: signed iOS build and TestFlight upload on Release tags
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 19:04:39 -04:00
7 changed files with 164 additions and 10 deletions
+5 -3
View File
@@ -113,9 +113,11 @@ in this order: a local `android/key.properties` (gitignored — points at a
gitignored keystore file, e.g. `android/app/release-keystore.jks`), then gitignored keystore file, e.g. `android/app/release-keystore.jks`), then
CI env vars (`RELEASE_KEYSTORE_PATH`/`_PASSWORD`, `RELEASE_KEY_ALIAS`/ CI env vars (`RELEASE_KEYSTORE_PATH`/`_PASSWORD`, `RELEASE_KEY_ALIAS`/
`_PASSWORD`, set from the same repo secrets by both `.gitea/workflows/ `_PASSWORD`, set from the same repo secrets by both `.gitea/workflows/
build.yml`, triggered by `RC*` tags and producing a sideloadable APK, and build.yml`, triggered by `RC-Android-*` tags and producing a sideloadable
`.gitea/workflows/release.yml`, triggered by `Release-*` tags and producing APK, and `.gitea/workflows/release.yml`, triggered by `Release-*` tags and
the `.aab` Play Console wants), then falls back to the debug key if neither producing the `.aab` Play Console wants; `.gitea/workflows/ios.yml` builds
and uploads the signed iOS app to TestFlight on `RC-iOS-*` and `Release-*`
tags, on a self-hosted macOS runner), then falls back to the debug key if neither
is configured. As long as the same dedicated release keystore backs both is configured. As long as the same dedicated release keystore backs both
`key.properties` locally and the Gitea secrets, local release builds and `key.properties` locally and the Gitea secrets, local release builds and
CI-built release APKs share one signature, so `adb install -r` works CI-built release APKs share one signature, so `adb install -r` works
+26
View File
@@ -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
+6 -5
View File
@@ -1,12 +1,13 @@
name: Build APK name: Build APK
# Deliberately not "on every push" - only when a release-candidate tag # Deliberately not "on every push" - only when an Android release-candidate
# (RC1, RC2, RC2026.1, ...) is pushed. Tag the commit you want built: # tag (RC-Android-1, RC-Android-2, ...) is pushed. Tag the commit you want built:
# git tag RC1 && git push origin RC1 # git tag RC-Android-1 && git push origin RC-Android-1
# iOS has its own RC-iOS-* tags (ios.yml); Release-* builds both platforms.
on: on:
push: push:
tags: tags:
- "RC*" - "RC-Android-*"
jobs: jobs:
build: build:
@@ -72,7 +73,7 @@ jobs:
# talks to this same Gitea instance automatically via the # talks to this same Gitea instance automatically via the
# `github.token`/`github.server_url` context Gitea Actions # `github.token`/`github.server_url` context Gitea Actions
# provides for compatibility. Release is named/tagged after # provides for compatibility. Release is named/tagged after
# whatever tag triggered this run (e.g. "RC1"). # whatever tag triggered this run (e.g. "RC-Android-1").
uses: akkuman/gitea-release-action@v1 uses: akkuman/gitea-release-action@v1
with: with:
files: ${{ env.APK_PATH }} files: ${{ env.APK_PATH }}
+100
View File
@@ -0,0 +1,100 @@
name: Build iOS
# Tag scheme (shared with the Android workflows):
# Release-<version> -> release.yml (Play bundle) AND this workflow
# RC-Android-<n> -> build.yml (APK) only
# RC-iOS-<n> -> this workflow only
# git tag RC-iOS-1 && git push origin RC-iOS-1
# Builds a signed .ipa and uploads it to TestFlight. Needs a Mac runner
# registered with the `macos` label (gitea-runner in host mode) with Xcode
# installed; Linux runners cannot build iOS.
on:
push:
tags:
- "Release-*"
- "RC-iOS-*"
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
# Only Release-* tags carry a version; RC-iOS-* tags are free-form.
if: startsWith(github.ref_name, 'Release-')
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-<version>.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"
+2 -2
View File
@@ -3,7 +3,7 @@ name: Build App Bundle
# Deliberately not "on every push" - only when a real release tag (Release-1.0.0, # Deliberately not "on every push" - only when a real release tag (Release-1.0.0,
# Release-1.2.3, ...) is pushed. Tag the commit you want built: # Release-1.2.3, ...) is pushed. Tag the commit you want built:
# git tag Release-1.0.0 && git push origin Release-1.0.0 # git tag Release-1.0.0 && git push origin Release-1.0.0
# Unlike the RC* flow (build.yml), this builds the .aab Play Console wants, # Unlike the RC-Android-* flow (build.yml), this builds the .aab Play Console wants,
# not a sideloadable .apk. # not a sideloadable .apk.
on: on:
push: push:
@@ -56,7 +56,7 @@ jobs:
run: flutter analyze run: flutter analyze
- name: Decode release keystore - name: Decode release keystore
# Same key as the RC* APK builds, so a Play Store upload's signature # Same key as the RC-Android-* APK builds, so a Play Store upload's signature
# matches anything sideloaded from a release build. See # matches anything sideloaded from a release build. See
# android/app/build.gradle.kts. # android/app/build.gradle.kts.
run: echo "$RELEASE_KEYSTORE_BASE64" | base64 -d > "${{ runner.temp }}/release-keystore.jks" run: echo "$RELEASE_KEYSTORE_BASE64" | base64 -d > "${{ runner.temp }}/release-keystore.jks"
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>Q3JLTAG9PV</string>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>provisioningProfiles</key>
<dict>
<key>dev.ayushya.noo</key>
<string>NooProfile</string>
<key>dev.ayushya.noo.ShareExtension</key>
<string>NooShareSheetProfile</string>
</dict>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
+2
View File
@@ -86,5 +86,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
</dict> </dict>
</plist> </plist>