fix and update
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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}" <<EOF
|
||||
storePassword=${ANDROID_KEYSTORE_PASSWORD}
|
||||
keyPassword=${ANDROID_KEY_PASSWORD}
|
||||
keyAlias=${ANDROID_KEY_ALIAS}
|
||||
storeFile=../../../.secrets/lendry-id-release.keystore
|
||||
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
|
||||
}
|
||||
|
||||
write_credentials_files() {
|
||||
local password="$1"
|
||||
|
||||
cat > "${CREDENTIALS_FILE}" <<EOF
|
||||
# Сгенерировано $(date -u +"%Y-%m-%dT%H:%M:%SZ") — храните в безопасном месте.
|
||||
ANDROID_KEYSTORE_FILE=${KEYSTORE_FILE}
|
||||
ANDROID_KEY_ALIAS=${KEY_ALIAS}
|
||||
ANDROID_KEYSTORE_PASSWORD=${password}
|
||||
ANDROID_KEY_PASSWORD=${password}
|
||||
EOF
|
||||
chmod 600 "${PROPS_TEMPLATE}" || true
|
||||
echo "==> Восстановлен keystore.properties.template из signing-credentials.env"
|
||||
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
|
||||
exit 0
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
STORE_PASSWORD="$(gen_secret)"
|
||||
KEY_PASSWORD="$(gen_secret)"
|
||||
rm -f "${KEYSTORE_FILE}"
|
||||
|
||||
echo "==> Генерация release keystore для подписи APK..."
|
||||
# JKS + один пароль — максимально совместимо с Android Gradle signing.
|
||||
keytool -genkeypair -v \
|
||||
-storetype PKCS12 \
|
||||
-storetype JKS \
|
||||
-keystore "${KEYSTORE_FILE}" \
|
||||
-alias "${KEY_ALIAS}" \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity 10000 \
|
||||
-storepass "${STORE_PASSWORD}" \
|
||||
-keypass "${KEY_PASSWORD}" \
|
||||
-storepass "${password}" \
|
||||
-keypass "${password}" \
|
||||
-dname "CN=Lendry ID, OU=Mobile, O=Lendry, C=RU"
|
||||
|
||||
chmod 600 "${KEYSTORE_FILE}" || true
|
||||
|
||||
cat > "${CREDENTIALS_FILE}" <<EOF
|
||||
# Сгенерировано $(date -u +"%Y-%m-%dT%H:%M:%SZ") — храните в безопасном месте.
|
||||
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
|
||||
storePassword=${STORE_PASSWORD}
|
||||
keyPassword=${KEY_PASSWORD}
|
||||
keyAlias=${KEY_ALIAS}
|
||||
storeFile=../../../.secrets/lendry-id-release.keystore
|
||||
EOF
|
||||
chmod 600 "${KEYSTORE_PROPS_TEMPLATE}" || 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 успешно создан и проверен"
|
||||
|
||||
@@ -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<ApplicationExtension>("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 {
|
||||
|
||||
Reference in New Issue
Block a user