diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 643fc2a..dfd6a30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,3 +110,8 @@ jobs: --generate-notes \ --title "MegaProxy ${GITHUB_REF_NAME}" fi + + - name: Upload Google Play internal draft + env: + SUPPLY_JSON_KEY_DATA: ${{ secrets.SUPPLY_JSON_KEY_DATA }} + run: bundle exec fastlane android play_release track:internal release_status:draft diff --git a/AGENTS.md b/AGENTS.md index 9db592c..e2f6012 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,6 +54,9 @@ branch names, credentials, signing material, or other secrets. - Release builds and signing are separate from PR CI. Release artifacts are created only through the dedicated release workflow/Fastlane lane. +- Google Play uploads use `SUPPLY_JSON_KEY_DATA` with the complete service-account JSON in both + local env and GitHub Actions secrets. The tag release workflow uploads an internal draft after + publishing GitHub artifacts; PR workflows must never receive this secret. - After creating and verifying a release, update the corresponding F-Droid submission/build recipe when required. The repository's F-Droid-related files are for reproducible verification, not an excuse to maintain a duplicate unused build path. diff --git a/README.md b/README.md index 6983334..cfab9a3 100644 --- a/README.md +++ b/README.md @@ -342,8 +342,15 @@ and `SHA256SUMS` are written to `dist/release`. The App Bundle contains every su stores generate and serve optimized device-specific APK splits from it. Go native symbols are provided as `mega-proxy-native-debug-symbols.zip` for upload in Play Console. +To upload the AAB and native symbols through Fastlane, configure a Play service account and run +`bundle exec fastlane android play_release` (internal draft by default). See the +[Google Play release instructions](docs/en/fastlane.md#google-play-releases) +([по-русски](docs/ru/fastlane.md#релизы-в-google-play)) for credentials, validation and production uploads. + Pushing a version tag runs the same Fastlane release lane in GitHub Actions, builds and verifies -every APK and the App Bundle, and attaches the artifacts to a GitHub Release. The tag must match +every APK and the App Bundle, attaches the artifacts to a GitHub Release, then uploads the AAB +and native symbols as a Google Play internal draft. Configure the repository Actions secret +`SUPPLY_JSON_KEY_DATA` with the complete service-account JSON key for that upload. The tag must match `v` followed by the current `versionName` in `app/build.gradle.kts`. Create that tag with `git tag` and push the specific tag with `git push origin`; do not reuse a historical release tag. diff --git a/docs/en/fastlane.md b/docs/en/fastlane.md index fae3313..7eda3aa 100644 --- a/docs/en/fastlane.md +++ b/docs/en/fastlane.md @@ -46,6 +46,7 @@ That command lists the lanes available in the checked-out version of the project | `bundle exec fastlane android test` | Runs `native_tests` and `android_checks`; this is the normal pre-commit command. | | `bundle exec fastlane android debug_artifact` | Builds `app/build/outputs/apk/debug/app-debug.apk`. | | `bundle exec fastlane android release_artifacts` | Builds and verifies the signed release APKs, AAB, native debug symbols, and `SHA256SUMS` in `dist/release`. | +| `bundle exec fastlane android play_release` | Uploads an existing signed AAB and native symbols to Google Play; defaults to an internal draft. | The release lane requires the signing configuration described in [Signed release builds](../../README.md#signed-release-builds). It builds artifacts but does not @@ -65,6 +66,78 @@ creates or updates one APK-links comment on the pull request. It does not check execute pull-request code or artifacts. These are test artifacts only: neither APK is signed with the MegaProxy release key, and neither is published as a GitHub Release or sent to an app store. +## Google Play releases + +`play_release` uploads an existing signed AAB and its matching native symbols for +`net.megaproxy487`. Build them with `release_artifacts` or download both from the same verified +GitHub Release. The lane does not build or sign files. Use an unused, increasing `versionCode`. +The AAB must be signed with the upload key registered in Play Console. + +Before the first API upload, create the app in Play Console, configure Play App Signing and upload +an initial build manually. Enable the Google Play Developer API in a Google Cloud project, create +a service account and invite its email in Play Console with access to this app and permissions +for the intended test/production tracks. Keep the JSON key outside the repository and supply its contents through `SUPPLY_JSON_KEY_DATA`. +See [Google API setup](https://developers.google.com/android-publisher/getting_started) and +[Fastlane supply setup](https://docs.fastlane.tools/actions/upload_to_play_store/#setup). + +Store `export SUPPLY_JSON_KEY_DATA=...` with the complete, shell-quoted JSON in the local +`~/.config/megaproxy/release.env` file. Keep this file outside the repository with permissions +`0600`. Run from the repository root: + +```shell +source "$HOME/.config/megaproxy/release.env" +bundle exec fastlane android release_artifacts +bundle exec fastlane android play_release validate_only:true +bundle exec fastlane android play_release +``` + +`SUPPLY_JSON_KEY_DATA` is Fastlane's standard environment variable for the complete JSON key, +not a file path or Base64 string. Sourcing `release.env` exports it for Fastlane; if your +environment already supplies it, no additional key setup is needed. + +In GitHub, create an Actions secret named `SUPPLY_JSON_KEY_DATA` containing the same complete JSON. +The existing tag-triggered release workflow passes it only to the upload step: + +```yaml +- name: Upload Google Play draft + env: + SUPPLY_JSON_KEY_DATA: ${{ secrets.SUPPLY_JSON_KEY_DATA }} + run: bundle exec fastlane android play_release track:internal release_status:draft +``` + +After building and verifying the artifacts and publishing the GitHub Release, the workflow uploads +the matching AAB and symbols from `dist/release` as an internal draft. A missing secret or failed +Play upload fails the workflow; the already published GitHub Release remains available. Do not +pass the key as a lane argument or print it in logs. PR workflows must not receive it. + +The default upload creates a **draft on the internal track**. `validate_only:true` uploads to a +temporary Google Play edit and asks the API to validate it without committing a release; it needs +credentials and network access and is not an offline dry run. Review/complete a draft in Play Console. +For a new AAB that should be released directly to testers or production, explicitly select: + +```shell +bundle exec fastlane android play_release track:internal release_status:completed +bundle exec fastlane android play_release track:production release_status:completed +``` + +Run only the command for the intended destination. Google review, app eligibility and managed +publishing can still delay availability. To promote an already uploaded version, use Play Console; +this lane uploads a new AAB and does not promote existing releases. + +| Option | Default / behavior | +| --- | --- | +| `aab` | `dist/release/mega-proxy.aab` | +| `symbols` | `dist/release/mega-proxy-native-debug-symbols.zip`; required and must match the AAB | +| `track` | `internal`; also accepts `alpha`, `beta`, `production` or a custom track ID | +| `release_status` | `draft`; supports `draft` or `completed` | +| `validate_only` | `false`; accepts only `true` or `false` | + +`MEGAPROXY_RELEASE_DIR` overrides the default artifact directory. Relative file paths are resolved +from the repository root. Metadata, changelogs, images and screenshots are not uploaded; the +F-Droid listing under `fastlane/metadata/android` is left separate from Play listing management. +The tag workflow publishes a GitHub Release and a Google Play internal draft. PR CI must not +receive the Play JSON key or invoke `play_release`. + ## Updating Fastlane Update Fastlane deliberately and commit both dependency files: diff --git a/docs/ru/fastlane.md b/docs/ru/fastlane.md index f7fe382..429740a 100644 --- a/docs/ru/fastlane.md +++ b/docs/ru/fastlane.md @@ -47,6 +47,7 @@ bundle exec fastlane lanes | `bundle exec fastlane android test` | Выполняет `native_tests` и `android_checks`; основная команда перед коммитом. | | `bundle exec fastlane android debug_artifact` | Собирает `app/build/outputs/apk/debug/app-debug.apk`. | | `bundle exec fastlane android release_artifacts` | Собирает и проверяет подписанные APK, AAB, native debug symbols и `SHA256SUMS` в `dist/release`. | +| `bundle exec fastlane android play_release` | Загружает готовый подписанный AAB и native symbols в Google Play; по умолчанию создаёт internal-черновик. | Для release lane нужна конфигурация подписи из раздела [Signed release builds](../../README.md#signed-release-builds). Lane только собирает артефакты: он @@ -67,6 +68,79 @@ checkout, не скачивает и не исполняет код или ар один из APK не подписан официальным release-ключом MegaProxy, не публикуется в GitHub Releases и не отправляется в магазин приложений. +## Релизы в Google Play + +`play_release` загружает готовый подписанный AAB и соответствующие native symbols для +`net.megaproxy487`. Соберите их через `release_artifacts` или скачайте оба файла из одного +проверенного GitHub Release. Lane не собирает и не подписывает файлы. Для нового релиза нужен +неиспользованный возрастающий `versionCode`. AAB должен быть подписан upload key, +зарегистрированным в Play Console. + +Перед первой загрузкой через API создайте приложение в Play Console, настройте Play App Signing +и вручную загрузите первоначальную сборку. Включите Google Play Developer API в проекте Google +Cloud, создайте сервисный аккаунт и пригласите его email в Play Console с доступом к приложению +и правами для нужных тестовых/production-треков. Храните JSON-ключ вне репозитория и передавайте его содержимое через `SUPPLY_JSON_KEY_DATA`. +См. [настройку Google API](https://developers.google.com/android-publisher/getting_started) и +[настройку Fastlane supply](https://docs.fastlane.tools/actions/upload_to_play_store/#setup). + +Сохраните `export SUPPLY_JSON_KEY_DATA=...` с полным JSON в корректных shell-кавычках в локальном +файле `~/.config/megaproxy/release.env`. Храните его вне репозитория с правами `0600`. +Запускайте из корня репозитория: + +```shell +source "$HOME/.config/megaproxy/release.env" +bundle exec fastlane android release_artifacts +bundle exec fastlane android play_release validate_only:true +bundle exec fastlane android play_release +``` + +`SUPPLY_JSON_KEY_DATA` — штатная переменная окружения Fastlane с полным содержимым JSON-ключа, +а не путём к файлу или строкой Base64. Загрузка `release.env` экспортирует её для Fastlane; +если окружение уже задаёт переменную, дополнительная настройка ключа не нужна. + +В GitHub создайте Actions secret `SUPPLY_JSON_KEY_DATA` с тем же полным JSON. +Существующий release-workflow по тегу передаёт его только шагу загрузки: + +```yaml +- name: Upload Google Play draft + env: + SUPPLY_JSON_KEY_DATA: ${{ secrets.SUPPLY_JSON_KEY_DATA }} + run: bundle exec fastlane android play_release track:internal release_status:draft +``` + +После сборки и проверки артефактов и публикации GitHub Release workflow загружает соответствующие +AAB и symbols из `dist/release` как internal-черновик. Отсутствие секрета или ошибка загрузки в Play +завершает workflow с ошибкой; уже опубликованный GitHub Release остаётся доступным. Не передавайте +ключ аргументом lane и не выводите его в логи. Workflow для PR не должны получать этот ключ. + +По умолчанию создаётся **черновик в треке internal**. `validate_only:true` загружает файлы во +временную транзакцию Google Play и проверяет её через API без сохранения релиза; нужны ключ +и сеть, это не локальный dry run. Проверьте и завершите черновик в Play Console. +Для нового AAB, который нужно сразу отправить тестировщикам или в production, явно укажите: + +```shell +bundle exec fastlane android play_release track:internal release_status:completed +bundle exec fastlane android play_release track:production release_status:completed +``` + +Выполняйте только команду для нужного направления. Проверка Google, доступность публикации для +приложения и managed publishing могут задержать появление релиза. Уже загруженную версию +продвигайте через Play Console: lane загружает новый AAB и не продвигает существующие релизы. + +| Параметр | Значение по умолчанию / поведение | +| --- | --- | +| `aab` | `dist/release/mega-proxy.aab` | +| `symbols` | `dist/release/mega-proxy-native-debug-symbols.zip`; обязателен и должен соответствовать AAB | +| `track` | `internal`; также принимает `alpha`, `beta`, `production` или ID пользовательского трека | +| `release_status` | `draft`; поддерживаются `draft` и `completed` | +| `validate_only` | `false`; принимает только `true` или `false` | + +`MEGAPROXY_RELEASE_DIR` меняет каталог артефактов по умолчанию. Относительные пути считаются от +корня репозитория. Метаданные, changelog, изображения и скриншоты не загружаются; каталог F-Droid +`fastlane/metadata/android` остаётся отдельным от управления карточкой Play. +Workflow по тегу публикует GitHub Release и internal-черновик Google Play. CI для PR не должен +получать JSON-ключ Play или вызывать `play_release`. + ## Обновление Fastlane Fastlane следует обновлять явно, после чего проверить и закоммитить оба файла зависимостей: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ba4ff8b..2e484a1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -119,4 +119,63 @@ platform :android do sh(File.join(project_root, "scripts", "build-release-bundle.sh")) UI.success("Release artifacts: dist/release") end + + desc "Upload a signed release AAB and native symbols to Google Play (internal draft by default)" + lane :play_release do |options| + allowed_options = %i[aab symbols track release_status validate_only] + unknown_options = options.keys - allowed_options + UI.user_error!("Unknown play_release options: #{unknown_options.join(', ')}") unless unknown_options.empty? + + track = options.fetch(:track, "internal").to_s + UI.user_error!("track must not be empty") if track.strip.empty? + release_status = options.fetch(:release_status, "draft").to_s + unless %w[draft completed].include?(release_status) + UI.user_error!("release_status must be draft or completed") + end + validate_only = options.fetch(:validate_only, false).to_s + unless %w[true false].include?(validate_only) + UI.user_error!("validate_only must be true or false") + end + + json_key_data = ENV["SUPPLY_JSON_KEY_DATA"] + if json_key_data.to_s.strip.empty? + UI.user_error!("Set SUPPLY_JSON_KEY_DATA to the service-account JSON contents") + end + begin + credentials = JSON.parse(json_key_data) + rescue JSON::ParserError + UI.user_error!("SUPPLY_JSON_KEY_DATA must contain valid JSON") + end + unless credentials.is_a?(Hash) && credentials["type"] == "service_account" && + %w[client_email private_key token_uri].all? { |key| credentials[key].is_a?(String) && !credentials[key].strip.empty? } + UI.user_error!("SUPPLY_JSON_KEY_DATA must contain a service-account key with client_email, private_key and token_uri") + end + release_dir = File.expand_path(ENV.fetch("MEGAPROXY_RELEASE_DIR", "dist/release"), project_root) + files = { + aab: File.expand_path(options.fetch(:aab, File.join(release_dir, "mega-proxy.aab")), project_root), + mapping: File.expand_path(options.fetch(:symbols, File.join(release_dir, "mega-proxy-native-debug-symbols.zip")), project_root) + } + files.each do |name, path| + unless File.file?(path) && File.readable?(path) && File.size?(path) + UI.user_error!("#{name} must be a readable, non-empty file: #{path}") + end + end + UI.user_error!("aab must have the .aab extension") unless File.extname(files[:aab]) == ".aab" + UI.user_error!("symbols must have the .zip extension") unless File.extname(files[:mapping]) == ".zip" + + upload_to_play_store( + **files, + json_key_data: json_key_data, + package_name: "net.megaproxy487", + track: track, + release_status: release_status, + validate_only: validate_only == "true", + skip_upload_apk: true, + skip_upload_aab: false, + skip_upload_metadata: true, + skip_upload_changelogs: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end end diff --git a/fastlane/README.md b/fastlane/README.md index fee6a39..5fb7c21 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -14,9 +14,11 @@ Fastlane is the supported entry point for tests and build artifacts. Install Rub | `bundle exec fastlane android test` | Run all native and Android checks | | `bundle exec fastlane android debug_artifact` | Produce `app/build/outputs/apk/debug/app-debug.apk` | | `bundle exec fastlane android release_artifacts` | Produce signed APKs, AAB, native symbols, and checksums in `dist/release` | +| `bundle exec fastlane android play_release` | Upload AAB and native symbols to Google Play (internal draft by default) | The release lane deliberately delegates signing and artifact verification to the repository's existing release scripts. It requires the signing environment documented in the root README. -Publishing to an app store is not performed by any lane. +`play_release` uploads an existing signed AAB and matching native symbols to Google Play. It +defaults to an internal draft and requires the JSON key contents in `SUPPLY_JSON_KEY_DATA`; see the setup guides below. Full setup and CI scope rules: [English](../docs/en/fastlane.md) / [Русский](../docs/ru/fastlane.md).