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

26 KiB
Raw Blame History

Подробная инструкция по развертыванию и запуску HAN Chat

Эта инструкция описывает первый запуск текущего проекта на одной виртуальной машине с Ubuntu 24.04. Все команды на VM предполагают, что проект расположен в /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-реализациями.

2. Первичный вход на VM

Подключитесь к созданной VM облачным пользователем:

ssh <cloud-user>@<VM_IP>

Проверьте версию ОС:

cat /etc/os-release

Должна использоваться Ubuntu 24.04 или более новая версия.

3. Передача и запуск скрипта настройки VM

Сначала передайте на VM только подготовительный скрипт. Например, с локального компьютера:

scp deployment/scripts/setup-vm.sh <cloud-user>@<VM_IP>:/tmp/setup-vm.sh

На VM выполните:

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-вход. Не используйте HARDEN_SSH=true, пока не проверили вход пользователем deploy по ключу в отдельной сессии.

Если SSH работает на нестандартном порту или имя внешнего интерфейса известно заранее, передайте параметры:

sudo SSH_PORT=2222 EXTERNAL_IF=ens3 /tmp/setup-vm.sh

После завершения выйдите из SSH-сессии: членство deploy в группе docker начинает действовать только после нового входа.

exit
ssh deploy@<VM_IP>
docker version
docker compose version

4. Копирование проекта на VM

Вариант A — через Git

Это предпочтительный вариант: Git применит правило LF для shell-скриптов.

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 является корнем репозитория:

cp -a /tmp/han-chat-source/codebase/backend/. /opt/han-chat/backend/

Вариант B — архивом с локального компьютера

Создайте архив именно из содержимого каталога backend, включая скрытые файлы:

tar -C HAN_chat_specification/codebase/backend -czf han-chat-backend.tar.gz .
scp han-chat-backend.tar.gz deploy@<VM_IP>:/tmp/

На VM:

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

Проверьте наличие точки запуска:

test -f /opt/han-chat/backend/docker-compose.yml
test -f /opt/han-chat/backend/.env.example

5. Настройка DNS и сетевого доступа

Создайте DNS-запись:

chat.example.ru A <ПУБЛИЧНЫЙ_IP_VM>

Дождитесь обновления DNS:

getent ahostsv4 chat.example.ru

В облачной группе безопасности VM разрешите входящие подключения:

  • TCP 80 из интернета;
  • TCP 443 из интернета;
  • SSH только из доверенной сети или с административного IP.

Не открывайте наружу порты 6379, 4317, 4318, 8000, 8080 и 9000.

В группе безопасности PostgreSQL разрешите входящий трафик на порт PostgreSQL только от приватного адреса или группы безопасности VM.

6. Подготовка управляемого PostgreSQL

Создайте одну базу данных:

han_chat

В ней нужны схемы:

han_app
bitrix_local
bitrix_sync
message_safety
keycloak

Для текущей MVP-реализации используются следующие пользователи:

han_app
bitrix_local_app
bitrix_sync_user
message_safety_app
keycloak_user

Создать пользователей и схемы можно через панель провайдера либо от имени администратора PostgreSQL. Пример 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:

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.

Проверьте сетевую доступность:

nc -vz <PG_HOST> 6432

Замените 6432 на фактический порт провайдера.

7. Подготовка Selectel S3

Создайте три приватных бакета:

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. Создание файла окружения

На VM:

cd /opt/han-chat/backend
umask 077
cp .env.example .env
chmod 600 .env
nano .env

Замените все change-me и адреса example.*.

8.1. Основные адреса

Для домена chat.example.ru:

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

Укажите фактические host, port, database, пользователей и пароли:

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

DATABASE_URL=postgresql+asyncpg://han_app:<PASSWORD>@<PG_HOST>:6432/han_chat?sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem&options=-csearch_path%3Dhan_app
BITRIX_DATABASE_URL=postgresql://bitrix_local_app:<PASSWORD>@<PG_HOST>:6432/han_chat?sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem&options=-csearch_path%3Dbitrix_local
BITRIX_SYNC_DATABASE_URL=postgresql://bitrix_sync_user:<PASSWORD>@<PG_HOST>:6432/han_chat?sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem&options=-csearch_path%3Dbitrix_sync
MESSAGE_SAFETY_DATABASE_URL=postgresql://message_safety_app:<PASSWORD>@<PG_HOST>:6432/han_chat?sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem&options=-csearch_path%3Dmessage_safety

KEYCLOAK_DB_URL=jdbc:postgresql://<PG_HOST>:6432/han_chat?currentSchema=keycloak&sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem
KEYCLOAK_DB_USERNAME=keycloak_user
KEYCLOAK_DB_PASSWORD=<PASSWORD>

Если пароль содержит @, :, /, ?, # или %, его необходимо URL-кодировать внутри PostgreSQL URL.

8.3. Генерация секретов

Для обычных токенов используйте:

openssl rand -hex 32

Для BITRIX_TOKEN_ENCRYPTION_KEY нужен URL-safe Base64 ключ ровно из 32 байт:

python3 -c 'import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'

Заполните все секреты. Две пары должны совпадать:

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>

Остальные токены должны быть разными:

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

Создайте три разных пароля и продублируйте их в URL:

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

В MVP реализован только mock OTP. Для запуска:

KEYCLOAK_OTP_MOCK_ENABLED=true
KEYCLOAK_OTP_MOCK_RISK_ACCEPTED=true
KEYCLOAK_OTP_MOCK_CODE=<ТЕСТОВЫЙ_КОД_НЕ_КОРОЧЕ_16_СИМВОЛОВ>

Этот код будет вводиться пользователем при тестовой авторизации. Не используйте его как production-механизм доставки OTP.

