Skip to content

Основной конфиг (config.<mode>.yml)

Конфигурация разделена на несколько областей:

  1. app — параметры самого приложения (сервер, HTTP-клиенты, флаги безопасности, сессии, очистка кэша, модули).
  2. acp — авторизация ACP через HELM service token.
  3. infrastructure — подключения к внешним сервисам: PostgreSQL, Redis, Cloudflare и платформы.
  4. domain — бизнес-настройки (стримы, ивенты, конкурсы, соперники, user-data, очистка, заказы, кланы и войны кланов).
  5. observability — метрики и интеграция Prometheus.
  6. tasks — системные cron-задачи.
  7. alerts — назначения и шаблоны оповещений.

Ниже описан каждый блок.

1. app

app.server

yaml
app:
server:
    id: 1            # произвольная строка/число, попадает в X-Server-Id
    host: 0.0.0.0    # слушаемый адрес
    port: 8080       # порт HTTP-сервера

app.http.timeouts

Таймауты для общего HTTP-клиента, используются стримами, traffic-flow и т.д. Формат — time.Duration.

yaml
app:
  http:
    timeouts:
      request: 30s
      dial: 20s
      keep_alive: 20s
      tls_handshake: 10s
      response_header: 10s
      handler: 30s          # таймаут обработки HTTP-запроса (middleware)

app.security

yaml
app:
  security:
    secret: "..."          # главный секрет (подписи платформ и т.д.)
    disable_auth: false     # глобально выключить проверку платформенных токенов
    enable_cors: false      # включить permissive CORS middleware (только для dev/тестов)
    max_body_size: 4194304  # лимит тела запроса в БАЙТАХ (integer); 4194304 = 4 MB
    rate_limit:
      enabled: false       # НЕ рекомендуется за Cloudflare/Nginx — используйте WAF Rate Limiting
      rps: 20              # запросов в секунду на IP (ненадёжно за reverse-proxy)
      burst: 40            # максимальный burst на IP
    jwt:
      secret: "..."        # отдельный ключ для подписи сессионных JWT (HMAC-SHA256)
      ttl: 24h             # время жизни токена

Rate limiter хранится in-memory; при превышении возвращается HTTP 429 Too Many Requests.

jwt.secret — отдельный ключ, используемый для подписи и верификации JWT-токенов V2 сессий. Может быть использован другими сервисами для проверки токена. jwt.ttl — время жизни токена (формат time.Duration).

app.security.traffic_flow_webhook_secret

Секрет для проверки подписи входящих вебхуков traffic-flow. Значение сравнивается с заголовком X-Webhook-Secret при обработке /ext/traffic-flows/webhook; при несовпадении запрос отклоняется.

yaml
app:
  security:
    traffic_flow_webhook_secret: "..."

Ключ читается кодом (modules/implementations/traffic_flow.go), но пока НЕ объявлен в config.schema.json. Поскольку app.security в схеме имеет additionalProperties: false, при включённой строгой валидации это поле может не пройти проверку — задавайте его через переменную окружения (MARV__APP__SECURITY__TRAFFIC_FLOW_WEBHOOK_SECRET) или дополните схему.

app.session.single_mode (DEPRECATED)

Deprecated: single-session mode — это legacy V1 функциональность. V2 использует исключительно JWT-авторизацию (Authorization: Bearer). Эти параметры будут удалены в будущих версиях.

yaml
app:
  session:
    single_mode:
      enabled: false  # V1 only: если true — выдаём X-Set-Session-Token и проверяем X-Session-Token
      blocking: false # V1 only: если true — блокируем запрос без актуальной сессии

app.online_ttl

Время, в течение которого пользователь считается «онлайн» после последнего запроса. Используется OnlineTracker для маркеров присутствия в Redis.

yaml
app:
  online_ttl: 5m   # формат time.Duration; по умолчанию 5m

app.scheduler

Настройки планировщика cron-задач.

yaml
app:
  scheduler:
    task_timeout: 1h   # формат time.Duration; таймаут на выполнение одной задачи

task_timeout — предельное время выполнения отдельной cron-задачи (infrastructure/scheduler/manager.go). По истечении контекст задачи отменяется.

