доработки в файлах раскатки
This commit is contained in:
@@ -0,0 +1,769 @@
|
|||||||
|
# Подробная инструкция по развертыванию и запуску 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 <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-вход. Не используйте
|
||||||
|
`HARDEN_SSH=true`, пока не проверили вход пользователем `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. Создание файла окружения
|
||||||
|
|
||||||
|
На 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=<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. Генерация секретов
|
||||||
|
|
||||||
|
Для обычных токенов используйте:
|
||||||
|
|
||||||
|
```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=<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
|
||||||
|
|
||||||
|
Создайте три разных пароля и продублируйте их в URL:
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
В 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=<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>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.7. Bitrix24
|
||||||
|
|
||||||
|
До установки локального приложения заполните:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
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` целевые значения:
|
||||||
|
|
||||||
|
```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
|
||||||
|
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 <SERVICE_NAME>
|
||||||
|
docker inspect "$(docker compose --env-file .env ps -q <SERVICE_NAME>)"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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/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 <SERVICE_NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 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`.
|
||||||
@@ -20,6 +20,15 @@ downgrade, or a mutable image tag during deployment.
|
|||||||
|
|
||||||
## Gate 2 — VM hardening
|
## Gate 2 — VM hardening
|
||||||
|
|
||||||
|
On a fresh Ubuntu 24.04 VM, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo deployment/scripts/setup-vm.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Before setting `HARDEN_SSH=true`, verify key-based access in a separate SSH
|
||||||
|
session. The script header documents its parameters and safe defaults.
|
||||||
|
|
||||||
- [ ] Ubuntu 24.04, NTP, unattended security updates and disk alerts are active.
|
- [ ] Ubuntu 24.04, NTP, unattended security updates and disk alerts are active.
|
||||||
- [ ] Key-only deploy account works in a second session; root/password SSH is off.
|
- [ ] Key-only deploy account works in a second session; root/password SSH is off.
|
||||||
- [ ] UFW/cloud SG and `DOCKER-USER` policy survive reboot.
|
- [ ] UFW/cloud SG and `DOCKER-USER` policy survive reboot.
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
# Инструкция по развертыванию HAN Chat в production-like окружении
|
||||||
|
|
||||||
|
Это исполняемый чек-лист для контура на одной виртуальной машине. PostgreSQL и S3
|
||||||
|
используются как внешние управляемые сервисы. Во время развертывания запрещено
|
||||||
|
использовать `docker compose down -v`, откат миграций Alembic и изменяемые теги образов.
|
||||||
|
|
||||||
|
Подробная пошаговая инструкция для первого запуска находится в
|
||||||
|
`deployment/DEPLOYMENT_GUIDE.ru.md`.
|
||||||
|
|
||||||
|
## Этап 0 — решения и зоны ответственности
|
||||||
|
|
||||||
|
- [ ] Зафиксированы SHA/дайджесты релиза, окно обслуживания, дежурный и ответственный за откат.
|
||||||
|
- [ ] Согласованы RPO/RTO; начальные цели: RPO PostgreSQL не более 15 минут и RTO не более 4 часов.
|
||||||
|
- [ ] Выбран удаленный OTLP-бэкенд либо принято ограничение на использование только отладочного контура.
|
||||||
|
- [ ] Явно приняты риски mock OTP, заглушки Safety и заглушки bitrix-sync.
|
||||||
|
|
||||||
|
## Этап 1 — VPC, DNS и группы безопасности
|
||||||
|
|
||||||
|
- [ ] Управляемый PostgreSQL имеет только приватную точку доступа и принимает трафик от группы безопасности VM.
|
||||||
|
- [ ] Из интернета доступны только TCP-порты VM 80/443; SSH ограничен VPN или CIDR администраторов.
|
||||||
|
- [ ] Порты 6379, 4317/4318, 8000, 8080 и 9000 закрыты для внешнего доступа.
|
||||||
|
- [ ] DNS-запись `A` для `PUBLIC_HOST` указывает на VM; исходящий HTTPS доступен.
|
||||||
|
|
||||||
|
## Этап 2 — защита виртуальной машины
|
||||||
|
|
||||||
|
На новой Ubuntu 24.04 можно выполнить подготовительный скрипт:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo deployment/scripts/setup-vm.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Перед включением `HARDEN_SSH=true` обязательно проверьте вход по ключу в отдельной
|
||||||
|
SSH-сессии. Параметры запуска и безопасные значения по умолчанию описаны в начале скрипта.
|
||||||
|
|
||||||
|
- [ ] Установлена Ubuntu 24.04; работают NTP, автоматические обновления безопасности и оповещения о заполнении диска.
|
||||||
|
- [ ] Вход учетной записью развертывания по ключу проверен во второй сессии; вход root и SSH по паролю отключены.
|
||||||
|
- [ ] Правила UFW/облачной группы безопасности и политика `DOCKER-USER` сохраняются после перезагрузки.
|
||||||
|
- [ ] Docker Engine и Compose поддерживают `include` и полную форму `env_file`.
|
||||||
|
|
||||||
|
## Этап 3 — управляемый PostgreSQL
|
||||||
|
|
||||||
|
- [ ] Включены ежедневные резервные копии, PITR, защита от удаления, шифрование и оповещения.
|
||||||
|
- [ ] CA-сертификат провайдера установлен по пути `PG_CA_HOST_PATH`; все DSN используют `verify-full`.
|
||||||
|
- [ ] Для схем `han_app`, `bitrix_local`, `bitrix_sync`, `message_safety`, `keycloak`
|
||||||
|
созданы отдельные роли миграций и выполнения; запрет лишних прав проверен тестами.
|
||||||
|
- [ ] Миграции проверены на пустой БД и на клоне БД предыдущего релиза.
|
||||||
|
|
||||||
|
## Этап 4 — Selectel S3
|
||||||
|
|
||||||
|
- [ ] Бакеты карантина, вложений и документов закрыты от публичного доступа и зашифрованы.
|
||||||
|
- [ ] Права API ограничены префиксами; учетные данные Safety имеют доступ к карантину только на чтение.
|
||||||
|
- [ ] CORS бакетов разрешает только точный HTTPS-origin браузера и необходимые заголовки PUT.
|
||||||
|
- [ ] Срок хранения карантина превышает время Safety polling/recovery; политика хранения данных согласована.
|
||||||
|
|
||||||
|
## Этап 5 — неизменяемый релиз
|
||||||
|
|
||||||
|
- [ ] Репозиторий переключен на утвержденный SHA в detached-режиме; рабочее дерево чистое.
|
||||||
|
- [ ] Образы сервисов неизменяемы и просканированы; нерешенных критических и высоких уязвимостей нет.
|
||||||
|
- [ ] Корневой `docker-compose.yml` является единственной точкой запуска.
|
||||||
|
|
||||||
|
## Этап 6 — окружение и секреты
|
||||||
|
|
||||||
|
```sh
|
||||||
|
umask 077
|
||||||
|
cp .env.example .env
|
||||||
|
chmod 600 .env
|
||||||
|
# Замените заглушки через защищенный редактор или менеджер секретов.
|
||||||
|
./scripts/validate-env .env
|
||||||
|
docker compose --env-file .env config --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Парные токены совпадают, PostgreSQL проверяет TLS, публичные URL используют HTTPS.
|
||||||
|
- [ ] Риск mock OTP принят; все секреты уникальны и содержат не менее 128 бит энтропии.
|
||||||
|
- [ ] Установлено `FRONTEND_DEV_PROXY_ENABLED=false`; таймауты Safety и nginx согласованы.
|
||||||
|
|
||||||
|
## Этап 7 — образы и статический frontend
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose --env-file .env pull
|
||||||
|
docker compose --env-file .env build --pull frontend-static nginx redis
|
||||||
|
docker compose --env-file .env run --rm frontend-static
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Экспорт frontend проверен и просканирован, затем скопирован сервисом `frontend-static` в именованный volume.
|
||||||
|
- [ ] Артефакты сборки не содержат секретов и непредусмотренных source map.
|
||||||
|
- [ ] На диске VM остается не менее 30% свободного места.
|
||||||
|
|
||||||
|
## Этап 8 — топология
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose --env-file .env config --services
|
||||||
|
python3 -m unittest discover -s tests -v
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Только nginx публикует `80:80` и `443:443`; сервиса PostgreSQL в Compose нет.
|
||||||
|
- [ ] Присутствуют volumes Redis AOF/RDB/ACL и постоянной очереди OTEL.
|
||||||
|
- [ ] Сети `backend` и `observability` являются внутренними.
|
||||||
|
|
||||||
|
## Этап 9 — первоначальная настройка ACME/TLS
|
||||||
|
|
||||||
|
Установите `NGINX_TLS_ENABLED=false` только для команды первоначального запуска:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
NGINX_TLS_ENABLED=false docker compose --env-file .env up -d nginx
|
||||||
|
docker compose --profile certbot run --rm certbot certonly \
|
||||||
|
--webroot -w /var/www/certbot -d "$PUBLIC_HOST" \
|
||||||
|
--cert-name "$PUBLIC_HOST" --email "$ACME_EMAIL" \
|
||||||
|
--agree-tos --no-eff-email --non-interactive
|
||||||
|
docker compose --env-file .env up -d --force-recreate nginx
|
||||||
|
docker compose exec -T nginx nginx -t -c /tmp/nginx.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Сначала выполните проверку с параметром Certbot `--staging`. Установите systemd-таймер,
|
||||||
|
запускающий `deployment/scripts/ssl-renew.sh` дважды в сутки, и проверьте
|
||||||
|
`certbot renew --dry-run`. Включайте HSTS только после проверки цепочки сертификатов,
|
||||||
|
имени хоста, перенаправления и поддержки TLS 1.2/1.3.
|
||||||
|
|
||||||
|
## Этап 10 — миграции и начальные данные
|
||||||
|
|
||||||
|
Создайте у провайдера точку восстановления PITR, затем выполните:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PITR_MARKER_CONFIRMED=true deployment/scripts/migrate.sh
|
||||||
|
deployment/scripts/seed.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Активны ожидаемые ревизии Alembic; runtime-пользователи не выполняли DDL.
|
||||||
|
- [ ] Повторный seed завершается успешно; обязательные настройки не содержат секретов.
|
||||||
|
- [ ] Схема остается обратно совместимой с образами предыдущего релиза.
|
||||||
|
|
||||||
|
## Этап 11 — Keycloak
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d keycloak
|
||||||
|
docker compose ps keycloak
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Issuer discovery/JWKS точно совпадает с публичным HTTPS URL `/auth`.
|
||||||
|
- [ ] Frontend-клиент является публичным PKCE S256; implicit, password и social flows отключены.
|
||||||
|
- [ ] Неверный или повторно использованный OTP и превышение лимитов безопасно отклоняются; settings bridge работает fail-closed.
|
||||||
|
- [ ] Временный администратор удален либо его пароль изменен; для именного администратора включена MFA.
|
||||||
|
|
||||||
|
## Этап 12 — последовательный запуск и готовность
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d redis
|
||||||
|
docker compose up -d keycloak otel-collector
|
||||||
|
docker compose up -d message-safety
|
||||||
|
docker compose up -d api-backend
|
||||||
|
docker compose up -d bitrix-local-app bitrix-sync
|
||||||
|
docker compose up -d nginx
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Нет циклических перезапусков и OOM; критические readiness-проверки успешны.
|
||||||
|
- [ ] Сохраняется только документированная деградация: Bitrix не установлен и bitrix-sync работает как заглушка.
|
||||||
|
- [ ] Внешний запрос `/internal/*` возвращает 404; OTEL принимает телеметрию.
|
||||||
|
|
||||||
|
## Этап 13 — Bitrix24
|
||||||
|
|
||||||
|
- [ ] URL установки, обработчика и placement используют точные публичные HTTPS-пути.
|
||||||
|
- [ ] Коннектор `han_mobile_app` активен в Открытой линии 8; события привязаны однократно.
|
||||||
|
- [ ] OAuth зашифрован; callback-, application- и service-токены не попадают в логи.
|
||||||
|
- [ ] Исходящие сообщения и ответы оператора идемпотентны; внутренний статус не опубликован наружу.
|
||||||
|
|
||||||
|
## Этап 14 — smoke- и E2E-тесты
|
||||||
|
|
||||||
|
```sh
|
||||||
|
deployment/scripts/smoke.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Успешны сценарии гостя, OTP/PKCE/bootstrap/session, обновления токена и выхода.
|
||||||
|
- [ ] Проверены Safety allow/deny/pending/timeout и один параллельный медленный poll.
|
||||||
|
- [ ] Проверены карантин, перенос и удаление файлов, скачивание только владельцем и аудит.
|
||||||
|
- [ ] Проверены переподключение WS с REST-сверкой, 404 при обращении к чужому ресурсу, идемпотентность и 429.
|
||||||
|
- [ ] Логи не содержат PII, текстов сообщений, токенов и query-параметров presigned URL.
|
||||||
|
|
||||||
|
## Этап 15 — наблюдаемость
|
||||||
|
|
||||||
|
- [ ] Известный request ID связывает трассировку nginx, API и downstream-сервисов; UX ID не используется как label.
|
||||||
|
- [ ] Все три сигнала поступают в выбранный бэкенд; SLO-запросы и оповещения проверены.
|
||||||
|
- [ ] При недоступности удаленного сервиса ограниченная постоянная очередь заполняется и опустошается без остановки бизнес-функций.
|
||||||
|
- [ ] Тестовые секреты и PII отсутствуют; проверены метрики перезапуска, потерь, отказов и очереди Collector.
|
||||||
|
|
||||||
|
Только для локальной приемки запустите отладочный Collector с удалением чувствительных данных:
|
||||||
|
`docker compose --profile observability-local up -d otel-collector-local`.
|
||||||
|
|
||||||
|
## Этап 16 — открытие трафика
|
||||||
|
|
||||||
|
- [ ] Этапы 0–15 подписаны; имеются свежие подтверждения backup/PITR и предыдущие образы.
|
||||||
|
- [ ] HSTS включен; дайджесты релиза, версии схем и realm зафиксированы.
|
||||||
|
- [ ] Активных инцидентов нет; дежурный и владелец продукта приняли ограничения заглушек.
|
||||||
|
- [ ] В течение 60 минут контролируются 5xx, auth, доставка, БД, Redis, OOM, очередь OTEL и Bitrix.
|
||||||
|
|
||||||
|
## Резервное копирование и восстановление
|
||||||
|
|
||||||
|
Основным механизмом являются backup/PITR провайдера. Дополнительный проверенный логический дамп:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PG_BACKUP_DSN='postgresql://...?...sslmode=verify-full&sslrootcert=...' \
|
||||||
|
deployment/scripts/backup.sh /opt/han-chat/backups
|
||||||
|
```
|
||||||
|
|
||||||
|
Ежеквартально восстанавливайте PostgreSQL и S3 в изолированной VPC, развертывайте те же
|
||||||
|
дайджесты образов, не направляйте туда production DNS и callbacks Bitrix, выполняйте
|
||||||
|
smoke-тесты и фиксируйте фактические RPO/RTO. Redis можно восстановить пустым:
|
||||||
|
его AOF/RDB не является резервной копией бизнес-данных.
|
||||||
|
|
||||||
|
## Откат
|
||||||
|
|
||||||
|
Откатывайтесь только на образы, совместимые с текущей схемой:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
SCHEMA_BACKWARD_COMPATIBLE_CONFIRMED=true \
|
||||||
|
deployment/scripts/rollback.sh /secure/path/previous-release.env
|
||||||
|
ENV_FILE=/secure/path/previous-release.env deployment/scripts/smoke.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Никогда не выполняйте downgrade Alembic. После обратно несовместимой миграции используйте
|
||||||
|
исправление вперед либо согласованный PITR с восстановлением S3 и сверкой Bitrix во время
|
||||||
|
технического обслуживания. Всегда проверяйте outbox, inbox и recovery, чтобы сообщение
|
||||||
|
с неопределенным статусом не было отправлено повторно.
|
||||||
@@ -0,0 +1,492 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Первичная подготовка Ubuntu 24.04 для HAN Chat.
|
||||||
|
#
|
||||||
|
# Скрипт настраивает только VM: пользователя развертывания, базовые пакеты,
|
||||||
|
# Docker/Compose, UFW, fail2ban, DOCKER-USER, swap и каталоги проекта.
|
||||||
|
# PostgreSQL и S3 остаются внешними управляемыми сервисами. Скрипт не создает
|
||||||
|
# .env, секреты, DNS, S3-бакеты, схемы БД и TLS-сертификаты.
|
||||||
|
#
|
||||||
|
# Запуск на свежей VM:
|
||||||
|
# chmod +x deployment/scripts/setup-vm.sh
|
||||||
|
# sudo deployment/scripts/setup-vm.sh
|
||||||
|
#
|
||||||
|
# Основные параметры:
|
||||||
|
# DEPLOY_USER=deploy
|
||||||
|
# DEPLOY_DIR=/opt/han-chat/backend
|
||||||
|
# SSH_PORT=22
|
||||||
|
# TIMEZONE=Europe/Moscow
|
||||||
|
# SWAP_SIZE_GB=4
|
||||||
|
# EXTERNAL_IF=ens3
|
||||||
|
# PUBLIC_DOCKER_PORTS=80,443
|
||||||
|
# COPY_SSH_KEYS=true
|
||||||
|
# HARDEN_SSH=false
|
||||||
|
# RESET_UFW=false
|
||||||
|
# SKIP_APT_UPGRADE=false
|
||||||
|
#
|
||||||
|
# HARDEN_SSH=true разрешено использовать только после проверки входа по ключу
|
||||||
|
# в отдельной SSH-сессии. По умолчанию парольный вход не отключается.
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
DEPLOY_USER="${DEPLOY_USER:-deploy}"
|
||||||
|
DEPLOY_DIR="${DEPLOY_DIR:-/opt/han-chat/backend}"
|
||||||
|
SSH_PORT="${SSH_PORT:-22}"
|
||||||
|
TIMEZONE="${TIMEZONE:-Europe/Moscow}"
|
||||||
|
SWAP_SIZE_GB="${SWAP_SIZE_GB:-4}"
|
||||||
|
EXTERNAL_IF="${EXTERNAL_IF:-}"
|
||||||
|
PUBLIC_DOCKER_PORTS="${PUBLIC_DOCKER_PORTS:-80,443}"
|
||||||
|
COPY_SSH_KEYS="${COPY_SSH_KEYS:-true}"
|
||||||
|
HARDEN_SSH="${HARDEN_SSH:-false}"
|
||||||
|
RESET_UFW="${RESET_UFW:-false}"
|
||||||
|
SKIP_APT_UPGRADE="${SKIP_APT_UPGRADE:-false}"
|
||||||
|
LOG_FILE="${LOG_FILE:-/var/log/han-chat-vm-setup.log}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
step() {
|
||||||
|
log ""
|
||||||
|
log "==> $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
log "ОШИБКА: $*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
on_error() {
|
||||||
|
local exit_code=$?
|
||||||
|
log "ОШИБКА: команда завершилась с кодом ${exit_code}, строка ${BASH_LINENO[0]}"
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
trap on_error ERR
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
[[ "${EUID:-$(id -u)}" -eq 0 ]] || die "Запустите скрипт через sudo"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_parameters() {
|
||||||
|
[[ "$DEPLOY_USER" =~ ^[a-z_][a-z0-9_-]*$ ]] || die "Некорректный DEPLOY_USER"
|
||||||
|
[[ "$DEPLOY_DIR" == /* ]] || die "DEPLOY_DIR должен быть абсолютным путем"
|
||||||
|
[[ "$SSH_PORT" =~ ^[0-9]+$ ]] || die "SSH_PORT должен быть числом"
|
||||||
|
((SSH_PORT >= 1 && SSH_PORT <= 65535)) || die "SSH_PORT вне диапазона"
|
||||||
|
[[ "$SWAP_SIZE_GB" =~ ^[0-9]+$ ]] || die "SWAP_SIZE_GB должен быть целым числом"
|
||||||
|
[[ "$PUBLIC_DOCKER_PORTS" =~ ^[0-9]+(,[0-9]+)*$ ]] \
|
||||||
|
|| die "PUBLIC_DOCKER_PORTS должен иметь вид 80,443"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_os() {
|
||||||
|
step "Проверка операционной системы"
|
||||||
|
[[ -r /etc/os-release ]] || die "Не найден /etc/os-release"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/os-release
|
||||||
|
[[ "${ID:-}" == "ubuntu" ]] || die "Поддерживается только Ubuntu"
|
||||||
|
local major="${VERSION_ID%%.*}"
|
||||||
|
((major >= 24)) || die "Требуется Ubuntu 24.04 или новее"
|
||||||
|
log "Обнаружена ${PRETTY_NAME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_system() {
|
||||||
|
step "Обновление системы и установка пакетов"
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update
|
||||||
|
if [[ "$SKIP_APT_UPGRADE" != "true" ]]; then
|
||||||
|
apt-get dist-upgrade -y
|
||||||
|
fi
|
||||||
|
apt-get install -y \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
dos2unix \
|
||||||
|
fail2ban \
|
||||||
|
git \
|
||||||
|
gnupg \
|
||||||
|
iptables \
|
||||||
|
jq \
|
||||||
|
logrotate \
|
||||||
|
netcat-openbsd \
|
||||||
|
openssl \
|
||||||
|
python3 \
|
||||||
|
python3-venv \
|
||||||
|
rsync \
|
||||||
|
unattended-upgrades \
|
||||||
|
ufw
|
||||||
|
apt-get autoremove -y
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_time() {
|
||||||
|
step "Настройка времени"
|
||||||
|
timedatectl set-timezone "$TIMEZONE"
|
||||||
|
timedatectl set-ntp true
|
||||||
|
}
|
||||||
|
|
||||||
|
create_deploy_user() {
|
||||||
|
step "Пользователь развертывания"
|
||||||
|
if ! id "$DEPLOY_USER" >/dev/null 2>&1; then
|
||||||
|
useradd --create-home --shell /bin/bash "$DEPLOY_USER"
|
||||||
|
log "Создан пользователь ${DEPLOY_USER}"
|
||||||
|
else
|
||||||
|
log "Пользователь ${DEPLOY_USER} уже существует"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -m 700 -o "$DEPLOY_USER" -g "$DEPLOY_USER" \
|
||||||
|
"/home/${DEPLOY_USER}/.ssh"
|
||||||
|
|
||||||
|
local source_user="${SUDO_USER:-}"
|
||||||
|
local source_keys=""
|
||||||
|
local target_keys="/home/${DEPLOY_USER}/.ssh/authorized_keys"
|
||||||
|
if [[ -n "$source_user" && "$source_user" != "root" ]]; then
|
||||||
|
source_keys="/home/${source_user}/.ssh/authorized_keys"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$COPY_SSH_KEYS" == "true" && ! -s "$target_keys" && -s "$source_keys" ]]; then
|
||||||
|
install -m 600 -o "$DEPLOY_USER" -g "$DEPLOY_USER" "$source_keys" "$target_keys"
|
||||||
|
log "SSH-ключи скопированы от ${source_user}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -s "$target_keys" ]]; then
|
||||||
|
log "ПРЕДУПРЕЖДЕНИЕ: у ${DEPLOY_USER} отсутствует authorized_keys"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_layout() {
|
||||||
|
step "Каталоги HAN Chat"
|
||||||
|
install -d -m 755 -o "$DEPLOY_USER" -g "$DEPLOY_USER" "$DEPLOY_DIR"
|
||||||
|
install -d -m 700 -o "$DEPLOY_USER" -g "$DEPLOY_USER" \
|
||||||
|
"${DEPLOY_DIR}/secrets" \
|
||||||
|
"${DEPLOY_DIR}/secrets/pg" \
|
||||||
|
"${DEPLOY_DIR}/backups"
|
||||||
|
|
||||||
|
local env_file="${DEPLOY_DIR}/.env"
|
||||||
|
if [[ -f "$env_file" ]]; then
|
||||||
|
chown "$DEPLOY_USER:$DEPLOY_USER" "$env_file"
|
||||||
|
chmod 600 "$env_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_swap() {
|
||||||
|
step "Настройка swap"
|
||||||
|
if ((SWAP_SIZE_GB == 0)); then
|
||||||
|
log "Создание swap отключено"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if swapon --show=NAME --noheadings | grep -qx '/swapfile'; then
|
||||||
|
log "Swap уже подключен"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ ! -f /swapfile ]]; then
|
||||||
|
fallocate -l "${SWAP_SIZE_GB}G" /swapfile
|
||||||
|
chmod 600 /swapfile
|
||||||
|
mkswap /swapfile
|
||||||
|
fi
|
||||||
|
swapon /swapfile
|
||||||
|
grep -q '^/swapfile ' /etc/fstab \
|
||||||
|
|| printf '/swapfile none swap sw 0 0\n' >>/etc/fstab
|
||||||
|
printf 'vm.swappiness = 10\n' >/etc/sysctl.d/99-han-chat-swappiness.conf
|
||||||
|
sysctl --system >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_sysctl() {
|
||||||
|
step "Настройка сетевого стека"
|
||||||
|
cat >/etc/sysctl.d/99-han-chat-hardening.conf <<'EOF'
|
||||||
|
net.ipv4.ip_forward = 1
|
||||||
|
net.ipv4.tcp_syncookies = 1
|
||||||
|
net.ipv4.conf.all.accept_redirects = 0
|
||||||
|
net.ipv4.conf.default.accept_redirects = 0
|
||||||
|
net.ipv4.conf.all.send_redirects = 0
|
||||||
|
net.ipv4.conf.default.send_redirects = 0
|
||||||
|
net.ipv4.conf.all.rp_filter = 1
|
||||||
|
net.ipv4.conf.default.rp_filter = 1
|
||||||
|
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||||
|
net.ipv4.tcp_fin_timeout = 30
|
||||||
|
EOF
|
||||||
|
sysctl --system >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker() {
|
||||||
|
step "Установка Docker Engine и Compose"
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
||||||
|
| gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
|
||||||
|
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/os-release
|
||||||
|
printf '%s\n' \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" \
|
||||||
|
>/etc/apt/sources.list.d/docker.list
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -m 755 /etc/docker
|
||||||
|
cat >/etc/docker/daemon.json <<'EOF'
|
||||||
|
{
|
||||||
|
"live-restore": true,
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "50m",
|
||||||
|
"max-file": "5"
|
||||||
|
},
|
||||||
|
"userland-proxy": false
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
systemctl enable --now docker
|
||||||
|
systemctl restart docker
|
||||||
|
usermod -aG docker "$DEPLOY_USER"
|
||||||
|
|
||||||
|
docker compose version >/dev/null \
|
||||||
|
|| die "Docker Compose plugin не установлен"
|
||||||
|
log "$(docker --version)"
|
||||||
|
log "$(docker compose version)"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_ufw() {
|
||||||
|
step "Настройка UFW"
|
||||||
|
if [[ "$RESET_UFW" == "true" ]]; then
|
||||||
|
ufw --force reset
|
||||||
|
fi
|
||||||
|
ufw default deny incoming
|
||||||
|
ufw default allow outgoing
|
||||||
|
ufw allow "${SSH_PORT}/tcp" comment 'HAN Chat SSH'
|
||||||
|
ufw limit "${SSH_PORT}/tcp" comment 'HAN Chat SSH rate limit'
|
||||||
|
ufw allow 80/tcp comment 'HAN Chat HTTP'
|
||||||
|
ufw allow 443/tcp comment 'HAN Chat HTTPS'
|
||||||
|
ufw logging medium
|
||||||
|
ufw --force enable
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_fail2ban() {
|
||||||
|
step "Настройка fail2ban для SSH"
|
||||||
|
cat >/etc/fail2ban/jail.d/han-chat.local <<EOF
|
||||||
|
[DEFAULT]
|
||||||
|
bantime = 2h
|
||||||
|
findtime = 10m
|
||||||
|
maxretry = 5
|
||||||
|
backend = systemd
|
||||||
|
banaction = ufw
|
||||||
|
|
||||||
|
[sshd]
|
||||||
|
enabled = true
|
||||||
|
port = ${SSH_PORT}
|
||||||
|
maxretry = 3
|
||||||
|
EOF
|
||||||
|
systemctl enable --now fail2ban
|
||||||
|
systemctl restart fail2ban
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_unattended_upgrades() {
|
||||||
|
step "Автоматические обновления безопасности"
|
||||||
|
cat >/etc/apt/apt.conf.d/51han-chat-unattended <<'EOF'
|
||||||
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||||
|
Unattended-Upgrade::Automatic-Reboot "false";
|
||||||
|
EOF
|
||||||
|
dpkg-reconfigure -f noninteractive unattended-upgrades
|
||||||
|
systemctl enable --now unattended-upgrades
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_docker_firewall() {
|
||||||
|
step "Фильтрация опубликованных Docker-портов"
|
||||||
|
cat >/etc/default/han-chat-docker-firewall <<EOF
|
||||||
|
EXTERNAL_IF=${EXTERNAL_IF}
|
||||||
|
PUBLIC_DOCKER_PORTS=${PUBLIC_DOCKER_PORTS}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >/usr/local/sbin/han-chat-docker-firewall <<'FIREWALL'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/default/han-chat-docker-firewall
|
||||||
|
|
||||||
|
external_if="${EXTERNAL_IF:-}"
|
||||||
|
if [[ -z "$external_if" ]]; then
|
||||||
|
external_if="$(ip -4 route show default | awk '{print $5; exit}')"
|
||||||
|
fi
|
||||||
|
[[ -n "$external_if" ]] || {
|
||||||
|
echo "Не удалось определить внешний интерфейс; задайте EXTERNAL_IF" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
iptables -N HAN-CHAT-DOCKER 2>/dev/null || true
|
||||||
|
iptables -F HAN-CHAT-DOCKER
|
||||||
|
|
||||||
|
iptables -A HAN-CHAT-DOCKER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
|
||||||
|
iptables -A HAN-CHAT-DOCKER -i lo -j RETURN
|
||||||
|
|
||||||
|
IFS=',' read -ra ports <<<"$PUBLIC_DOCKER_PORTS"
|
||||||
|
for port in "${ports[@]}"; do
|
||||||
|
[[ "$port" =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "Некорректный порт: $port" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
iptables -A HAN-CHAT-DOCKER -i "$external_if" -p tcp --dport "$port" -j RETURN
|
||||||
|
done
|
||||||
|
|
||||||
|
# Блокируется только новый входящий трафик с внешнего интерфейса в Docker bridge.
|
||||||
|
# Исходящий и межконтейнерный трафик этой цепочкой не затрагивается.
|
||||||
|
iptables -A HAN-CHAT-DOCKER -i "$external_if" -o docker+ -j DROP
|
||||||
|
iptables -A HAN-CHAT-DOCKER -i "$external_if" -o br+ -j DROP
|
||||||
|
iptables -A HAN-CHAT-DOCKER -j RETURN
|
||||||
|
|
||||||
|
while iptables -C DOCKER-USER -j HAN-CHAT-DOCKER 2>/dev/null; do
|
||||||
|
iptables -D DOCKER-USER -j HAN-CHAT-DOCKER
|
||||||
|
done
|
||||||
|
iptables -I DOCKER-USER 1 -j HAN-CHAT-DOCKER
|
||||||
|
FIREWALL
|
||||||
|
chmod 750 /usr/local/sbin/han-chat-docker-firewall
|
||||||
|
|
||||||
|
cat >/etc/systemd/system/han-chat-docker-firewall.service <<'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=HAN Chat firewall for Docker published ports
|
||||||
|
After=docker.service network-online.target
|
||||||
|
Wants=docker.service network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/sbin/han-chat-docker-firewall
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
install -d -m 755 /etc/systemd/system/docker.service.d
|
||||||
|
cat >/etc/systemd/system/docker.service.d/han-chat-firewall.conf <<'EOF'
|
||||||
|
[Service]
|
||||||
|
ExecStartPost=-/usr/local/sbin/han-chat-docker-firewall
|
||||||
|
EOF
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now han-chat-docker-firewall.service
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_ssh() {
|
||||||
|
step "Проверка SSH hardening"
|
||||||
|
if [[ "$HARDEN_SSH" != "true" ]]; then
|
||||||
|
log "HARDEN_SSH=false: парольный вход не изменен"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
[[ -s "/home/${DEPLOY_USER}/.ssh/authorized_keys" ]] \
|
||||||
|
|| die "Нельзя включить HARDEN_SSH: authorized_keys пользователя пуст"
|
||||||
|
|
||||||
|
cat >/etc/ssh/sshd_config.d/99-han-chat.conf <<EOF
|
||||||
|
PermitRootLogin no
|
||||||
|
PasswordAuthentication no
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
X11Forwarding no
|
||||||
|
AllowTcpForwarding no
|
||||||
|
MaxAuthTries 3
|
||||||
|
ClientAliveInterval 120
|
||||||
|
ClientAliveCountMax 2
|
||||||
|
Port ${SSH_PORT}
|
||||||
|
EOF
|
||||||
|
sshd -t || die "Проверка конфигурации sshd не пройдена"
|
||||||
|
systemctl reload ssh
|
||||||
|
log "SSH hardening включен"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_ssl_timer_if_possible() {
|
||||||
|
step "Таймер продления TLS"
|
||||||
|
local renew_script="${DEPLOY_DIR}/deployment/scripts/ssl-renew.sh"
|
||||||
|
if [[ ! -x "$renew_script" ]]; then
|
||||||
|
log "Проект еще не скопирован: таймер TLS будет установлен при повторном запуске"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >/etc/systemd/system/han-chat-ssl-renew.service <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Renew HAN Chat TLS certificate
|
||||||
|
After=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=${DEPLOY_USER}
|
||||||
|
WorkingDirectory=${DEPLOY_DIR}
|
||||||
|
ExecStart=${renew_script}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >/etc/systemd/system/han-chat-ssl-renew.timer <<'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=Run HAN Chat TLS renewal twice daily
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* 03,15:20:00
|
||||||
|
RandomizedDelaySec=30m
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now han-chat-ssl-renew.timer
|
||||||
|
}
|
||||||
|
|
||||||
|
verify() {
|
||||||
|
step "Проверка результата"
|
||||||
|
local failed=0
|
||||||
|
systemctl is-active --quiet docker || { log "FAIL: Docker не активен"; failed=1; }
|
||||||
|
systemctl is-active --quiet fail2ban || { log "FAIL: fail2ban не активен"; failed=1; }
|
||||||
|
ufw status | grep -q 'Status: active' || { log "FAIL: UFW не активен"; failed=1; }
|
||||||
|
iptables -C DOCKER-USER -j HAN-CHAT-DOCKER 2>/dev/null \
|
||||||
|
|| { log "FAIL: цепочка HAN-CHAT-DOCKER не подключена"; failed=1; }
|
||||||
|
docker compose version >/dev/null || { log "FAIL: Compose недоступен"; failed=1; }
|
||||||
|
[[ -d "$DEPLOY_DIR" ]] || { log "FAIL: отсутствует ${DEPLOY_DIR}"; failed=1; }
|
||||||
|
((failed == 0)) || die "Базовая проверка VM не пройдена"
|
||||||
|
log "Базовая проверка VM пройдена"
|
||||||
|
}
|
||||||
|
|
||||||
|
summary() {
|
||||||
|
step "Настройка VM завершена"
|
||||||
|
cat <<EOF | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
Пользователь развертывания: ${DEPLOY_USER}
|
||||||
|
Каталог Compose: ${DEPLOY_DIR}
|
||||||
|
Открытые порты: ${SSH_PORT}, 80, 443
|
||||||
|
Лог настройки: ${LOG_FILE}
|
||||||
|
|
||||||
|
Следующие действия:
|
||||||
|
1. Проверьте вход в новой SSH-сессии:
|
||||||
|
ssh ${DEPLOY_USER}@<VM_IP>
|
||||||
|
2. Скопируйте содержимое codebase/backend в:
|
||||||
|
${DEPLOY_DIR}
|
||||||
|
3. Поместите CA PostgreSQL:
|
||||||
|
${DEPLOY_DIR}/secrets/pg/ca.pem
|
||||||
|
4. Создайте и заполните окружение:
|
||||||
|
cd ${DEPLOY_DIR}
|
||||||
|
cp .env.example .env
|
||||||
|
chmod 600 .env
|
||||||
|
./scripts/validate-env .env
|
||||||
|
5. Продолжите с Gate 7 в:
|
||||||
|
deployment/RUNBOOK.ru.md
|
||||||
|
6. После копирования проекта повторно запустите этот скрипт для установки TLS-таймера.
|
||||||
|
|
||||||
|
Важно: членство в группе docker начнет действовать после нового входа в систему.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_root
|
||||||
|
install -d -m 755 "$(dirname "$LOG_FILE")"
|
||||||
|
touch "$LOG_FILE"
|
||||||
|
chmod 600 "$LOG_FILE"
|
||||||
|
validate_parameters
|
||||||
|
check_os
|
||||||
|
update_system
|
||||||
|
configure_time
|
||||||
|
create_deploy_user
|
||||||
|
configure_layout
|
||||||
|
configure_swap
|
||||||
|
configure_sysctl
|
||||||
|
install_docker
|
||||||
|
configure_ufw
|
||||||
|
configure_fail2ban
|
||||||
|
configure_unattended_upgrades
|
||||||
|
configure_docker_firewall
|
||||||
|
configure_ssh
|
||||||
|
install_ssl_timer_if_possible
|
||||||
|
verify
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# usefull commands
|
||||||
|
Убрать переносы строк: sed -i 's/\r$//' name-file.sh #sed -i 's/\r$//' /tmp/setup-vm.sh
|
||||||
|
docker compose stop # мягко останавливает контейнеры (не удаляет)
|
||||||
|
sudo shutdown -h now # выглючить ВМ
|
||||||
|
|
||||||
|
Туннель до БД: ssh -i C:\Users\MI\.ssh\hansel -L 5432:10.105.90.166:5432 root@111.88.219.213 -N
|
||||||
|
|
||||||
|
++ ssh -i C:\Users\MI\.ssh\hansel root@111.88.219.213
|
||||||
|
|
||||||
|
++ scp -i C:\Users\MI\.ssh\hansel -r "C:\Users\MI\Documents\Assistent\HAN_chat_specification\codebase\backend\deployment\scripts\setup-vm.sh" root@111.88.219.213:/tmp/setup-vm.sh
|
||||||
|
chmod +x /tmp/setup-vm.sh
|
||||||
|
sudo /tmp/setup-vm.sh
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user