x-api-secret-environment: &api-secret-environment HAN_SECRET_VARS: >- DATABASE_URL REDIS_URL REDIS_REALTIME_URL MESSAGE_SAFETY_SERVICE_TOKEN BITRIX_LOCAL_APP_INTERNAL_TOKEN BITRIX_API_INBOX_TOKEN KEYCLOAK_SETTINGS_BRIDGE_TOKEN SELECTEL_S3_ACCESS_KEY SELECTEL_S3_SECRET_KEY CURSOR_HMAC_SECRET DATABASE_URL_FILE: /run/secrets/api_database_url REDIS_URL_FILE: /run/secrets/api_redis_url REDIS_REALTIME_URL_FILE: /run/secrets/api_redis_realtime_url MESSAGE_SAFETY_SERVICE_TOKEN_FILE: /run/secrets/message_safety_service_token BITRIX_LOCAL_APP_INTERNAL_TOKEN_FILE: /run/secrets/bitrix_local_app_internal_token BITRIX_API_INBOX_TOKEN_FILE: /run/secrets/bitrix_api_inbox_token KEYCLOAK_SETTINGS_BRIDGE_TOKEN_FILE: /run/secrets/keycloak_settings_bridge_token SELECTEL_S3_ACCESS_KEY_FILE: /run/secrets/selectel_s3_access_key SELECTEL_S3_SECRET_KEY_FILE: /run/secrets/selectel_s3_secret_key CURSOR_HMAC_SECRET_FILE: /run/secrets/cursor_hmac_secret x-api-secrets: &api-secrets - api_database_url - api_redis_url - api_redis_realtime_url - message_safety_service_token - bitrix_local_app_internal_token - bitrix_api_inbox_token - keycloak_settings_bridge_token - selectel_s3_access_key - selectel_s3_secret_key - cursor_hmac_secret x-api-runtime: &api-runtime build: context: ../../api-backend image: ${API_BACKEND_IMAGE:-han-chat-api-backend:local} environment: <<: *api-secret-environment APP_ENV: ${APP_ENV:-production-like} LOG_LEVEL: ${LOG_LEVEL:-INFO} RELEASE_VERSION: ${RELEASE_VERSION:-unknown} KEYCLOAK_PUBLIC_URL: ${KEYCLOAK_PUBLIC_URL} KEYCLOAK_INTERNAL_URL: ${KEYCLOAK_INTERNAL_URL:-http://keycloak:8080/auth} KEYCLOAK_REALM: ${KEYCLOAK_REALM:-han-chat} KEYCLOAK_AUDIENCE: ${KEYCLOAK_AUDIENCE:-han-chat-api} MESSAGE_SAFETY_URL: ${MESSAGE_SAFETY_URL} MESSAGE_SAFETY_API_PREFIX: /internal/safety/v2 MESSAGE_SAFETY_CA_FILE: /run/config/message-safety-internal-ca.pem MESSAGE_SAFETY_POST_TIMEOUT_SEC: ${MESSAGE_SAFETY_POST_TIMEOUT_SEC:-5} MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC: ${MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC:-2} MESSAGE_SAFETY_TASK_POLL_MAX_SEC: ${MESSAGE_SAFETY_TASK_POLL_MAX_SEC:-300} BITRIX_LOCAL_APP_BASE_URL: ${BITRIX_LOCAL_APP_BASE_URL:-http://bitrix-local-app:8080} BITRIX_LOCAL_APP_HTTP_TIMEOUT_SEC: ${BITRIX_LOCAL_APP_HTTP_TIMEOUT_SEC:-20} SELECTEL_S3_ENDPOINT_URL: ${SELECTEL_S3_ENDPOINT_URL} SELECTEL_S3_BUCKET_DOCUMENTS: ${SELECTEL_S3_BUCKET_DOCUMENTS} SELECTEL_S3_BUCKET_ATTACHMENTS: ${SELECTEL_S3_BUCKET_ATTACHMENTS} SELECTEL_S3_BUCKET_QUARANTINE: ${SELECTEL_S3_BUCKET_QUARANTINE} TRUSTED_PROXY_CIDRS: ${TRUSTED_PROXY_CIDRS:-127.0.0.1/32} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4317} OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME_API:-api-backend} secrets: *api-secrets volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true - type: bind source: ${MESSAGE_SAFETY_CA_HOST_PATH} target: /run/config/message-safety-internal-ca.pem read_only: true networks: [backend, observability, egress] security_opt: ["no-new-privileges:true"] ulimits: core: {soft: 0, hard: 0} logging: driver: json-file options: {max-size: "50m", max-file: "5"} x-sms-api-environment: &sms-api-environment HAN_SECRET_VARS: >- SMS_DATABASE_URL SMS_SERVICE_TOKEN IDGTL_SMS_CALLBACK_USERNAME IDGTL_SMS_CALLBACK_PASSWORD SMS_DATABASE_URL_FILE: /run/secrets/sms_database_url SMS_SERVICE_TOKEN_FILE: /run/secrets/sms_service_token IDGTL_SMS_CALLBACK_USERNAME_FILE: /run/secrets/idgtl_sms_callback_username IDGTL_SMS_CALLBACK_PASSWORD_FILE: /run/secrets/idgtl_sms_callback_password IDGTL_SMS_BASE_URL: ${IDGTL_SMS_BASE_URL:-https://direct.i-dgtl.ru} IDGTL_SMS_CALLBACK_PUBLIC_URL: ${IDGTL_SMS_CALLBACK_PUBLIC_URL} LOG_LEVEL: ${LOG_LEVEL:-INFO} APP_ENV: ${APP_ENV:-production-like} RELEASE_VERSION: ${RELEASE_VERSION:-unknown} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4317} OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME_SMS_API:-sms-service} x-sms-api-secrets: &sms-api-secrets - sms_database_url - sms_service_token - idgtl_sms_callback_username - idgtl_sms_callback_password x-sms-runtime: &sms-runtime build: context: ../../sms-service image: ${SMS_SERVICE_IMAGE:-han-chat-sms-service:local} environment: *sms-api-environment secrets: *sms-api-secrets volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true security_opt: ["no-new-privileges:true"] ulimits: core: {soft: 0, hard: 0} logging: driver: json-file options: {max-size: "50m", max-file: "5"} services: frontend-static: build: context: ../../frontend-test-site target: static args: EXPO_PUBLIC_API_BASE_URL: ${PUBLIC_WEB_URL} EXPO_PUBLIC_AUTH_BASE_URL: ${PUBLIC_AUTH_URL} EXPO_PUBLIC_KEYCLOAK_REALM: ${KEYCLOAK_REALM:-han-chat} EXPO_PUBLIC_KEYCLOAK_CLIENT_ID: han-chat-frontend EXPO_PUBLIC_APP_ENV: ${APP_ENV:-production-like} image: han-chat-frontend-static:${RELEASE_VERSION:-local} volumes: - frontend-static:/output restart: "no" read_only: true tmpfs: - /tmp:size=8m,mode=1777 cap_drop: ["ALL"] security_opt: ["no-new-privileges:true"] ulimits: core: {soft: 0, hard: 0} keycloak: build: context: ../../keycloak image: ${KEYCLOAK_IMAGE:-han-chat-keycloak:local} environment: HAN_SECRET_VARS: >- KC_DB_PASSWORD KC_BOOTSTRAP_ADMIN_PASSWORD KEYCLOAK_OTP_MOCK_CODE KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY KEYCLOAK_OTP_HMAC_KEY KEYCLOAK_SETTINGS_BRIDGE_TOKEN KEYCLOAK_SMS_SERVICE_TOKEN KC_DB_PASSWORD_FILE: /run/secrets/keycloak_db_password KC_BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/keycloak_admin_password KEYCLOAK_OTP_MOCK_CODE_FILE: /run/secrets/keycloak_otp_mock_code KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY_FILE: /run/secrets/keycloak_yandex_captcha_server_key KEYCLOAK_OTP_HMAC_KEY_FILE: /run/secrets/keycloak_otp_hmac_key KEYCLOAK_SETTINGS_BRIDGE_TOKEN_FILE: /run/secrets/keycloak_settings_bridge_token KEYCLOAK_SMS_SERVICE_TOKEN_FILE: /run/secrets/keycloak_sms_service_token KC_DB: postgres KC_DB_URL: ${KEYCLOAK_DB_URL} KC_DB_USERNAME: ${KEYCLOAK_DB_USERNAME} KC_DB_SCHEMA: ${KEYCLOAK_DB_SCHEMA:-keycloak} KC_PROXY_HEADERS: xforwarded KC_HTTP_ENABLED: "true" KC_HTTP_RELATIVE_PATH: /auth KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL} KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN} KEYCLOAK_OTP_MOCK_ENABLED: ${KEYCLOAK_OTP_MOCK_ENABLED:-false} KEYCLOAK_YANDEX_CAPTCHA_ENABLED: ${KEYCLOAK_YANDEX_CAPTCHA_ENABLED:-false} KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY: ${KEYCLOAK_YANDEX_CAPTCHA_CLIENT_KEY:-} KEYCLOAK_OTP_SETTINGS_MAX_STALE_SEC: ${KEYCLOAK_OTP_SETTINGS_MAX_STALE_SEC:-300} KEYCLOAK_SETTINGS_BRIDGE_URL: ${KEYCLOAK_SETTINGS_BRIDGE_URL:-http://api-backend:8000/internal/settings/v1/otp} KEYCLOAK_SMS_SERVICE_URL: ${KEYCLOAK_SMS_SERVICE_URL:-http://sms-service:8080} secrets: - keycloak_db_password - keycloak_admin_password - keycloak_otp_mock_code - keycloak_yandex_captcha_server_key - keycloak_otp_hmac_key - keycloak_settings_bridge_token - keycloak_sms_service_token command: ["start", "--optimized", "--import-realm"] expose: ["8080", "9000"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [public, backend, observability, egress] healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && printf 'GET /auth/health/ready HTTP/1.0\r\n\r\n' >&3 && grep -q '200 OK' <&3"] interval: 15s timeout: 5s retries: 12 start_period: 60s restart: unless-stopped security_opt: ["no-new-privileges:true"] ulimits: core: {soft: 0, hard: 0} logging: driver: json-file options: {max-size: "50m", max-file: "5"} sms-service: <<: *sms-runtime expose: ["8080"] networks: [backend, observability, egress] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/ready', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped sms-worker: <<: *sms-runtime command: ["han-sms-worker"] environment: <<: *sms-api-environment HAN_SECRET_VARS: >- SMS_DATABASE_URL SMS_SERVICE_TOKEN IDGTL_SMS_API_KEY IDGTL_SMS_CALLBACK_USERNAME IDGTL_SMS_CALLBACK_PASSWORD IDGTL_SMS_API_KEY_FILE: /run/secrets/idgtl_sms_api_key OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME_SMS_WORKER:-sms-worker} SMS_METRICS_PORT: ${SMS_METRICS_PORT:-9464} secrets: - sms_database_url - sms_service_token - idgtl_sms_api_key - idgtl_sms_callback_username - idgtl_sms_callback_password expose: ["9464"] networks: [backend, observability, egress] depends_on: sms-service: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-sms-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped message-safety: build: context: ../../message-safety image: ${MESSAGE_SAFETY_IMAGE:-han-chat-message-safety:local} environment: HAN_SECRET_VARS: MESSAGE_SAFETY_REDIS_URL MESSAGE_SAFETY_SERVICE_TOKEN MESSAGE_SAFETY_REDIS_URL_FILE: /run/secrets/message_safety_redis_url MESSAGE_SAFETY_SERVICE_TOKEN_FILE: /run/secrets/message_safety_service_token APP_ENV: ${APP_ENV:-production-like} MESSAGE_SAFETY_RULES_VERSION: ${MESSAGE_SAFETY_RULES_VERSION:-2026-01-01} MESSAGE_SAFETY_TASK_TTL_SEC: ${MESSAGE_SAFETY_TASK_TTL_SEC:-900} secrets: - message_safety_redis_url - message_safety_service_token expose: ["8080"] networks: [backend, observability, egress] depends_on: redis: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/ready', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped security_opt: ["no-new-privileges:true"] ulimits: core: {soft: 0, hard: 0} logging: driver: json-file options: {max-size: "50m", max-file: "5"} api-backend: <<: *api-runtime expose: ["8000"] depends_on: redis: {condition: service_healthy} keycloak: {condition: service_healthy} message-safety: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped delivery-worker: <<: *api-runtime command: ["han-delivery-worker"] depends_on: api-backend: {condition: service_healthy} bitrix-local-app: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-delivery-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped safety-recovery-worker: <<: *api-runtime command: ["han-safety-worker"] depends_on: api-backend: {condition: service_healthy} message-safety: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-safety-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped cleanup-worker: <<: *api-runtime command: ["han-cleanup-worker"] depends_on: api-backend: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-cleanup-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped notification-expire-worker: <<: *api-runtime command: ["han-notification-expire-worker"] depends_on: api-backend: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-notification-expire-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped notification-draft-cleanup-worker: <<: *api-runtime command: ["han-notification-draft-cleanup-worker"] depends_on: api-backend: {condition: service_healthy} message-safety: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "from pathlib import Path; assert b'han-notification-draft-cleanup-worker' in Path('/proc/1/cmdline').read_bytes()"] interval: 30s timeout: 5s retries: 3 start_period: 10s restart: unless-stopped bitrix-local-app: build: context: ../../bitrix-local-app image: ${BITRIX_LOCAL_APP_IMAGE:-han-chat-bitrix-local-app:local} environment: HAN_SECRET_VARS: >- BITRIX_DATABASE_URL BITRIX_CLIENT_SECRET BITRIX_APPLICATION_TOKEN BITRIX_INTERNAL_API_TOKEN BITRIX_API_FORWARD_TOKEN BITRIX_TOKEN_ENCRYPTION_KEY BITRIX_DATABASE_URL_FILE: /run/secrets/bitrix_database_url BITRIX_CLIENT_SECRET_FILE: /run/secrets/bitrix_client_secret BITRIX_APPLICATION_TOKEN_FILE: /run/secrets/bitrix_application_token BITRIX_INTERNAL_API_TOKEN_FILE: /run/secrets/bitrix_internal_api_token BITRIX_API_FORWARD_TOKEN_FILE: /run/secrets/bitrix_api_forward_token BITRIX_TOKEN_ENCRYPTION_KEY_FILE: /run/secrets/bitrix_token_encryption_key APP_ENV: ${APP_ENV:-production-like} BITRIX_CLIENT_ID: ${BITRIX_CLIENT_ID} BITRIX_CONNECTOR_ID: ${BITRIX_CONNECTOR_ID:-han_mobile_app} BITRIX_CONNECTOR_NAME: ${BITRIX_CONNECTOR_NAME:-HAN Mobile App} BITRIX_OPEN_LINE_ID: ${BITRIX_OPEN_LINE_ID} BITRIX_EXPECTED_DOMAIN: ${BITRIX_EXPECTED_DOMAIN} BITRIX_PUBLIC_BASE_URL: ${BITRIX_PUBLIC_BASE_URL} BITRIX_API_FORWARD_URL: ${BITRIX_API_FORWARD_URL:-http://api-backend:8000/internal/openlines/v1/inbox} BITRIX_HTTP_TIMEOUT_SEC: ${BITRIX_HTTP_TIMEOUT_SEC:-10} secrets: - bitrix_database_url - bitrix_client_secret - bitrix_application_token - bitrix_internal_api_token - bitrix_api_forward_token - bitrix_token_encryption_key expose: ["8080"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [backend, observability, egress] depends_on: api-backend: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/live', timeout=3)"] interval: 10s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped security_opt: ["no-new-privileges:true"] ulimits: core: {soft: 0, hard: 0} logging: driver: json-file options: {max-size: "50m", max-file: "5"} bitrix-sync: build: context: ../../bitrix-sync image: ${BITRIX_SYNC_IMAGE:-han-chat-bitrix-sync:local} environment: HAN_SECRET_VARS: BITRIX_SYNC_DATABASE_URL BITRIX_SYNC_SERVICE_TOKEN BITRIX_SYNC_DATABASE_URL_FILE: /run/secrets/bitrix_sync_database_url BITRIX_SYNC_SERVICE_TOKEN_FILE: /run/secrets/bitrix_sync_service_token APP_ENV: ${APP_ENV:-production-like} BITRIX_SYNC_ENABLED: ${BITRIX_SYNC_ENABLED:-false} secrets: - bitrix_sync_database_url - bitrix_sync_service_token expose: ["8080"] volumes: - type: bind source: ${PG_CA_HOST_PATH} target: /run/secrets/pg-ca.pem read_only: true networks: [backend, observability, egress] depends_on: redis: {condition: service_healthy} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health/live', timeout=3)"] interval: 15s timeout: 5s retries: 12 start_period: 30s restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: driver: json-file options: {max-size: "50m", max-file: "5"} secrets: api_database_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/DATABASE_URL api_redis_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/REDIS_URL api_redis_realtime_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/REDIS_REALTIME_URL message_safety_service_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/MESSAGE_SAFETY_SERVICE_TOKEN bitrix_local_app_internal_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_LOCAL_APP_INTERNAL_TOKEN bitrix_api_inbox_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_API_INBOX_TOKEN keycloak_settings_bridge_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_SETTINGS_BRIDGE_TOKEN selectel_s3_access_key: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/SELECTEL_S3_ACCESS_KEY selectel_s3_secret_key: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/SELECTEL_S3_SECRET_KEY cursor_hmac_secret: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/CURSOR_HMAC_SECRET sms_database_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/SMS_DATABASE_URL sms_service_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/SMS_SERVICE_TOKEN idgtl_sms_api_key: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/IDGTL_SMS_API_KEY idgtl_sms_callback_username: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/IDGTL_SMS_CALLBACK_USERNAME idgtl_sms_callback_password: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/IDGTL_SMS_CALLBACK_PASSWORD message_safety_redis_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/MESSAGE_SAFETY_REDIS_URL bitrix_database_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_DATABASE_URL bitrix_client_secret: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_CLIENT_SECRET bitrix_application_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_APPLICATION_TOKEN bitrix_internal_api_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_INTERNAL_API_TOKEN bitrix_api_forward_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_API_FORWARD_TOKEN bitrix_token_encryption_key: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_TOKEN_ENCRYPTION_KEY bitrix_sync_database_url: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_SYNC_DATABASE_URL bitrix_sync_service_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/BITRIX_SYNC_SERVICE_TOKEN keycloak_db_password: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_DB_PASSWORD keycloak_admin_password: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_ADMIN_PASSWORD keycloak_otp_mock_code: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_OTP_MOCK_CODE keycloak_yandex_captcha_server_key: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_YANDEX_CAPTCHA_SERVER_KEY keycloak_otp_hmac_key: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_OTP_HMAC_KEY keycloak_sms_service_token: file: ${HAN_SECRETS_DIR:-/run/han-chat/secrets}/KEYCLOAK_SMS_SERVICE_TOKEN