fix and update
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 \
|
|
||||||
-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}" <<EOF
|
|
||||||
# Сгенерировано $(date -u +"%Y-%m-%dT%H:%M:%SZ") — храните в безопасном месте.
|
# Сгенерировано $(date -u +"%Y-%m-%dT%H:%M:%SZ") — храните в безопасном месте.
|
||||||
ANDROID_KEYSTORE_FILE=${KEYSTORE_FILE}
|
ANDROID_KEYSTORE_FILE=${KEYSTORE_FILE}
|
||||||
ANDROID_KEY_ALIAS=${KEY_ALIAS}
|
ANDROID_KEY_ALIAS=${KEY_ALIAS}
|
||||||
ANDROID_KEYSTORE_PASSWORD=${STORE_PASSWORD}
|
ANDROID_KEYSTORE_PASSWORD=${password}
|
||||||
ANDROID_KEY_PASSWORD=${KEY_PASSWORD}
|
ANDROID_KEY_PASSWORD=${password}
|
||||||
EOF
|
EOF
|
||||||
chmod 600 "${CREDENTIALS_FILE}" || true
|
chmod 600 "${CREDENTIALS_FILE}" || true
|
||||||
|
|
||||||
cat > "${KEYSTORE_PROPS_TEMPLATE}" <<EOF
|
cat > "${KEYSTORE_PROPS_TEMPLATE}" <<EOF
|
||||||
storePassword=${STORE_PASSWORD}
|
storePassword=${password}
|
||||||
keyPassword=${KEY_PASSWORD}
|
keyPassword=${password}
|
||||||
keyAlias=${KEY_ALIAS}
|
keyAlias=${KEY_ALIAS}
|
||||||
storeFile=../../../.secrets/lendry-id-release.keystore
|
storeFile=${KEYSTORE_FILE}
|
||||||
EOF
|
EOF
|
||||||
chmod 600 "${KEYSTORE_PROPS_TEMPLATE}" || true
|
chmod 600 "${KEYSTORE_PROPS_TEMPLATE}" || true
|
||||||
|
}
|
||||||
|
|
||||||
echo "==> Keystore создан: ${KEYSTORE_FILE}"
|
validate_keystore() {
|
||||||
echo "==> Пароли сохранены: ${CREDENTIALS_FILE}"
|
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 успешно создан и проверен"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user