Реализация на отдельных двух машинах с протестированным взаимодействием по проверке сообщений
This commit is contained in:
@@ -1,826 +1,11 @@
|
||||
# Подробная инструкция по развертыванию и запуску HAN Chat
|
||||
# Legacy deployment guide — не использовать в production
|
||||
|
||||
Эта инструкция описывает первый запуск **текущего legacy/stub проекта ВМ1** на одной виртуальной
|
||||
машине с Ubuntu 24.04. Она не разворачивает target ВМ2 Processing и не подтверждает production-готовность Message Safety v2. Все команды предполагают, что проект расположен в
|
||||
`/opt/han-chat/backend`, а команды Docker Compose выполняются из этого каталога.
|
||||
Этот файл сохранён только как совместимый указатель для старых ссылок.
|
||||
Single-VM/stub команды удалены: они выдавали `deploy` root-equivalent доступ к
|
||||
Docker, изменяли production-каталог и не обеспечивали target cutover ВМ1→ВМ2.
|
||||
|
||||
PostgreSQL и Selectel S3 не запускаются в Docker Compose: их необходимо создать
|
||||
заранее как внешние управляемые сервисы. Из интернета должны быть доступны только
|
||||
порты 80 и 443 виртуальной машины.
|
||||
Для fresh production ВМ1 используйте только
|
||||
[`RUNBOOK.production.ru.md`](RUNBOOK.production.ru.md).
|
||||
|
||||
## 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`.
|
||||
Production через этот legacy guide или stubs не поддерживается. In-place
|
||||
обновление legacy VM запрещено.
|
||||
|
||||
Reference in New Issue
Block a user