app.cache_clear

yaml
app:
  cache_clear:
    enabled: true       # чистить volatile-кэши при старте контейнера
    patterns:
      - "marv:cache:users:*"
      - "marv:cache:streams:*"

app.invitation_awards

Строки с JSON-наградой для получателя/отправителя инвайта:

yaml
app:
  invitation_awards:
    receiver: '{"currency":"coins","amount":100}'
    sender: '{"currency":"coins","amount":50}'

Зарезервировано / не подключено. Ключ присутствует в схеме, но в рантайме сейчас не читается: единственный потребитель — UserService.HandleInviterBonus — не имеет вызывающих сторон, поэтому награды по инвайтам не начисляются. Заполнение этих строк на текущее поведение не влияет.

app.modules

Список модулей для загрузки. Позволяет управлять набором функционала через конфиг (без изменения кода):

yaml
app:
  modules:
    - experiment
    - event
    - clan
    - message
    - bots
    - product
    - remote_config
    - rival
    - stream
    - ads
    - alert
    - order
    - traffic_flow
    - transaction
    - world

Модули system, user, cron_task, merchant, platform создаются по умолчанию и не нуждаются в перечислении; остальные — только если указаны. Полный набор допустимых значений (см. схему config.schema.json): ads, alert, bots, clan, cron_task, event, experiment, merchant, message, order, platform, product, remote_config, rival, stream, system, traffic_flow, transaction, user, world.

app.routes

Включение/отключение групп HTTP-маршрутов:

yaml
app:
  routes:
    acp: true       # Admin Control Panel
    api: true       # группа маршрутов /api
    ext: true       # внешние вебхуки (мерчанты)
    v1: true        # V1 API (deprecated)
    v2: true        # V2 API (основной)

В config.example.yml включены все группы; отключите ненужные, выставив false.

app.min_version

Минимальные версии клиента по маршрутам. Ключ — regex для HTTP-пути, значение — минимальная версия (semver). Заголовок X-Client-Version проверяется middleware; если версия ниже — возвращается ошибка.

yaml
app:
  min_version:
    "^/v2": "1.2.0"       # все /v2/* требуют клиент >= 1.2.0
    "^/v2/streams": "1.5.0" # стримы требуют >= 1.5.0

Если секция пуста или отсутствует — проверка версий отключена.

Прочие поля

  • app.cdn_url, app.log_dir, app.invite_bonus, app.validation.strict — доступны по тем же ключам внутри app.

2. Авторизация ACP

acp (HELM service token)

ACP-запросы аутентифицируются токеном, выпущенным HELM (RS256 JWT, iss: helm). MARV проверяет подпись по публичным ключам HELM и извлекает per-env доступ оператора:

yaml
acp:
  access_key: "your-project/environment"             # ключ HELM: <project-slug>/<base-name>
  jwks_url: "https://helm.example.com/v1/auth/jwks"  # откуда тянуть публичные ключи HELM
  jwks: "{}"                                         # кэш JWKS; заполняется задачей helm_certs

Email оператора берётся из claim sub; битовая маска доступа — из access["<access_key>"] (0, если ключа нет). jwks периодически обновляется системной задачей helm_certs.

Legacy Google OAuth-вход в ACP удалён (oauth.google / задача google_certs больше не существуют) — авторизация ACP идёт исключительно через HELM service token.

3. infrastructure

infrastructure.postgres

yaml
infrastructure:
  postgres:
    host: localhost
    port: 5432
    username: app
    password: secret
    database: marv
    pool:
      max_open: 20
      max_idle: 10
      timeout: 300s
    sslmode: disable
    dry_run: false
    # Опциональная read-реплика: SELECT'ы идут на неё, запись/транзакции — на primary.
    # Опустите блок для одного пула. Creds/db/sslmode наследуются от primary; нужен только host.
    # read:
    #   host: replica.internal
    #   pool: { max_open: 40, max_idle: 20 }

infrastructure.redis

yaml
infrastructure:
  redis:
    address: 127.0.0.1:6379
    password: ""
    db: 0
    pool: 10
    timeout: 300s     # используется как pool-timeout и default TTL
    store_ttl: 4h     # TTL для Set() без явного timeout

