#!/usr/bin/env bash # # scripts/gen-keystore.sh # --------------------------------------------------------------------------- # Generate a NEW dedicated release keystore for Wisp, then print the exact # commands to base64-encode it and the list of Gitea Actions secrets to set. # # Run this ONCE on a trusted machine. The resulting keystore is the SINGLE # source of truth for signing every future Wisp APK — if you lose it (or its # passwords), you cannot ship an update that upgrades an already-installed APK # (signature mismatch). BACK IT UP somewhere safe (password manager / offline). # # This script does NOT touch CI, does NOT commit anything, and does NOT generate # a "real" production key for you silently — YOU run it, YOU keep the output. # # Requires: keytool (ships with any JDK). # # Usage: # ./scripts/gen-keystore.sh # interactive password prompts # STORE_PASS=... KEY_PASS=... ./scripts/gen-keystore.sh # non-interactive # --------------------------------------------------------------------------- set -euo pipefail # --- Tunables (override via env) -------------------------------------------- # Output keystore path. Default lands beside the script, OUTSIDE the app tree so # you don't accidentally commit it (.gitignore already excludes *.jks/*.keystore, # but we keep it out of the way regardless). KEYSTORE_OUT="${KEYSTORE_OUT:-./wisp-release.keystore}" # Key alias. MUST match ANDROID_KEY_ALIAS in CI. scripts/ci-android-sign.sh and # the docs assume the alias 'wisp'. KEY_ALIAS="${KEY_ALIAS:-wisp}" # RSA 2048, valid for 10000 days (~27 years) — Google Play's minimum validity # recommendation for upload/app-signing keys is well within this. KEY_ALG="RSA" KEY_SIZE="2048" VALIDITY_DAYS="10000" # Distinguished Name. Edit to taste; only CN is really meaningful for an app key. DNAME="${DNAME:-CN=Wisp, OU=Wisp, O=briggen.dev, L=, ST=, C=DE}" log() { printf '[gen-keystore] %s\n' "$*"; } fail() { printf '[gen-keystore] ERROR: %s\n' "$*" >&2; exit 1; } # --- Preconditions ---------------------------------------------------------- command -v keytool >/dev/null 2>&1 || fail "keytool not found. Install a JDK (e.g. JDK 17)." if [[ -e "${KEYSTORE_OUT}" ]]; then fail "Refusing to overwrite existing keystore at ${KEYSTORE_OUT}. Move/delete it first." fi # --- Collect passwords ------------------------------------------------------ # Allow non-interactive use via env (STORE_PASS / KEY_PASS); otherwise prompt. # We do NOT echo passwords. For most setups STORE_PASS and KEY_PASS can be equal. STORE_PASS="${STORE_PASS:-}" KEY_PASS="${KEY_PASS:-}" if [[ -z "${STORE_PASS}" ]]; then read -r -s -p "Enter NEW keystore (store) password: " STORE_PASS; echo read -r -s -p "Confirm keystore password: " STORE_PASS_CONFIRM; echo [[ "${STORE_PASS}" == "${STORE_PASS_CONFIRM}" ]] || fail "Store passwords do not match." fi [[ "${#STORE_PASS}" -ge 6 ]] || fail "Keystore password must be at least 6 characters (keytool requirement)." if [[ -z "${KEY_PASS}" ]]; then read -r -s -p "Enter NEW key (alias) password [blank = same as store]: " KEY_PASS; echo KEY_PASS="${KEY_PASS:-$STORE_PASS}" fi # --- Generate the keystore -------------------------------------------------- # -storetype PKCS12 is the modern default (JKS is deprecated). keytool will emit # a PKCS12 keystore; that's fully supported by Gradle/apksigner. log "Generating ${KEY_ALG} ${KEY_SIZE} key, alias '${KEY_ALIAS}', valid ${VALIDITY_DAYS} days..." keytool -genkeypair \ -keystore "${KEYSTORE_OUT}" \ -storetype PKCS12 \ -alias "${KEY_ALIAS}" \ -keyalg "${KEY_ALG}" \ -keysize "${KEY_SIZE}" \ -validity "${VALIDITY_DAYS}" \ -dname "${DNAME}" \ -storepass "${STORE_PASS}" \ -keypass "${KEY_PASS}" log "Keystore created: ${KEYSTORE_OUT}" echo # --- Print follow-up instructions ------------------------------------------- # Show the EXACT base64 command and the secrets to configure in Gitea. We print # the base64 command (not the base64 itself, and never the passwords) so the # human runs it themselves and pastes into the Gitea secrets UI. cat < the Wisp repo -> Settings -> Actions -> Secrets, add: ANDROID_KEYSTORE_BASE64 = ANDROID_KEYSTORE_PASSWORD = ANDROID_KEY_ALIAS = ${KEY_ALIAS} ANDROID_KEY_PASSWORD = (These names match scripts/ci-android-sign.sh and .gitea/workflows/ci.yml.) 3) BACK UP "${KEYSTORE_OUT}" and BOTH passwords in a safe place (password manager / offline). If lost, you can never update an already-installed APK. 4) Do NOT commit the keystore. (.gitignore already excludes *.keystore/*.jks.) ============================================================================= EOF