From fb221c91bb14bdf0d7961626df57a3be941ad6c8 Mon Sep 17 00:00:00 2001 From: grendervill Date: Sat, 19 Sep 2026 22:08:47 +0300 Subject: [PATCH] =?UTF-8?q?fix(install):=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=B9=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA?= =?UTF-8?q?=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D1=89=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8F=D0=B5=D1=82=20=D1=81=D0=B5=D0=BA=D1=80?= =?UTF-8?q?=D0=B5=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Найдено на стенде: повторный `install.sh` без `--reconfigure` генерировал новые MASTER_KEY/SESSION_PEPPER/TOTP_ENCRYPTION_KEY, после чего email-индекс и TOTP-секреты существующих пользователей перестали расшифровываться (вход возвращал `auth.invalid_credentials`). - секреты переиспользуются при любом повторном запуске, а не только с `--reconfigure`; - добавлен явный флаг `--rotate-secrets` с предупреждением о последствиях; - age-ключ бэкапов сохраняется, старые бэкапы остаются читаемыми; - защита от потери `.env`: если база есть, а MASTER_KEY нет — установщик останавливается с объяснением вместо молчаливой генерации новых ключей; - `scripts/test-install.sh` сравнивает секреты до и после повторной установки и падает при их изменении. --- deploy/install.sh | 31 ++++++++++++++++++++++++++----- scripts/test-install.sh | 27 ++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index a4c142e..e487251 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -79,6 +79,8 @@ INSTANCE_NAME="${INSTANCE_NAME:-glchat}" MAIN_GUILD_NAME="${MAIN_GUILD_NAME:-Главный сервер}" ADMIN_EMAIL="${ADMIN_EMAIL:-}" ENABLE_ADMIN_2FA="${ENABLE_ADMIN_2FA:-0}" +# Явная ротация секретов: по умолчанию секреты существующего .env сохраняются. +ROTATE_SECRETS="${ROTATE_SECRETS:-0}" ADMIN_PASSWORD="${ADMIN_PASSWORD:-}" REGISTRATION_ENABLED="${REGISTRATION_ENABLED:-true}" TURN_ENABLED="${TURN_ENABLED:-true}" @@ -119,6 +121,8 @@ install.sh — установка glchat (AGENT.md 10). --email email для Let's Encrypt (обязателен, если TLS включён) --admin логин инстанс-администратора --password пароль инстанс-администратора (иначе — интерактивный ввод) + --rotate-secrets сменить секреты (MASTER_KEY, SESSION_PEPPER и др.): + ломает существующие данные, нужен чистый инстанс --admin-2fa включить 2FA инстанс-админу и напечатать секрет и резервные коды (по умолчанию включается вручную: glchat totp-setup --email ) @@ -188,6 +192,7 @@ parse_args() { --admin) ADMIN_EMAIL="${2:?}"; shift 2 ;; --password) ADMIN_PASSWORD="${2:?}"; shift 2 ;; --admin-2fa) ENABLE_ADMIN_2FA=1; shift ;; + --rotate-secrets) ROTATE_SECRETS=1; shift ;; --instance-name) INSTANCE_NAME="${2:?}"; shift 2 ;; --main-guild) MAIN_GUILD_NAME="${2:?}"; shift 2 ;; --ssh-port) SSH_PORT="${2:?}"; shift 2 ;; @@ -569,10 +574,14 @@ generate_secrets() { local env_existing=0 [ -f "$GLCHAT_ENV_FILE" ] && env_existing=1 + # Секреты переиспользуются при любом повторном запуске: MASTER_KEY + # расшифровывает email/PII и TOTP-секреты, SESSION_PEPPER — blind index, + # LIVEKIT_API_SECRET подписывает токены. Их смена делает существующие + # данные нечитаемыми, поэтому она возможна только явным --rotate-secrets. secret_or_new() { local key="$1" generator="${2:-rand_hex}" local current="" - if [ "$env_existing" = "1" ] && [ "$RECONFIGURE" = "1" ]; then + if [ "$env_existing" = "1" ] && [ "$ROTATE_SECRETS" != "1" ]; then current="$(env_get "$key" || true)" fi if [ -n "$current" ]; then @@ -582,6 +591,19 @@ generate_secrets() { fi } + if [ "$ROTATE_SECRETS" = "1" ] && [ "$env_existing" = "1" ]; then + warn "ВНИМАНИЕ: --rotate-secrets меняет MASTER_KEY/SESSION_PEPPER." + warn "Существующие email/PII, blind index и TOTP-секреты станут нечитаемыми:" + warn "нужен чистый инстанс (сначала сделайте бэкап и удалите базу)." + fi + + # Существующая база без секретов в .env — почти всегда потерянный .env: + # молча сгенерировать новые ключи значит сделать данные нечитаемыми. + if [ "$ROTATE_SECRETS" != "1" ] && [ -f "${GLCHAT_DATA_DIR}/db/glchat.db" ] \ + && [ -z "$(env_get MASTER_KEY || true)" ]; then + die "найдена база ${GLCHAT_DATA_DIR}/db/glchat.db, но в ${GLCHAT_ENV_FILE} нет MASTER_KEY: восстановите .env из бэкапа (иначе данные не расшифровать) или запустите с --rotate-secrets на чистой базе" + fi + SESSION_PEPPER="$(secret_or_new SESSION_PEPPER rand_hex)" MASTER_KEY="$(secret_or_new MASTER_KEY rand_hex)" TOTP_ENCRYPTION_KEY="$(secret_or_new TOTP_ENCRYPTION_KEY rand_hex)" @@ -601,10 +623,9 @@ generate_secrets() { # без смены владельца приложение не сможет создать базу (AGENT.md 4.3). set_data_ownership - if [ "$env_existing" = "1" ] && [ "$RECONFIGURE" = "1" ] && [ -f "${GLCHAT_DATA_DIR}/backups/age-key.txt" ]; then - AGE_KEY_FILE="${GLCHAT_DATA_DIR}/backups/age-key.txt" - else - AGE_KEY_FILE="${GLCHAT_DATA_DIR}/backups/age-key.txt" + # Ключ шифрования бэкапов тоже сохраняем: иначе старые бэкапы не прочитать. + AGE_KEY_FILE="${GLCHAT_DATA_DIR}/backups/age-key.txt" + if [ ! -f "$AGE_KEY_FILE" ]; then generate_age_key "$AGE_KEY_FILE" fi chmod 600 "$AGE_KEY_FILE" diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 24df496..26b8fd8 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -61,12 +61,33 @@ timeout 600 docker exec -i "$CONTAINER" bash -lc ' --admin admin@gl.test.local --password "test-password-123" --yes ' &2 + diff <(printf '%s\n' "$secrets_before") <(printf '%s\n' "$secrets_after") >&2 || true + exit 1 +fi +echo "секреты сохранены при повторной установке: ok" # Сборка образа внутри DinD невозможна (overlay поверх overlay), поэтому образ # собирается и проверяется отдельно на хосте (make docker-build + docker compose). -log "4/4 проверки состояния установки" +log "проверки состояния установки" docker exec "$CONTAINER" bash -lc ' set -e echo "--- артефакты ---"