# arch-04. Настройки и изменяемые параметры > **`.env`** — только несекретная инфраструктурная конфигурация. Production-секреты доставляются отдельно по [`arch-06-service-hosting-security.md`](arch-06-service-hosting-security.md). **`app_settings`** (App DB) — единственный источник бизнес-настроек. Имена полей и enum — [`arch-00-glossary.md`](arch-00-glossary.md). Docker Compose — [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md). ## Цель Параметры разделены по слоям: | Слой | Где | Что | |---|---|---| | **Инфраструктура** | `.env` | несекретные host/port/URL, nginx/TLS, режимы и технические параметры | | **Production-секреты** | Selectel Secrets Manager → `/run/han-chat/secrets`; для no-egress VM — root-owned files | credential-bearing DSN, пароли, private keys, service/webhook tokens, provider credentials | | **Бизнес-логика** | таблица **`app_settings`** | лимиты, флаги, телефоны, типы файлов, CORS, consent URLs | | **Настройки SMS runtime** | таблица **`sms.sms_setting`** | sender default, provider timeouts, callback flag, worker intervals | | **Контент** | `text_resources`, `popular_questions` | тексты UI | Managed PostgreSQL **поднимается до** развёртывания приложения. Бизнес-настройки **не дублируются** в `.env`: seed в `app_settings` выполняется миграцией/скриптом `module-01-api-backend` **до** первого запуска `api-backend`. ## Источники настроек ### `.env` — только несекретная инфраструктура Корневой `backend/.env` читается сервисами compose. В репозитории — `.env.example`, не `.env`. **Допустимо в `.env`:** - URL сервисов, публичные endpoint, порты; - host/port/database/schema без паролей и токенов; - параметры **nginx/TLS** и edge rate limits (`NGINX_RATE_LIMIT_*`); - идентификация Keycloak: realm, audience, public/internal URL; - переключатели OTP mock и Yandex SmartCaptcha, а также публичный CAPTCHA client key; mock code и CAPTCHA server key являются секретами; - `SECRETS_SOURCE=selectel|file`, который выбирает утверждённый механизм доставки, но не содержит secret value; - технические параметры сервисов, пока профильная спецификация не определила service-owned settings; для `sms-service` runtime-параметры уже вынесены в `sms.sms_setting`. **Запрещено в `.env`:** - credential-bearing DSN/URL; - пароли БД, Keycloak bootstrap password и OTP mock/HMAC secrets; - S3 access/secret keys; - Bitrix OAuth secrets и application/webhook tokens; - service tokens внутренних API; - SMS API key/callback credentials и OTLP auth header; - TLS private keys и любые иные credentials. Они доставляются как Compose/systemd secret files по arch-06. Бизнес-параметры ниже хранятся только в `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`). Обязательная safety-мнемоника MVP: | mnemonic | Назначение | |---|---| | `safety.chat.blocked` | Generic company-реплика при любом Message Safety deny; текст locale-aware, без раскрытия `rule_id` | Миграция/seed обязаны создать активную запись минимум для `ru`. Изменение `text_value` не требует redeploy Safety и не меняет API/error code. --- ## Требования к таблице `app_settings` Схема: **`han_app`**. Детальная DDL — `module-01-api-backend` и migration owner этой схемы; 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 | ### Правила 1. **Seed обязателен** до первого запуска `api-backend` в новой среде (миграция или idempotent seed-скрипт). 2. **`api-backend`** загружает настройки при старте; допускается in-memory cache с инвалидацией по `updated_at` (реализация — модуль). 3. Отсутствие **обязательного** ключа при старте → сервис **не** переходит в `ready` (fail-fast). 4. Публичные ключи (`is_public=true`) отдаются только через **строгий DTO** `app-config`, не raw dump таблицы. 5. Секреты и infra **не** хранятся в `app_settings`. ### Ключи MVP (seed) Полный пример значений — раздел «Seed MVP» ниже. Группы: | Группа | Ключи | |---|---| | Auth | `auth.phone.enabled`, `auth.password.enabled` | | OTP (продукт; потребитель — Keycloak SPI через settings bridge api-backend) | `otp.phone.max_send_attempts_per_24h`, `otp.phone.min_seconds_between_attempts`, `otp.phone.max_verify_attempts`, `otp.phone.code_length`, `otp.phone.ttl_seconds`, `otp.phone.sms_order_timeout_ms` | | Оператор | `operator.call.phone` | | Consent | `consent.personal_data.*`, `consent.privacy_policy.document_url`, `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.*` | | Notification Center | `notification.home.max_items`, `notification.center.max_items`, `notification.carousel.*`, `notification.hidden.default_ttl_days`, `notification.documents.max_files`, `notification.expire_job.run_at`, `notification.upload_draft.ttl_days` | | Rate limits (notifications) | `rate_limit.notifications_read.per_user`, `rate_limit.notifications_action.per_user`, `rate_limit.notification_upload.per_user`, `rate_limit.notifications_public.per_ip` | | UX | `ux.session.idle_timeout_minutes` | | Security | `security.cors.allowed_origins`, `security.public_cache.max_age_seconds` | ### Seed MVP ```text auth.phone.enabled=true auth.password.enabled=false otp.phone.max_send_attempts_per_24h=3 otp.phone.min_seconds_between_attempts=30 otp.phone.max_verify_attempts=5 otp.phone.code_length=6 otp.phone.ttl_seconds=60 otp.phone.sms_order_timeout_ms=3000 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.privacy_policy.document_url=https://www.han0107.ru/privacy 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.document_url=https://www.han0107.ru/privacy/ads-agree 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=presigned_put chat.attachments.safety_scan_required=true chat.attachments.presigned_upload_ttl_seconds=600 messages.max_text_length=4000 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 rate_limit.notifications_read.per_user=120/minute rate_limit.notifications_action.per_user=60/minute rate_limit.notification_upload.per_user=20/minute rate_limit.notifications_public.per_ip=60/minute notification.home.max_items=7 notification.center.max_items=15 notification.carousel.autoplay_enabled=false notification.carousel.autoplay_interval_ms=5000 notification.hidden.default_ttl_days=3 notification.documents.max_files=10 notification.expire_job.run_at=00:01 notification.upload_draft.ttl_days=7 ux.session.idle_timeout_minutes=30 security.cors.allowed_origins=https://tohin.ru security.public_cache.max_age_seconds=3600 ``` --- ## Service-owned настройки `sms-service` Параметры, изменение которых не меняет Compose, секреты, URL или сетевую топологию, хранятся в `sms.sms_setting`, а не в `.env`. Ключи и seed: ```text provider.idgtl.default_sender_name= provider.idgtl.connect_timeout_ms=3000 provider.idgtl.request_timeout_ms=70000 provider.idgtl.callback_enabled=true worker.poll_interval_ms=500 worker.lease_seconds=90 ``` В `.env` остаются только несекретные URL внутренних/внешних сервисов и технические параметры. `SMS_DATABASE_URL`, service tokens, Direct API key и callback credentials входят в runtime secret catalog. Детальный контракт — `module-11-idgtl-sms.md`. `` — обязательный deployment placeholder, а не допустимое production-значение. Перед real mode должны существовать active approved template `auth_otp` с точными placeholders `code`/`ttl_min` и согласованный `senderName`. Отсутствие template/sender делает readiness false. --- ## Service-owned настройки `message-safety` Runtime policy хранится в версионированной `message_safety.config_versions`, а не в `.env` и не в `han_app.app_settings`. Сюда входят task lease/deadline/attempts, internal rate/pending limits, retention/cache TTL, URL/DNS pipeline limits, KESL broker timeout/database age и enabled file MIME/size policy. Schema допускает возраст базы KESL не более `720` часов (30 дней), seed `max_signature_age_hours` равен `240` часам (10 дней). Полный schema/seed/activation contract — module-05 §10.1 и §15. Seed, JSON Schema и referenced artifacts входят в immutable Message Safety image. Их изменение требует одновременно нового pinned image digest и новой монотонной config version через config-admin job. Нельзя активировать config, который старый runtime image не валидирует, оставлять старый image как автоматический rollback после активации несовместимой schema или редактировать active row на месте. Rollback выполняется новой config version, совместимой с выбранным image; retired version повторно не активируется. `han_app.app_settings:chat.attachments.*` остаётся бизнес-настройкой api-backend. Message Safety не получает cross-schema read к `han_app`; файл допускается только при пересечении business allow-list, active safety policy и immutable detector manifest. Active policy может сузить manifest, но не добавить parser и не увеличить hard limit. В env Message Safety остаются только bootstrap/topology/capacity (`APP_ENV`, worker concurrency, DNS resolver, Unix socket KESL broker, S3/OTLP endpoints); credentials доставляются secret files. Rules/detector versions вычисляются/проверяются по immutable artifacts. `scanner_engine=kesl`; `signatures_version` вычисляется как hash KESL version + database date. Emergency MOCK остаётся в отдельном read-only bind file `root:han-message-safety 0640` с dedicated GID контейнера и намеренно не переносится в БД. --- ## Пример `.env.example` Только несекретная инфраструктура и выбор secret source. Бизнес-параметры — в seed `app_settings`, а перечисленные ниже runtime secrets — в конфигурации `han-secrets`, не в этом файле. ```text # ============================================================================= # Общие # ============================================================================= APP_ENV=production-like SECRETS_SOURCE=selectel API_PORT=8000 LOG_LEVEL=INFO # ============================================================================= # Managed PostgreSQL # ============================================================================= HAN_PG_HOST= HAN_PG_PORT=5433 HAN_PG_DATABASE=han_chat KC_DB_URL_PROPERTIES=currentSchema=keycloak # Runtime secrets: DATABASE_URL, BITRIX_DATABASE_URL, # BITRIX_SYNC_APP_DATABASE_URL, BITRIX_SYNC_DATABASE_URL, # MESSAGE_SAFETY_DATABASE_URL, SMS_DATABASE_URL, KEYCLOAK_DB_URL. # Selectel PgBouncer 5433: pool_mode=session; search_path задаётся на уровне ролей. # Не добавлять options=-csearch_path: pooler отклоняет этот startup parameter. # ============================================================================= # Публичные URL (HTTPS) # ============================================================================= PUBLIC_WEB_URL=https://tohin.ru PUBLIC_API_URL=https://tohin.ru/api PUBLIC_AUTH_URL=https://tohin.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 NGINX_RATE_LIMIT_NOTIFICATIONS_READ=120r/m NGINX_RATE_LIMIT_NOTIFICATIONS_ACTION=60r/m NGINX_RATE_LIMIT_NOTIFICATION_UPLOAD=20r/m NGINX_RATE_LIMIT_NOTIFICATIONS_PUBLIC=60r/m # ============================================================================= # Keycloak (mock остаётся true до controlled SMS cutover) # ============================================================================= KEYCLOAK_PUBLIC_URL=https://tohin.ru/auth KEYCLOAK_INTERNAL_URL=http://keycloak:8080 KEYCLOAK_REALM=han-chat KEYCLOAK_AUDIENCE=han-chat-api KEYCLOAK_OTP_MOCK_ENABLED=true KEYCLOAK_YANDEX_CAPTCHA_ENABLED=false KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY= KEYCLOAK_SMS_SERVICE_URL=http://sms-service:8080 # Runtime secrets: KEYCLOAK_OTP_MOCK_CODE, # KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY and OTP HMAC/bootstrap credentials. # ============================================================================= # Redis ВМ1 (I4: раздельные DB index) # ============================================================================= # /0 — api-backend: rate limits, idempotency # /1 — api-backend realtime/coordination (опционально; можно совместить с /0) REDIS_URL=redis://redis:6379/0 REDIS_REALTIME_URL=redis://redis:6379/1 # ============================================================================= # Service tokens (internal API) — runtime secret catalog, не .env # ============================================================================= # MESSAGE_SAFETY_SERVICE_TOKEN, BITRIX_LOCAL_APP_INTERNAL_TOKEN, # BITRIX_API_INBOX_TOKEN, BITRIX_INTERNAL_API_TOKEN, # BITRIX_API_FORWARD_TOKEN, BITRIX_SYNC_SERVICE_TOKEN, # KEYCLOAK_SETTINGS_BRIDGE_TOKEN, SMS_SERVICE_TOKEN, # KEYCLOAK_SMS_SERVICE_TOKEN, NOTIFICATIONS_TOKEN_PRODUCER_TEST. # ============================================================================= # SMS provider (секреты — runtime secret catalog; параметры — sms.sms_setting) # ============================================================================= IDGTL_SMS_BASE_URL=https://direct.i-dgtl.ru IDGTL_SMS_CALLBACK_PUBLIC_URL=https://tohin.ru/callbacks/idgtl/sms # Runtime secrets: IDGTL_SMS_API_KEY, IDGTL_SMS_CALLBACK_USERNAME, # IDGTL_SMS_CALLBACK_PASSWORD. # ============================================================================= # api-backend (интеграции + resilience I2) # ============================================================================= BITRIX_LOCAL_APP_BASE_URL=http://bitrix-local-app:8080 BITRIX_API_INBOX_PATH=/internal/openlines/v1/inbox MESSAGE_SAFETY_URL=https://processing.internal:8443 # Host-level Compose bind source; не передаётся приложению как runtime path. MESSAGE_SAFETY_CA_HOST_PATH=/opt/han-chat/secrets/processing-internal-ca.crt # Путь того же bind внутри контейнера api-backend. MESSAGE_SAFETY_CA_FILE=/run/han-chat/secrets/processing-internal-ca.crt MESSAGE_SAFETY_API_PREFIX=/internal/safety/v2 MESSAGE_SAFETY_CIRCUIT_FAILURE_THRESHOLD=5 MESSAGE_SAFETY_CIRCUIT_OPEN_SEC=30 BITRIX_LOCAL_APP_CIRCUIT_FAILURE_THRESHOLD=5 BITRIX_LOCAL_APP_CIRCUIT_OPEN_SEC=30 BITRIX_LOCAL_APP_HTTP_TIMEOUT_SEC=10 # ============================================================================= # bitrix-sync # ============================================================================= BITRIX_SYNC_ENABLED=true BITRIX_SYNC_MODE=full BITRIX_SYNC_PORTAL_HOST=han0107.bitrix24.ru BITRIX_SYNC_PORTAL_MEMBER_ID= BITRIX_SYNC_PUBLIC_BASE_URL=https://processing.example.ru BITRIX_WEBHOOK_ALLOWED_CIDRS= BITRIX_SYNC_CONTACT_USER_ID_FIELD=UF_CRM_... BITRIX_SYNC_CONTACT_REGISTERED_FIELD=UF_CRM_1778692456 BITRIX_SYNC_CONTACT_CITIZENSHIP_FIELD=UF_CRM_1768493029 BITRIX_SYNC_HTTP_TIMEOUT_SEC=10 BITRIX_SYNC_DB_POOL_SIZE=5 # Hot worker/rate/retry/reconciliation/alert parameters: # versioned bitrix_sync.settings, не env. # Runtime secrets: BITRIX_SYNC_DATABASE_URL, # BITRIX_SYNC_CRM_REST_WEBHOOK_URL, # BITRIX_SYNC_CONTACT_RECEIVER_TOKEN, # BITRIX_SYNC_ALERT_RECEIVER_TOKEN, # BITRIX_SYNC_SERVICE_TOKEN. # ============================================================================= # bitrix-local-app # ============================================================================= 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 # Runtime secrets: BITRIX_CLIENT_ID, BITRIX_CLIENT_SECRET, # BITRIX_APPLICATION_TOKEN and token encryption key. # ============================================================================= # api-backend → Message Safety (ВМ1 caller) # ============================================================================= MESSAGE_SAFETY_POST_TIMEOUT_SEC=5 MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC=2 MESSAGE_SAFETY_TASK_POLL_MAX_SEC=300 QUARANTINE_ORPHAN_RETENTION_HOURS=48 HAN_APP_SAFETY_CHECKPOINT_RETENTION_DAYS=7 HAN_APP_SAFETY_RECOVERY_MAX_SEC=1200 # ============================================================================= # message-safety (ВМ2 bootstrap/topology/capacity) # ============================================================================= # Service runtime policy находится в message_safety.config_versions. # Runtime secrets: MESSAGE_SAFETY_DATABASE_URL, # MESSAGE_SAFETY_REDIS_URL=redis://message_safety:@redis:6379/0, # MESSAGE_SAFETY_SERVICE_TOKEN, S3 quarantine read credentials. # Emergency mode находится только в root-owned # /etc/han-chat/message-safety-mode.env и меняется approved helper-ом. MESSAGE_SAFETY_WORKER_CONCURRENCY=5 MESSAGE_SAFETY_DNS_RESOLVERS= MESSAGE_SAFETY_KESL_SOCKET=/run/han-kesl/scan.sock # ============================================================================= # 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 # Runtime secrets: SELECTEL_S3_ACCESS_KEY, SELECTEL_S3_SECRET_KEY, # SELECTEL_S3_QUARANTINE_READ_ACCESS_KEY, # SELECTEL_S3_QUARANTINE_READ_SECRET_KEY. # ============================================================================= # Observability # ============================================================================= # На каждой VM это local Docker DNS; ВМ2 не указывает collector ВМ1. OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 OTEL_SERVICE_NAME_API=api-backend OTEL_SERVICE_NAME_SMS_API=sms-service OTEL_SERVICE_NAME_SMS_WORKER=sms-worker OTEL_TRACES_SAMPLER=always_on SMS_METRICS_PORT=9464 OTEL_REMOTE_ENDPOINT=192.168.0.5:4317 OTEL_REMOTE_TLS_INSECURE=true OTEL_QUEUE_SIZE=10000 # Runtime secret when configured: OTEL_REMOTE_AUTH_HEADER. ``` S3-клиенты используют только virtual-hosted addressing (`https://.s3.storage.selcloud.ru/`). Это часть контракта presigned URL и CORS Selectel; path-style адресация не поддерживается приложением. Production использует отдельные allow-listed env manifests ВМ1 и ВМ2; не все переменные примера копируются на оба хоста. На ВМ2 `han-secrets` под отдельным IAM principal материализует отдельный root-owned файл каждому сервису в `/run/han-chat/secrets`; общий bundle ВМ1/ВМ2 запрещён. Для production placeholders и примерные sender/template/credentials отклоняются `validate-env` и runtime manifest validation. `IDGTL_SMS_API_KEY` в secret catalog — выданный Direct готовый `TOKEN_1` для Basic, без повторного Base64. Реальный статический egress IP хранится в deployment inventory, а не env; если он не обеспечен NAT/сетевой конфигурацией, `KEYCLOAK_OTP_MOCK_ENABLED=false` запрещён. **Webhook-токены:** `BITRIX_APPLICATION_TOKEN` относится только к `bitrix-local-app`. CRM sync не использует local app/event handler; штатные HTTP-webhook роботы передают отдельные `BITRIX_SYNC_CONTACT_RECEIVER_TOKEN` и `BITRIX_SYNC_ALERT_RECEIVER_TOKEN` в query, поскольку custom Bearer header недоступен. Токены остаются secret-manager values, query исключается из logs/traces, а nginx дополнительно применяет `BITRIX_WEBHOOK_ALLOWED_CIDRS`. `BITRIX_SYNC_CRM_REST_WEBHOOK_URL` — отдельный секрет исходящего CRM REST-доступа sync-service. `NOTIFICATIONS_TOKEN_` — индивидуальный секрет продюсера Internal Notifications API. Для seed/smoke используется `NOTIFICATIONS_TOKEN_PRODUCER_TEST`; secret хранится только в secret store/runtime secret file, а `notification_sources.token_hash` — только hash. Инструкция не имеет `notification.instruction.allowed_hosts`: она всегда открывается в новой вкладке, iframe-режима нет. ## 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`, circuit/timeout vars, Redis `/0`/`/1` + service tokens; **бизнес-настройки** — из `app_settings`. OTP counters **не** ведёт. - `bitrix-sync`: non-secret `BITRIX_SYNC_ENABLED/MODE/PORTAL_HOST/PORTAL_MEMBER_ID`, `BITRIX_WEBHOOK_ALLOWED_CIDRS`, custom field names и capacity bootstrap; runtime secrets — database/inbound CRM REST webhook URL, Contact/alert receiver tokens и service token; hot policy — в `bitrix_sync.settings`. - `bitrix-sync` не читает `BITRIX_CLIENT_ID` / `BITRIX_CLIENT_SECRET`. ### `BITRIX_SYNC_ENABLED` | Значение | Поведение | |---|---| | `true` (default) | `bitrix-sync` обрабатывает `sync_queue` и принимает CRM webhook | | `false` | синхронизация с Bitrix24 CRM не выполняется; сервис стартует в no-op/degraded режиме; чат Open Lines через `bitrix-local-app` **не** затрагивается | При `false` контейнер остаётся live, `/health/ready` возвращает `503 sync_disabled`, worker/reconciliation не claim-ят работу. Public webhook не должен безусловно подтверждать событие как обработанное: до cutover endpoint закрывается на edge либо возвращает retryable `503`. Это не влияет на чат Open Lines. Feature flag, edge allow-list и readiness образуют единый fail-closed инвариант: при `false` активный nginx allow-list содержит только `deny all;`; при `true` требуется хотя бы один reviewed source CIDR и готовый receiver. Непустой allow-list при disabled, пустой allow-list при enabled или расхождение route/readiness являются preflight error. ### `bitrix_sync.settings` Versioned hot settings содержат batch size/wait, claim size, lease TTL, portal limiter refill/burst, max in-flight, retry base/max/horizon, Contact/alert reconciliation intervals, пороги всплеска Contact, восстановленных без webhook, и IDs/стадии/поля/SLA smart process. Новая версия активируется только после полной type/range/cross-field validation; невалидная версия не заменяет последнюю рабочую. Secrets, DSN, portal host/member ID, inbound source IP CIDR allow-list, custom Contact field names и cutover watermark не являются hot settings. Полный каталог и defaults — [`module-07-bitrix-sync.md`](../VM2_services/documentation/module-07-bitrix-sync.md), §12. ## Keycloak settings bridge для OTP OTP settings (`otp.phone.*`) хранятся в `app_settings`, но Keycloak не получает прямой доступ к схеме `han_app`. MVP-механизм: 1. `api-backend` читает публичные/служебные настройки из `app_settings` и кэширует их. 2. Для Keycloak SPI доступен internal endpoint `GET /internal/settings/v1/otp` в Docker/VPC-сети, защищённый service token. 3. Keycloak SPI читает limits, `otp.phone.code_length`, `otp.phone.ttl_seconds` и `otp.phone.sms_order_timeout_ms` через этот endpoint с локальным cache. 4. При недоступности settings bridge SPI использует последнее валидное cache-значение; если cache пустой — fail-closed и не выдаёт OTP. Challenge сохраняет snapshot TTL, длины кода и `settings_version`; изменение settings влияет только на новые challenges. Счётчики попыток OTP остаются в зоне Keycloak/SPI, не в `api-backend`. ## Разрешённые типы файлов чата (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` | | `messages.max_text_length` | `4000` (public/business limit; Safety hard ceiling остаётся `10000`) | Правило: файл принимается только если **и** расширение, **и** 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`](arch-03-docker-compose-blueprint.md).