Основной конфиг (config.<mode>.yml)
Конфигурация разделена на несколько областей:
app— параметры самого приложения (сервер, HTTP-клиенты, флаги безопасности, сессии, очистка кэша, модули).acp— авторизация ACP через HELM service token.infrastructure— подключения к внешним сервисам: PostgreSQL, Redis, Cloudflare и платформы.domain— бизнес-настройки (стримы, ивенты, конкурсы, соперники, user-data, очистка, заказы, кланы и войны кланов).observability— метрики и интеграция Prometheus.tasks— системные cron-задачи.alerts— назначения и шаблоны оповещений.
Ниже описан каждый блок.
1. app
app.server
app:
server:
id: 1 # произвольная строка/число, попадает в X-Server-Id
host: 0.0.0.0 # слушаемый адрес
port: 8080 # порт HTTP-сервераapp.http.timeouts
Таймауты для общего HTTP-клиента, используются стримами, traffic-flow и т.д. Формат — time.Duration.
app:
http:
timeouts:
request: 30s
dial: 20s
keep_alive: 20s
tls_handshake: 10s
response_header: 10s
handler: 30s # таймаут обработки HTTP-запроса (middleware)app.security
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; при несовпадении запрос отклоняется.
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). Эти параметры будут удалены в будущих версиях.
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.
app:
online_ttl: 5m # формат time.Duration; по умолчанию 5mapp.scheduler
Настройки планировщика cron-задач.
app:
scheduler:
task_timeout: 1h # формат time.Duration; таймаут на выполнение одной задачиtask_timeout — предельное время выполнения отдельной cron-задачи (infrastructure/scheduler/manager.go). По истечении контекст задачи отменяется.
app.cache_clear
app:
cache_clear:
enabled: true # чистить volatile-кэши при старте контейнера
patterns:
- "marv:cache:users:*"
- "marv:cache:streams:*"app.invitation_awards
Строки с JSON-наградой для получателя/отправителя инвайта:
app:
invitation_awards:
receiver: '{"currency":"coins","amount":100}'
sender: '{"currency":"coins","amount":50}'Зарезервировано / не подключено. Ключ присутствует в схеме, но в рантайме сейчас не читается: единственный потребитель —
UserService.HandleInviterBonus— не имеет вызывающих сторон, поэтому награды по инвайтам не начисляются. Заполнение этих строк на текущее поведение не влияет.
app.modules
Список модулей для загрузки. Позволяет управлять набором функционала через конфиг (без изменения кода):
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-маршрутов:
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; если версия ниже — возвращается ошибка.
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 доступ оператора:
acp:
access_key: "your-project/environment" # ключ HELM: <project-slug>/<base-name>
jwks_url: "https://helm.example.com/v1/auth/jwks" # откуда тянуть публичные ключи HELM
jwks: "{}" # кэш JWKS; заполняется задачей helm_certsEmail оператора берётся из 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
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
infrastructure:
redis:
address: 127.0.0.1:6379
password: ""
db: 0
pool: 10
timeout: 300s # используется как pool-timeout и default TTL
store_ttl: 4h # TTL для Set() без явного timeoutinfrastructure.cloudflare
Учетные данные для Cloudflare API.
infrastructure:
cloudflare:
account_id: "..."
api_token: "..."
creator_prefix: "example" # префикс имени для загружаемых в Cloudflare Stream креативовinfrastructure.platforms
Словарь платформ. Ключ — строковый api_type (числовой идентификатор платформы).
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 приложения |
uid | UID приложения |
secret_key | Секретный ключ |
application_key | Ключ приложения (OK) |
auth_token | Токен авторизации |
package | Имя пакета |
package_name | Имя пакета (MS Store, RuStore) |
client_id | Client ID (OAuth) |
refresh_token | Refresh Token (Bazaar) |
redirect_url | Redirect URL (OAuth) |
consumer_key | Consumer Key (Mobage) |
cert | Сертификат |
public_key | Публичный ключ (RuStore) |
private_key_id | ID приватного ключа (RuStore) |
company_id | ID компании (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
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
observability:
metrics:
http_buckets_ms: [50, 100, 200, 500, 1000, 2000, 5000]
prometheus_enabled: false6. tasks
tasks.system
Системные cron-задачи, которые поднимаются из конфигурации (без записи в БД).
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: leaderstats_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
Шлюз критических уведомлений.
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— текстовые шаблоны (Gotext/template). Доступные поля:Title,Message,Severity,Tags,Payload.
Полная JSON Schema расположена в infrastructure/config/schemas/config.schema.json.
