16 KiB
arch-04. Настройки и изменяемые параметры
.env— инфраструктура и секреты.app_settings(App DB) — единственный источник бизнес-настроек. Имена полей и enum —arch-00-glossary.md. Docker Compose —arch-03-docker-compose-blueprint.md.
Цель
Параметры разделены по слоям:
| Слой | Где | Что |
|---|---|---|
| Инфраструктура | .env |
подключения, URL, секреты, nginx/TLS, service tokens |
| Бизнес-логика | таблица app_settings |
лимиты, флаги, телефоны, типы файлов, CORS, consent URLs |
| Контент | text_resources, popular_questions |
тексты UI |
Managed PostgreSQL поднимается до развёртывания приложения. Бизнес-настройки не дублируются в .env: seed в app_settings выполняется миграцией/скриптом модуля database до первого запуска api-backend.
Источники настроек
.env — только инфраструктура
Корневой backend/.env читается сервисами compose. В репозитории — .env.example, не .env.
Допустимо в .env:
- URL сервисов, публичные endpoint, порты;
- строки подключения PostgreSQL, Redis, Keycloak DB;
- секреты: S3, Bitrix OAuth, service tokens, webhook-тokens;
- параметры nginx/TLS и edge rate limits (
NGINX_RATE_LIMIT_*); - идентификация Keycloak: realm, audience, public/internal URL;
- OTP-заглушка MVP (
KEYCLOAK_OTP_MOCK_*) — infra/dev-секрет, не бизнес-настройка; - технические таймауты worker-ов (
MESSAGE_SAFETY_*, интервалыbitrix-sync).
Запрещено в .env (→ только app_settings):
- включение/отключение OTP, OTP-лимиты для UI/продукта;
- телефон оператора, consent URLs/versions;
- лимиты приложения (сообщения, download URL, login);
- типы/размер файлов чата, UX idle timeout;
- CORS origins, feature flags frontend.
app_settings — бизнес-настройки (App DB)
Единственный источник правды для параметров, которые:
- меняет продукт/оператор без redeploy;
- отдаются в
GET /api/v1/public/app-config(публичные ключи); - используются
api-backend(и при необходимости другими сервисами) в runtime.
Позже — редактирование через админку; на MVP — seed-миграция.
text_resources / popular_questions
Контент UI — отдельные таблицы (не app_settings). Ключи MVP — TBD (спецификация frontend).
Требования к таблице app_settings
Схема: han_app. Детальная DDL — модуль database; arch фиксирует контракт.
Колонки (минимум)
| Колонка | Тип | Назначение |
|---|---|---|
setting_key |
varchar, PK |
Канонический ключ (auth.phone.enabled, см. ниже) |
setting_value |
text, NOT NULL |
Значение (строка; парсинг по типу) |
value_type |
enum |
boolean | integer | string | duration | string_list |
is_public |
boolean |
Разрешён в GET /api/v1/public/app-config |
description |
text, nullable |
Комментарий для админки/ops |
updated_at |
timestamptz |
Последнее изменение |
record_status |
char(1) |
Soft delete: 'A' active |
Правила
- Seed обязателен до первого запуска
api-backendв новой среде (миграция или idempotent seed-скрипт). api-backendзагружает настройки при старте; допускается in-memory cache с инвалидацией поupdated_at(реализация — модуль).- Отсутствие обязательного ключа при старте → сервис не переходит в
ready(fail-fast). - Публичные ключи (
is_public=true) отдаются только через строгий DTOapp-config, не raw dump таблицы. - Секреты и infra не хранятся в
app_settings.
Ключи MVP (seed)
Полный пример значений — раздел «Seed MVP» ниже. Группы:
| Группа | Ключи |
|---|---|
| Auth | auth.phone.enabled, auth.password.enabled |
| OTP (продукт) | otp.phone.max_send_attempts_per_24h, otp.phone.min_seconds_between_attempts |
| Оператор | operator.call.phone |
| Consent | consent.personal_data.*, consent.user_agreement.*, consent.marketing.* |
| Файлы чата | chat.attachments.* |
| Rate limits (app) | rate_limit.message_send.*, rate_limit.download_url.*, rate_limit.public_endpoints.*, rate_limit.login.* |
| UX | ux.session.idle_timeout_minutes |
| Security | security.cors.allowed_origins, security.public_cache.max_age_seconds |
Seed MVP
auth.phone.enabled=true
auth.password.enabled=false
otp.phone.max_send_attempts_per_24h=3
otp.phone.min_seconds_between_attempts=30
operator.call.phone=+74999591007
consent.personal_data.required=true
consent.personal_data.document_url=https://www.han0107.ru/privacy/persdata-agree-mobile
consent.personal_data.version=2026-06-10
consent.user_agreement.required=true
consent.user_agreement.document_url=https://www.han0107.ru/user-agreement
consent.user_agreement.version=2026-06-10
consent.marketing.required=false
consent.marketing.version=2026-06-10
chat.attachments.allowed_extensions=jpg,jpeg,png,webp,heic,heif,pdf
chat.attachments.allowed_mime_types=image/jpeg,image/png,image/webp,image/heic,image/heif,application/pdf
chat.attachments.disallowed_extensions=svg,doc,docx,xls,xlsx,csv
chat.attachments.max_size_mb=5
chat.attachments.storage=selectel_s3
chat.attachments.upload_mode=backend_controlled_upload
chat.attachments.safety_scan_required=true
rate_limit.message_send.per_user=30/minute
rate_limit.message_send.per_dialog=20/minute
rate_limit.download_url.per_user=60/hour
rate_limit.public_endpoints.per_ip=60/minute
rate_limit.login.per_ip=10/minute
ux.session.idle_timeout_minutes=30
security.cors.allowed_origins=https://tohin.ru,https://app.example.ru
security.public_cache.max_age_seconds=3600
Пример .env.example
Только инфраструктура. Бизнес-параметры — в seed app_settings.
# =============================================================================
# Общие
# =============================================================================
APP_ENV=production-like
API_PORT=8000
LOG_LEVEL=INFO
# =============================================================================
# Managed PostgreSQL
# =============================================================================
HAN_PG_HOST=<managed-pg-private-host>
HAN_PG_PORT=6432
HAN_PG_DATABASE=han_chat
DATABASE_URL=postgresql+asyncpg://han_app:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?options=-csearch_path%3Dhan_app
BITRIX_DATABASE_URL=postgresql://bitrix_local_app:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?options=-csearch_path%3Dbitrix_local
BITRIX_SYNC_APP_DATABASE_URL=postgresql://bitrix_sync_user:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?options=-csearch_path%3Dbitrix_sync%2Chan_app
BITRIX_SYNC_DATABASE_URL=postgresql://bitrix_sync_user:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?options=-csearch_path%3Dbitrix_sync
MESSAGE_SAFETY_DATABASE_URL=postgresql://message_safety_app:change-me@<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?options=-csearch_path%3Dmessage_safety
KEYCLOAK_DB_URL=jdbc:postgresql://<HAN_PG_HOST>:<HAN_PG_PORT>/<HAN_PG_DATABASE>?user=keycloak_user&password=change-me¤tSchema=keycloak
KC_DB_URL_PROPERTIES=currentSchema=keycloak
# =============================================================================
# Публичные URL (HTTPS)
# =============================================================================
PUBLIC_WEB_URL=https://app.example.ru
PUBLIC_API_URL=https://app.example.ru/api
PUBLIC_AUTH_URL=https://app.example.ru/auth
# =============================================================================
# nginx (edge, TLS, rate limits)
# =============================================================================
NGINX_HTTP_PORT=80
NGINX_HTTPS_PORT=443
TLS_CERT_PATH=/etc/nginx/certs/fullchain.pem
TLS_KEY_PATH=/etc/nginx/certs/privkey.pem
NGINX_TLS_PROTOCOLS=TLSv1.2 TLSv1.3
NGINX_HSTS_MAX_AGE=31536000
NGINX_CLIENT_MAX_BODY_SIZE=8m
NGINX_RATE_LIMIT_API=60r/m
NGINX_RATE_LIMIT_AUTH=10r/m
NGINX_RATE_LIMIT_DOWNLOADS=30r/m
NGINX_RATE_LIMIT_PUBLIC=60r/m
NGINX_RATE_LIMIT_POLLING=60r/m
# =============================================================================
# Keycloak (infra; OTP-заглушка — dev/MVP)
# =============================================================================
KEYCLOAK_PUBLIC_URL=https://app.example.ru/auth
KEYCLOAK_INTERNAL_URL=http://keycloak:8080
KEYCLOAK_REALM=han-chat
KEYCLOAK_AUDIENCE=han-chat-api
KEYCLOAK_OTP_MOCK_ENABLED=true
KEYCLOAK_OTP_MOCK_CODE=1234
# =============================================================================
# Redis
# =============================================================================
REDIS_URL=redis://redis:6379/0
# =============================================================================
# Service tokens (internal API) — все переменные только в backend/.env
# =============================================================================
MESSAGE_SAFETY_SERVICE_TOKEN=change-me
BITRIX_LOCAL_APP_INTERNAL_TOKEN=change-me
BITRIX_API_INBOX_TOKEN=change-me
BITRIX_INTERNAL_API_TOKEN=change-me
BITRIX_API_FORWARD_TOKEN=change-me
BITRIX_SYNC_SERVICE_TOKEN=change-me
# =============================================================================
# api-backend (интеграции)
# =============================================================================
BITRIX_LOCAL_APP_BASE_URL=http://bitrix-local-app:8080
BITRIX_API_INBOX_PATH=/internal/openlines/v1/inbox
MESSAGE_SAFETY_URL=http://message-safety:8080
# =============================================================================
# bitrix-sync
# =============================================================================
BITRIX_SYNC_CRM_BASE_URL=https://han0107.bitrix24.ru
BITRIX_SYNC_CRM_WEBHOOK_URL=change-me
BITRIX_SYNC_CONTACT_MAP_INTERVAL_SEC=60
BITRIX_SYNC_CONTACT_UPDATE_INTERVAL_SEC=30
BITRIX_SYNC_CRM_MAX_CONCURRENCY=2
BITRIX_SYNC_CONTACT_LIST_BATCH_SIZE=50
BITRIX_SYNC_WEBHOOK_TOKEN=change-me
# =============================================================================
# bitrix-local-app
# =============================================================================
BITRIX_CLIENT_ID=change-me
BITRIX_CLIENT_SECRET=change-me
BITRIX_CONNECTOR_ID=han_mobile_app
BITRIX_CONNECTOR_NAME=HAN Mobile App
BITRIX_OPEN_LINE_ID=8
BITRIX_PUBLIC_BASE_URL=https://tohin.ru/bitrix
BITRIX_API_FORWARD_URL=http://api-backend:8000/internal/openlines/v1/inbox
BITRIX_APPLICATION_TOKEN=change-me
# =============================================================================
# message-safety (technical)
# =============================================================================
MESSAGE_SAFETY_POST_TIMEOUT_SEC=5
MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC=2
MESSAGE_SAFETY_TASK_POLL_MAX_SEC=300
MESSAGE_SAFETY_RULES_VERSION=2026-01-01
# =============================================================================
# Frontend (nginx)
# =============================================================================
FRONTEND_STATIC_PATH=/usr/share/nginx/html
FRONTEND_DEV_PROXY_ENABLED=false
EXPO_DEV_SERVER_URL=http://host.docker.internal:8081
# =============================================================================
# Selectel S3
# =============================================================================
SELECTEL_S3_ENDPOINT_URL=https://s3.storage.selcloud.ru
SELECTEL_S3_BUCKET_DOCUMENTS=han-chat-documents
SELECTEL_S3_BUCKET_ATTACHMENTS=han-chat-attachments
SELECTEL_S3_BUCKET_QUARANTINE=han-chat-quarantine
SELECTEL_S3_ACCESS_KEY=change-me
SELECTEL_S3_SECRET_KEY=change-me
SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY=change-me
SELECTEL_S3_QUARANTINE_READ_SECRET_KEY=change-me
# =============================================================================
# Observability
# =============================================================================
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
Все переменные — только в backend/.env. Отдельного хранилища нет.
Webhook-токены (публичные callback, не service API): BITRIX_APPLICATION_TOKEN, BITRIX_SYNC_WEBHOOK_TOKEN.
Namespace переменных Bitrix
bitrix-local-app:BITRIX_CLIENT_*,BITRIX_CONNECTOR_*,BITRIX_PUBLIC_BASE_URL,BITRIX_DATABASE_URL,BITRIX_API_FORWARD_URL,BITRIX_APPLICATION_TOKEN+ service tokens.api-backend:BITRIX_LOCAL_APP_BASE_URL,MESSAGE_SAFETY_URL+ service tokens; бизнес-настройки — изapp_settings.bitrix-sync:BITRIX_SYNC_*,BITRIX_SYNC_WEBHOOK_TOKEN,BITRIX_SYNC_SERVICE_TOKEN.bitrix-syncне читаетBITRIX_CLIENT_ID/BITRIX_CLIENT_SECRET.
Разрешённые типы файлов чата (MVP)
Источник значений — ключи app_settings (раздел «Seed MVP»). Остальные arch-* ссылаются сюда.
| Ключ | MVP-значение |
|---|---|
chat.attachments.allowed_extensions |
jpg, jpeg, png, webp, heic, heif, pdf |
chat.attachments.allowed_mime_types |
image/jpeg, image/png, image/webp, image/heic, image/heif, application/pdf |
chat.attachments.max_size_mb |
5 |
Правило: файл принимается только если и расширение, и MIME в allow-list. Детальная проверка — модуль message-safety.
Публичный UI: GET /api/v1/public/app-config (строгий DTO, без секретов).
Публичный config endpoint
GET /api/v1/public/app-config — только ключи с is_public=true из app_settings:
- OTP по телефону (
auth.phone.enabled); - номер оператора;
- типы файлов и max size;
ux.session.idle_timeout_minutes;- feature flags;
- публичные лимиты для подсказок UI.
Секреты, service tokens, внутренние URL не возвращаются. DTO явный, не сериализация всей таблицы. Rate limit: 60/min per IP. Cache-Control: public, max-age из security.public_cache.max_age_seconds.
Публичный content endpoint
GET /api/v1/public/content — text_resources для текущего языка. Те же требования безопасности, что у config.
Nginx и HTTPS
Infra-переменные — .env.example (NGINX_*, TLS_*). Edge rate limits (NGINX_RATE_LIMIT_*) не дублируют rate_limit.* из app_settings: nginx — защита периметра, app — бизнес-лимиты в backend.
Реализация — arch-03-docker-compose-blueprint.md.