fix and update

This commit is contained in:
lendry
2026-07-02 16:40:04 +03:00
parent 2f76b28339
commit ef7f0c5380
3 changed files with 158 additions and 55 deletions

View File

@@ -46,6 +46,19 @@ apps/frontend/public/downloads/lendry-id.apk
Keystore и пароли создаются автоматически при первой сборке и сохраняются в `tauri_app/.secrets/` (не коммитятся в git). 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 через: После этого кнопка «Скачать приложение» на странице входа скачивает APK через:
```text ```text

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Создаёт release keystore для подписи APK, если его ещё нет. # Создаёт и проверяет release keystore для подписи APK.
set -euo pipefail set -euo pipefail
SECRETS_DIR="${ANDROID_SECRETS_DIR:-/workspace/tauri_app/.secrets}" 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}" KEY_ALIAS="${ANDROID_KEY_ALIAS:-lendry-id}"
CREDENTIALS_FILE="${SECRETS_DIR}/signing-credentials.env" CREDENTIALS_FILE="${SECRETS_DIR}/signing-credentials.env"
KEYSTORE_PROPS_TEMPLATE="${SECRETS_DIR}/keystore.properties.template" KEYSTORE_PROPS_TEMPLATE="${SECRETS_DIR}/keystore.properties.template"
FORCE_REGENERATE="${ANDROID_KEYSTORE_FORCE_REGENERATE:-0}"
mkdir -p "${SECRETS_DIR}" mkdir -p "${SECRETS_DIR}"
chmod 700 "${SECRETS_DIR}" || true chmod 700 "${SECRETS_DIR}" || true
if [[ -f "${KEYSTORE_FILE}" ]]; then read_credentials() {
echo "==> Android keystore уже существует: ${KEYSTORE_FILE}" STORE_PASSWORD=""
if [[ ! -f "${PROPS_TEMPLATE}" && -f "${CREDENTIALS_FILE}" ]]; then KEY_PASSWORD=""
if [[ -f "${CREDENTIALS_FILE}" ]]; then
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "${CREDENTIALS_FILE}" source "${CREDENTIALS_FILE}"
cat > "${PROPS_TEMPLATE}" <<EOF STORE_PASSWORD="${ANDROID_KEYSTORE_PASSWORD:-}"
storePassword=${ANDROID_KEYSTORE_PASSWORD} KEY_PASSWORD="${ANDROID_KEY_PASSWORD:-${ANDROID_KEYSTORE_PASSWORD:-}}"
keyPassword=${ANDROID_KEY_PASSWORD} elif [[ -f "${KEYSTORE_PROPS_TEMPLATE}" ]]; then
keyAlias=${ANDROID_KEY_ALIAS} STORE_PASSWORD="$(grep -E '^storePassword=' "${KEYSTORE_PROPS_TEMPLATE}" | head -n1 | cut -d= -f2- || true)"
storeFile=../../../.secrets/lendry-id-release.keystore KEY_PASSWORD="$(grep -E '^keyPassword=' "${KEYSTORE_PROPS_TEMPLATE}" | head -n1 | cut -d= -f2- || true)"
EOF KEY_PASSWORD="${KEY_PASSWORD:-${STORE_PASSWORD}}"
chmod 600 "${PROPS_TEMPLATE}" || true
echo "==> Восстановлен 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
fi fi
} }
STORE_PASSWORD="$(gen_secret)" write_credentials_files() {
KEY_PASSWORD="$(gen_secret)" local password="$1"
echo "==> Генерация release keystore для подписи APK..." cat > "${CREDENTIALS_FILE}" <<EOF
keytool -genkeypair -v \ # Сгенерировано $(date -u +"%Y-%m-%dT%H:%M:%SZ") — храните в безопасном месте.
-storetype PKCS12 \ ANDROID_KEYSTORE_FILE=${KEYSTORE_FILE}
ANDROID_KEY_ALIAS=${KEY_ALIAS}
ANDROID_KEYSTORE_PASSWORD=${password}
ANDROID_KEY_PASSWORD=${password}
EOF
chmod 600 "${CREDENTIALS_FILE}" || true
cat > "${KEYSTORE_PROPS_TEMPLATE}" <<EOF
storePassword=${password}
keyPassword=${password}
keyAlias=${KEY_ALIAS}
storeFile=${KEYSTORE_FILE}
EOF
chmod 600 "${KEYSTORE_PROPS_TEMPLATE}" || true
}
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}" \ -keystore "${KEYSTORE_FILE}" \
-alias "${KEY_ALIAS}" \ -alias "${KEY_ALIAS}" \
-keyalg RSA \ -keyalg RSA \
-keysize 2048 \ -keysize 2048 \
-validity 10000 \ -validity 10000 \
-storepass "${STORE_PASSWORD}" \ -storepass "${password}" \
-keypass "${KEY_PASSWORD}" \ -keypass "${password}" \
-dname "CN=Lendry ID, OU=Mobile, O=Lendry, C=RU" -dname "CN=Lendry ID, OU=Mobile, O=Lendry, C=RU"
chmod 600 "${KEYSTORE_FILE}" || true chmod 600 "${KEYSTORE_FILE}" || true
write_credentials_files "${password}"
cat > "${CREDENTIALS_FILE}" <<EOF echo "==> Keystore создан: ${KEYSTORE_FILE}"
# Сгенерировано $(date -u +"%Y-%m-%dT%H:%M:%SZ") — храните в безопасном месте. echo "==> Пароли сохранены: ${CREDENTIALS_FILE}"
ANDROID_KEYSTORE_FILE=${KEYSTORE_FILE} }
ANDROID_KEY_ALIAS=${KEY_ALIAS}
ANDROID_KEYSTORE_PASSWORD=${STORE_PASSWORD}
ANDROID_KEY_PASSWORD=${KEY_PASSWORD}
EOF
chmod 600 "${CREDENTIALS_FILE}" || true
cat > "${KEYSTORE_PROPS_TEMPLATE}" <<EOF if [[ "${FORCE_REGENERATE}" == "1" && -f "${KEYSTORE_FILE}" ]]; then
storePassword=${STORE_PASSWORD} backup_broken_keystore
keyPassword=${KEY_PASSWORD} rm -f "${KEYSTORE_FILE}" "${CREDENTIALS_FILE}" "${KEYSTORE_PROPS_TEMPLATE}"
keyAlias=${KEY_ALIAS} fi
storeFile=../../../.secrets/lendry-id-release.keystore
EOF
chmod 600 "${KEYSTORE_PROPS_TEMPLATE}" || true
echo "==> Keystore создан: ${KEYSTORE_FILE}" if [[ -f "${KEYSTORE_FILE}" ]]; then
echo "==> Пароли сохранены: ${CREDENTIALS_FILE}" 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 успешно создан и проверен"

View File

@@ -27,10 +27,33 @@ else
exit 1 exit 1
fi 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 chmod 600 "${KEYSTORE_PROPS}" || true
cat > "${SIGNING_SCRIPT}" <<'EOF' cat > "${SIGNING_SCRIPT}" <<'EOF'
import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.dsl.ApplicationExtension
import java.io.File
import java.util.Properties import java.util.Properties
val keystoreProperties = Properties().apply { val keystoreProperties = Properties().apply {
@@ -40,10 +63,11 @@ val keystoreProperties = Properties().apply {
extensions.configure<ApplicationExtension>("android") { extensions.configure<ApplicationExtension>("android") {
signingConfigs { signingConfigs {
create("release") { create("release") {
val storePasswordValue = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias") keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword") keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = rootProject.file(keystoreProperties.getProperty("storeFile")) storeFile = File(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword") storePassword = storePasswordValue
} }
} }
buildTypes { buildTypes {