diff --git a/.github/workflows/circuits.yml b/.github/workflows/circuits.yml index eff278f44..dc41e1462 100644 --- a/.github/workflows/circuits.yml +++ b/.github/workflows/circuits.yml @@ -1,21 +1,12 @@ name: Circuits CI on: - push: - branches: - - dev - - main - - openpassportv2 - paths: - - "circuits/**" - - "common/**" pull_request: branches: - dev + - staging - main - - openpassportv2 paths: - "circuits/**" - - "common/**" jobs: run_circuit_tests: if: github.event.pull_request.draft == false diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 2cc5cad9d..cbad83f0b 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -1,15 +1,9 @@ name: Contracts CI on: - push: - branches: - - dev - - main - paths: - - "contracts/**" - - "common/**" pull_request: branches: - dev + - staging - main paths: - "contracts/**" diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index a8b92f84a..54133ec98 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -30,6 +30,7 @@ env: permissions: contents: write pull-requests: write + id-token: write on: workflow_dispatch: @@ -116,6 +117,8 @@ jobs: echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV" echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" + + - name: Set up Xcode if: inputs.platform != 'android' uses: maxim-lobanov/setup-xcode@v1 @@ -520,12 +523,17 @@ jobs: with: app_path: ${{ env.APP_PATH }} - - name: Commit updated build number + - name: Open PR for iOS build number bump if: ${{ !env.ACT && success() }} - uses: ./.github/actions/push-changes + uses: peter-evans/create-pull-request@v6 with: - commit_message: "incrementing ios build number for version ${{ env.VERSION }}" - commit_paths: "./app/version.json" + title: "chore: bump iOS build for ${{ env.VERSION }}" + body: "Automated bump of iOS build number by CI" + commit-message: "chore: incrementing ios build number for version ${{ env.VERSION }} [github action]" + branch: ci/bump-ios-build-${{ github.run_id }} + base: staging + add-paths: | + app/version.json - name: Monitor cache usage if: always() @@ -564,6 +572,68 @@ jobs: - uses: actions/checkout@v4 if: inputs.platform != 'ios' + - uses: 'google-github-actions/auth@v2' + with: + project_id: 'plucky-tempo-454713-r0' + workload_identity_provider: 'projects/852920390127/locations/global/workloadIdentityPools/gh-self/providers/github-by-repos' + service_account: "self-xyz@plucky-tempo-454713-r0.iam.gserviceaccount.com" + # Fail fast: set up JDK for keytool and verify Android secrets early + - name: Setup Java environment + if: inputs.platform != 'ios' + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: ${{ env.JAVA_VERSION }} + + - name: Decode Android Secrets + if: inputs.platform != 'ios' + run: | + echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }} + + - name: Verify Android Secrets + if: inputs.platform != 'ios' + run: | + # Verify Google Cloud auth via Workload Identity Federation (ADC) + if [ -z "$GOOGLE_APPLICATION_CREDENTIALS" ] || [ ! -f "$GOOGLE_APPLICATION_CREDENTIALS" ]; then + echo "❌ Error: GOOGLE_APPLICATION_CREDENTIALS not set or file missing. Ensure google-github-actions/auth ran." + exit 1 + fi + # Verify keystore file exists and is valid + if [ ! -f "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" ]; then + echo "❌ Error: Keystore file was not created successfully" + exit 1 + fi + # Try to verify the keystore with the provided password + if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >/dev/null 2>&1; then + echo "❌ Error: Invalid keystore password" + exit 1 + fi + # Verify the key alias exists + if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" >/dev/null 2>&1; then + echo "❌ Error: Key alias '${{ secrets.ANDROID_KEY_ALIAS }}' not found in keystore" + exit 1 + fi + # Verify the key password + if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" -keypass "${{ secrets.ANDROID_KEY_PASSWORD }}" >/dev/null 2>&1; then + echo "❌ Error: Invalid key password" + exit 1 + fi + + # Detect keystore type and export for later steps + KEYSTORE_TYPE=$(keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" 2>/dev/null | awk -F': ' '/Keystore type:/ {print $2; exit}') + if [ -z "$KEYSTORE_TYPE" ]; then + echo "❌ Error: Unable to determine keystore type" + exit 1 + fi + echo "ANDROID_KEYSTORE_TYPE=$KEYSTORE_TYPE" >> "$GITHUB_ENV" + echo "Detected keystore type: $KEYSTORE_TYPE" + + # Ensure the alias holds a PrivateKeyEntry (required for signing) + if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" -keypass "${{ secrets.ANDROID_KEY_PASSWORD }}" | grep -q "Entry type: PrivateKeyEntry"; then + echo "❌ Error: Alias '${{ secrets.ANDROID_KEY_ALIAS }}' is not a PrivateKeyEntry" + exit 1 + fi + echo "✅ All Android secrets verified successfully!" - name: Read and sanitize Node.js version shell: bash run: | @@ -652,12 +722,6 @@ jobs: workspace: ${{ env.WORKSPACE }} # android specific steps - - name: Setup Java environment - if: inputs.platform != 'ios' - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: ${{ env.JAVA_VERSION }} - name: Setup Android SDK if: inputs.platform != 'ios' @@ -693,47 +757,7 @@ jobs: run: | echo "org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8" >> ${{ env.APP_PATH }}/android/gradle.properties - - name: Decode Android Secrets - if: inputs.platform != 'ios' - run: | - echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }} - echo "${{ secrets.ANDROID_PLAY_STORE_JSON_KEY_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.ANDROID_PLAY_STORE_JSON_KEY_PATH }} - # run secrets check after keytool has been setup - - name: Verify Android Secrets - if: inputs.platform != 'ios' - run: | - # Verify Play Store JSON key base64 secret exists and is valid - if [ -z "${{ secrets.ANDROID_PLAY_STORE_JSON_KEY_BASE64 }}" ]; then - echo "❌ Error: Play Store JSON key base64 secret cannot be empty" - exit 1 - fi - # Verify the base64 can be decoded - if ! echo "${{ secrets.ANDROID_PLAY_STORE_JSON_KEY_BASE64 }}" | base64 --decode >/dev/null 2>&1; then - echo "❌ Error: Invalid Play Store JSON key base64 format" - exit 1 - fi - # Verify keystore file exists and is valid - if [ ! -f "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" ]; then - echo "❌ Error: Keystore file was not created successfully" - exit 1 - fi - # Try to verify the keystore with the provided password - if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >/dev/null 2>&1; then - echo "❌ Error: Invalid keystore password" - exit 1 - fi - # Verify the key alias exists - if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" >/dev/null 2>&1; then - echo "❌ Error: Key alias '${{ secrets.ANDROID_KEY_ALIAS }}' not found in keystore" - exit 1 - fi - # Verify the key password - if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" -keypass "${{ secrets.ANDROID_KEY_PASSWORD }}" >/dev/null 2>&1; then - echo "❌ Error: Invalid key password" - exit 1 - fi - echo "✅ All Android secrets verified successfully!" - name: Build and upload to Google Play Internal Testing if: inputs.platform != 'ios' @@ -744,7 +768,6 @@ jobs: ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_PACKAGE_NAME: ${{ secrets.ANDROID_PACKAGE_NAME }} - ANDROID_PLAY_STORE_JSON_KEY_PATH: ${{ env.APP_PATH }}${{ env.ANDROID_PLAY_STORE_JSON_KEY_PATH }} NODE_OPTIONS: "--max-old-space-size=8192" SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }} SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }} @@ -785,12 +808,17 @@ jobs: with: app_path: ${{ env.APP_PATH }} - - name: Commit updated build version + - name: Open PR for Android build number bump if: ${{ !env.ACT && success() }} - uses: ./.github/actions/push-changes + uses: peter-evans/create-pull-request@v6 with: - commit_message: "incrementing android build version for version ${{ env.VERSION }}" - commit_paths: "./app/version.json" + title: "chore: bump Android build for ${{ env.VERSION }}" + body: "Automated bump of Android build number by CI" + commit-message: "chore: incrementing android build version for version ${{ env.VERSION }} [github action]" + branch: ci/bump-android-build-${{ github.run_id }} + base: staging + add-paths: | + app/version.json - name: Monitor cache usage if: always() @@ -882,37 +910,20 @@ jobs: echo "ℹ️ Version already up to date or no version field in version.json" fi - - name: Commit and push version files - run: | - cd ${{ github.workspace }} - - # Configure git - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Check if there are any changes to commit - if git diff --quiet app/version.json app/package.json yarn.lock 2>/dev/null; then - echo "No changes to version files, skipping commit" - else - # Stage the changes - git add app/version.json app/package.json yarn.lock 2>/dev/null || true - - # Create commit message based on which platforms were deployed - COMMIT_MSG="chore: update version files after" - if [ "${{ needs.build-ios.result }}" = "success" ] && [ "${{ needs.build-android.result }}" = "success" ]; then - COMMIT_MSG="$COMMIT_MSG iOS and Android deployment" - elif [ "${{ needs.build-ios.result }}" = "success" ]; then - COMMIT_MSG="$COMMIT_MSG iOS deployment" - else - COMMIT_MSG="$COMMIT_MSG Android deployment" - fi - COMMIT_MSG="$COMMIT_MSG [skip ci]" - - # Commit and push - git commit -m "$COMMIT_MSG" - git push - echo "✅ Committed version file changes" - fi + - name: Open PR to update version files + uses: peter-evans/create-pull-request@v6 + with: + title: "chore: update version files after deployment" + body: | + Automated update of version files after successful deployment. + Includes updates to `app/version.json`, `app/package.json`, and `yarn.lock`. + commit-message: "chore: update version files after deployment [skip ci]" + branch: ci/update-version-${{ github.run_id }} + base: staging + add-paths: | + app/version.json + app/package.json + yarn.lock # Create git tags after successful deployment create-release-tags: diff --git a/README.md b/README.md index 874616ec2..36de48442 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ These guides provide comprehensive context for AI-assisted development with Chat We are actively looking for contributors. Please check the [open issues](https://github.com/selfxyz/self/issues) if you don't know were to start! We offer bounties for significant contributions. +> **Important:** Please open your pull request from the `staging` branch. Pull requests from other branches will be automatically closed. + + ## Contact us [Contact us](https://t.me/selfprotocolbuilder) on telegram for feedback or questions. diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 3ad892572..8d2e35c64 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -355,8 +355,9 @@ platform :android do "ANDROID_KEY_ALIAS", "ANDROID_KEY_PASSWORD", "ANDROID_PACKAGE_NAME", - "ANDROID_PLAY_STORE_JSON_KEY_PATH", ] + # Only require JSON key path when not running in CI (local development) + required_env_vars << "ANDROID_PLAY_STORE_JSON_KEY_PATH" if local_development Fastlane::Helpers.verify_env_vars(required_env_vars) @@ -375,19 +376,22 @@ platform :android do target_platform = options[:track] == "production" ? "Google Play" : "Internal Testing" should_upload = Fastlane::Helpers.should_upload_app(target_platform) - validate_play_store_json_key( - json_key: ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"], - ) + # Validate JSON key only in local development; CI uses Workload Identity Federation (ADC) + if local_development + validate_play_store_json_key( + json_key: ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"], + ) + end Fastlane::Helpers.with_retry(max_retries: 3, delay: 10) do gradle( task: "clean bundleRelease --stacktrace --info", project_dir: "android/", properties: { - "android.injected.signing.store.file" => ENV["ANDROID_KEYSTORE_PATH"], - "android.injected.signing.store.password" => ENV["ANDROID_KEYSTORE_PASSWORD"], - "android.injected.signing.key.alias" => ENV["ANDROID_KEY_ALIAS"], - "android.injected.signing.key.password" => ENV["ANDROID_KEY_PASSWORD"], + "MYAPP_UPLOAD_STORE_FILE" => ENV["ANDROID_KEYSTORE_PATH"], + "MYAPP_UPLOAD_STORE_PASSWORD" => ENV["ANDROID_KEYSTORE_PASSWORD"], + "MYAPP_UPLOAD_KEY_ALIAS" => ENV["ANDROID_KEY_ALIAS"], + "MYAPP_UPLOAD_KEY_PASSWORD" => ENV["ANDROID_KEY_PASSWORD"] == "EMPTY" ? "" : ENV["ANDROID_KEY_PASSWORD"], }, ) end @@ -399,16 +403,38 @@ platform :android do else if should_upload begin - upload_to_play_store( + upload_options = { track: options[:track], - json_key: ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"], package_name: ENV["ANDROID_PACKAGE_NAME"], skip_upload_changelogs: true, skip_upload_images: true, skip_upload_screenshots: true, track_promote_release_status: "completed", aab: android_aab_path, - ) + } + # In local development, use the JSON key file; in CI rely on ADC + if local_development + upload_options[:json_key] = ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"] + else + # In CI, try to use ADC credentials file directly + adc_creds_path = ENV["GOOGLE_APPLICATION_CREDENTIALS"] + if adc_creds_path && File.exist?(adc_creds_path) + UI.message("🔑 Using ADC credentials file: #{adc_creds_path}") + begin + # Try passing the credentials file content as json_key_data + creds_content = File.read(adc_creds_path) + upload_options[:json_key_data] = creds_content + rescue => e + UI.error("Failed to read ADC credentials: #{e.message}") + # Fallback: let supply try to use ADC automatically + UI.message("🔄 Falling back to automatic ADC detection") + end + else + UI.error("❌ ADC credentials not found at: #{adc_creds_path}") + end + end + + upload_to_play_store(upload_options) rescue => e if e.message.include?("forbidden") || e.message.include?("403") || e.message.include?("insufficientPermissions") UI.error("❌ Play Store upload failed: Insufficient permissions") diff --git a/app/version.json b/app/version.json index b969cf758..e13cc77cc 100644 --- a/app/version.json +++ b/app/version.json @@ -1,10 +1,10 @@ { "ios": { - "build": 163, + "build": 165, "lastDeployed": "2025-08-08T22:35:10Z" }, "android": { - "build": 85, - "lastDeployed": "2025-08-08T15:13:41Z" + "build": 86, + "lastDeployed": "2025-08-20T21:30:05Z" } }