infrastructure.cloudflare

Учетные данные для Cloudflare API.

yaml
infrastructure:
  cloudflare:
    account_id: "..."
    api_token: "..."
    creator_prefix: "example"   # префикс имени для загружаемых в Cloudflare Stream креативов

infrastructure.platforms

Словарь платформ. Ключ — строковый api_type (числовой идентификатор платформы).

yaml
infrastructure:
  platforms:
    "2":
      type: vk
      credentials:
        id: "7365134"
        secret_key: "..."
    "30":
      type: ms_start
      credentials:
        id: "9NPZT17FTVZ4"
        package_name: "HGPOINTLTD.MergeHotel"
        secret_key: "..."
      merchant_profiles:
        default:
          type: xsolla
          merchant_id: "702055"
          project_id: "265997"
          api_key: "..."
          webhook_secret: "..."
        promo:
          type: xsolla
          merchant_id: "702055"
          project_id: "400001"
          api_key: "..."
          webhook_secret: "..."

type — тип платформы. Допустимые значения (enum схемы config.schema.json): default, unauthorized, ok, vk, fb, fb_login, mm, mobage, dr, fs, gm, app_store, play_market, amazon, ms_store, ru_store, bazaar, galaxy_store, yandex, exe, beeline, play_deck, ms_start, tg, crazy_games, discord, myket, orbit, jest, google_signin, apple_signin, game_center, play_games.

name (опционально) — человекочитаемое имя платформы.

credentials — учётные данные платформы. Набор полей зависит от типа платформы:

ПолеОписание
idИдентификатор приложения
app_idАльтернативный ID приложения
uidUID приложения
secret_keyСекретный ключ
application_keyКлюч приложения (OK)
auth_tokenТокен авторизации
packageИмя пакета
package_nameИмя пакета (MS Store, RuStore)
client_idClient ID (OAuth)
refresh_tokenRefresh Token (Bazaar)
redirect_urlRedirect URL (OAuth)
consumer_keyConsumer Key (Mobage)
certСертификат
public_keyПубличный ключ (RuStore)
private_key_idID приватного ключа (RuStore)
company_idID компании (RuStore)
bot_tokenТокен бота (Telegram)

merchant_profiles — словарь профилей мерчанта. Ключ — произвольное имя профиля (используется в маршрутах /ext/{api_type}/{profile}/{action}).

Поля профиля:

ПолеОбязательноеОписание
typeдаТип мерчанта (xsolla, rustore, ...)
merchant_idнетID мерчанта
project_idнетID проекта
api_keyнетAPI-ключ
webhook_secretнетСекрет для подписи вебхуков
company_idнетID компании (RuStore)
private_key_idнетID приватного ключа (RuStore)

Для Xsolla Basic Auth (merchant_id:api_key) вычисляется автоматически при каждом запросе.

Профиль default используется, если в маршруте не указан конкретный профиль.

4. domain