8.6. S3

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

SELECTEL_S3_ACCESS_KEY=<API_ACCESS_KEY>
SELECTEL_S3_SECRET_KEY=<API_SECRET_KEY>
SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY=<SAFETY_READ_ACCESS_KEY>
SELECTEL_S3_QUARANTINE_READ_SECRET_KEY=<SAFETY_READ_SECRET_KEY>

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

До установки локального приложения заполните:

BITRIX_CLIENT_ID=<BITRIX_CLIENT_ID>
BITRIX_CLIENT_SECRET=<BITRIX_CLIENT_SECRET>
BITRIX_CONNECTOR_ID=han_mobile_app
BITRIX_OPEN_LINE_ID=8
BITRIX_PUBLIC_BASE_URL=https://chat.example.ru/bitrix
BITRIX_TOKEN_ENCRYPTION_KEY=<URLSAFE_BASE64_KEY>

BITRIX_APPLICATION_TOKEN можно окончательно задать после создания приложения в Bitrix24. До этого используйте отдельное случайное значение, проходящее валидацию окружения.

8.8. TLS и наблюдаемость

До выпуска сертификата оставьте в .env целевые значения:

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

Выполните:

cd /opt/han-chat/backend
./scripts/validate-env .env
docker compose --env-file .env config --quiet
docker compose --env-file .env config --services
python3 -m unittest discover -s tests -v

Не переходите к следующему шагу, пока все команды не завершатся успешно.

Посмотрите итоговую конфигурацию портов:

docker compose --env-file .env config | grep -n 'published:'

Публиковаться должны только 80 и 443 у nginx.

10. Сборка образов

Соберите все локальные образы:

cd /opt/han-chat/backend
docker compose --env-file .env build --pull

Проверьте список:

docker compose --env-file .env images

Сборка Keycloak включает Java OTP SPI, а сборка frontend-static экспортирует тестовый Expo Web frontend.

11. Миграции БД и начальные настройки

Перед миграциями создайте backup/PITR marker в панели провайдера PostgreSQL. Затем:

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 должен быть безопасным:

deployment/scripts/seed.sh

12. Запуск внутренних сервисов

Сначала запустите Redis:

docker compose --env-file .env up -d redis
docker compose --env-file .env ps redis

Затем Keycloak и OpenTelemetry:

docker compose --env-file .env up -d keycloak otel-collector
docker compose --env-file .env ps keycloak otel-collector

Первый запуск Keycloak может занять несколько минут: он создаст свои таблицы и импортирует realm han-chat.

После готовности Keycloak:

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:

docker compose --env-file .env logs --tail=200 <SERVICE_NAME>
docker inspect "$(docker compose --env-file .env ps -q <SERVICE_NAME>)"

13. Первоначальный выпуск TLS-сертификата

Для ACME требуется работающий nginx по HTTP. В .env оставьте NGINX_TLS_ENABLED=true, но первый nginx запустите с временным переопределением:

NGINX_TLS_ENABLED=false \
  docker compose --env-file .env up -d frontend-static nginx

Проверьте HTTP:

curl -I http://chat.example.ru/

Сначала рекомендуется проверить Certbot через staging:

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:

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:

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-таймер продления сертификата:

sudo /opt/han-chat/backend/deployment/scripts/setup-vm.sh
systemctl status han-chat-ssl-renew.timer

14. Запуск всего контура

Теперь можно привести весь проект к состоянию, описанному Compose:

cd /opt/han-chat/backend
docker compose --env-file .env up -d
docker compose --env-file .env ps

Проверьте, что контейнеры не перезапускаются:

docker compose --env-file .env ps
docker compose --env-file .env logs --since=10m

15. Публичная проверка

Запустите smoke-тест:

cd /opt/han-chat/backend
deployment/scripts/smoke.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 не должен быть опубликован:

curl -i https://chat.example.ru/internal/safety/v1/messages/check

Ожидаемый статус — 404.

Откройте в браузере:

https://chat.example.ru/

Для тестовой авторизации используйте значение KEYCLOAK_OTP_MOCK_CODE из .env.

16. Подключение Bitrix24

В настройках локального приложения Bitrix24 задайте HTTPS-адреса:

Установка:   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 файла .env.
  3. Пересоздайте сервис:
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 по ключу в отдельной сессии:

sudo HARDEN_SSH=true \
  /opt/han-chat/backend/deployment/scripts/setup-vm.sh

Не закрывайте текущую SSH-сессию, пока не проверили новый вход.

18. Обычный перезапуск проекта

Для штатного запуска после перезагрузки VM:

cd /opt/han-chat/backend
docker compose --env-file .env up -d
docker compose --env-file .env ps

Для перезапуска одного сервиса:

docker compose --env-file .env restart api-backend

После изменения .env используйте пересоздание, а не restart:

docker compose --env-file .env up -d --force-recreate <SERVICE_NAME>

19. Обновление версии проекта

Перед обновлением:

  1. Создайте backup/PITR marker PostgreSQL.
  2. Сохраните текущие image digests.
  3. Получите новый код.
  4. Проверьте .env.
  5. Пересоберите образы.
  6. Примените миграции и seed.
  7. Пересоздайте сервисы.

Команды:

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. Диагностика

Состояние сервисов:

docker compose --env-file .env ps

Все логи:

docker compose --env-file .env logs --tail=300

Логи конкретного сервиса:

docker compose --env-file .env logs -f api-backend

Проверка firewall:

sudo ufw status verbose
sudo iptables -L HAN-CHAT-DOCKER -n -v

Проверка сертификата:

openssl s_client -connect chat.example.ru:443 -servername chat.example.ru \
  </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates

Проверка свободного места:

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.