Files
han-app/codebase/backend/deployment/DEPLOYMENT_GUIDE.ru.md
T

827 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Подробная инструкция по развертыванию и запуску HAN Chat
Эта инструкция описывает первый запуск **текущего legacy/stub проекта ВМ1** на одной виртуальной
машине с Ubuntu 24.04. Она не разворачивает target ВМ2 Processing и не подтверждает production-готовность Message Safety v2. Все команды предполагают, что проект расположен в
`/opt/han-chat/backend`, а команды Docker Compose выполняются из этого каталога.
PostgreSQL и Selectel S3 не запускаются в Docker Compose: их необходимо создать
заранее как внешние управляемые сервисы. Из интернета должны быть доступны только
порты 80 и 443 виртуальной машины.
## 1. Что потребуется до начала работы
Подготовьте:
1. Виртуальную машину с Ubuntu 24.04 и минимум 4 vCPU, 8 ГБ RAM и 40 ГБ диска.
2. SSH-доступ к VM пользователем с правом `sudo`.
3. Домен, например `chat.example.ru`, и возможность изменить его DNS.
4. Управляемый PostgreSQL, доступный VM по приватной сети.
5. Три приватных бакета Selectel S3.
6. Учетные данные приложения Bitrix24.
7. При необходимости — удаленный OTLP-бэкенд для телеметрии.
8. Локальную копию каталога `HAN_chat_specification/codebase/backend` либо URL
Git-репозитория, из которого его можно получить.
Для первого тестового запуска допустимы mock OTP, заглушка Message Safety и
заглушка bitrix-sync. Они не являются полноценными production-реализациями.
Целевой cutover выполняется по `modules/module-10-deployment-runbook.md`: самостоятельная ВМ2, root Compose/systemd unit, собственный nginx с public exact CRM webhook `80/443` и private Message Safety listener `8443`, раздельные TLS-контуры, secrets/IAM, egress allow-list и local OTEL Collector. Не переносите команды этого single-VM guide на ВМ2 без VM2-specific manifests.
## 2. Первичный вход на VM
Подключитесь к созданной VM облачным пользователем:
```sh
ssh <cloud-user>@<VM_IP>
```
Проверьте версию ОС:
```sh
cat /etc/os-release
```
Должна использоваться Ubuntu 24.04 или более новая версия.
## 3. Передача и запуск скрипта настройки VM
Сначала передайте на VM только подготовительный скрипт. Например, с локального
компьютера:
```sh
scp deployment/scripts/setup-vm.sh <cloud-user>@<VM_IP>:/tmp/setup-vm.sh
```
На VM выполните:
```sh
chmod +x /tmp/setup-vm.sh
sudo /tmp/setup-vm.sh
```
Скрипт:
- обновит Ubuntu и установит базовые пакеты;
- создаст пользователя `deploy`;
- установит Docker Engine и Docker Compose;
- настроит UFW, fail2ban и цепочку `DOCKER-USER`;
- откроет только SSH, HTTP и HTTPS;
- создаст `/opt/han-chat/backend`;
- создаст swap;
- включит автоматические обновления безопасности;
- отключит парольный SSH-вход и X11 forwarding;
- заблокирует локальные пароли `root` и `deploy` после проверки SSH-ключей.
Если `authorized_keys` пользователя `deploy` отсутствует, скрипт остановится до
блокировки паролей. `HARDEN_SSH=true` дополнительно запрещает прямой вход
пользователем `root` и SSH TCP forwarding; включайте этот режим только после
проверки входа пользователем `deploy` по ключу в отдельной сессии.
Если SSH работает на нестандартном порту или имя внешнего интерфейса известно
заранее, передайте параметры:
```sh
sudo SSH_PORT=2222 EXTERNAL_IF=ens3 /tmp/setup-vm.sh
```
После завершения выйдите из SSH-сессии: членство `deploy` в группе `docker`
начинает действовать только после нового входа.
```sh
exit
ssh deploy@<VM_IP>
docker version
docker compose version
```
## 4. Копирование проекта на VM
### Вариант A — через Git
Это предпочтительный вариант: Git применит правило LF для shell-скриптов.
```sh
git clone <URL_РЕПОЗИТОРИЯ> /tmp/han-chat-source
cp -a /tmp/han-chat-source/HAN_chat_specification/codebase/backend/. \
/opt/han-chat/backend/
cd /opt/han-chat/backend
```
Если `HAN_chat_specification` является корнем репозитория:
```sh
cp -a /tmp/han-chat-source/codebase/backend/. /opt/han-chat/backend/
```
### Вариант B — архивом с локального компьютера
Создайте архив именно из содержимого каталога `backend`, включая скрытые файлы:
```sh
tar -C HAN_chat_specification/codebase/backend -czf han-chat-backend.tar.gz .
scp han-chat-backend.tar.gz deploy@<VM_IP>:/tmp/
```
На VM:
```sh
cd /opt/han-chat/backend
tar -xzf /tmp/han-chat-backend.tar.gz
# Обязательно при копировании с Windows:
find . -type f \( -name '*.sh' -o -name 'validate-env' \) -exec dos2unix {} +
chmod +x scripts/validate-env deployment/scripts/*.sh redis/scripts/*.sh nginx/scripts/*.sh
```
Проверьте наличие точки запуска:
```sh
test -f /opt/han-chat/backend/docker-compose.yml
test -f /opt/han-chat/backend/.env.example
```
## 5. Настройка DNS и сетевого доступа
Создайте DNS-запись:
```text
chat.example.ru A <ПУБЛИЧНЫЙ_IP_VM>
```
Дождитесь обновления DNS:
```sh
getent ahostsv4 chat.example.ru
```
В облачной группе безопасности VM разрешите входящие подключения:
- TCP 80 из интернета;
- TCP 443 из интернета;
- SSH только из доверенной сети или с административного IP.
Не открывайте наружу порты 6379, 4317, 4318, 8000, 8080 и 9000.
В группе безопасности PostgreSQL разрешите входящий трафик на порт PostgreSQL
только от приватного адреса или группы безопасности VM.
## 6. Подготовка управляемого PostgreSQL
Создайте одну базу данных:
```text
han_chat
```
В ней нужны схемы:
```text
han_app
bitrix_local
bitrix_sync
message_safety
keycloak
```
Для текущей MVP-реализации используются следующие пользователи:
```text
han_app
bitrix_local_app
bitrix_sync_user
message_safety_app
keycloak_user
```
Создать пользователей и схемы можно через панель провайдера либо от имени
администратора PostgreSQL. Пример SQL:
```sql
CREATE ROLE han_app LOGIN PASSWORD '<HAN_APP_PASSWORD>';
CREATE ROLE bitrix_local_app LOGIN PASSWORD '<BITRIX_LOCAL_PASSWORD>';
CREATE ROLE bitrix_sync_user LOGIN PASSWORD '<BITRIX_SYNC_PASSWORD>';
CREATE ROLE message_safety_app LOGIN PASSWORD '<SAFETY_PASSWORD>';
CREATE ROLE keycloak_user LOGIN PASSWORD '<KEYCLOAK_PASSWORD>';
CREATE SCHEMA IF NOT EXISTS han_app AUTHORIZATION han_app;
CREATE SCHEMA IF NOT EXISTS bitrix_local AUTHORIZATION bitrix_local_app;
CREATE SCHEMA IF NOT EXISTS bitrix_sync AUTHORIZATION bitrix_sync_user;
CREATE SCHEMA IF NOT EXISTS message_safety AUTHORIZATION message_safety_app;
CREATE SCHEMA IF NOT EXISTS keycloak AUTHORIZATION keycloak_user;
GRANT CONNECT ON DATABASE han_chat TO
han_app, bitrix_local_app, bitrix_sync_user, message_safety_app, keycloak_user;
```
Текущие migration jobs используют те же DSN, что и сервисы. Поэтому владельцы
схем должны иметь право создавать таблицы в своих схемах. Для более строгого
production-разделения migration/runtime ролей потребуется отдельная настройка
DSN и прав, которой в текущем `.env.example` нет.
Скачайте CA-сертификат PostgreSQL у провайдера и поместите его на VM:
```sh
mkdir -p /opt/han-chat/backend/secrets/pg
cp /путь/к/ca.pem /opt/han-chat/backend/secrets/pg/ca.pem
chmod 644 /opt/han-chat/backend/secrets/pg/ca.pem
```
CA-сертификат не является секретом. Права `644` нужны, чтобы его могли прочитать
контейнеры, работающие не от root.
Проверьте сетевую доступность:
```sh
nc -vz <PG_HOST> 6432
```
Замените `6432` на фактический порт провайдера.
## 7. Подготовка Selectel S3
Создайте три приватных бакета:
```text
han-chat-quarantine
han-chat-attachments
han-chat-documents
```
Создайте две пары ключей:
1. Ключ API с правом чтения и записи в бакеты.
2. Отдельный ключ Message Safety только с правом чтения карантина.
Для бакетов запретите публичный доступ. Для браузерной загрузки настройте CORS:
- Allowed origin: `https://chat.example.ru`;
- Methods: `PUT`, `GET`, `HEAD`;
- Headers: `Content-Type`, `x-amz-*`;
- Expose header: `ETag`.
Для карантина задайте lifecycle удаления объектов с запасом относительно
`MESSAGE_SAFETY_TASK_TTL_SEC`.
## 8. Создание файла окружения
`.env` содержит только несекретную конфигурацию. На VM:
```sh
cd /opt/han-chat/backend
umask 077
cp .env.example .env
chmod 600 .env
nano .env
```
Замените несекретные адреса `example.*`. Не добавляйте в `.env` пароли, токены,
ключи, credential-bearing DSN или пути `*_FILE`. Установите отдельно проверенный
launcher `deployment/secrets/han-secrets` из ops-пакета. Его интерфейс:
```sh
deployment/secrets/han-secrets run --config .env -- <command>
```
Launcher читает `SECRETS_SOURCE=file|selectel`, устанавливает
`HAN_SECRETS_ACTIVE=1`, выдаёт значения только дочернему процессу и не печатает
их. Рекомендуемый `HAN_RUNTIME_SECRET_MANIFEST` содержит только пары
`SECRET_KEY=/absolute/protected/path`; файлы имеют mode `0400`/`0600`.
### 8.1. Основные адреса
Для домена `chat.example.ru`:
```dotenv
APP_ENV=production-like
RELEASE_VERSION=2026-07-13-1
PUBLIC_HOST=chat.example.ru
PUBLIC_WEB_URL=https://chat.example.ru
PUBLIC_API_URL=https://chat.example.ru/api
PUBLIC_AUTH_URL=https://chat.example.ru/auth
KEYCLOAK_PUBLIC_URL=https://chat.example.ru/auth
KEYCLOAK_INTERNAL_URL=http://keycloak:8080/auth
KEYCLOAK_REALM=han-chat
KEYCLOAK_AUDIENCE=han-chat-api
```
### 8.2. PostgreSQL
В `.env` укажите только host, port, database и путь к публичному CA:
```dotenv
HAN_PG_HOST=<PG_HOST>
HAN_PG_PORT=6432
HAN_PG_DATABASE=han_chat
PG_CA_HOST_PATH=/opt/han-chat/backend/secrets/pg/ca.pem
KEYCLOAK_DB_SCHEMA=keycloak
```
Все service DSN, включая JDBC и backup DSN, формирует secret backend. Runtime
validator проверяет `verify-full`, `sslrootcert` и запрет `options/currentSchema`
без вывода строк подключения.
Порт `5433` используется с PgBouncer в режиме `session`. Не добавляйте
`options=-csearch_path...` или JDBC-параметр `currentSchema`: они передают
startup parameter `search_path`, который Selectel PgBouncer отклоняет. Для
Keycloak схема задаётся отдельно через `KEYCLOAK_DB_SCHEMA`.
Для каждой сервисной роли заранее задайте database-level `search_path`.
Если пароль содержит `@`, `:`, `/`, `?`, `#` или `%`, его необходимо
URL-кодировать внутри PostgreSQL URL.
### 8.3. Подготовка runtime-секретов
Генерируйте секреты вне shell history средствами secret manager. Не выполняйте
`export TOKEN=...` и не вставляйте значения в команды. Имена обязательных
runtime-переменных определены в `scripts/validate-env`; парные токены связываются
в secret backend.
```sh
openssl rand -hex 32
```
Для `BITRIX_TOKEN_ENCRYPTION_KEY` нужен URL-safe Base64 ключ ровно из 32 байт:
```sh
python3 -c 'import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'
```
Ни один из этих секретов не добавляется в `.env`. Пары проверяются runtime
validator без вывода значений.
```dotenv
BITRIX_LOCAL_APP_INTERNAL_TOKEN=<TOKEN_A>
BITRIX_INTERNAL_API_TOKEN=<TOKEN_A>
BITRIX_API_FORWARD_TOKEN=<TOKEN_B>
BITRIX_API_INBOX_TOKEN=<TOKEN_B>
```
Остальные токены должны быть разными:
```dotenv
MESSAGE_SAFETY_SERVICE_TOKEN=<UNIQUE_TOKEN>
BITRIX_SYNC_SERVICE_TOKEN=<UNIQUE_TOKEN>
KEYCLOAK_SETTINGS_BRIDGE_TOKEN=<UNIQUE_TOKEN>
CURSOR_HMAC_SECRET=<UNIQUE_TOKEN>
KEYCLOAK_OTP_HMAC_KEY=<UNIQUE_TOKEN_НЕ_КОРОЧЕ_32_БАЙТ>
BITRIX_TOKEN_ENCRYPTION_KEY=<URLSAFE_BASE64_KEY>
KEYCLOAK_ADMIN_PASSWORD=<UNIQUE_ADMIN_PASSWORD>
```
### 8.4. Redis
Создайте три разных пароля в secret backend; там же сформируйте Redis URL.
Следующий блок описывает логический контракт и не является содержимым `.env`:
```dotenv
REDIS_API_PASSWORD=<REDIS_API_PASSWORD>
REDIS_SAFETY_PASSWORD=<REDIS_SAFETY_PASSWORD>
REDIS_HEALTH_PASSWORD=<REDIS_HEALTH_PASSWORD>
REDIS_URL=redis://api_backend:<REDIS_API_PASSWORD>@redis:6379/0
REDIS_REALTIME_URL=redis://api_backend:<REDIS_API_PASSWORD>@redis:6379/1
MESSAGE_SAFETY_REDIS_URL=redis://message_safety:<REDIS_SAFETY_PASSWORD>@redis:6379/2
```
### 8.5. Mock OTP
В текущих deploy-артефактах реализован только mock OTP. Для запуска до controlled SMS rollout:
```dotenv
KEYCLOAK_OTP_MOCK_ENABLED=true
KEYCLOAK_OTP_MOCK_RISK_ACCEPTED=true
```
`KEYCLOAK_OTP_MOCK_CODE` хранится только в secret backend. Не используйте mock
как production-механизм доставки OTP.
Целевой real mode задаёт `modules/module-11-idgtl-sms.md`: Keycloak генерирует и локально проверяет OTP, `sms-service` надёжно записывает заказ/журнал, worker вызывает i-Digital Direct, callback обновляет только delivery journal. Нельзя просто установить `KEYCLOAK_OTP_MOCK_ENABLED=false`.
До переключения необходимы: schema/role `sms` и migrations/seed, active approved `auth_otp` (`code`, `ttl_min`), согласованный sender, Direct `TOKEN_1`, парные service tokens, отдельные callback credentials, exact nginx callback route, подтверждённый source IP Direct и статический egress IP worker. Сначала deploy при mock=true, затем provider smoke/callback/redaction evidence и только после этого cutover. Rollback возвращает mock без удаления SMS schema/journal.
### 8.6. S3
```dotenv
SELECTEL_S3_ENDPOINT_URL=https://s3.storage.selcloud.ru
SELECTEL_S3_BUCKET_QUARANTINE=han-chat-quarantine
SELECTEL_S3_BUCKET_ATTACHMENTS=han-chat-attachments
SELECTEL_S3_BUCKET_DOCUMENTS=han-chat-documents
```
Обе пары S3 credentials хранятся только в secret backend.
API backend принудительно использует virtual-hosted addressing:
`https://<bucket>.s3.storage.selcloud.ru/<object-key>`. Это обязательно для
браузерных presigned PUT и CORS в Selectel; path-style URL для этого сценария не
используйте. DNS и исходящий HTTPS с ВМ должны разрешать поддомены бакетов.
### 8.7. Bitrix24
До установки локального приложения загрузите credentials в secret backend.
В `.env` остаются только несекретные connector/public URL параметры:
```dotenv
BITRIX_CONNECTOR_ID=han_mobile_app
BITRIX_OPEN_LINE_ID=8
BITRIX_PUBLIC_BASE_URL=https://chat.example.ru/bitrix
```
`BITRIX_APPLICATION_TOKEN` сохраняется в secret backend после создания
приложения и никогда не помещается в `.env`.
### 8.8. TLS и наблюдаемость
До выпуска сертификата оставьте в `.env` целевые значения:
```dotenv
NGINX_TLS_ENABLED=true
NGINX_TLS_CERTIFICATE=/etc/letsencrypt/live/chat.example.ru/fullchain.pem
NGINX_TLS_CERTIFICATE_KEY=/etc/letsencrypt/live/chat.example.ru/privkey.pem
ACME_EMAIL=<ADMIN_EMAIL>
```
Если удаленный OTLP-бэкенд пока не выбран, укажите временные непубличные значения
и примите ограничение: Collector будет пытаться отправлять телеметрию и сохранять
ее в ограниченной очереди. Перед production-запуском задайте реальный endpoint.
## 9. Проверка окружения и конфигурации Compose
Выполните:
```sh
cd /opt/han-chat/backend
./scripts/validate-env .env
sudo systemctl restart han-secrets@production.service
sudo ./scripts/validate-env .env \
--runtime-manifest /run/han-chat/secrets/manifest
sudo deployment/secrets/han-compose config --quiet
sudo deployment/secrets/han-compose config --services
python3 -m unittest discover -s tests -v
```
Не переходите к следующему шагу, пока все команды не завершатся успешно.
Посмотрите итоговую конфигурацию портов:
```sh
docker compose --env-file .env config | grep -n 'published:'
```
Публиковаться должны только 80 и 443 у nginx.
## 10. Сборка образов
Соберите все локальные образы:
```sh
cd /opt/han-chat/backend
docker compose --env-file .env build --pull
```
Проверьте список:
```sh
docker compose --env-file .env images
```
Сборка Keycloak включает Java OTP SPI, а сборка `frontend-static` экспортирует
тестовый Expo Web frontend.
## 11. Миграции БД и начальные настройки
Перед миграциями создайте backup/PITR marker в панели провайдера PostgreSQL.
Затем:
```sh
cd /opt/han-chat/backend
PITR_MARKER_CONFIRMED=true deployment/scripts/migrate.sh
deployment/scripts/seed.sh
```
Скрипт применит миграции `han_app`, `bitrix_local` и baseline `bitrix_sync`, после
чего загрузит `deployment/app-settings.production-like.yaml`.
Повторный запуск seed должен быть безопасным:
```sh
deployment/scripts/seed.sh
```
## 12. Запуск внутренних сервисов
Сначала запустите Redis:
```sh
docker compose --env-file .env up -d redis
docker compose --env-file .env ps redis
```
Затем Keycloak и OpenTelemetry:
```sh
docker compose --env-file .env up -d keycloak otel-collector
docker compose --env-file .env ps keycloak otel-collector
```
Первый запуск Keycloak может занять несколько минут: он создаст свои таблицы и
импортирует realm `han-chat`.
После готовности Keycloak:
```sh
docker compose --env-file .env up -d message-safety
docker compose --env-file .env up -d api-backend
docker compose --env-file .env up -d bitrix-local-app bitrix-sync
docker compose --env-file .env up -d \
delivery-worker safety-recovery-worker cleanup-worker
docker compose --env-file .env ps
```
Если сервис не становится healthy:
```sh
docker compose --env-file .env logs --tail=200 <SERVICE_NAME>
docker inspect "$(docker compose --env-file .env ps -q <SERVICE_NAME>)"
```
### 12.1. Повторная раскатка upstream при уже работающем nginx
Nginx разрешает Docker DNS имена upstream при загрузке конфигурации. После
`up --build`, `pull`, rollback или `--force-recreate` контейнер может получить
новый IP, а работающий nginx продолжит использовать старый и вернёт `502
Connection refused`.
После пересоздания `api-backend`, `keycloak`, `sms-service` или
`bitrix-local-app` обязательно выполните:
```sh
docker compose --env-file .env up -d --wait \
api-backend keycloak sms-service bitrix-local-app
docker compose --env-file .env exec -T nginx \
nginx -t -c /tmp/nginx.conf
docker compose --env-file .env kill -s HUP nginx
curl -fsS "https://${PUBLIC_HOST}/api/v1/public/app-config" | jq
curl -fsS \
"https://${PUBLIC_HOST}/auth/realms/han-chat/.well-known/openid-configuration" |
jq
```
Не используйте bare-команды `nginx -t` и `nginx -s reload`: рабочая
конфигурация находится в `/tmp/nginx.conf`, PID — в `/tmp/nginx.pid`, а
контейнер использует read-only filesystem.
## 13. Первоначальный выпуск TLS-сертификата
Для ACME требуется работающий nginx по HTTP. В `.env` оставьте
`NGINX_TLS_ENABLED=true`, но первый nginx запустите с временным переопределением:
```sh
NGINX_TLS_ENABLED=false \
docker compose --env-file .env up -d frontend-static nginx
```
Проверьте HTTP:
```sh
curl -I http://chat.example.ru/
```
Сначала рекомендуется проверить Certbot через staging:
```sh
docker compose --env-file .env --profile certbot run --rm certbot certonly \
--staging \
--webroot -w /var/www/certbot \
-d chat.example.ru \
--cert-name chat.example.ru-staging \
--email <ADMIN_EMAIL> \
--agree-tos --no-eff-email --non-interactive
```
После успешного staging-теста выпустите рабочий сертификат с основным cert-name
без `--staging`:
```sh
docker compose --env-file .env --profile certbot run --rm certbot certonly \
--webroot -w /var/www/certbot \
-d chat.example.ru \
--cert-name chat.example.ru \
--email <ADMIN_EMAIL> \
--agree-tos --no-eff-email --non-interactive
```
Пересоздайте nginx уже с TLS:
```sh
docker compose --env-file .env up -d --force-recreate nginx
docker compose --env-file .env exec -T nginx nginx -t -c /tmp/nginx.conf
curl -I https://chat.example.ru/
```
Повторно запустите VM setup, чтобы он обнаружил проект и установил systemd-таймер
продления сертификата:
```sh
sudo /opt/han-chat/backend/deployment/scripts/setup-vm.sh
systemctl status han-chat-ssl-renew.timer
```
## 14. Запуск всего контура
Теперь можно привести весь проект к состоянию, описанному Compose:
```sh
cd /opt/han-chat/backend
docker compose --env-file .env up -d
docker compose --env-file .env ps
```
Проверьте, что контейнеры не перезапускаются:
```sh
docker compose --env-file .env ps
docker compose --env-file .env logs --since=10m
```
## 15. Публичная проверка
Запустите smoke-тест:
```sh
cd /opt/han-chat/backend
deployment/scripts/smoke.sh
```
Также вручную проверьте:
```sh
curl -fsS https://chat.example.ru/api/v1/public/app-config | jq
curl -fsS https://chat.example.ru/api/v1/public/content | jq
curl -fsS \
https://chat.example.ru/auth/realms/han-chat/.well-known/openid-configuration | jq
```
Внутренний API не должен быть опубликован:
```sh
curl -i https://chat.example.ru/internal/safety/v2/messages/check
```
Ожидаемый статус — `404`.
Откройте в браузере:
```text
https://chat.example.ru/
```
Для тестовой авторизации получите mock code утверждённым защищённым способом,
не читая его из `.env` и не помещая в shell history.
## 16. Подключение Bitrix24
В настройках локального приложения Bitrix24 задайте HTTPS-адреса:
```text
Установка: https://chat.example.ru/bitrix/install
Обработчик: https://chat.example.ru/bitrix/handler
Placement: https://chat.example.ru/bitrix/placement
```
После установки:
1. Получите и сохраните application token.
2. Сохраните его как `BITRIX_APPLICATION_TOKEN` в secret backend.
3. Пересоздайте сервис:
```sh
docker compose --env-file .env up -d --force-recreate bitrix-local-app
docker compose --env-file .env logs --tail=200 bitrix-local-app
```
Проверьте коннектор `han_mobile_app` и Открытую линию 8.
## 17. Включение SSH hardening
Только после успешного входа пользователем `deploy` по ключу в отдельной сессии:
```sh
sudo HARDEN_SSH=true \
/opt/han-chat/backend/deployment/scripts/setup-vm.sh
```
Не закрывайте текущую SSH-сессию, пока не проверили новый вход.
## 18. Обычный перезапуск проекта
Для штатного запуска после перезагрузки VM:
```sh
cd /opt/han-chat/backend
docker compose --env-file .env up -d
docker compose --env-file .env ps
```
Для перезапуска одного сервиса:
```sh
docker compose --env-file .env restart api-backend
```
После изменения `.env` используйте пересоздание, а не `restart`:
```sh
docker compose --env-file .env up -d --force-recreate <SERVICE_NAME>
```
## 19. Обновление версии проекта
Перед обновлением:
1. Создайте backup/PITR marker PostgreSQL.
2. Сохраните текущие image digests.
3. Получите новый код.
4. Проверьте несекретный `.env` и runtime secret set.
5. Пересоберите образы.
6. Примените миграции и seed.
7. Пересоздайте сервисы.
Команды:
```sh
cd /opt/han-chat/backend
./scripts/validate-env .env
docker compose --env-file .env build --pull
PITR_MARKER_CONFIRMED=true deployment/scripts/migrate.sh
deployment/scripts/seed.sh
docker compose --env-file .env up -d
deployment/scripts/smoke.sh
```
## 20. Диагностика
Состояние сервисов:
```sh
docker compose --env-file .env ps
```
Все логи:
```sh
docker compose --env-file .env logs --tail=300
```
Логи конкретного сервиса:
```sh
docker compose --env-file .env logs -f api-backend
```
Проверка firewall:
```sh
sudo ufw status verbose
sudo iptables -L HAN-CHAT-DOCKER -n -v
```
Проверка сертификата:
```sh
openssl s_client -connect chat.example.ru:443 -servername chat.example.ru \
</dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
```
Проверка свободного места:
```sh
df -h
docker system df
```
Не выполняйте `docker compose down -v`: эта команда удалит именованные volumes.
Не выполняйте Alembic downgrade. Для отката используйте
`deployment/scripts/rollback.sh` и инструкции из `RUNBOOK.ru.md`.
## 21. Когда развертывание можно считать завершенным
Проект запущен корректно, если:
- `docker compose ps` показывает healthy для критических сервисов;
- `deployment/scripts/smoke.sh` завершается успешно;
- открывается тестовый frontend;
- проходит авторизация через mock OTP;
- отправляются текстовые и файловые сообщения;
- внутренние URL возвращают 404 снаружи;
- TLS-сертификат действителен;
- логи не содержат токены, PII и тексты сообщений;
- настроены резервное копирование PostgreSQL и продление TLS.
Для формальной production-like приемки после этого пройдите контрольные этапы
из `deployment/RUNBOOK.ru.md`.