yaml
domain:
  stream:
    delete_after: 1080h
    entrance_timeout: 900s
    proxy_url: "https://..."
    subdomain: "example.cloudflarestream.com"
  events:
    lookback: 336h
    dead_letter_queue:
      enabled: true
      max_retries: 3
  contest:
    group_size: 50
  rivals:
    max_count: 100
    compatible_api_types:
      vk: [1,2,3]
  user_updates:
    batch_size: 250
    ttl: 168h                # TTL записей в очереди обновлений
  cleanup:
    data_retention: 4320h   # хранить данные user/world столько (180d); используется задачей data_clean
  orders:
    ttl_days: 7              # через сколько дней удалять pending-заказы (cron order_cleanup)
  clans:
    member_cap: 50                     # дефолтный размер ростера нового клана
    join_request_limit: 5              # макс. активных заявок на игрока
    snapshot_min_write_interval: 60s   # rate-limit записи живого снапшота (анти-bloat)
    leader_inactive_succession: 336h   # авто-передача лидерства при простое лидера (14d)
    inactive_autokick: 720h            # мягкий авто-кик неактивных участников (30d)
    badwords: []                       # блоклист имени/тега при создании (case/leet-insensitive)
  clan_war:
    participation_mode: auto           # auto | opt_in
    min_members: 1
    power_band_pct: 30                 # подбор: противники в пределах этого % силы
    attacks_per_player: 2
    retention_wars: 2                  # сколько прошлых войн отдавать в истории
    retention: 720h                    # GC: удалять finished-войны, чей конец завершился раньше (30d)
    attack_reserve_timeout: 180s       # держать зарезервированную атаку до просрочки
    attack_reserve_on_expire: refund   # refund | consume — что делать с атакой при просрочке
    phases:                            # упорядоченная последовательность фаз; каждая длится свой duration
      - { type: points,   duration: 24h, winning_points: 1 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: points,   duration: 24h, winning_points: 2 }
      - { type: battle,   duration: 24h, winning_points: 4, points_per_win: 1 }
      - { type: results,  duration: 24h }
    tier:                              # tier_points клана (счётчик побед в войнах) → тир
      points_per_win: 1
      points_per_loss: 0
      thresholds: { E: 0, D: 5, C: 10, B: 15, A: 20, "S+": 25, "S++": 35, "S+++": 50 }
    rewards:                           # числа-плейсхолдеры (баланс — на стороне гейм-дизайна)
      delivery: mail                   # mail: сервер кладёт в почту | claim: клиент забирает через clans/war/rewards
      min_points_for_win: 100          # win-награда только участникам, набравшим больше
      common:                          # общая награда пер-тир: <tier>: { win: {...}, lose: {...} }
        E:    { win: { gold: 1000 }, lose: { gold: 500 } }
        "S+": { win: { gold: 5300, tickets: 7800 }, lose: { gold: 2650, tickets: 3900 } }
      individual:                      # кумулятивная лестница (до 25 стадий): набрал points → выдаётся reward
        - { points: 500,  reward: { gold: 100 } }
        - { points: 1000, reward: { gold: 200 } }

domain.clans — параметры уровня клана; domain.clan_war — цикл войны (сиблинг domain.rivals/contest/events/stream). Скалярные длительности — Go duration-строки (Xh/Xm/Xs; дни — часами, напр. 336h = 14d, 720h = 30d), как везде в конфиге. Пустой badwords = фильтр выключен.

Война — это упорядоченный список фаз (phases), каждая со своим duration. Планировщик закрывает активную фазу по истечении окна (сравнивает очки сторон → победитель фазы → начисляет winning_points, ничья — обоим → фиксирует MVP) и активирует следующую, либо финиширует войну (победитель — по сумме winning points; ничья → без победителя). Типы фаз: points (игроки репортят очки), battle (1×1 хендшейк, points_per_win за побеждённого врага, каждого — раз), results (пауза), squad5x5 (бой 5×5, squad_size бойцов). Порядок и состав меняются в phases — движок один. tier маппит счётчик tier_points клана в тир по порогам (тир замораживается на войне и задаёт размер наград). rewards.delivery выбирает доставку: mail (сервер кладёт в почту, хранится ~месяц) или claim (клиент забирает через clans/war/rewards + clans/war/rewards/claim); начисление идемпотентно (reconcile в планировщике переживает сбои).

5. observability

yaml
observability:
  metrics:
    http_buckets_ms: [50, 100, 200, 500, 1000, 2000, 5000]
    prometheus_enabled: false

6. tasks

tasks.system

Системные cron-задачи, которые поднимаются из конфигурации (без записи в БД).

