Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ Google Play delivers the app instead of sideloading, starting with the **interna
- Target API level 36 or higher: Play has rejected new apps and updates below it since 31 August 2026. The step fails below.
- 16 KB page size: native libraries' load segments aligned to 16 KB. The step reports any library that is not, as a warning for now.
- **Version codes.** Play takes each version code once per app. Production's grows with each release (`0.10.0` → `10000`, [releasing](releasing.md)), so production uploads once per release. The staging app counts commits instead, so every build of `main` is a new version (it needs the full history, which a run with `ref` has).
- **Signing.** Play signs what it delivers (Play App Signing). On the first upload of each app, choose to upload your own key (the Console's "Export and upload a key from a Java keystore", with the PEPK tool it links), so an APK from Build APK and the app from Play carry the same signature and update each other. With Google's key instead, a sideloaded install has to be removed once before Play can update it.
- **The first bundle of each app** is uploaded by hand in the Play Console: the API can neither create an app nor choose its signing key. Play Console → Create app → the package (`org.openclapper`, `org.openclapper.staging`) → Testing → Internal testing → Create release → the `.aab` from Build APK. Then add the testers' emails and open the opt-in link on each phone once.
- **Signing.** Play signs what it delivers (Play App Signing). Let Google generate each app's signing key (the Console's default on the first release); your deployment's key from `scripts/keystore.sh` becomes the app's **upload key**: Build APK signs every bundle with it, and Play accepts only bundles signed with it. A lost upload key can be reset through Google, so no app is ever stuck. The price: an APK from Build APK (signed with your key) and the app from Play (signed with Google's) cannot update each other, so a phone has one or the other; uninstall to switch. (Uploading your own key as the signing key with Google's PEPK tool keeps them interchangeable, but a lost key then means an app that can never be updated.)
- **The first bundle of each app** is uploaded by hand in the Play Console: the API can neither create an app nor choose its signing key. Play Console → Create app → the package (`org.openclapper`, `org.openclapper.staging`) → Testing → Internal testing → Create release → keep "Google-generated key" → the `.aab` from Build APK, which registers its key as the upload key. Then add the testers' emails and open the opt-in link on each phone once.
- **Uploads from the workflow.** Google Cloud Console → a project → IAM → Service accounts → create one and a JSON key for it. Play Console → Users and permissions → invite the service account's email with "Release to testing tracks" on the app (on each app). Store the JSON as the secret `PLAY_SERVICE_ACCOUNT_JSON` (in each GitHub environment that uploads). Then Build APK with `track: internal` uploads the bundle after the build (`scripts/play-upload.mjs`: Google's androidpublisher API with the service account, no dependency). A version code Play already has is a notice, not a failure. An app that was never published takes draft releases only: the run says so, and you roll the release out in the Console once. A newly invited service account may be refused for up to a day.

## The free plan
Expand Down
Loading