From d0e473920c7c2178fc2f0bff93e4a46e464c4b4c Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Tue, 14 Jul 2026 20:24:26 +0200 Subject: [PATCH 1/2] docs(android): refresh the ANDROID_PLAN RESUME block Keystore + its Gitea secrets are set (owner) under the RuStore-specific ANDROID_RUSTORE_* names; the payments E10/E11 (#266/#267) + the signing-secret rename (#268) landed on development; the release gate is now the RuStore account + the promote/tag chain. Adds a native-notifications (push) track and the test-APK note for the next session. --- ANDROID_PLAN.md | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/ANDROID_PLAN.md b/ANDROID_PLAN.md index 8937aea..d7433b0 100644 --- a/ANDROID_PLAN.md +++ b/ANDROID_PLAN.md @@ -62,19 +62,20 @@ client-only (no server change). Kept current as parts land so a fresh session resumes without re-deriving. Verify against code. -> ### ▶ RESUME HERE — 2026-07-13 (documents track + release-prep) +> ### ▶ RESUME HERE — 2026-07-14 (keystore secrets set; payments landed; gate = RuStore account + merge/tag) > -> **Where we are.** The legal-documents track, the release-prep hygiene, **and the icon rebrand** are -> DONE. The RuStore release is now gated on **owner-side** items only (keystore, RuStore account) plus -> the merge/tag chain. Do not re-do or re-litigate the finished work; do not re-ask the locked decisions. +> **Where we are.** The legal-documents track, the release-prep hygiene, the icon rebrand **and the +> release keystore** are DONE (the keystore + its Gitea secrets set by the owner — see below). The +> RuStore MVP is now gated on **one owner-side item** — the **RuStore developer account** (owner going +> ИП; RuStore updated its requirements) — plus the agent's **promote/tag → signed-build** chain. Also +> landed on `development` since: the payments **E10 multi-shop split** (#266) + **E11 kill switch** +> (#267), both dormant / fail-open (no bearing on the Android build), and the signing-secret rename +> `ANDROID_*` → **`ANDROID_RUSTORE_*`** (#268). Do not re-do the finished work or re-ask the locked +> decisions. > -> **Branches / PRs (current working branch: `feature/android-release-prep`).** -> - `development` — has the **legal-documents track**, merged via **PR #253** (green, deployed to the -> test contour). `/privacy/`, `/eula/`, `/offer/` are live there, bilingual RU/EN. -> - `feature/android-release-prep` — the **release-prep hygiene**, **PR #254 OPEN, green, NOT merged** -> (the owner chose to hold the merge). It is branched off `development`, so it also contains the -> legal-documents track. Merge #254 into `development` when ready — needs **owner approval** (the -> agent cannot self-approve). +> **Branches / PRs.** All merged to `development` (contour-green): legal documents (#253); release-prep +> hygiene + the re-enabled `android-build` workflow (#254); the payments E10/E11 (#266/#267); the +> signing-secret rename (#268). No open Android branch. > > **Done this session.** > - **Legal documents** (satisfies the RuStore *hosted privacy-policy URL* prerequisite): authored @@ -113,12 +114,12 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif > 2. **Merge PR #254** → `development` (owner approval), then **promote `development` → `master`** and > **tag `vX.Y.Z`** for the release (the version stamps the APK `versionName`/`versionCode` and the > `X-Client-Version` header). -> 3. **Release keystore.** OWNER generates it (`keytool -genkeypair -v -keystore erudit-release.jks -> -alias erudit -keyalg RSA -keysize 4096 -validity 10000`), base64-encodes it, sets the four Gitea -> **secrets** `ANDROID_KEYSTORE_BASE64` / `ANDROID_KEYSTORE_PASSWORD` / `ANDROID_KEY_ALIAS` / -> `ANDROID_KEY_PASSWORD`, and backs the `.jks` up **off-host** (loss ⇒ the app can never be updated). -> The AGENT provides the exact commands on request. Without the secrets the workflow builds an -> UNSIGNED APK (a dry run still proves the pipeline). +> 3. **Release keystore — DONE (owner, 2026-07-14).** `erudit-release.jks` (RSA 4096, alias `erudit`, +> PKCS12) generated + backed up off-host; the four Gitea secrets are set under the RuStore-specific +> names `ANDROID_RUSTORE_KEYSTORE_BASE64` / `ANDROID_RUSTORE_KEYSTORE_PASSWORD` / +> `ANDROID_RUSTORE_KEY_ALIAS` / `ANDROID_RUSTORE_KEY_PASSWORD` (loss ⇒ the app can never be updated — +> the cert is pinned at the first RuStore upload). Google Play, later, would use its own +> `ANDROID_PLAY_*` upload key (Play App Signing) — a separate key. > 4. **RuStore developer account.** OWNER (verified legal entity or self-employed / самозанятый). Gates > publication, not the build. > 5. **Dispatch the signed build.** AGENT: on `master`, dispatch `android-build` (`confirm=build`) via @@ -132,6 +133,16 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif > 8. **Upload to RuStore.** OWNER. After publication, set the `ANDROID_RUSTORE_URL` Gitea variable to > the store deep-link so the update button works on future gated releases (empty until then — the > gate is dormant in the MVP). +> +> **Next tracks (design in a future session — interview the owner first).** +> - **Native notifications (push).** Promote FCM/push from "Out of scope" to a real track: a Capacitor +> push plugin, the Android send path (FCM — but **check RuStore's own push service vs FCM on RuStore +> devices**, since Google Play Services may be absent), token registration, and reusing the existing +> server notification dispatcher. Owner will bring specifics. +> - **A test APK for the emulator** (owner asked): the signed APK comes from `android-build` (needs the +> promote/tag chain, step 2). A quick **debug** APK is buildable without the release chain if the host +> has the Android SDK + JDK 21 (`pnpm build` → `cap sync` → `assembleDebug`). +> - Possibly other native polish (owner to flag). - **A. Capacitor scaffolding — ✅ DONE & verified (2026-07-12).** Capacitor 8 native project generated under `ui/android/` (tracked), `@capacitor/*` deps + `capacitor.config.ts`, hardware Back in -- 2.52.0 From 0e11817654bac427e38e38637b7a71f26983632b Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Tue, 14 Jul 2026 20:43:58 +0200 Subject: [PATCH 2/2] fix(android): pin build-tools to 36.0.0 for all modules AGP 8.13 defaults to build-tools 35.0.0, but the provisioned / read-only CI SDK (/opt/android-sdk) has only 36.0.0 installed, so a gradle build (the app + the cap-sync-generated Capacitor modules) fails trying to auto-install 35 into a read-only dir. Pin buildToolsVersion 36.0.0 for every Android sub-project (compileSdk-matching) so the CI android-build signed release and local builds work. Verified: a local assembleDebug now builds app-debug.apk. ci.yaml does not build Android (only the manual android-build workflow does), so this is verified locally. --- ui/android/build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ui/android/build.gradle b/ui/android/build.gradle index f8f0e43..471660d 100644 --- a/ui/android/build.gradle +++ b/ui/android/build.gradle @@ -24,6 +24,17 @@ allprojects { } } +// Pin build-tools to the compileSdk-matching version for every Android module (the app + the +// Capacitor modules regenerated by `cap sync`), so AGP does not fall back to its default (35.0.0), +// which the provisioned / read-only CI SDK does not have installed. +subprojects { + afterEvaluate { + if (project.hasProperty('android')) { + android.buildToolsVersion = "36.0.0" + } + } +} + task clean(type: Delete) { delete rootProject.buildDir } -- 2.52.0