yaml
tasks:
  system:
    - name: "Reload HELM JWKS"
      type: helm_certs
      schedule: "0 */6 * * *"
      enabled: true
      start_immediately: true
      scope: all          # JWKS нужен на каждом узле; leader (по умолчанию) или all
    - name: "Persist user data updates"
      type: user_data_batch_update
      schedule: "*/5 * * * *"
      enabled: true
      scope: leader
    - name: "Dispatch alerts"
      type: alert_dispatcher
      schedule: "*/1 * * * *"
      enabled: true
      scope: leader
    - name: "Clean old data"
      type: data_clean
      schedule: "0 2 * * *"
      enabled: true
      scope: leader
    - name: "Stats snapshot"
      type: stats_snapshot
      schedule: "0 * * * *"
      enabled: true
      start_immediately: true
      scope: leader
    - name: "Order cleanup"
      type: order_cleanup
      schedule: "0 3 * * *"
      enabled: true
      scope: leader
    - name: "User backup"
      type: user_backup
      schedule: "0 4 * * *"    # ежедневно 04:00 (последние 24ч)
      enabled: true
      scope: leader
    - name: "World backup"
      type: world_backup
      schedule: "30 4 * * *"   # ежедневно 04:30 (последние 24ч)
      enabled: true
      scope: leader
    - name: "Clan war matchmaker"
      type: clan_war_matchmaker
      schedule: "0 17 * * 1"    # Пн 17:00 UTC — открыть войны нового цикла
      enabled: true
      start_immediately: false
      scope: leader
    - name: "Clan war scheduler"
      type: clan_war_scheduler
      schedule: "*/10 * * * *"  # продвинуть фазы войны + просрочить зависшие атаки
      enabled: true
      start_immediately: false
      scope: leader
    - name: "Clan maintenance"
      type: clan_maintenance
      schedule: "0 3 * * *"     # ежедневно: наследование лидера + автокик неактивных + GC старых войн
      enabled: true
      start_immediately: false
      scope: leader

stats_snapshot — периодически рассчитывает агрегированную статистику пользователей (total, today, week, month, DAU, MAU) и сохраняет в in-memory StatsStore. Результат отдаётся через POST /acp/system/overview. Рекомендуется запускать раз в час на лидере; при start_immediately: true первый расчёт происходит сразу после старта.

scope управляет тем, где выполняется задача:

  • leader — только на активном лидере (рекомендовано для операций с единственным воркером: перенос очередей из Redis в БД и т.п.)
  • all — на каждом сервере (подходит для задач вроде обновления сертификатов или прогрева кэшей).

Клановые cron-задачи (все scope: leader):

  • clan_war_matchmaker — открывает войны нового цикла, подбирая подходящие кланы и материализуя фазы из расписания (те же, что и ручной POST /acp/clans/war/run-matchmaking).
  • clan_war_scheduler — продвигает фазы войн (закрывает истёкшую → активирует следующую → финиширует войну), начисляет награды завершённых войн (reconcile, идемпотентно) и просрочивает зависшие атаки. Ручной POST /acp/clans/war/advance двигает только фазы, но НЕ начисляет награды и НЕ просрочивает атаки — это делает лишь этот крон.
  • clan_maintenance — суточная уборка: наследование лидерства при простое (leader_inactive_succession), мягкий автокик неактивных участников (inactive_autokick) и retention-GC старых войн (clan_war.retention).

7. alerts

Шлюз критических уведомлений.

yaml
alerts:
  destinations:
    - name: payments_slack
      provider: slack                # slack / telegram / helm
      endpoint: "https://hooks.slack.com/services/..."
      severities: [critical]
      tags: ["payments"]
      template: payment_critical
    - name: oncall_tg
      provider: telegram
      token: "<bot-token>"
      chat_id: -1001234567
      severities: [critical, warning]
      template: default_telegram
    - name: helm_alerts
      provider: helm                 # пересылка событий в HELM
      endpoint: "https://helm.example.com/ext/alerts/ingest"
      token: ""                      # HELM ext.api_key — override через .env
      severities: [info, warning, critical]
  templates:
    - id: payment_critical
      provider: slack
      text: |
        :rotating_light: *{{ .Title }}*
        {{ .Message }}
        {{ if .Payload.transaction_id }}ID: `{{ .Payload.transaction_id }}`{{ end }}
    - id: default_telegram
      provider: telegram
      text: |
        {{ .Title }}
        {{ .Message }}
        severity={{ .Severity }}
  • destinations — куда отправлять события (Slack webhook, Telegram-чат и т.д.), плюс фильтры по severity/tag, выбор шаблона.
  • templates — текстовые шаблоны (Go text/template). Доступные поля: Title, Message, Severity, Tags, Payload.

Полная JSON Schema расположена в infrastructure/config/schemas/config.schema.json.