diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 7de814b..6563d8c 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -8,11 +8,15 @@ jobs: deploy-playstore: name: Build & Deploy to Play Store runs-on: self-hosted - if: false steps: - uses: actions/checkout@v4 + - name: Enable Nix flakes + run: | + mkdir -p ~/.config/nix + echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf + - name: Prepare Keystore env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} @@ -24,15 +28,8 @@ jobs: exit 1 fi - - name: Build App Bundle + - name: Build & Deploy to Play Store env: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - run: nix develop --command task build-android-bundle - - # Play Store upload disabled for now - # - name: Upload to Play Store - # env: - # PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }} - # run: | - # echo "$PLAY_STORE_CONFIG_JSON" > play-store-key.json - # # nix develop --command fvm flutter pub run supply ... + PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }} + run: nix develop --command task deploy-android-bundle diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18b6d38..d849a72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: forbidden-files-hook name: check for forbidden home-directory files language: system - entry: task check-hygiene + entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command task check-hygiene' pass_filenames: false always_run: true - id: dart-check diff --git a/Taskfile.yml b/Taskfile.yml index 27c5c3c..bff3da3 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -268,6 +268,15 @@ tasks: cmds: - ANDROID_HOME=${ANDROID_HOME:-$HOME/Android/Sdk} fvm flutter build apk --release --no-pub | grep -Ev "was tree-shaken|Tree-shaking can be disabled" + deploy-android-bundle: + desc: Build release AAB and upload to Play Store internal track + deps: [build-android-bundle] + preconditions: + - sh: test -n "$PLAY_STORE_CONFIG_JSON" + msg: "PLAY_STORE_CONFIG_JSON is not set" + cmds: + - python3 scripts/deploy_playstore.py + build-android-bundle: desc: Build a release App Bundle (AAB) deps: [_preflight, _android-sdk-check, _pub-get, generate-changelog] diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 984cce3..85bcfea 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -53,6 +53,9 @@ android { isMinifyEnabled = false isShrinkResources = false + ndk { + debugSymbolLevel = "FULL" + } } } } diff --git a/flake.nix b/flake.nix index b093a96..a9feb0c 100644 --- a/flake.nix +++ b/flake.nix @@ -81,7 +81,10 @@ curl jq sqlite - python3 # used by stalwart-dev/start to pick random ports + # python3 base + Google Play API client (for scripts/deploy_playstore.py) + (python3.withPackages (ps: with ps; [ + google-api-python-client + ])) # used by stalwart-dev/start and deploy_playstore.py fgj # Codeberg/Forgejo CLI (like gh for GitHub) ]); diff --git a/scripts/deploy_playstore.py b/scripts/deploy_playstore.py new file mode 100755 index 0000000..8173f72 --- /dev/null +++ b/scripts/deploy_playstore.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Upload an Android App Bundle to the Google Play Store internal track.""" + +import json +import os +import sys + +from google.oauth2 import service_account +from googleapiclient.discovery import build +from googleapiclient.http import MediaFileUpload + +PACKAGE_NAME = "de.sharedinbox.mua" +AAB_PATH = "build/app/outputs/bundle/release/app-release.aab" +TRACK = "internal" + + +def main(): + config_json = os.environ.get("PLAY_STORE_CONFIG_JSON") + if not config_json: + print("Error: PLAY_STORE_CONFIG_JSON environment variable not set", file=sys.stderr) + sys.exit(1) + + if not os.path.exists(AAB_PATH): + print(f"Error: AAB not found at {AAB_PATH}", file=sys.stderr) + sys.exit(1) + + creds = service_account.Credentials.from_service_account_info( + json.loads(config_json), + scopes=["https://www.googleapis.com/auth/androidpublisher"], + ) + + service = build("androidpublisher", "v3", credentials=creds) + + edit = service.edits().insert(body={}, packageName=PACKAGE_NAME).execute() + edit_id = edit["id"] + + media = MediaFileUpload(AAB_PATH, mimetype="application/octet-stream", resumable=True) + bundle = ( + service.edits() + .bundles() + .upload(packageName=PACKAGE_NAME, editId=edit_id, media_body=media) + .execute() + ) + version_code = bundle["versionCode"] + print(f"Uploaded AAB, version code: {version_code}") + + service.edits().tracks().update( + packageName=PACKAGE_NAME, + editId=edit_id, + track=TRACK, + body={"releases": [{"versionCodes": [version_code], "status": "completed"}]}, + ).execute() + + service.edits().commit(packageName=PACKAGE_NAME, editId=edit_id).execute() + print(f"Deployed version {version_code} to {TRACK} track") + + +if __name__ == "__main__": + main() diff --git a/t.sh b/t.sh deleted file mode 100755 index bb9758c..0000000 --- a/t.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Helper script to generate a production Android Keystore. -# Run this from the project root. - -# Define the path relative to the project root -KEYSTORE_PATH="android/app/upload-keystore.jks" - -echo "Generating keystore at: $KEYSTORE_PATH" - -keytool -genkey -v \ - -keystore "$KEYSTORE_PATH" \ - -alias upload \ - -keyalg RSA \ - -keysize 2048 \ - -validity 10000 - -echo "" -echo "Done." -echo "1. Remember your password!" -echo "2. Back up $KEYSTORE_PATH safely." -echo "3. Do NOT commit the .jks file or passwords to git." diff --git a/to-playstore.md b/to-playstore.md index 25409ab..a7aa25a 100644 --- a/to-playstore.md +++ b/to-playstore.md @@ -12,7 +12,7 @@ Data Protection blabla page! * **Taskfile:** Added `task build-android-bundle` to generate the `.aab` file. * **CI Workflow:** Created `.forgejo/workflows/release.yml` which triggers on merge to `main`. -## 2. What you need to do next + ### A. Create the Keystore Run the helper script I created for you: ```bash @@ -26,6 +26,7 @@ Go to **Settings > Actions > Secrets** in your Codeberg repo and add: 2. **`ANDROID_KEYSTORE_PASSWORD`**: Your keystore password. 3. **`PLAY_STORE_CONFIG_JSON`**: The JSON key from your Google Play Service Account. + ### C. First Manual Upload Google Play requires the **very first upload** to be done manually through the web console: 1. Generate your keystore using `./t.sh`. @@ -37,6 +38,9 @@ Google Play requires the **very first upload** to be done manually through the w 3. Upload the resulting `.aab` from `build/app/outputs/bundle/release/app-release.aab` to the Play Console (Internal Testing or Production track). 4. This "locks in" your signing key. +## 2. What you need to do next + + ## 3. Firebase Test Lab Once you have the Service Account JSON, you can add a task to `Taskfile.yml` to run automated tests on real devices: ```yaml