mirror of
https://github.com/tiennm99/loto.git
synced 2026-09-02 14:20:46 +00:00
docs: add Play Store auto-publish setup guide
This commit is contained in:
@@ -41,7 +41,10 @@ with the subproject it serves and filtered on the paths it cares about.
|
||||
| `web-firebase-hosting-merge` | push to `main` touching `web/` | deploys to Firebase Hosting |
|
||||
| `web-firebase-hosting-pull-request` | PR touching `web/` | Firebase preview channel |
|
||||
| `android-build-debug` | push/PR touching `web/` or `android/` | unsigned debug APK artifact |
|
||||
| `android-release` | tag `v*.*.*` | signed AAB + APK on the GH Release |
|
||||
| `android-release` | tag `v*.*.*` | signed AAB + APK on the GH Release, plus Play Store internal-track upload |
|
||||
|
||||
Release/secret setup for the Play Store pipeline is documented in
|
||||
[`docs/play-store-publishing.md`](docs/play-store-publishing.md).
|
||||
|
||||
## History
|
||||
|
||||
|
||||
+8
-10
@@ -150,16 +150,14 @@ inside `android/android/`.
|
||||
|
||||
### Auto-publish setup (after first manual upload)
|
||||
|
||||
1. Create/select a Google Cloud project and enable the **Google Play Android Developer API**
|
||||
2. In Google Cloud Console, create a **Service Account** without granting broad Cloud project roles
|
||||
3. **Keys → Add Key → JSON** — download the JSON file
|
||||
4. In Play Console → **Users and permissions**, invite the service-account email and grant app-scoped *Release apps to testing tracks* + *View app information* permissions for Lo To
|
||||
5. Copy the entire JSON contents into a GitHub repo secret named `PLAY_SERVICE_ACCOUNT_JSON`
|
||||
6. Tag a release (`git tag v1.0.1 && git push origin v1.0.1`) — `android-release.yml` will:
|
||||
- Build signed AAB + APK
|
||||
- Upload to GitHub Release
|
||||
- **If the secret is set**: upload AAB to Play Console **Internal track**
|
||||
7. Promote internal → closed → open → production via the Play Console UI (or change `tracks: internal` in `android-release.yml` to automate further)
|
||||
See [`docs/play-store-publishing.md`](../docs/play-store-publishing.md) for the
|
||||
full walkthrough: service-account creation, granting Play Console permissions,
|
||||
setting the GitHub secrets (bash + PowerShell commands), cutting a release,
|
||||
and troubleshooting. Short version: once `PLAY_SERVICE_ACCOUNT_JSON` is set,
|
||||
every `v*.*.*` tag builds a signed AAB + APK, attaches both to a GitHub
|
||||
Release, and uploads the AAB to the Play Console **Internal track**. Promote
|
||||
internal → closed → open → production via the Play Console UI (or change
|
||||
`tracks: internal` in `android-release.yml` to automate further).
|
||||
|
||||
**Important:** every release must increment `versionCode` in `android/app/build.gradle` before tagging — Play Console rejects duplicate versionCodes.
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# Google Play Auto-Publish Setup
|
||||
|
||||
How to configure GitHub secrets so pushing a `v*.*.*` tag builds a signed
|
||||
AAB/APK, attaches both to a GitHub Release, and uploads the AAB to the Play
|
||||
Console **Internal track** automatically. Driven by
|
||||
[`.github/workflows/android-release.yml`](../.github/workflows/android-release.yml).
|
||||
|
||||
Verified working: tag `v0.0.2` (2026-08-05) built, released, and uploaded to
|
||||
the internal track end-to-end.
|
||||
|
||||
## Prerequisites (one-time, manual)
|
||||
|
||||
These cannot be automated:
|
||||
|
||||
1. Google Play Console account ($25 one-time) at [play.google.com/console](https://play.google.com/console/signup)
|
||||
2. App entry created with package name `com.miti99.loto`
|
||||
3. **First AAB uploaded manually** to the Internal Testing track via the Play
|
||||
Console UI — Google requires the first upload to be manual
|
||||
4. Store listing completed (icon, screenshots, descriptions, content rating,
|
||||
privacy policy URL, data safety form)
|
||||
|
||||
## Signing secrets (required for any release build)
|
||||
|
||||
| Secret | Description |
|
||||
|--------|-------------|
|
||||
| `KEYSTORE_BASE64` | Base64 of the PKCS12 keystore file |
|
||||
| `KEYSTORE_PASSWORD` | Keystore password |
|
||||
| `KEY_ALIAS` | Key alias (list aliases: `keytool -list -keystore your.p12`) |
|
||||
| `KEY_PASSWORD` | Key password |
|
||||
|
||||
Encode the keystore:
|
||||
|
||||
```bash
|
||||
# bash / Git Bash
|
||||
base64 -w0 miti99-apps.p12 | gh secret set KEYSTORE_BASE64 -R tiennm99/loto
|
||||
```
|
||||
|
||||
```powershell
|
||||
# PowerShell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\path\to\miti99-apps.p12")) |
|
||||
gh secret set KEYSTORE_BASE64 -R tiennm99/loto
|
||||
```
|
||||
|
||||
Set the remaining three with `gh secret set <NAME> -R tiennm99/loto` (prompts
|
||||
for the value interactively, keeping it out of shell history).
|
||||
|
||||
## Play Store auto-publish: the 5 steps
|
||||
|
||||
Do this **after** the first manual AAB upload has been accepted.
|
||||
|
||||
How the pieces fit: the service account lives in a Google Cloud project, but
|
||||
its **authority to publish comes from Play Console, not from Cloud IAM**. The
|
||||
GCP side only creates an identity and a key; the Play Console side grants the
|
||||
actual publishing rights. That is why the service account needs **zero IAM
|
||||
roles** in the Cloud project.
|
||||
|
||||
### Step 1 — Create a GCP project and enable the API
|
||||
|
||||
1. Open [console.cloud.google.com](https://console.cloud.google.com) and sign
|
||||
in with any Google account (it does **not** have to be the Play Console
|
||||
owner account).
|
||||
2. Project picker (top bar) → **New Project** → name it e.g.
|
||||
`loto-play-publishing` → **Create**. Reusing an existing project is fine.
|
||||
3. **APIs & Services → Library** → search **Google Play Android Developer
|
||||
API** → **Enable**.
|
||||
|
||||
Or with the `gcloud` CLI:
|
||||
|
||||
```bash
|
||||
gcloud projects create loto-play-publishing
|
||||
gcloud config set project loto-play-publishing
|
||||
gcloud services enable androidpublisher.googleapis.com
|
||||
```
|
||||
|
||||
### Step 2 — Create the service account (no IAM roles)
|
||||
|
||||
1. **IAM & Admin → Service Accounts → Create service account**.
|
||||
2. Name: e.g. `play-publisher` (email becomes
|
||||
`play-publisher@loto-play-publishing.iam.gserviceaccount.com`).
|
||||
3. **"Grant this service account access to project" — skip it.** Leave the
|
||||
role empty; publishing rights come from Play Console in Step 4. Granting
|
||||
Editor/Owner here is a common mistake that only widens the blast radius if
|
||||
the key leaks.
|
||||
4. **"Grant users access to this service account" — skip it** too. **Done**.
|
||||
|
||||
```bash
|
||||
gcloud iam service-accounts create play-publisher \
|
||||
--display-name "Play Store publisher (CI)"
|
||||
```
|
||||
|
||||
### Step 3 — Download a JSON key
|
||||
|
||||
1. On the service account row → **⋮ → Manage keys** (or the **Keys** tab) →
|
||||
**Add Key → Create new key → JSON → Create**. The file downloads once and
|
||||
cannot be re-downloaded — treat it like a password.
|
||||
2. Keep it **outside the repo** (e.g. `~/secrets/`). It goes into a GitHub
|
||||
secret in Step 5 and can be deleted locally afterwards.
|
||||
|
||||
```bash
|
||||
gcloud iam service-accounts keys create service-account.json \
|
||||
--iam-account play-publisher@loto-play-publishing.iam.gserviceaccount.com
|
||||
```
|
||||
|
||||
### Step 4 — Add the service account to Play Console
|
||||
|
||||
Done in Play Console by the **account owner** (or an admin who can manage
|
||||
users):
|
||||
|
||||
1. Copy the service-account **email** from Step 2.
|
||||
2. [play.google.com/console](https://play.google.com/console) → **Users and
|
||||
permissions** → **Invite new users**.
|
||||
3. Paste the service-account email. No invitation email is sent for service
|
||||
accounts — access activates as soon as you save.
|
||||
4. Under **App permissions** tab → **Add app** → select **Lo To
|
||||
(`com.miti99.loto`)** — scope access to this one app instead of
|
||||
account-wide permissions.
|
||||
5. Tick exactly these two permissions:
|
||||
- **View app information and download bulk reports (read-only)** — the
|
||||
API needs it to read the app's edit state
|
||||
- **Release apps to testing tracks** — enough for the workflow's
|
||||
`tracks: internal` upload
|
||||
Leave everything else (production releases, store presence, financial
|
||||
data, user management) unchecked. If you later automate production rollout
|
||||
(`tracks: production`), come back and add **Release to production, exclude
|
||||
devices, and use Play App Signing**.
|
||||
6. **Invite user → Send invite**.
|
||||
|
||||
Propagation is usually instant, but the very first API call can take up to
|
||||
~24 h after the account's first-ever manual upload; if the workflow fails
|
||||
with a 401/403 right after setup, wait and re-run before changing anything.
|
||||
|
||||
### Step 5 — Store the JSON as a GitHub secret
|
||||
|
||||
Name it `PLAY_SERVICE_ACCOUNT_JSON`:
|
||||
|
||||
```bash
|
||||
# bash / Git Bash
|
||||
gh secret set PLAY_SERVICE_ACCOUNT_JSON -R tiennm99/loto < path/to/service-account.json
|
||||
```
|
||||
|
||||
```powershell
|
||||
# PowerShell — '<' redirection is NOT supported, pipe instead:
|
||||
Get-Content -Raw C:\path\to\service-account.json |
|
||||
gh secret set PLAY_SERVICE_ACCOUNT_JSON -R tiennm99/loto
|
||||
```
|
||||
|
||||
The workflow is gated on this secret: if it is missing, the Play upload step
|
||||
skips silently and the run still produces a GitHub Release. This means tags
|
||||
work before Play setup is finished.
|
||||
|
||||
## Cutting a release
|
||||
|
||||
1. Bump **both** values in `android/android/app/build.gradle` — Play rejects
|
||||
duplicate `versionCode`s:
|
||||
|
||||
```groovy
|
||||
versionCode 3 // must increase every release
|
||||
versionName "0.0.3" // should match the tag
|
||||
```
|
||||
|
||||
2. Commit, push, tag:
|
||||
|
||||
```bash
|
||||
git add android/android/app/build.gradle
|
||||
git commit -m "chore(android): bump version to 0.0.3 (versionCode 3)"
|
||||
git push origin main
|
||||
git tag v0.0.3
|
||||
git push origin v0.0.3
|
||||
```
|
||||
|
||||
3. Watch and verify:
|
||||
|
||||
```bash
|
||||
gh run list -R tiennm99/loto --workflow android-release.yml --limit 1
|
||||
gh run watch <run-id> -R tiennm99/loto --exit-status
|
||||
gh release view v0.0.3 -R tiennm99/loto
|
||||
```
|
||||
|
||||
Promotion beyond the internal track (closed → open → production) stays manual
|
||||
in the Play Console UI, or change `tracks: internal` in
|
||||
`android-release.yml` to automate further.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Play upload fails with duplicate versionCode** — `versionCode` in
|
||||
`android/android/app/build.gradle` was not bumped before tagging.
|
||||
- **`The '<' operator is reserved for future use`** — PowerShell does not
|
||||
support `<` input redirection; use the `Get-Content -Raw ... |` form above.
|
||||
- **Play upload step skipped** — `PLAY_SERVICE_ACCOUNT_JSON` secret is not
|
||||
set (check `gh secret list -R tiennm99/loto`), or was set after the run
|
||||
started.
|
||||
- **Play upload fails with 401/403** — either the service account was not
|
||||
invited in Play Console (Step 4), the app was not added under its App
|
||||
permissions, or the Play Developer API access has not finished propagating
|
||||
(can take up to ~24 h after the first-ever manual upload). Verify the
|
||||
invite, wait, then re-run the job.
|
||||
- **First upload rejected** — Google requires the very first AAB to be
|
||||
uploaded manually through the Play Console UI; the API can only publish
|
||||
after that.
|
||||
|
||||
**Never commit** `*.jks`, `*.keystore`, `*.p12`, service-account JSON, or `.env`.
|
||||
Reference in New Issue
Block a user