From d0037747b51d87939d246e15d6779093f3ebc7da Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 2 Jul 2026 21:53:53 +0200 Subject: [PATCH] chore(android): add persistent release-signing keystore generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Android APK's signature identifies the app to the OS; a new build must carry the same one as the last install or Android refuses it as an update. generate-keystore.sh creates the keystore once, interactively, and refuses to run again once one exists to avoid ever changing it by accident. The resulting file is git-ignored — it's a secret that must live outside the repo, backed up separately. --- .gitignore | 2 + docker/keystore/README.md | 37 ++++++++++++++++ docker/keystore/generate-keystore.sh | 65 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 docker/keystore/README.md create mode 100755 docker/keystore/generate-keystore.sh diff --git a/.gitignore b/.gitignore index 5f6286c..6f9b6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,8 @@ temp/ *.key *.pfx *.p12 +*.keystore +*.jks secrets/ # Wallet data and local runtime state diff --git a/docker/keystore/README.md b/docker/keystore/README.md new file mode 100644 index 0000000..ce4e0bb --- /dev/null +++ b/docker/keystore/README.md @@ -0,0 +1,37 @@ +# Android release-signing keystore + +This folder holds the persistent keystore used to sign release APKs built by +`docker/build.sh android`. Every release must be signed with the **same** +key, or Android refuses to install a new build over a previously installed +one (`INSTALL_FAILED_UPDATE_INCOMPATIBLE`) and forces the user to uninstall +first — which deletes the app's data. + +## First-time setup + +```bash +./docker/keystore/generate-keystore.sh +``` + +Run this **once**, on whichever machine will keep producing release builds. +It creates `release.keystore` in this folder and asks for a keystore +password (and, optionally, a separate key password). Nothing is written to +git — `release.keystore` is ignored by `.gitignore`, and the script never +saves the passwords anywhere. + +`docker/build.sh android` will refuse to run without this file, and will +prompt you for the same passwords at every build. + +## Back it up + +The keystore file and its passwords cannot be regenerated: if lost, no +future release can ever update existing installs in place again — every +user would need to uninstall and reinstall from scratch. Copy +`release.keystore` and the passwords to a password manager or encrypted +backup outside this repository as soon as you generate them. + +## What NOT to do + +- Do not commit `release.keystore` (or any `*.jks`/`*.keystore` file) to git. +- Do not re-run `generate-keystore.sh` once a keystore already exists — it + refuses on purpose; delete the file yourself only if you fully accept the + consequences above. diff --git a/docker/keystore/generate-keystore.sh b/docker/keystore/generate-keystore.sh new file mode 100755 index 0000000..dcca46e --- /dev/null +++ b/docker/keystore/generate-keystore.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Generates the persistent Android release-signing keystore used by +# docker/build.sh. Every APK built with this keystore carries the same +# signature, which is what lets Android treat a new release as an update to +# a previously installed one instead of a conflicting, different app. +# +# Run this ONCE. Re-running it after the keystore already exists is refused +# below on purpose: regenerating it changes the signature, and every device +# with a prior release installed would then need a full uninstall (and lose +# any app data that isn't in the wallet file itself) to receive the next +# update. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KEYSTORE_PATH="${SCRIPT_DIR}/release.keystore" +ALIAS="palladiumwallet" + +if [[ -f "$KEYSTORE_PATH" ]]; then + echo "A keystore already exists at $KEYSTORE_PATH — refusing to overwrite it." >&2 + echo "Delete it manually first only if you understand the consequences above." >&2 + exit 1 +fi + +command -v keytool &>/dev/null || { + echo "keytool not found on PATH — install a JDK (e.g. openjdk-17-jdk) and retry." >&2 + exit 1 +} + +echo "Creating the Palladium Wallet Android release-signing keystore." +echo "This file and the passwords you enter now must be kept safe OUTSIDE this" +echo "repository (password manager, encrypted backup, ...). They are never" +echo "committed to git. Losing them means no future release can ever update" +echo "existing installs in place — only a fresh keystore + full user reinstall." +echo + +read -r -s -p "Keystore password (min 6 characters): " KS_PASS +echo +read -r -s -p "Confirm keystore password: " KS_PASS_CONFIRM +echo +if [[ "$KS_PASS" != "$KS_PASS_CONFIRM" ]]; then + echo "Passwords do not match." >&2 + exit 1 +fi +if [[ "${#KS_PASS}" -lt 6 ]]; then + echo "Password too short (keytool requires at least 6 characters)." >&2 + exit 1 +fi + +read -r -s -p "Key password (press Enter to reuse the keystore password): " KEY_PASS +echo +KEY_PASS="${KEY_PASS:-$KS_PASS}" + +export KS_PASS KEY_PASS +keytool -genkeypair -v \ + -keystore "$KEYSTORE_PATH" \ + -alias "$ALIAS" \ + -keyalg RSA -keysize 2048 -validity 10000 \ + -storepass:env KS_PASS -keypass:env KEY_PASS \ + -dname "CN=PalladiumWallet, OU=PalladiumWallet, O=PalladiumWallet, C=IT" +unset KS_PASS KEY_PASS + +echo +echo "Keystore created at $KEYSTORE_PATH (git-ignored, alias: $ALIAS)." +echo "Back it up now, together with the passwords — it cannot be regenerated." +echo "./docker/build.sh android will prompt for these same passwords at build time."