From 4d8e856fd8a5d7d4b81bec0504ec1da9b76a8d3e Mon Sep 17 00:00:00 2001 From: mi Date: Mon, 13 Jul 2026 13:44:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B2=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=D1=85=20?= =?UTF-8?q?=D1=80=D0=B0=D1=81=D0=BA=D0=B0=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/deployment/DEPLOYMENT_GUIDE.ru.md | 769 ++++++++++++++++++ codebase/backend/deployment/RUNBOOK.md | 9 + codebase/backend/deployment/RUNBOOK.ru.md | 222 +++++ .../backend/deployment/scripts/setup-vm.sh | 492 +++++++++++ deploy-steps.md | 14 + 5 files changed, 1506 insertions(+) create mode 100644 codebase/backend/deployment/DEPLOYMENT_GUIDE.ru.md create mode 100644 codebase/backend/deployment/RUNBOOK.ru.md create mode 100644 codebase/backend/deployment/scripts/setup-vm.sh create mode 100644 deploy-steps.md diff --git a/codebase/backend/deployment/DEPLOYMENT_GUIDE.ru.md b/codebase/backend/deployment/DEPLOYMENT_GUIDE.ru.md new file mode 100644 index 0000000..2929272 --- /dev/null +++ b/codebase/backend/deployment/DEPLOYMENT_GUIDE.ru.md @@ -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 @ +``` + +Проверьте версию ОС: + +```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= +``` + +### 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`. diff --git a/codebase/backend/deployment/RUNBOOK.md b/codebase/backend/deployment/RUNBOOK.md index 682a403..9aa8067 100644 --- a/codebase/backend/deployment/RUNBOOK.md +++ b/codebase/backend/deployment/RUNBOOK.md @@ -20,6 +20,15 @@ downgrade, or a mutable image tag during deployment. ## 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. - [ ] Key-only deploy account works in a second session; root/password SSH is off. - [ ] UFW/cloud SG and `DOCKER-USER` policy survive reboot. diff --git a/codebase/backend/deployment/RUNBOOK.ru.md b/codebase/backend/deployment/RUNBOOK.ru.md new file mode 100644 index 0000000..d596c9d --- /dev/null +++ b/codebase/backend/deployment/RUNBOOK.ru.md @@ -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, чтобы сообщение +с неопределенным статусом не было отправлено повторно. diff --git a/codebase/backend/deployment/scripts/setup-vm.sh b/codebase/backend/deployment/scripts/setup-vm.sh new file mode 100644 index 0000000..979c118 --- /dev/null +++ b/codebase/backend/deployment/scripts/setup-vm.sh @@ -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 </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 </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 </etc/systemd/system/han-chat-ssl-renew.service </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 < +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 "$@" diff --git a/deploy-steps.md b/deploy-steps.md new file mode 100644 index 0000000..274aa48 --- /dev/null +++ b/deploy-steps.md @@ -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 + +