From 33c92e38b76ad84aaeeab2c9bcc8d410c1331ba4 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 21 Aug 2026 18:27:13 +0200 Subject: [PATCH] Give the release build a signing key it can actually reach The Android job has four secrets in it -- keystore, its password, the key alias and key password -- and the repo had none of them set. That fails in the worst available way: `echo "" | base64 -d` exits 0 and writes a zero-byte file, so the keystore step goes green and the failure surfaces minutes later inside gradle's signing task, at the tail of a ~1h20m run. Generated a 4096-bit RSA key (PKCS12, valid to 2054, alias `bikecontrol`) and uploaded all four to Gitea with `tea actions secrets create --stdin`. PKCS12 does not support a key password differing from the store password, so ANDROID_KEY_PASSWORD is deliberately the same value as ANDROID_KEYSTORE_PASSWORD rather than a second secret. The password is hex on purpose. CI writes keystore.properties through an unquoted heredoc, so the shell expands `$` and backticks, and .properties treats backslash as an escape -- hex is inert in both. Local side: android-keystore/ holds the key and its password, gitignored as a directory so the password file is covered as well as the *.jks glob. scripts/local-keystore.sh points a local build at it by writing gen/android/keystore.properties, the same file CI writes from secrets. `tauri android init` deletes that file, so the script is idempotent and meant to be re-run after any init. Verified: a local `cargo tauri android build --apk` now produces an APK that apksigner reports as CN=BikeControl, O=Tourolle, C=FR, where before it was silently debug-signed -- build.gradle.kts falls back to the debug signature when keystore.properties is absent rather than failing. The keystore is NOT recoverable if lost: Android will refuse any future update signed by a different key. It needs a backup somewhere off this machine. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 2 ++ scripts/local-keystore.sh | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 scripts/local-keystore.sh diff --git a/.gitignore b/.gitignore index 2568565..3683c94 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ src-tauri/android/**/.gradle/ *.jks *.keystore src-tauri/android/keystore.properties +# The whole directory, so the password kept beside the key is covered too. +android-keystore/ # Editors / OS .DS_Store diff --git a/scripts/local-keystore.sh b/scripts/local-keystore.sh new file mode 100755 index 0000000..af0af4a --- /dev/null +++ b/scripts/local-keystore.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh +# Point a local release build at the real signing key. +# +# app/build.gradle.kts reads rootProject/keystore.properties and, when it is +# absent, leaves the release variant debug-signed rather than failing. CI writes +# that file from secrets; this writes it from android-keystore/ so a local +# `cargo tauri android build --apk` produces an APK signed with the same key a +# release is, which is what makes a locally-built APK a valid update to an +# installed one rather than a conflicting-signature install failure. +# +# gen/ is regenerated by `tauri android init`, which deletes this file. Re-run +# after any init — it is cheap and idempotent. +# +# The keystore and its password live in android-keystore/, which is gitignored. +set -e + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +KEYSTORE="$ROOT/android-keystore/bikecontrol-release.jks" +PASSFILE="$ROOT/android-keystore/keystore-password.txt" +DEST="$ROOT/src-tauri/gen/android/keystore.properties" + +[ -f "$KEYSTORE" ] || { echo "❌ no keystore at $KEYSTORE" >&2; exit 1; } +[ -f "$PASSFILE" ] || { echo "❌ no password file at $PASSFILE" >&2; exit 1; } +[ -d "$(dirname "$DEST")" ] || { echo "❌ no gen/android — run 'cargo tauri android init' first" >&2; exit 1; } + +# Absolute storeFile: gradle resolves a relative one against the app module dir, +# not the root project. +umask 077 +{ + echo "storeFile=$KEYSTORE" + echo "storePassword=$(cat "$PASSFILE")" + echo "keyAlias=bikecontrol" + echo "keyPassword=$(cat "$PASSFILE")" +} > "$DEST" + +echo "✅ wrote $DEST (storeFile=$KEYSTORE, keyAlias=bikecontrol)"