diff --git a/.github/workflows/build-ios-testflight.yml b/.github/workflows/build-ios-testflight.yml new file mode 100644 index 000000000..40151b56a --- /dev/null +++ b/.github/workflows/build-ios-testflight.yml @@ -0,0 +1,266 @@ +name: Hash Wallet iOS TestFlight build + +# Phase 2 of iOS CI: full signed build that uploads to TestFlight. +# Phase 1 (build-ios-sim.yml) validates the Mac runner with no signing — +# run that first if TestFlight blows up unexpectedly. +# +# Required Gitea Actions secrets (all account/app credentials): +# APPLE_TEAM_ID — 10-char (D8PL9F7X33) +# APPLE_KEY_ID — 10-char (CHK6B69G58) +# APPLE_ISSUER_ID — UUID +# APPLE_API_KEY_P8_BASE64 — base64 of AuthKey_.p8 +# APPLE_DIST_CERT_P12_BASE64 — base64 of distribution.p12 +# APPLE_DIST_CERT_P12_PASSWORD — password set during openssl pkcs12 export +# APPLE_PROVISIONING_PROFILE_BASE64 — base64 of Hash_Wallet.mobileprovision +# APPLE_KEYCHAIN_PASSWORD — any string, used to create the temp keychain +# TROCADOR_* — reused from Linux/Android + +on: + # Don't auto-trigger on every push — TestFlight uploads are slow and use + # build numbers. Manual trigger only; later we can add a workflow_dispatch + # input for build number or auto-bump. + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + build: + runs-on: macos-latest + env: + APP_IOS_TYPE: cakewallet + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Show toolchain + run: | + set -x + flutter --version + xcodebuild -version + pod --version + uname -m + + # ---- External prebuilt deps ----------------------------------------- + - name: Fetch prebuilt torch_dart + run: | + set -x -e + pushd scripts + rm -rf torch_dart torch_dart.tar.gz + wget -q https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz -O torch_dart.tar.gz + mkdir torch_dart + tar -xzf torch_dart.tar.gz -C torch_dart + rm torch_dart.tar.gz + popd + + - name: Fetch prebuilt reown_flutter + run: | + set -x -e + pushd scripts + rm -rf reown_flutter reown_flutter.tar.gz + wget -q https://github.com/cake-tech/reown_flutter/releases/download/v0.0.4/reown_flutter-v0.0.4.tar.gz -O reown_flutter.tar.gz + mkdir reown_flutter + tar -xzf reown_flutter.tar.gz -C reown_flutter + rm reown_flutter.tar.gz + popd + + - name: Clone BitBox Flutter + run: | + set -x -e + pushd scripts + ./build_bitbox_flutter.sh + popd + + - name: Fetch prebuilt monero_c bundle + run: | + set -x -e + ./scripts/prepare_moneroc.sh + MONERO_C_TAG=$(cd scripts/monero_c && git describe --tags) + mkdir -p "scripts/monero_c/release/$MONERO_C_TAG" + pushd "scripts/monero_c/release/$MONERO_C_TAG" + wget -q https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip + unzip -q release-bundle.zip + rm release-bundle.zip + popd + + # ---- Configure: pubspec.yaml, Info.plist, etc. ---------------------- + - name: Run iOS configure (cakewallet profile) + run: | + set -x -e + pushd scripts/ios + source ./app_env.sh cakewallet + ./app_config.sh + popd + + # ---- Secrets --------------------------------------------------------- + - name: Generate per-module secrets.g.dart files (empty defaults) + run: dart run tool/generate_new_secrets.dart + + - name: Inject Trocador affiliate secrets + env: + TROCADOR_API_KEY: ${{ secrets.TROCADOR_API_KEY }} + TROCADOR_MONERO_API_KEY: ${{ secrets.TROCADOR_MONERO_API_KEY }} + TROCADOR_EXCHANGE_MARKUP: ${{ secrets.TROCADOR_EXCHANGE_MARKUP }} + run: | + sed -i '' \ + -e "s|const trocadorApiKey = '';|const trocadorApiKey = '${TROCADOR_API_KEY}';|" \ + -e "s|const trocadorMoneroApiKey = '';|const trocadorMoneroApiKey = '${TROCADOR_MONERO_API_KEY}';|" \ + -e "s|const trocadorExchangeMarkup = '';|const trocadorExchangeMarkup = '${TROCADOR_EXCHANGE_MARKUP:-1}';|" \ + lib/.secrets.g.dart + + # ---- Apple signing setup -------------------------------------------- + - name: Create temp keychain + import distribution cert + env: + APPLE_DIST_CERT_P12_BASE64: ${{ secrets.APPLE_DIST_CERT_P12_BASE64 }} + APPLE_DIST_CERT_P12_PASSWORD: ${{ secrets.APPLE_DIST_CERT_P12_PASSWORD }} + APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} + run: | + set -e + KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db" + # Create + unlock dedicated keychain so we don't touch the user's login keychain + security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" # 6 hours + security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + # Add to user search list so codesign can find it + security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"//g') + # Import the .p12 + P12="$RUNNER_TEMP/dist.p12" + echo "$APPLE_DIST_CERT_P12_BASE64" | base64 -d > "$P12" + security import "$P12" -k "$KEYCHAIN_PATH" -P "$APPLE_DIST_CERT_P12_PASSWORD" \ + -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + # Sanity-check: list identities the keychain offers + security find-identity -v -p codesigning "$KEYCHAIN_PATH" + rm -f "$P12" + + - name: Install provisioning profile + parse name/UUID for ExportOptions + env: + APPLE_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }} + run: | + set -e + PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles" + mkdir -p "$PROFILES_DIR" + PROFILE="$RUNNER_TEMP/Hash_Wallet.mobileprovision" + echo "$APPLE_PROVISIONING_PROFILE_BASE64" | base64 -d > "$PROFILE" + + # Parse the .mobileprovision to get its UUID + Name (CMS-decoded plist). + PROFILE_UUID=$(security cms -D -i "$PROFILE" | plutil -extract UUID raw -o - -) + PROFILE_NAME=$(security cms -D -i "$PROFILE" | plutil -extract Name raw -o - -) + echo "Profile UUID: $PROFILE_UUID" + echo "Profile name: $PROFILE_NAME" + echo "PROFILE_UUID=$PROFILE_UUID" >> "$GITHUB_ENV" + echo "PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV" + + # Xcode looks profiles up by UUID filename. + cp "$PROFILE" "$PROFILES_DIR/$PROFILE_UUID.mobileprovision" + ls -la "$PROFILES_DIR" | head -10 + + - name: Install App Store Connect API key for altool + env: + APPLE_API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }} + APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} + run: | + set -e + mkdir -p "$HOME/.appstoreconnect/private_keys" + echo "$APPLE_API_KEY_P8_BASE64" | base64 -d > "$HOME/.appstoreconnect/private_keys/AuthKey_${APPLE_KEY_ID}.p8" + ls -la "$HOME/.appstoreconnect/private_keys/" + + # ---- Flutter init + codegen + pod install --------------------------- + - name: Initialize Flutter SDK (iOS precache) + run: | + flutter --version + flutter precache --ios --no-linux --no-android --no-macos --no-windows --no-fuchsia --no-web || true + + - name: Build generated code + run: bash model_generator.sh + + - name: Generate localization + run: dart run tool/generate_localization.dart + + - name: pod install + run: | + cd ios + pod install --repo-update + + # ---- Generate ExportOptions.plist + build IPA ----------------------- + - name: Write ExportOptions.plist + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + cat > ios/ExportOptions.plist < + + + + method + app-store + teamID + ${APPLE_TEAM_ID} + signingStyle + manual + signingCertificate + Apple Distribution + provisioningProfiles + + com.suchsoftware.hashwallet + ${PROFILE_NAME} + + uploadBitcode + + uploadSymbols + + destination + export + stripSwiftSymbols + + + + EOF + cat ios/ExportOptions.plist + + - name: Build IPA + run: | + set -x -e + flutter build ipa --release \ + --dart-define-from-file=env.json \ + --export-options-plist=ios/ExportOptions.plist + ls -la build/ios/ipa/ + + - name: Upload IPA artifact + uses: actions/upload-artifact@v3 + with: + name: hash-wallet-ios-${{ github.sha }} + path: build/ios/ipa/*.ipa + retention-days: 30 + + # ---- Upload to TestFlight ------------------------------------------- + - name: Upload to TestFlight via altool + env: + APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + run: | + set -e + IPA=$(ls build/ios/ipa/*.ipa | head -1) + echo "Uploading $IPA to TestFlight..." + xcrun altool --upload-app \ + --type ios \ + --file "$IPA" \ + --apiKey "$APPLE_KEY_ID" \ + --apiIssuer "$APPLE_ISSUER_ID" + + # ---- Cleanup keychain so we don't leak it across builds ------------- + - name: Cleanup + if: always() + env: + APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} + run: | + set +e + KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db" + security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null + rm -rf "$HOME/.appstoreconnect/private_keys" + rm -f "$HOME/Library/MobileDevice/Provisioning Profiles"/*.mobileprovision + true