# Подробная инструкция по развертыванию и запуску 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 облачным пользователем: ```sh ssh @ ``` Проверьте версию ОС: ```sh cat /etc/os-release ``` Должна использоваться Ubuntu 24.04 или более новая версия. ## 3. Передача и запуск скрипта настройки VM Сначала передайте на VM только подготовительный скрипт. Например, с локального компьютера: ```sh scp deployment/scripts/setup-vm.sh @:/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-вход. Не используйте `HARDEN_SSH=true`, пока не проверили вход пользователем `deploy` по ключу в отдельной сессии. Если SSH работает на нестандартном порту или имя внешнего интерфейса известно заранее, передайте параметры: ```sh sudo SSH_PORT=2222 EXTERNAL_IF=ens3 /tmp/setup-vm.sh ``` После завершения выйдите из SSH-сессии: членство `deploy` в группе `docker` начинает действовать только после нового входа. ```sh exit ssh deploy@ docker version docker compose version ``` ## 4. Копирование проекта на VM ### Вариант A — через Git Это предпочтительный вариант: Git применит правило LF для shell-скриптов. ```sh git clone /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@:/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 ''; CREATE ROLE bitrix_local_app LOGIN PASSWORD ''; CREATE ROLE bitrix_sync_user LOGIN PASSWORD ''; CREATE ROLE message_safety_app LOGIN PASSWORD ''; CREATE ROLE keycloak_user LOGIN 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 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. Создание файла окружения На VM: ```sh cd /opt/han-chat/backend umask 077 cp .env.example .env chmod 600 .env nano .env ``` Замените все `change-me` и адреса `example.*`. ### 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 Укажите фактические host, port, database, пользователей и пароли: ```dotenv HAN_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:@:6432/han_chat?sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem&options=-csearch_path%3Dhan_app BITRIX_DATABASE_URL=postgresql://bitrix_local_app:@: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:@: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:@:6432/han_chat?sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem&options=-csearch_path%3Dmessage_safety KEYCLOAK_DB_URL=jdbc:postgresql://:6432/han_chat?currentSchema=keycloak&sslmode=verify-full&sslrootcert=/run/secrets/pg-ca.pem KEYCLOAK_DB_USERNAME=keycloak_user KEYCLOAK_DB_PASSWORD= ``` Если пароль содержит `@`, `:`, `/`, `?`, `#` или `%`, его необходимо URL-кодировать внутри PostgreSQL URL. ### 8.3. Генерация секретов Для обычных токенов используйте: ```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())' ``` Заполните все секреты. Две пары должны совпадать: ```dotenv BITRIX_LOCAL_APP_INTERNAL_TOKEN= BITRIX_INTERNAL_API_TOKEN= BITRIX_API_FORWARD_TOKEN= BITRIX_API_INBOX_TOKEN= ``` Остальные токены должны быть разными: ```dotenv MESSAGE_SAFETY_SERVICE_TOKEN= BITRIX_SYNC_SERVICE_TOKEN= KEYCLOAK_SETTINGS_BRIDGE_TOKEN= CURSOR_HMAC_SECRET= KEYCLOAK_OTP_HMAC_KEY= BITRIX_TOKEN_ENCRYPTION_KEY= KEYCLOAK_ADMIN_PASSWORD= ``` ### 8.4. Redis Создайте три разных пароля и продублируйте их в URL: ```dotenv REDIS_API_PASSWORD= REDIS_SAFETY_PASSWORD= REDIS_HEALTH_PASSWORD= REDIS_URL=redis://api_backend:@redis:6379/0 REDIS_REALTIME_URL=redis://api_backend:@redis:6379/1 MESSAGE_SAFETY_REDIS_URL=redis://message_safety:@redis:6379/2 ``` ### 8.5. Mock OTP В MVP реализован только mock OTP. Для запуска: ```dotenv KEYCLOAK_OTP_MOCK_ENABLED=true KEYCLOAK_OTP_MOCK_RISK_ACCEPTED=true KEYCLOAK_OTP_MOCK_CODE=<ТЕСТОВЫЙ_КОД_НЕ_КОРОЧЕ_16_СИМВОЛОВ> ``` Этот код будет вводиться пользователем при тестовой авторизации. Не используйте его как production-механизм доставки OTP. ### 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 SELECTEL_S3_ACCESS_KEY= SELECTEL_S3_SECRET_KEY= SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY= SELECTEL_S3_QUARANTINE_READ_SECRET_KEY= ``` API backend принудительно использует virtual-hosted addressing: `https://.s3.storage.selcloud.ru/`. Это обязательно для браузерных presigned PUT и CORS в Selectel; path-style URL для этого сценария не используйте. DNS и исходящий HTTPS с ВМ должны разрешать поддомены бакетов. ### 8.7. Bitrix24 До установки локального приложения заполните: ```dotenv BITRIX_CLIENT_ID= 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= ``` `BITRIX_APPLICATION_TOKEN` можно окончательно задать после создания приложения в Bitrix24. До этого используйте отдельное случайное значение, проходящее валидацию окружения. ### 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= ``` Если удаленный OTLP-бэкенд пока не выбран, укажите временные непубличные значения и примите ограничение: Collector будет пытаться отправлять телеметрию и сохранять ее в ограниченной очереди. Перед production-запуском задайте реальный endpoint. ## 9. Проверка окружения и конфигурации Compose Выполните: ```sh 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 ``` Не переходите к следующему шагу, пока все команды не завершатся успешно. Посмотрите итоговую конфигурацию портов: ```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 docker inspect "$(docker compose --env-file .env ps -q )" ``` ## 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 \ --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 \ --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/v1/messages/check ``` Ожидаемый статус — `404`. Откройте в браузере: ```text https://chat.example.ru/ ``` Для тестовой авторизации используйте значение `KEYCLOAK_OTP_MOCK_CODE` из `.env`. ## 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` файла `.env`. 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 ``` ## 19. Обновление версии проекта Перед обновлением: 1. Создайте backup/PITR marker PostgreSQL. 2. Сохраните текущие image digests. 3. Получите новый код. 4. Проверьте `.env`. 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 | 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`.