Ship a signed release APK

The release build type reuses the debug signing config, so a downloaded APK
installs over an existing one instead of demanding an uninstall. This never
goes to Play, which rejects debug keys anyway, so there is no private key
worth safeguarding for years and no way to strand anyone on a version that
can no longer be updated.

That choice is load-bearing in one direction: a release build is not
DEBUGGABLE, so adb shell run-as stops reading app data back out, and there is
no in-app restore. A signature change would have cost the drill progress.

npm run release stages release/404-Layers-<version>.apk with a checksum and
verifies the signature, since an unsigned APK builds fine and only fails at
install time, on someone else's phone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 21:51:01 +03:00
parent 09feb899d6
commit 1d2cbda50d
6 changed files with 103 additions and 6 deletions

View File

@@ -172,9 +172,29 @@ now empty and bar styling is configured in `capacitor.config.json`
**No community plugins.** Only first-party: `@capacitor/core`, `/cli`,
`/android`, `/haptics`, `/preferences`, `/app`.
**Debug signing.** The debug keystore is stable per-machine, so Phase 2's APK
installs over Phase 1's without an uninstall and drill progress survives.
`appId` is `dev.ncode.sp404layers` and must not change for the same reason.
**Debug signing, releases included.** The debug keystore is stable per-machine,
so Phase 2's APK installs over Phase 1's without an uninstall and drill progress
survives. `appId` is `dev.ncode.sp404layers` and must not change for the same
reason.
The `release` build type reuses that key on purpose, via the plugin's built-in
`signingConfig signingConfigs.debug`, so no keystore path or password lands in a
tracked file. This only ever ships as a sideloaded APK, never to Play — which
rejects debug keys anyway. The trade: anyone can build an APK that upgrades over
it, against which there is no key to keep safe for years and no way to strand
anyone on a version that can no longer be updated.
Verified rather than assumed: the release APK's signer digest is
`2494c983…551cc`, identical to every debug build, so it installs in place. That
matters more than it looks, because a release build is **not `DEBUGGABLE`** — the
`adb shell run-as` trick that reads `CapacitorStorage.xml` back out stops working
the moment one is installed, and there is no in-app restore to fall back on. A
signature change would therefore have cost the drill progress outright.
`npm run release` (`scripts/release.mjs`) builds it, stages
`release/404-Layers-<version>.apk` with a `.sha256` beside it, and fails loudly
if apksigner reports no certificate: an unsigned APK builds happily and only
fails at install time, on someone else's phone. `release/` is gitignored.
## Phase 2: the React port
@@ -187,6 +207,7 @@ kit — the app is four tabs and a reducer, and none of those earn their keep.
| `npm run test:watch` | Watch mode |
| `npm run typecheck` | `tsc --noEmit` |
| `npm run extract:data` | Regenerate the combo table from `reference/` |
| `npm run release` | Build the signed release APK into `release/`, with checksum |
```
src/domain/