From 81ce1bb94b9fb14d0a6474225c3b5c81aebdb2c7 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:13:40 -0400 Subject: [PATCH 1/9] Add TestFlight CI/CD workflow and update signing config - Update bundle ID to com.brettonauerbach.stillpoint and set Team ID - Create ExportOptions.plist for App Store distribution - Add GitHub Actions workflow triggered by ios-v* tags - Add RELEASING.md with setup and release instructions Closes #51 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 104 +++++++++++++++++++++++++++ ios/ExportOptions.plist | 16 +++++ ios/RELEASING.md | 61 ++++++++++++++++ ios/project.yml | 4 +- 4 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ios-testflight.yml create mode 100644 ios/ExportOptions.plist create mode 100644 ios/RELEASING.md diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml new file mode 100644 index 00000000..f3872d3f --- /dev/null +++ b/.github/workflows/ios-testflight.yml @@ -0,0 +1,104 @@ +name: Build & Upload to TestFlight + +on: + push: + tags: + - 'ios-v*' + +concurrency: + group: ios-testflight + cancel-in-progress: false + +jobs: + build-and-upload: + runs-on: macos-15 + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install XcodeGen + run: brew install xcodegen + + - name: Generate Xcode project + working-directory: ios + run: xcodegen generate + + - name: Install Apple certificate and provisioning profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} + run: | + # Create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PASSWORD=$(openssl rand -hex 16) + + # Import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + + # Create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # Apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + + - name: Build archive + working-directory: ios + run: | + xcodebuild archive \ + -project StillPoint.xcodeproj \ + -scheme StillPoint \ + -configuration Release \ + -archivePath $RUNNER_TEMP/StillPoint.xcarchive \ + -destination 'generic/platform=iOS' \ + DEVELOPMENT_TEAM=T5UU4BP6AV \ + CODE_SIGN_STYLE=Automatic \ + -allowProvisioningUpdates + + - name: Export IPA + run: | + xcodebuild -exportArchive \ + -archivePath $RUNNER_TEMP/StillPoint.xcarchive \ + -exportOptionsPlist ios/ExportOptions.plist \ + -exportPath $RUNNER_TEMP/export \ + -allowProvisioningUpdates + + - name: Upload to TestFlight + env: + APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }} + APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }} + APPSTORE_API_PRIVATE_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }} + run: | + # Write API key to file + mkdir -p ~/.private_keys + echo "$APPSTORE_API_PRIVATE_KEY" > ~/.private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 + + xcrun notarytool store-credentials "AC_PASSWORD" \ + --apple-id "" \ + --team-id T5UU4BP6AV \ + --password "" 2>/dev/null || true + + xcrun altool --upload-app \ + --type ios \ + --file "$(find $RUNNER_TEMP/export -name '*.ipa')" \ + --apiKey "$APPSTORE_API_KEY_ID" \ + --apiIssuer "$APPSTORE_API_ISSUER_ID" + + - name: Clean up keychain and provisioning profile + if: always() + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 2>/dev/null || true + rm -f ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision diff --git a/ios/ExportOptions.plist b/ios/ExportOptions.plist new file mode 100644 index 00000000..d81d34a2 --- /dev/null +++ b/ios/ExportOptions.plist @@ -0,0 +1,16 @@ + + + + + method + app-store-connect + signingStyle + automatic + teamID + T5UU4BP6AV + uploadSymbols + + destination + upload + + diff --git a/ios/RELEASING.md b/ios/RELEASING.md new file mode 100644 index 00000000..29cfcb6b --- /dev/null +++ b/ios/RELEASING.md @@ -0,0 +1,61 @@ +# Releasing Still Point for iOS + +## Prerequisites + +The following GitHub repository secrets must be configured (Settings → Secrets and variables → Actions): + +| Secret | Description | How to get it | +|--------|-------------|---------------| +| `BUILD_CERTIFICATE_BASE64` | Base64-encoded `.p12` distribution certificate | Export from Keychain Access, then `base64 -i cert.p12 \| pbcopy` | +| `P12_PASSWORD` | Password used when exporting the `.p12` | The password you set during export | +| `BUILD_PROVISION_PROFILE_BASE64` | Base64-encoded `.mobileprovision` file | Download from developer.apple.com, then `base64 -i profile.mobileprovision \| pbcopy` | +| `APPSTORE_API_KEY_ID` | App Store Connect API Key ID | From appstoreconnect.apple.com → Users and Access → Integrations | +| `APPSTORE_API_ISSUER_ID` | App Store Connect API Issuer ID | Same page as above | +| `APPSTORE_API_PRIVATE_KEY` | Contents of the `.p8` API key file | Paste the full file contents including BEGIN/END lines | + +## Releasing to TestFlight + +1. Update the version in `ios/project.yml`: + ```yaml + MARKETING_VERSION: "1.1.0" + CURRENT_PROJECT_VERSION: 2 + ``` + +2. Commit the version bump: + ```bash + git add ios/project.yml + git commit -m "Bump iOS version to 1.1.0 (build 2)" + ``` + +3. Tag and push: + ```bash + git tag ios-v1.1.0 + git push origin main --tags + ``` + +4. The GitHub Actions workflow builds and uploads to TestFlight automatically. + +5. After Apple processes the build (~15 minutes), it appears in the TestFlight app on your device. + +## Version numbering + +- `MARKETING_VERSION` — the user-facing version (e.g., `1.0.0`, `1.1.0`) +- `CURRENT_PROJECT_VERSION` — the build number, must increment with every upload (e.g., `1`, `2`, `3`) +- Tag format: `ios-v{MARKETING_VERSION}` (e.g., `ios-v1.0.0`) + +## Submitting to the App Store + +The same build uploaded to TestFlight can be submitted to the App Store: + +1. Go to [appstoreconnect.apple.com](https://appstoreconnect.apple.com) → your app +2. Fill in the required metadata (screenshots, description, privacy policy URL) +3. Select the TestFlight build under the version +4. Click "Add for Review" + +## Troubleshooting + +**Build fails with signing error:** Verify the certificate hasn't expired and the provisioning profile includes the correct bundle ID (`com.brettonauerbach.stillpoint`). + +**Upload fails with authentication error:** Regenerate the App Store Connect API key and update the GitHub secrets. + +**Build number conflict:** `CURRENT_PROJECT_VERSION` must be unique per upload. Increment it even for re-uploads of the same marketing version. diff --git a/ios/project.yml b/ios/project.yml index 1a8c0a06..0229677a 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -30,10 +30,10 @@ targets: - Fonts/JetBrainsMono-Variable.ttf settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.stillpoint.app + PRODUCT_BUNDLE_IDENTIFIER: com.brettonauerbach.stillpoint MARKETING_VERSION: "1.0.0" CURRENT_PROJECT_VERSION: 1 - DEVELOPMENT_TEAM: "" + DEVELOPMENT_TEAM: T5UU4BP6AV CODE_SIGN_STYLE: Automatic SWIFT_VERSION: "5.9" INFOPLIST_KEY_UIApplicationSceneManifest_Generation: "YES" From ba2feebff7dd80aac2750a185ef3f3b0ce376382 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:17:04 -0400 Subject: [PATCH 2/9] Fix CR findings: bundle prefix, dead code, IPA validation - Update bundleIdPrefix to match new bundle ID - Remove dead notarytool credentials step - Add IPA path validation before upload - Simplify upload step to use altool directly Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 13 ++++++++----- ios/project.yml | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml index f3872d3f..9f64e996 100644 --- a/.github/workflows/ios-testflight.yml +++ b/.github/workflows/ios-testflight.yml @@ -86,14 +86,17 @@ jobs: mkdir -p ~/.private_keys echo "$APPSTORE_API_PRIVATE_KEY" > ~/.private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 - xcrun notarytool store-credentials "AC_PASSWORD" \ - --apple-id "" \ - --team-id T5UU4BP6AV \ - --password "" 2>/dev/null || true + # Find the IPA + IPA_PATH=$(find $RUNNER_TEMP/export -name '*.ipa' | head -1) + if [ -z "$IPA_PATH" ]; then + echo "Error: No IPA file found in $RUNNER_TEMP/export" + exit 1 + fi + echo "Uploading $IPA_PATH" xcrun altool --upload-app \ --type ios \ - --file "$(find $RUNNER_TEMP/export -name '*.ipa')" \ + --file "$IPA_PATH" \ --apiKey "$APPSTORE_API_KEY_ID" \ --apiIssuer "$APPSTORE_API_ISSUER_ID" diff --git a/ios/project.yml b/ios/project.yml index 0229677a..34f55f27 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -1,6 +1,6 @@ name: StillPoint options: - bundleIdPrefix: com.stillpoint + bundleIdPrefix: com.brettonauerbach.stillpoint deploymentTarget: iOS: "17.0" xcodeVersion: "15.0" From 4d1ab96260aa36c2f97f7976116907c356d29cb0 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:19:01 -0400 Subject: [PATCH 3/9] Clean up API key and scoped provisioning profile in CI - Remove API key file (~/.private_keys/) in cleanup step - Scope provisioning profile cleanup to the specific file installed Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml index 9f64e996..5985bee1 100644 --- a/.github/workflows/ios-testflight.yml +++ b/.github/workflows/ios-testflight.yml @@ -100,8 +100,10 @@ jobs: --apiKey "$APPSTORE_API_KEY_ID" \ --apiIssuer "$APPSTORE_API_ISSUER_ID" - - name: Clean up keychain and provisioning profile + - name: Clean up keychain, provisioning profile, and API key if: always() run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 2>/dev/null || true - rm -f ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision + rm -f $RUNNER_TEMP/build_pp.mobileprovision 2>/dev/null || true + rm -f ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision 2>/dev/null || true + rm -rf ~/.private_keys 2>/dev/null || true From d0d7054ac0ef5915973aa74927f9f3f7954d51c0 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:22:26 -0400 Subject: [PATCH 4/9] Replace deprecated altool with xcodebuild export+upload - Use xcodebuild -exportArchive with auth flags for direct upload - Remove separate Export IPA step (now combined with upload) - Fix bundleIdPrefix to be a true prefix (com.brettonauerbach) - Track provisioning profile install path explicitly Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 39 +++++++++++----------------- ios/project.yml | 2 +- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml index 5985bee1..8f3f5a06 100644 --- a/.github/workflows/ios-testflight.yml +++ b/.github/workflows/ios-testflight.yml @@ -36,6 +36,7 @@ jobs: PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db KEYCHAIN_PASSWORD=$(openssl rand -hex 16) + PP_INSTALL_PATH=~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision # Import certificate and provisioning profile from secrets echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH @@ -53,7 +54,7 @@ jobs: # Apply provisioning profile mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles - cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + cp "$PP_PATH" "$PP_INSTALL_PATH" - name: Build archive working-directory: ios @@ -68,37 +69,27 @@ jobs: CODE_SIGN_STYLE=Automatic \ -allowProvisioningUpdates - - name: Export IPA - run: | - xcodebuild -exportArchive \ - -archivePath $RUNNER_TEMP/StillPoint.xcarchive \ - -exportOptionsPlist ios/ExportOptions.plist \ - -exportPath $RUNNER_TEMP/export \ - -allowProvisioningUpdates - - - name: Upload to TestFlight + - name: Upload to App Store Connect env: APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }} APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }} APPSTORE_API_PRIVATE_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }} run: | - # Write API key to file + # Write API key to file for authentication mkdir -p ~/.private_keys echo "$APPSTORE_API_PRIVATE_KEY" > ~/.private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 - # Find the IPA - IPA_PATH=$(find $RUNNER_TEMP/export -name '*.ipa' | head -1) - if [ -z "$IPA_PATH" ]; then - echo "Error: No IPA file found in $RUNNER_TEMP/export" - exit 1 - fi - echo "Uploading $IPA_PATH" - - xcrun altool --upload-app \ - --type ios \ - --file "$IPA_PATH" \ - --apiKey "$APPSTORE_API_KEY_ID" \ - --apiIssuer "$APPSTORE_API_ISSUER_ID" + # Export and upload in one step using xcodebuild + # The ExportOptions.plist has destination=upload and method=app-store-connect + # which makes xcodebuild upload directly to App Store Connect / TestFlight + xcodebuild -exportArchive \ + -archivePath $RUNNER_TEMP/StillPoint.xcarchive \ + -exportOptionsPlist ios/ExportOptions.plist \ + -exportPath $RUNNER_TEMP/export \ + -allowProvisioningUpdates \ + -authenticationKeyPath ~/.private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 \ + -authenticationKeyID "$APPSTORE_API_KEY_ID" \ + -authenticationKeyIssuerID "$APPSTORE_API_ISSUER_ID" - name: Clean up keychain, provisioning profile, and API key if: always() diff --git a/ios/project.yml b/ios/project.yml index 34f55f27..01324386 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -1,6 +1,6 @@ name: StillPoint options: - bundleIdPrefix: com.brettonauerbach.stillpoint + bundleIdPrefix: com.brettonauerbach deploymentTarget: iOS: "17.0" xcodeVersion: "15.0" From 51f9565f5ed02d83432a3c89e170e71e6b3ae860 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:24:46 -0400 Subject: [PATCH 5/9] Fix keychain list preservation and clean up certificate file - Preserve existing keychains when adding temp keychain to search list - Remove decoded .p12 certificate in cleanup step Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml index 8f3f5a06..03825db9 100644 --- a/.github/workflows/ios-testflight.yml +++ b/.github/workflows/ios-testflight.yml @@ -50,7 +50,7 @@ jobs: # Import certificate to keychain security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH - security list-keychain -d user -s $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH $(security list-keychains -d user | tr -d '"') # Apply provisioning profile mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles @@ -95,6 +95,7 @@ jobs: if: always() run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 2>/dev/null || true + rm -f $RUNNER_TEMP/build_certificate.p12 2>/dev/null || true rm -f $RUNNER_TEMP/build_pp.mobileprovision 2>/dev/null || true rm -f ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision 2>/dev/null || true rm -rf ~/.private_keys 2>/dev/null || true From 026be4d7e100c477b2ad0c7e4eb88b4b7f1b088e Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:27:26 -0400 Subject: [PATCH 6/9] Remove redundant build settings from CI workflow Team ID and signing style are already set in project.yml. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml index 03825db9..b3a4ca04 100644 --- a/.github/workflows/ios-testflight.yml +++ b/.github/workflows/ios-testflight.yml @@ -65,8 +65,6 @@ jobs: -configuration Release \ -archivePath $RUNNER_TEMP/StillPoint.xcarchive \ -destination 'generic/platform=iOS' \ - DEVELOPMENT_TEAM=T5UU4BP6AV \ - CODE_SIGN_STYLE=Automatic \ -allowProvisioningUpdates - name: Upload to App Store Connect From 6db8f8d9f8acc22472d9450fdb2991f83c36614f Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:30:54 -0400 Subject: [PATCH 7/9] Use printf for multiline API key to preserve newlines Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ios-testflight.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios-testflight.yml b/.github/workflows/ios-testflight.yml index b3a4ca04..7e4a91a5 100644 --- a/.github/workflows/ios-testflight.yml +++ b/.github/workflows/ios-testflight.yml @@ -75,7 +75,7 @@ jobs: run: | # Write API key to file for authentication mkdir -p ~/.private_keys - echo "$APPSTORE_API_PRIVATE_KEY" > ~/.private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 + printf '%s\n' "$APPSTORE_API_PRIVATE_KEY" > ~/.private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 # Export and upload in one step using xcodebuild # The ExportOptions.plist has destination=upload and method=app-store-connect From 0ff22d581c398f8831ae981a322c321f358fd00f Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 17:52:24 -0400 Subject: [PATCH 8/9] Note P12 password location in RELEASING.md Co-Authored-By: Claude Opus 4.6 (1M context) --- ios/RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RELEASING.md b/ios/RELEASING.md index 29cfcb6b..c9447e5d 100644 --- a/ios/RELEASING.md +++ b/ios/RELEASING.md @@ -7,7 +7,7 @@ The following GitHub repository secrets must be configured (Settings → Secrets | Secret | Description | How to get it | |--------|-------------|---------------| | `BUILD_CERTIFICATE_BASE64` | Base64-encoded `.p12` distribution certificate | Export from Keychain Access, then `base64 -i cert.p12 \| pbcopy` | -| `P12_PASSWORD` | Password used when exporting the `.p12` | The password you set during export | +| `P12_PASSWORD` | Password used when exporting the `.p12` | Stored in the project root `.env` file as `P12_PASSWORD` | | `BUILD_PROVISION_PROFILE_BASE64` | Base64-encoded `.mobileprovision` file | Download from developer.apple.com, then `base64 -i profile.mobileprovision \| pbcopy` | | `APPSTORE_API_KEY_ID` | App Store Connect API Key ID | From appstoreconnect.apple.com → Users and Access → Integrations | | `APPSTORE_API_ISSUER_ID` | App Store Connect API Issuer ID | Same page as above | From 2ae9cc51a699960be16d947ce41c6d8655896dfb Mon Sep 17 00:00:00 2001 From: auerbachb Date: Mon, 30 Mar 2026 18:00:55 -0400 Subject: [PATCH 9/9] Fix CR findings: remove .env reference, scope tag push - Remove P12_PASSWORD .env storage reference from RELEASING.md - Push specific tag instead of --tags to avoid unintended publishes Co-Authored-By: Claude Opus 4.6 (1M context) --- ios/RELEASING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RELEASING.md b/ios/RELEASING.md index c9447e5d..c343b8c3 100644 --- a/ios/RELEASING.md +++ b/ios/RELEASING.md @@ -7,7 +7,7 @@ The following GitHub repository secrets must be configured (Settings → Secrets | Secret | Description | How to get it | |--------|-------------|---------------| | `BUILD_CERTIFICATE_BASE64` | Base64-encoded `.p12` distribution certificate | Export from Keychain Access, then `base64 -i cert.p12 \| pbcopy` | -| `P12_PASSWORD` | Password used when exporting the `.p12` | Stored in the project root `.env` file as `P12_PASSWORD` | +| `P12_PASSWORD` | Password used when exporting the `.p12` | The password you set during `.p12` export | | `BUILD_PROVISION_PROFILE_BASE64` | Base64-encoded `.mobileprovision` file | Download from developer.apple.com, then `base64 -i profile.mobileprovision \| pbcopy` | | `APPSTORE_API_KEY_ID` | App Store Connect API Key ID | From appstoreconnect.apple.com → Users and Access → Integrations | | `APPSTORE_API_ISSUER_ID` | App Store Connect API Issuer ID | Same page as above | @@ -30,7 +30,7 @@ The following GitHub repository secrets must be configured (Settings → Secrets 3. Tag and push: ```bash git tag ios-v1.1.0 - git push origin main --tags + git push origin ios-v1.1.0 ``` 4. The GitHub Actions workflow builds and uploads to TestFlight automatically.