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 "--- артефакты ---"