From 74013f22df440137f86febb4962164256daa1402 Mon Sep 17 00:00:00 2001 From: bubulux Date: Fri, 23 Oct 2026 13:08:08 +0200 Subject: [PATCH] Play signing: Google-generated app signing key, the deployment's key as upload key (OpenClapper-dev/deploy#9) Decided 2026-10-23: a lost upload key can be reset through Google, a lost app signing key cannot. The trade-off, that a sideloaded APK and the Play install no longer update each other, is spelled out in the Play Store section. Co-Authored-By: Claude Opus 5.5 --- docs/self-hosting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/self-hosting.md b/docs/self-hosting.md index 58ad530..4003cc9 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -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