146 lines
4.7 KiB
Bash
146 lines
4.7 KiB
Bash
#!/usr/bin/env bash
|
||
# Создаёт и проверяет release keystore для подписи APK.
|
||
set -euo pipefail
|
||
|
||
SECRETS_DIR="${ANDROID_SECRETS_DIR:-/workspace/tauri_app/.secrets}"
|
||
KEYSTORE_FILE="${ANDROID_KEYSTORE_FILE:-${SECRETS_DIR}/lendry-id-release.keystore}"
|
||
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
|
||
|
||
read_credentials() {
|
||
STORE_PASSWORD=""
|
||
KEY_PASSWORD=""
|
||
if [[ -f "${CREDENTIALS_FILE}" ]]; then
|
||
# shellcheck disable=SC1090
|
||
source "${CREDENTIALS_FILE}"
|
||
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 "${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}" \
|
||
-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 успешно создан и проверен"
|