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) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 18:27:13 +02:00
co-authored by Claude Opus 5
parent db974dcac1
commit 33c92e38b7
2 changed files with 38 additions and 0 deletions
+36
View File
@@ -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)"