From ef7f0c538038dff87ae0264ffbe305b21841ee8a Mon Sep 17 00:00:00 2001 From: lendry Date: Thu, 2 Jul 2026 16:40:04 +0300 Subject: [PATCH] fix and update --- tauri_app/README.md | 13 ++ tauri_app/docker/ensure-android-keystore.sh | 172 ++++++++++++++------ tauri_app/docker/patch-android-signing.sh | 28 +++- 3 files changed, 158 insertions(+), 55 deletions(-) diff --git a/tauri_app/README.md b/tauri_app/README.md index 2bfda2f..9246e71 100644 --- a/tauri_app/README.md +++ b/tauri_app/README.md @@ -46,6 +46,19 @@ apps/frontend/public/downloads/lendry-id.apk Keystore и пароли создаются автоматически при первой сборке и сохраняются в `tauri_app/.secrets/` (не коммитятся в git). +Если сборка падает с `KeytoolException` / `not properly padded`, keystore и пароли рассинхронизированы. Пересоздайте их: + +```bash +rm -rf tauri_app/.secrets +./install.sh --build-apk +``` + +Или одной командой: + +```bash +ANDROID_KEYSTORE_FORCE_REGENERATE=1 ./install.sh --build-apk +``` + После этого кнопка «Скачать приложение» на странице входа скачивает APK через: ```text diff --git a/tauri_app/docker/ensure-android-keystore.sh b/tauri_app/docker/ensure-android-keystore.sh index 10fe60e..7cb268d 100644 --- a/tauri_app/docker/ensure-android-keystore.sh +++ b/tauri_app/docker/ensure-android-keystore.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Создаёт release keystore для подписи APK, если его ещё нет. +# Создаёт и проверяет release keystore для подписи APK. set -euo pipefail SECRETS_DIR="${ANDROID_SECRETS_DIR:-/workspace/tauri_app/.secrets}" @@ -7,73 +7,139 @@ KEYSTORE_FILE="${ANDROID_KEYSTORE_FILE:-${SECRETS_DIR}/lendry-id-release.keystor KEY_ALIAS="${ANDROID_KEY_ALIAS:-lendry-id}" CREDENTIALS_FILE="${SECRETS_DIR}/signing-credentials.env" KEYSTORE_PROPS_TEMPLATE="${SECRETS_DIR}/keystore.properties.template" +FORCE_REGENERATE="${ANDROID_KEYSTORE_FORCE_REGENERATE:-0}" mkdir -p "${SECRETS_DIR}" chmod 700 "${SECRETS_DIR}" || true -if [[ -f "${KEYSTORE_FILE}" ]]; then - echo "==> Android keystore уже существует: ${KEYSTORE_FILE}" - if [[ ! -f "${PROPS_TEMPLATE}" && -f "${CREDENTIALS_FILE}" ]]; then +read_credentials() { + STORE_PASSWORD="" + KEY_PASSWORD="" + if [[ -f "${CREDENTIALS_FILE}" ]]; then # shellcheck disable=SC1090 source "${CREDENTIALS_FILE}" - cat > "${PROPS_TEMPLATE}" < Восстановлен keystore.properties.template из signing-credentials.env" - fi - exit 0 -fi - -if ! command -v keytool >/dev/null 2>&1; then - echo "keytool не найден — нужен JDK для генерации keystore" >&2 - exit 1 -fi - -gen_secret() { - if command -v openssl >/dev/null 2>&1; then - openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 24 - else - date +%s%N | sha256sum | head -c 24 + STORE_PASSWORD="${ANDROID_KEYSTORE_PASSWORD:-}" + KEY_PASSWORD="${ANDROID_KEY_PASSWORD:-${ANDROID_KEYSTORE_PASSWORD:-}}" + elif [[ -f "${KEYSTORE_PROPS_TEMPLATE}" ]]; then + STORE_PASSWORD="$(grep -E '^storePassword=' "${KEYSTORE_PROPS_TEMPLATE}" | head -n1 | cut -d= -f2- || true)" + KEY_PASSWORD="$(grep -E '^keyPassword=' "${KEYSTORE_PROPS_TEMPLATE}" | head -n1 | cut -d= -f2- || true)" + KEY_PASSWORD="${KEY_PASSWORD:-${STORE_PASSWORD}}" fi } -STORE_PASSWORD="$(gen_secret)" -KEY_PASSWORD="$(gen_secret)" +write_credentials_files() { + local password="$1" -echo "==> Генерация release keystore для подписи APK..." -keytool -genkeypair -v \ - -storetype PKCS12 \ - -keystore "${KEYSTORE_FILE}" \ - -alias "${KEY_ALIAS}" \ - -keyalg RSA \ - -keysize 2048 \ - -validity 10000 \ - -storepass "${STORE_PASSWORD}" \ - -keypass "${KEY_PASSWORD}" \ - -dname "CN=Lendry ID, OU=Mobile, O=Lendry, C=RU" - -chmod 600 "${KEYSTORE_FILE}" || true - -cat > "${CREDENTIALS_FILE}" < "${CREDENTIALS_FILE}" < "${KEYSTORE_PROPS_TEMPLATE}" < "${KEYSTORE_PROPS_TEMPLATE}" < Keystore создан: ${KEYSTORE_FILE}" -echo "==> Пароли сохранены: ${CREDENTIALS_FILE}" +validate_keystore() { + local store_password="$1" + local key_password="$2" + + [[ -n "${store_password}" ]] || return 1 + [[ -f "${KEYSTORE_FILE}" ]] || return 1 + + keytool -list \ + -keystore "${KEYSTORE_FILE}" \ + -storepass "${store_password}" \ + -alias "${KEY_ALIAS}" >/dev/null 2>&1 || return 1 + + # Для JKS/PKCS12 у Gradle storePassword и keyPassword должны совпадать. + if [[ "${key_password}" != "${store_password}" ]]; then + return 1 + fi + + return 0 +} + +backup_broken_keystore() { + local backup_dir="${SECRETS_DIR}/backup-$(date +%Y%m%d-%H%M%S)" + mkdir -p "${backup_dir}" + for file in "${KEYSTORE_FILE}" "${CREDENTIALS_FILE}" "${KEYSTORE_PROPS_TEMPLATE}"; do + if [[ -f "${file}" ]]; then + cp "${file}" "${backup_dir}/" + fi + done + echo "==> Старые файлы подписи сохранены в ${backup_dir}" +} + +gen_secret() { + if command -v openssl >/dev/null 2>&1; then + openssl rand -hex 16 + else + date +%s%N | sha256sum | awk '{print substr($1,1,32)}' + fi +} + +create_keystore() { + local password="$1" + + if ! command -v keytool >/dev/null 2>&1; then + echo "keytool не найден — нужен JDK для генерации keystore" >&2 + exit 1 + fi + + rm -f "${KEYSTORE_FILE}" + + echo "==> Генерация release keystore для подписи APK..." + # JKS + один пароль — максимально совместимо с Android Gradle signing. + keytool -genkeypair -v \ + -storetype JKS \ + -keystore "${KEYSTORE_FILE}" \ + -alias "${KEY_ALIAS}" \ + -keyalg RSA \ + -keysize 2048 \ + -validity 10000 \ + -storepass "${password}" \ + -keypass "${password}" \ + -dname "CN=Lendry ID, OU=Mobile, O=Lendry, C=RU" + + chmod 600 "${KEYSTORE_FILE}" || true + write_credentials_files "${password}" + + echo "==> Keystore создан: ${KEYSTORE_FILE}" + echo "==> Пароли сохранены: ${CREDENTIALS_FILE}" +} + +if [[ "${FORCE_REGENERATE}" == "1" && -f "${KEYSTORE_FILE}" ]]; then + backup_broken_keystore + rm -f "${KEYSTORE_FILE}" "${CREDENTIALS_FILE}" "${KEYSTORE_PROPS_TEMPLATE}" +fi + +if [[ -f "${KEYSTORE_FILE}" ]]; then + read_credentials + if validate_keystore "${STORE_PASSWORD}" "${KEY_PASSWORD}"; then + write_credentials_files "${STORE_PASSWORD}" + echo "==> Android keystore проверен: ${KEYSTORE_FILE}" + exit 0 + fi + + echo "==> Keystore повреждён или пароли не совпадают — пересоздаём release keystore" >&2 + backup_broken_keystore + rm -f "${KEYSTORE_FILE}" "${CREDENTIALS_FILE}" "${KEYSTORE_PROPS_TEMPLATE}" +fi + +create_keystore "$(gen_secret)" + +if ! validate_keystore "$(grep -E '^storePassword=' "${KEYSTORE_PROPS_TEMPLATE}" | cut -d= -f2-)" "$(grep -E '^keyPassword=' "${KEYSTORE_PROPS_TEMPLATE}" | cut -d= -f2-)"; then + echo "Не удалось проверить только что созданный keystore" >&2 + exit 1 +fi + +echo "==> Keystore успешно создан и проверен" diff --git a/tauri_app/docker/patch-android-signing.sh b/tauri_app/docker/patch-android-signing.sh index a9ac33f..cd49f25 100644 --- a/tauri_app/docker/patch-android-signing.sh +++ b/tauri_app/docker/patch-android-signing.sh @@ -27,10 +27,33 @@ else exit 1 fi +# Абсолютный путь надёжнее относительного при Gradle rootProject.file(). +if grep -q '^storeFile=' "${KEYSTORE_PROPS}"; then + sed -i "s|^storeFile=.*|storeFile=${KEYSTORE_FILE}|" "${KEYSTORE_PROPS}" +else + printf '\nstoreFile=%s\n' "${KEYSTORE_FILE}" >> "${KEYSTORE_PROPS}" +fi + +STORE_PASSWORD="$(grep -E '^storePassword=' "${KEYSTORE_PROPS}" | head -n1 | cut -d= -f2- || true)" +KEY_ALIAS_PROP="$(grep -E '^keyAlias=' "${KEYSTORE_PROPS}" | head -n1 | cut -d= -f2- || true)" +KEY_ALIAS_PROP="${KEY_ALIAS_PROP:-lendry-id}" + +if [[ -z "${STORE_PASSWORD}" ]]; then + echo "storePassword не найден в ${KEYSTORE_PROPS}" >&2 + exit 1 +fi + +if ! keytool -list -keystore "${KEYSTORE_FILE}" -storepass "${STORE_PASSWORD}" -alias "${KEY_ALIAS_PROP}" >/dev/null 2>&1; then + echo "Keystore не читается с паролями из ${KEYSTORE_PROPS}." >&2 + echo "Удалите tauri_app/.secrets и пересоберите APK, либо задайте ANDROID_KEYSTORE_FORCE_REGENERATE=1." >&2 + exit 1 +fi + chmod 600 "${KEYSTORE_PROPS}" || true cat > "${SIGNING_SCRIPT}" <<'EOF' import com.android.build.api.dsl.ApplicationExtension +import java.io.File import java.util.Properties val keystoreProperties = Properties().apply { @@ -40,10 +63,11 @@ val keystoreProperties = Properties().apply { extensions.configure("android") { signingConfigs { create("release") { + val storePasswordValue = keystoreProperties.getProperty("storePassword") keyAlias = keystoreProperties.getProperty("keyAlias") keyPassword = keystoreProperties.getProperty("keyPassword") - storeFile = rootProject.file(keystoreProperties.getProperty("storeFile")) - storePassword = keystoreProperties.getProperty("storePassword") + storeFile = File(keystoreProperties.getProperty("storeFile")) + storePassword = storePasswordValue } } buildTypes {