INOTrackбыстрота, надежность и ахуенность

Обслуживание инстанса

Все команды — через tracker-ctl, который install.sh кладёт в /usr/local/bin/tracker-ctl. Работает из любого каталога, ориентируется на INOTRACK_HOME (по умолчанию /opt/inotrack, там же docker-compose.yml и .env).

tracker-ctl status                  # docker compose ps
tracker-ctl logs [service...]       # docker compose logs -f --tail=200
tracker-ctl update                  # новая версия + миграции + rollback при неуспехе
tracker-ctl backup                  # Postgres + ClickHouse + .env + storage, локально/S3
tracker-ctl backup --to s3://бакет/префикс   # + офсайт-копия, проверка, ротация (см. «Офсайт-бэкапы»)
tracker-ctl backup keygen           # сгенерировать BACKUP_ENCRYPTION_KEY (сохраните вне сервера!)
tracker-ctl backup schedule daily [--at 03:00] [--to s3://…]   # ежедневный таймер
tracker-ctl backup schedule off     # снять расписание (то же: --remove)
tracker-ctl backup schedule         # cron по BACKUP_SCHEDULE_CRON
tracker-ctl restore --from <путь|s3://бакет/префикс[/архив]>   # восстановление из бэкапа
tracker-ctl domain add|rm <домен>   # пул трекер-доменов + пересоздание Caddy
tracker-ctl license info|refresh    # статус лицензии локально / принудительная активация-heartbeat
tracker-ctl doctor                  # диагностика: DNS/TLS/БД/буфер/очередь/диск
tracker-ctl uninstall [--purge] [--yes] [--no-backup]   # удаление инстанса

status / logs

status — обёртка над docker compose ps, logs — над docker compose logs -f --tail=200 (можно указать конкретные сервисы: tracker-ctl logs tracker-core workers).

doctor

Диагностика одной командой, без чтения логов вручную. Проверяет:

  • наличие docker/docker compose;
  • обязательные переменные в .env;
  • состояние контейнеров compose;
  • доступность Postgres/ClickHouse/Redis;
  • DNS и TLS каждого домена в пуле (панель + трекер-домены);
  • связь с license-server;
  • свежесть последнего бэкапа;
  • свободное место на диске;
  • лаг буфера кликов и длину очереди исходящих постбеков (через metrics-probe внутри контейнера tracker-core);
  • каталог WAL кликов (INOTRACK_WAL_PATH) глазами процесса ядра: доступен на запись (иначе FAIL), не в памяти (tmpfs) и на томе, а не в слое контейнера — в compose это том inotrack-wal, в k8s hostPath узла (click-wal-durability.md); возраст самого старого клика в WAL (WARN с 10 минут, FAIL с часа);
  • что база GeoIP загружена и собрана не раньше 45 дней назад (иначе автообновление её не обновляет);
  • что у каждого фонового цикла (вебхуки правил, пуши, алерты, синхронизации) ровно один лидер: опрашиваются tracker-core и workers; ноль лидеров — WARN, больше одного — FAIL;
  • активные edge-ноды: молчащие дольше 10 минут, ни разу не выходившие на связь и устаревшие (версия отстаёт от INOTRACK_VERSION central или нет обязательной возможности probes);
  • Telegram для алертов: без INOTRACK_TELEGRAM_TOKEN — строка INFO с подсказкой, что алерты в ленте панели (колокольчик). INFO на итог и код выхода не влияет;
  • режим лицензии ядра: ok, soft (грейс — WARN со сроком до hard) или hard (FAIL с причиной из /readyz админского порта: отзыв, нет активации, офлайн-грейс исчерпан); /readyz, не готовый по другой причине (Redis, Postgres, GeoIP), — отдельная строка WARN; последний успешный обмен с license-server — WARN, если больше 3 часов назад или не было ни разу.

На edge-ноде проверки лидеров, реестра нод и Telegram пропускаются — это всё на central; режим лицензии нода получает от central, поэтому строки license refresh у неё нет. Вместо них на ноде проверяется автообновление (tracker-ctl edge update schedule): режим INOTRACK_EDGE_AUTO_UPDATE (off — INFO), для режима auto — INOTRACK_EDGE_AUTO_UPDATE=1 в окружении контейнера ноды, таймер inotrack-edge-update.timer (установлен, активен, в режиме из .env, последний запуск без ошибки) или строка cron без systemd и последняя попытка обновления, закончившаяся откатом (WARN, пока следующее обновление не пройдёт). Если образ в контейнере старше tracker-ctl на хосте, новые проверки пишут WARN «обновите инстанс».

Код выхода — 1, если есть хотя бы один FAIL. Флагов нет — запускает все проверки сразу и печатает список «что не так».

tracker-ctl doctor

ClickHouse: версия и дневная сводка отчётов

Миграция 255_costs_order_key.sql расширяет ключ замены расходов. Ручной откат: clickhouse-client --multiquery < deploy/clickhouse/costs-order-key-rollback.sql. Он снова схлопывает строки, различающиеся только campaign_id/creative/country, и теряет расходы; перед выполнением остановите cost-sync и сделайте бэкап. Для отката бинаря откат таблицы не нужен: колонки не менялись, расширенный ключ совместим со старым кодом.

Продукт работает с ClickHouse любой версии, которая вообще поднимается — жёсткого минимума нет. Стандартная установка поднимает свой ClickHouse, закреплённый на 24.8 (deploy/docker-compose.yml/deploy/k8s/clickhouse.yaml); если вместо этого вы подключаете уже имеющийся у вас ClickHouse (INOTRACK_CH_ADDRS в .env), версия может быть и старше.

Единственное, что зависит от версии, — дневная сводка отчётов (inotrack.stats_daily / stats_daily_recent, ускоряет отчёты за длинные периоды): она построена на REFRESHABLE MATERIALIZED VIEW, появившемся в ClickHouse 23.12. На версии старше:

  • миграции применяются полностью, трекер стартует и работает штатно — приём кликов, постбеки, финансы, панель не зависят от сводки;
  • при старте в логе central/worker будет WARN: эта версия ClickHouse не поддерживает REFRESHABLE MATERIALIZED VIEW — ... дневная сводка отчётов ... создана не будет;
  • отчёты за длинные периоды (год и более) считаются по сырым таблицам напрямую — числа верные, но на большом объёме заметно медленнее, чем со сводкой (движок отчётов сам проверяет готовность сводки перед каждым использованием и никогда не отдаёт частично наполненные данные).

Апгрейд до 23.12+ впоследствии ничего не требует вручную: следующий рестарт central/worker сам накатит пропущенную часть миграции и наполнит сводку.

⚠️ Апгрейд денежной логики сводки (миграция 201_stats_daily_decision.sql). Эта миграция пересоздаёт stats_daily/stats_daily_recent с новым SELECT (деньги по действующему решению сети, а не по сырым строкам) и очищает обе таблицы (TRUNCATE) — старые данные посчитаны ещё прежней формулой и до наполнения разошлись бы с raw-путём. После применения миграции трекер сам запускает разовый SYSTEM REFRESH VIEW в фоне и не блокирует старт; пока сводка не наполнилась, отчёты за длинные периоды сами уходят на сырые таблицы — цифры верные, просто медленнее до конца пересборки (обычно минуты).

Отдельная ловушка — rolling-деплой на нескольких репликах central/worker: health-кэш готовности сводки (5 минут) — локальный для каждого процесса, а не общий. Реплика, которая уже перезапустилась после апгрейда и видит пустую (ещё не наполненную) сводку, может неверно закэшировать её как "здорова" на срок до 5 минут раньше, чем сводка реально наполнится — отчёты за UTC-период, обслуженные ЭТОЙ репликой в это окно, покажут выручку около нуля при ненулевом расходе (то же следствие, что и "холодный старт" из предыдущего абзаца, но не по факту пустой сводки, а по факту устаревшего локального кэша одной конкретной реплики). Рекомендованный порядок апгрейда на проде с несколькими репликами — stop → migrate → start (штатная последовательность tracker-ctl update), а не rolling-рестарт по одной реплике: так миграция и разовое наполнение сводки успевают завершиться до того, как хоть одна реплика начнёт отдавать трафик и кэшировать состояние сводки.

Бэкап и восстановление

tracker-ctl backup собирает в один .tar.gz: дамп Postgres (всегда полный), дамп таблиц ClickHouse (формат Native — см. «Инкрементальные бэкапы ClickHouse» ниже), копию .env, том storage (загруженные ленды и файлы) и каталог лицензии (machine-id + кэш демона, см. раздел «Отпечаток инстанса» ниже). Опционально грузит архив в S3-совместимое хранилище (BACKUP_S3_* в .env) и чистит старые бэкапы по BACKUP_RETENTION_DAYS (дефолт 14 дней). Расписание ставится отдельно:

tracker-ctl backup schedule daily --to s3://бакет/префикс   # systemd-таймер, 03:00 (+ до 30 мин)
tracker-ctl backup schedule daily --at 02:30                 # время — локальное время сервера
tracker-ctl backup schedule            # cron по BACKUP_SCHEDULE_CRON (дефолт "0 3 * * *")
tracker-ctl backup schedule off        # снять расписание в любом виде (то же: --remove)

daily ставит systemd-таймер inotrack-backup.timer (журнал — journalctl -u inotrack-backup.service; пропущенный из-за выключенного сервера запуск догоняется после загрузки), на хостах без systemd — строку cron (лог BACKUP_LOCAL_DIR/backup-cron.log). Любой вариант сначала снимает остальные, двух расписаний одновременно не бывает. install.sh при установке ставит cron по BACKUP_SCHEDULE_CRON; расписание, поставленное через backup schedule daily, повторная установка не трогает.

Офсайт-бэкапы (S3-совместимое хранилище)

Локальный BACKUP_LOCAL_DIR лежит на том же сервере: умер диск или сервер — вместе с данными пропали и бэкапы. Офсайт-копия уезжает в объектное хранилище другого провайдера:

tracker-ctl backup keygen                              # один раз; СОХРАНИТЕ ключ вне сервера
tracker-ctl backup --to s3://my-bucket/inotrack        # бэкап + выгрузка + проверка + ротация
tracker-ctl backup schedule daily --to s3://my-bucket/inotrack
tracker-ctl restore --from s3://my-bucket/inotrack     # самый свежий архив из бакета
tracker-ctl restore --from s3://my-bucket/inotrack/inotrack-backup-incr-20260102-030000.tar.gz

Бакет и префикс — в URL, эндпоинт и ключи доступа — в .env (в командной строке ключей нет: она видна в ps и попадает в юнит таймера):

Хранилище BACKUP_S3_ENDPOINT BACKUP_S3_REGION
AWS S3 s3.<регион>.amazonaws.com (или пусто — выводится из региона) eu-central-1 и т.п.
Cloudflare R2 <account_id>.r2.cloudflarestorage.com auto
Yandex Object Storage storage.yandexcloud.net ru-central1
Selectel s3.<пул>.storage.selcloud.ru пул, например ru-1
MinIO minio.example.com:9000 (BACKUP_S3_USE_SSL=0 для http) us-east-1

BACKUP_S3_ACCESS_KEY/BACKUP_S3_SECRET_KEY — ключи с правами на чтение, запись, листинг и удаление в бакете. Адресация BACKUP_S3_URL_STYLE: auto (virtual-host для AWS, path для остальных), path или virtual; бакет с точкой в имени всегда адресуется path-style (wildcard-сертификат провайдера его не покрывает).

  • Шифрование. С --to архив без BACKUP_ENCRYPTION_KEY не выгружается (только явным --allow-unencrypted): к стороннему провайдеру уходит весь бизнес. Ключ — tracker-ctl backup keygen (уже заданный ключ не перезаписывает) или openssl rand -hex 32. Ключ — только в .env сервера, который и есть то, что может умереть: сохраните его в менеджере паролей сразу, иначе офсайт-копия бесполезна. Для восстановления на новом сервере впишите ключ и BACKUP_S3_* в его .env до restore.
  • Проверка целостности. При выгрузке хранилище само сверяет Content-MD5 и подписанный SHA-256 тела (битый в пути архив отклоняется), SHA-256 архива пишется в метаданные объекта. После выгрузки HEAD сверяет размер и ETag — MD5, посчитанный хранилищем по записанным байтам. Где ETag не MD5 (SSE-KMS, SSE-C), объект скачивается целиком и SHA-256 пересчитывается автоматически; --verify-download делает это всегда (вдвое больше трафика). Не прошедший проверку объект удаляется из бакета, backup завершается ошибкой (ненулевой код, видна в журнале таймера). restore при скачивании сверяет SHA-256.
  • Цепочка в бакете. Инкремент выгружается, только если его родитель уже лежит под тем же префиксом; иначе (первый --to в новый префикс, сбой прошлой выгрузки) вместо инкремента делается full — офсайт-копия всегда восстановима сама по себе.
  • Ротация. После успешной выгрузки в бакете остаются BACKUP_S3_KEEP последних архивов этого инстанса (0 — не удалять; не задано — 14 для --to, 0 для выгрузки через BACKUP_S3_* без --to, чтобы обновление не начало само удалять старые копии). Инкремент без своего full бесполезен, поэтому ротация не удаляет full и инкременты, на которые опирается оставляемый инкремент (их может оказаться больше N). Каждый архив помечается меткой инстанса (метаданные x-amz-meta-inotrack-instance, хэш хоста INOTRACK_PUBLIC_URL): архивы других серверов в том же бакете/префиксе и архивы без метки ротация не трогает. Удаляются только объекты вида <префикс>/inotrack-backup-*.tar.gz (и их .manifest.json); чужие файлы и вложенные «каталоги» не трогаются. Несколько серверов всё же лучше разводить по разным префиксам: restore --from s3://бакет/префикс без имени архива при архивах нескольких инстансов под префиксом берёт самый свежий архив ЭТОГО инстанса (метка по INOTRACK_PUBLIC_URL из .env), а если его там нет — отказывается и просит указать архив явно (имя показывается перед подтверждением). Локальные архивы чистит BACKUP_RETENTION_DAYS, независимо от бакета.
  • Без --to tracker-ctl backup по-прежнему выгружает в BACKUP_S3_BUCKET под BACKUP_S3_PREFIX (дефолт backups), если заданы BACKUP_S3_ENDPOINT и BACKUP_S3_BUCKET; незашифрованный архив в этом режиме выгружается с предупреждением, как и раньше.
  • Выгрузка — одним PUT, без multipart: предел одиночного PUT у S3 — 5 ГБ. Если full-архив к нему подбирается, сократите объём TTL ClickHouse (deploy/clickhouse/ttl-policy.sql).

Рост бэкапов на 1-10М кликов/сутки. Пять больших таблиц ClickHouse (clicks, conversions, costs, blocked, funnel_steps) выгружаются полностью раз в неделю, а в остальные ночи — только окном с момента предыдущего бэкапа (подробности — «Инкрементальные бэкапы ClickHouse» ниже). Postgres, storage и мелкие таблицы ClickHouse выгружаются целиком при каждом запуске. Диск под BACKUP_LOCAL_DIR всё равно стоит держать отдельно от clickhouse-data и следить за ним (tracker-ctl doctor, алерт InotrackDiskSpaceLow): недельный полный дамп clicks на 10М/сутки весит сотни гигабайт к концу срока хранения; сократить его можно TTL (deploy/clickhouse/ttl-policy.sql).

Восстановление:

tracker-ctl restore --from /opt/inotrack/backups/inotrack-backup-full-20260101-030000.tar.gz
tracker-ctl restore --from s3://my-bucket/inotrack          # самый свежий архив в бакете
tracker-ctl restore --from-s3 --from backups/inotrack-backup-incr-....tar.gz   # ключ в BACKUP_S3_BUCKET
tracker-ctl restore --from <архив> --new-instance   # клонирование инстанса, см. ниже

--from может указывать и на full, и на incremental архив — если это incremental, restore сам находит и применяет всю цепочку (full + промежуточные инкременты) вплоть до указанного звена, см. ниже.

⚠️ .env из архива бэкапа целиком не применяется — пароли баз в нём от другой установки (другие тома). Файл кладётся рядом как .env.restored для ручной сверки (домены, S3, Telegram, флаги). Из него restore сам переносит в действующий .env только ключи, без которых данные бэкапа нечитаемы: ключи шифрования секретов модулей (INOTRACK_*_ENC_KEY — Cloudflare, интеграции, push, экспорт, трекер позиций, мобильное приложение), соль хэша IP (INOTRACK_IP_HASH_SALT) и INOTRACK_JWT_SECRET (сессии панели, подпись автообновления edge-нод). Прежний .env остаётся рядом как .env.before-restore; --keep-env — не переносить (если вы уже свели .env вручную). С --new-instance (клон) INOTRACK_JWT_SECRET не переносится: сессии клона не должны действовать на оригинале.

Восстановление перезаписывает текущие Postgres/ClickHouse/storage содержимым бэкапа — команда просит подтверждение, если не передан --yes. На время заливки restore останавливает tracker-core, workers и admin и в конце пересоздаёт их с новым .env; опубликованные конфиги потоков в Redis сбрасываются, и ядро при старте публикует потоки из восстановленного Postgres (клик-путь читает потоки только из Redis — без этого после восстановления на чистый сервер каждый клик уходил бы на запасной адрес).

Восстановление ClickHouse и refreshable MV

Дневная сводка (stats_daily_mv, stats_daily_recent_mv) — это REFRESHABLE MATERIALIZED VIEW. На закреплённой версии ClickHouse 24.8 любые операции с ними (создание, RESTORE, SYSTEM REFRESH VIEW) требуют настройки allow_experimental_refreshable_materialized_view = 1, иначе сервер отвечает Code: 344 (SUPPORT_IS_DISABLED) (с 24.10 фича вышла из experimental, имя настройки по-прежнему принимается и ничего не ломает).

  • tracker-ctl restore выставляет настройку сам: проверяет, знает ли её сервер (system.settings), и передаёт её в каждый вызов clickhouse-client восстановления. На ClickHouse старше 23.12 (refreshable MV нет вовсе) настройка не передаётся.

  • Миграции (tracker-ctl migrate, старт central/worker) выставляют её в самом CREATE MATERIALIZED VIEW ... SETTINGS allow_experimental_refreshable_materialized_view = 1 — чистая установка отдельной настройки не требует.

  • Вручную (нативный BACKUP/RESTORE ClickHouse, накат DDL из SHOW CREATE и т.п.) — выставьте настройку в сессии до восстановления:

    SET allow_experimental_refreshable_materialized_view = 1;
    RESTORE DATABASE inotrack FROM Disk('backups', 'inotrack.zip');
    

    Через clickhouse-client — аргументом: clickhouse-client --allow_experimental_refreshable_materialized_view=1 --query "RESTORE ...". Если объекты создаются по одному, порядок такой: сначала таблицы (источники clicks, conversions и целевые stats_daily, stats_daily_recent, stats_hourly), потом их данные, и только потом вьюхи (stats_hourly_mv, stats_daily_mv, stats_daily_recent_mv): вьюха с TO требует существующую целевую таблицу, а обычная MV, созданная раньше загрузки источников, дописала бы агрегаты поверх восстановленных. После этого SYSTEM REFRESH VIEW inotrack.stats_daily_mv (и stats_daily_recent_mv) пересчитает сводку сразу, не дожидаясь расписания.

Схему tracker-ctl restore не восстанавливает: дамп содержит только данные, таблицы и вьюхи создаёт tracker-ctl migrate, так что порядок «таблицы → данные → пересчёт MV» команда соблюдает сама.

Инкрементальные бэкапы ClickHouse

При 1–10М кликов/сутки (≈300М строк/мес, TTL клика 13 мес.) полный ежесуточный дамп самых крупных таблиц ClickHouse растёт неограниченно по времени/IO/диску. Поэтому дампится целиком (full) не всё:

  • Постоянно full, при каждом запуске: Postgres, том storage, каталог лицензии и мелкие/агрегатные таблицы ClickHouse (stats_hourly, landing_events, smartlink_clicks, stats_daily, stats_daily_recent) — они либо малы сами по себе, либо не растут пропорционально трафику.
  • Инкрементально, окном по времени — пять самых больших event-таблиц: clicks, conversions, costs, blocked, funnel_steps. Окно строится по колонке ts (время события) для всех, кроме costs — там окно по synced_at (момент фактической записи строки), а не по day: ресинк источника может пересчитать расход за день месячной давности, и такая коррекция должна попасть в ближайший инкремент, а не потеряться до следующего full. Окно берётся с нахлёстом 2 часа поверх watermark предыдущего бэкапа — запас на задержку доставки буфера кликов/очереди постбеков; повторная вставка строк, уже попавших в предыдущий бэкап, безопасна (clicks, conversions и costs — ReplacingMergeTree, ClickHouse схлопывает дубли при мёрдже/FINAL; про blocked и funnel_steps — предупреждение ниже).

Режим:

tracker-ctl backup                 # автовыбор: full раз в неделю, остальное — incremental
tracker-ctl backup --full          # форсировать full
tracker-ctl backup --incremental   # форсировать incremental (на пустой истории всё равно даст full)

Без флага (это и есть путь из cron-расписания) команда сама решает: если самому свежему full больше 7 суток (или его вообще ещё не было) — делает full, иначе — incremental от последнего бэкапа в цепочке. Расписание (BACKUP_SCHEDULE_CRON) задаёт, как ЧАСТО запускать backup (обычно ежесуточно); какой из этих запусков окажется full, решает уже сама команда — итоговая картина: full раз в неделю + incremental ежедневно, без отдельной настройки.

Оценка диска: при 5М кликов/сутки incremental-дамп большой пятёрки — это данные примерно одних суток (плюс 2-часовой нахлёст), а не всей 13-месячной истории — на два порядка меньше полного дампа тех же таблиц. Полный дамп нужен только раз в неделю; за неделю суммарный объём бэкапов (1 full + 6 incremental) заметно меньше семи ежедневных full-дампов, которые снимались раньше.

Манифест (manifest.json внутри архива + незашифрованный сайдкар <архив>.manifest.json рядом с ним) хранит: тип бэкапа, ссылку на родителя в цепочке (parent_archive), окно [watermark_from, watermark_to), список таблиц, снятых окном, и sha256 дампа каждой из большой пятёрки.

Восстановление цепочки: tracker-ctl restore --from <любой архив цепочки> сам идёт по parent_archive до full, распаковывает каждое звено, проверяет контрольные суммы и непрерывность цепочки (совпадение parent_id и отсутствие разрыва по времени между watermark_to одного звена и watermark_from следующего) — отказывает явной ошибкой, если где-то в цепочке не хватает архива (удалён retention'ом или вручную) или манифест повреждён, вместо того, чтобы молча восстановить неполные данные. Дальше: Postgres/storage/лицензия/ мелкие CH-таблицы берутся из последнего звена цепочки (они и так full в каждом бэкапе); большая пятёрка восстанавливается всей цепочкой по порядку (full, затем каждый incremental — обычным INSERT, без TRUNCATE между звеньями), а ReplacingMergeTree/FINAL схлопывают дубли внахлёст сами. Архивы без manifest.json (сняты до появления инкрементальных бэкапов) по-прежнему восстанавливаются как раньше — как самодостаточный full.

⚠️ blocked и funnel_steps — обычный MergeTree (без версии), не ReplacingMergeTree: в отличие от clicks/conversions/costs, дубли в них не схлопываются, поэтому окно их инкремента берётся БЕЗ двухчасового нахлёста — ровно от верхней границы окна предыдущего звена. Сама верхняя граница для этих двух таблиц (диагностические логи блокировок и шагов воронки, не деньги) — момент бэкапа минус 5 минут (запас на запись батчами): последние 5 минут попадают в следующее звено. Нахлёст остаётся только у ReplacingMergeTree (clicks/conversions/costs), где до мёрджа/FINAL возможно временное дублирование.

Retention (BACKUP_RETENTION_DAYS) удаляет бэкапы старше возраста по дате файла, но никогда не удалит full, от которого через цепочку всё ещё зависит ещё не удаляемый incremental — иначе цепочка порвалась бы молча (full создаётся раз в неделю и обычно живёт дольше горизонта retention, а нужен всем инкрементам недели, что от него отсчитываются).

Шифрование бэкапов

В архиве — полный дамп Postgres/ClickHouse (офферы, ставки, рекламодатели, домены трафика, финансы, хэши паролей пользователей) и machine-id лицензии (см. «Отпечаток инстанса» ниже). Утечка ОДНОГО файла архива — это весь ваш бизнес целиком, у кого бы он ни оказался: на диске сервера, в бакете S3, в почте, куда вы его скопировали вручную.

Задайте BACKUP_ENCRYPTION_KEY в .env — архив будет зашифрован AES-256-GCM ещё до записи на диск (потоково, кадрами; поэтому шифрование не требует свободной памяти, равной размеру архива, — работает и на десятках гигабайт дампа):

openssl rand -hex 32   # вставьте результат как BACKUP_ENCRYPTION_KEY в .env

Сохраните этот ключ ОТДЕЛЬНО от бэкапов — в менеджере паролей, сейфе организации, где угодно, но не рядом с архивами (не в том же каталоге, не в том же бакете S3, не в том же письме). Смысл шифрования бэкапа — предположение, что носитель самого бэкапа может быть скомпрометирован (украден диск, взломан бакет); если ключ лежит там же, это предположение ничего не даёт. tracker-ctl со своей стороны не кладёт BACKUP_ENCRYPTION_KEY внутрь архива: копия .env в архиве (env.backup, см. ниже) содержит этот ключ ЗАМЕНЁННЫМ на плейсхолдер — ответственность за сохранность самого ключа остаётся полностью на операторе. Потерянный ключ — это потерянный бэкап: расшифровать архив без ключа невозможно (это и есть смысл шифрования), tracker-ctl restore в этом случае откажет внятной ошибкой, а не молчаливой порчей данных. Периодически проверяйте, что ключ ещё на месте и восстановление с ним реально работает (tracker-ctl restore --from <свежий архив> --new-instance на тестовом хосте — безопасный способ проверить, не трогая прод).

Если BACKUP_ENCRYPTION_KEY не задан, tracker-ctl backup не отказывается делать бэкап — он выполняется как раньше, незашифрованным, но громко предупреждает об этом при каждом запуске (в консоли и в backup-cron.log, если бэкап идёт по расписанию). Отказ делать бэкап из-за отсутствия ключа шифрования был бы хуже: инстанс, который просто ещё не завёл ключ после обновления tracker-ctl, остался бы вовсе без резервных копий — для disaster recovery это строже риска незашифрованного архива (тот же риск, что был у всех бэкапов этого продукта до появления BACKUP_ENCRYPTION_KEY).

Восстановление определяет формат архива по содержимому файла, а не по расширению или имени — tracker-ctl restore одинаково понимает и старые архивы, снятые до того, как вы задали ключ, и новые зашифрованные. Для зашифрованного архива нужен BACKUP_ENCRYPTION_KEY в .env целевого инстанса (тот же ключ, каким архив был зашифрован — при переносе на новый сервер скопируйте его в .env нового хоста заранее, до tracker-ctl restore). Неверный ключ — явная ошибка ДО начала распаковки, а не повреждённые данные на диске.

Восстановление и лицензия (machine-id) — поведение по умолчанию и --new-instance

Бэкап несёт каталог лицензии (machine-id, см. «Отпечаток инстанса» ниже), и restore восстанавливает его по умолчанию — целевой инстанс получает ТОТ ЖЕ отпечаток лицензии, что был у инстанса на момент снятия копии. Это сознательный выбор для единственного сценария, ради которого существует tracker-ctl restore, — disaster recovery (диск умер, переезд на новое железо): без восстановления machine-id license-server видел бы возврат из бэкапа как новую установку и списывал бы лишнюю активацию из лимита при каждом восстановлении, хотя физически это тот же клиент.

Продукт не может автоматически отличить disaster recovery от клонирования инстанса (вторая параллельная копия продукта из того же бэкапа) — оба выглядят одинаково: архив разворачивается на другом хосте. Если вам действительно нужен клон (например, staging-копия для тестов), а не замена оригинала, передайте флаг --new-instance — он пропускает восстановление каталога лицензии, и при следующем запуске демон сгенерирует собственный machine-id (новый отпечаток, потребуется отдельная активация). Без этого флага клон будет представляться license-серверу той же машиной, что и оригинал, — сервер расценит это как обход лицензии.

Правило простое: переносите/восстанавливаете существующий инстанс — обычный restore, ничего не передавайте; делаете вторую копию рядом с работающим оригиналом — обязательно --new-instance. И restore, и restore --new-instance печатают явное сообщение о том, что произошло с machine-id, — поведение не зависит от догадки команды о намерении оператора.

Перенос на новый сервер (восстановление после потери сервера)

Порядок проверяется сквозным тестом: установка → данные → бэкап → чистый сервер → восстановление → вход, клики, постбеки (deploy/install-smoke, шаги restore-*).

1. На старом сервере, если он ещё жив, — остановите приём кликов и снимите свежий полный бэкап. Остановка нужна, чтобы клики из буфера на диске (WAL, том inotrack-wal) успели записаться в ClickHouse: сам WAL в бэкап не входит (-t 60 — ядру хватает времени дописать буфер, по умолчанию compose ждёт 10 с). Ключ шифрования бэкапов (backup keygen) должен быть заведён заранее и лежать вне сервера.

cd /opt/inotrack && docker compose stop -t 60 tracker-core workers
tracker-ctl backup --full

Сервер уже мёртв — берите последний архив из S3 (restore --from s3://бакет/префикс ниже) или с той машины, куда копировали локальные архивы. Старый сервер дальше не запускайте: восстановленный инстанс получает тот же отпечаток лицензии (см. выше).

2. DNS панели и трекер-доменов — на IP нового сервера (без этого Caddy не выпустит сертификаты).

3. На новом сервере — установка той же командой и с тем же ключом лицензии, что и в первый раз (docs/install.md): она поднимает пустые базы со схемой. Установка активирует лицензию с новым отпечатком и на время занимает ещё один слот лицензии (restore вернёт прежний отпечаток). Если лимит экземпляров лицензии уже исчерпан, активация не пройдёт — до переноса обратитесь в поддержку, чтобы освободить слот. Затем ключ шифрования бэкапов (для s3:// — и BACKUP_S3_*) в .env новой установки, архив — в каталог бэкапов, и восстановление:

sed -i 's/^BACKUP_ENCRYPTION_KEY=.*/BACKUP_ENCRYPTION_KEY=<ключ из менеджера паролей>/' /opt/inotrack/.env
tracker-ctl restore --from /opt/inotrack/backups/<архив> --yes
tracker-ctl status

4. Проверка. Вход в панель — с паролем и 2FA старого сервера (пароль из сводки новой установки больше не действует: пользователи — из бэкапа), API-токены прежние. Клик по ссылке потока ведёт на оффер — потоки публикуются ядром в течение минуты после старта. Постбеки по кликам, сделанным до бэкапа, принимаются: клик находится в ClickHouse.

5. Что сделать руками. Сверьте .env.restored с .env (S3, Telegram, свои флаги), поставьте расписание бэкапов заново (tracker-ctl backup schedule daily --to … — установка ставит только cron по BACKUP_SCHEDULE_CRON). Edge-ноды ходят к central по адресу из своего .env — при смене адреса поправьте его на нодах; ключ подписи автообновлений нод (INOTRACK_JWT_SECRET) restore переносит, перевыпускать его не нужно.

Что не входит в бэкап

Что Где Что будет после восстановления на чистый сервер
Конфиги потоков клик-пути Redis flow:cfg:* Публикуются заново из Postgres при старте ядра — ничего делать не нужно.
Счётчики кап, блокировки кап Redis caps:* Счёт в текущем окне капы начинается с нуля: до конца окна оффер может получить трафик сверх капы. Блокировки по лимитам оффера восстанавливает синхронизация кап.
Метки автопаузы офферов Redis caps:capped_mark:*, caps:broken_mark:* По ним капа и health-check сами возвращают оффер в работу. Оффер, который на момент бэкапа стоял на автопаузе (статус «капа» или «битая ссылка»), сам уже не вернётся: найдите такие офферы в списке по статусу и включите вручную.
Уникальность кликов, частота ботов Redis Окна начинаются заново: повторный визит в пределах окна уникальности потока снова считается уникальным.
Дедуп входящих постбеков Redis pb:dedup:* Повтор постбека, уже принятого до бэкапа, обрабатывается заново: в ClickHouse дублем не станет (ключ конверсии — clickid, цель, транзакция), но исходящий постбек источнику и вебхуки по нему могут уйти повторно.
Кэш кликов для постбеков Redis click:* Не нужен: постбек находит клик в ClickHouse.
Клики, ещё не записанные в ClickHouse том inotrack-wal Теряются. Обычно WAL пуст (запись — раз в секунды); копится, только пока ClickHouse недоступен — поэтому перед финальным бэкапом ядро останавливают (шаг 1).
Базы GeoIP том inotrack-geoip Не нужны: база из образа, затем автообновление (с MaxMind — по ключу INOTRACK_GEOIP_MAXMIND_LICENSE_KEY, перенесите его из .env.restored).
Сертификаты том caddy-data Выпускаются заново, когда DNS указывает на новый сервер.
История метрик и дашбордов тома prometheus-data, grafana-data Начинается заново.
Остальной .env — В архиве (.env.restored), переносится вручную, кроме ключей данных (см. выше).
Расписание бэкапов systemd/cron хоста Ставится заново (шаг 5).

Добавление/удаление трекер-домена

Из панели (основной путь)

Раздел «Трекер-домены» → «Добавить домен», затем A/CNAME-запись домена — на этот сервер (или «Подключить через Cloudflare», если задан токен). Больше ничего делать не нужно:

  • docker compose (Caddy). В deploy/Caddyfile есть блок https:// с tls { on_demand }. На первое TLS-рукопожатие с новым именем Caddy спрашивает ядро GET http://tracker-core:8081/internal/tls/ask?domain=<имя> и выпускает сертификат, только если домен есть в пуле и включён (ответ 200; иначе 404 — чужие имена сертификат не получают). Эндпоинт есть только на админском порту 8081 и наружу не публикуется. Установкам старше этой версии нужен свежий Caddyfile: tracker-ctl update теперь доставляет его из целевого образа (подробности — «Обновление» ниже).

  • k3s (Traefik + cert-manager). У Traefik нет on-demand TLS, поэтому ядро само держит Ingress inotrack-pool-domains (env INOTRACK_K8S_POOL_INGRESS у Deployment tracker-core): по правилу и отдельному tls-блоку на каждый активный домен пула → Service tracker-core:8080. Обновляется сразу после добавления/правки/удаления домена в панели и заново на каждом тике health-check. cert-manager заводит Certificate на каждый домен отдельно — домен, который не валидируется, не ломает сертификаты соседей. Права — ServiceAccount tracker-core + Role inotrack-domains-ingress (только ingresses в своём namespace), см. deploy/k8s/tracker-core.yaml. Настройки:

    env по умолчанию зачем
    INOTRACK_K8S_POOL_INGRESS пусто (выключено) имя Ingress пула
    INOTRACK_K8S_POOL_ISSUER letsencrypt-http01 ClusterIssuer. HTTP-01 годится для любого домена, который смотрит на кластер; DNS-01 (letsencrypt-inotrack) — только для зон в аккаунте Cloudflare, на который настроен issuer
    INOTRACK_K8S_POOL_TLS_OPTIONS traefik-direct@kubernetescrd TLSOption Traefik; - — без аннотации. Без неё origin-lock кластера рвёт рукопожатие
    INOTRACK_K8S_POOL_SERVICE / _SERVICE_PORT tracker-core / 8080 куда проксировать
    INOTRACK_K8S_POOL_INGRESS_CLASS traefik ingressClassName

    Домены, у которых уже есть свой статический Ingress (например, в deploy/k8s-instances/*/ingress.yaml), в пуле тоже работают: оба маршрута ведут в tracker-core:8080, просто сертификат выпускается дважды.

Панель показывает «не обслуживается этим сервером» только ПОСЛЕ первой проверки здоровья, когда https://<домен>/healthz ответил не этот трекер (PointsToTracker=false). Такой домен нельзя сделать основным (кнопка неактивна, ядро отвечает 409). До первой проверки бейджа нет — судить не по чему; запустите «Проверить сейчас».

Ссылки смартлинков, лендингов, воронок и потоков строятся от основного домена пула (GET /domains/tracking-base, поле tracking_url в ответах API); INOTRACK_PUBLIC_URL — только запасной вариант, пока основного домена нет.

Статически, командой

tracker-ctl domain add track2.example.com
tracker-ctl domain rm track2.example.com

Правит TRACKER_DOMAINS в .env и пересоздаёт контейнер Caddy с новым списком (короткий перезапуск TLS-фронта, остальные сервисы не затрагиваются) — править конфиг руками не нужно, TLS-сертификат Caddy получит автоматически при первом запросе к домену. После add не забудьте направить A-запись домена на этот сервер. Нужно для установок со старым Caddyfile без on-demand блока и для доменов вне пула панели.

Нельзя удалить последний домен из TRACKER_DOMAINS — сначала добавьте новый, иначе статический блок Caddy останется без домена вовсе.

Обновление

tracker-ctl update                 # показать версии, спросить подтверждение, обновить
tracker-ctl update --yes           # без вопроса (скрипты; без терминала --yes обязателен)
tracker-ctl update --yes --backup  # сначала tracker-ctl backup, при его сбое обновление не начнётся
tracker-ctl update --check         # только проверить: код 0 — актуально, 10 — есть новее

Спрашивает у license-server версию канала (INOTRACK_UPDATE_CHANNEL: stable по умолчанию или beta), записывает её в INOTRACK_VERSION, тянет образы, прогоняет миграции внутри нового образа, ждёт, пока tracker-core станет healthy. Сбой на любом шаге — автоматический откат на прежнюю версию (INOTRACK_VERSION в .env возвращается, старые образы уже на диске). Истёкший license-token на хосте update продлевает сам.

Чего update не делает намеренно:

  • не понижает версию. Установлена 1.3.0, а в канале 1.2.0 (сменили beta → stable) — сообщит и выйдет: миграции схемы обратно не ходят;
  • не прыгает через min_supported. Если установленная версия ниже минимальной для прямого перехода, update откажется и назовёт промежуточную версию: поставьте её (INOTRACK_VERSION=<промежуточная> в .env, docker compose pull && up -d, миграции — tracker-ctl migrate), затем снова tracker-ctl update.

Файлы деплоя обновляются вместе с версией. tracker-ctl update и tracker-ctl edge update для стандартной установки доставляют docker-compose.yml, Caddyfile, .env.example и конфиги ClickHouse/Prometheus/Grafana из целевого образа. .env не меняется: секреты, домены и флаги сохраняются. Новые ключи .env.example выводятся списком; задайте нужные значения самостоятельно. Если не заданы обязательные переменные compose, доставка пропускается с предупреждением.

Перед заменой файлы сохраняются в /opt/inotrack/state/deploy-backup/, манифест версии и хешей — в state/deploy-files.json. При первой доставке после старой установки файлы заменяются с бэкапом. Дальше правки оператора сохраняются, а новая версия кладётся рядом как <файл>.dist: перенесите нужные изменения вручную или примените deploy-files apply --force. При сбое обновления файлы восстанавливаются вместе с прежними образами. Caddy пересоздаётся, если менялся его конфиг; запущенные Prometheus/Grafana перезапускаются. ClickHouse перечитывает config.d/users.d автоматически. Проверка окружения сообщает ключи, заданные в .env, но отсутствующие в контейнере tracker-core.

tracker-ctl deploy-files status
tracker-ctl deploy-files apply --yes
tracker-ctl deploy-files rollback --yes  # восстановить файлы; затем docker compose up -d

У команд есть --home <каталог> и --json. Бэкап один, следующая доставка его заменяет. Отключить доставку: update --no-deploy-files, edge update --no-deploy-files или INOTRACK_UPDATE_DEPLOY_FILES=0 (также false) в .env. На edge с пользовательским compose (--compose-file, --service, --image-var, свои пути/сервис в INOTRACK_EDGE_*) файлы не заменяются. Старый образ без команды deploy-files обновляется как прежде, с предупреждением; после обновления CLI можно выполнить доставку вручную. Если хостовый CLI ещё не поддерживал deploy-files, первое обновление заменит сам CLI; затем один раз выполните tracker-ctl deploy-files apply --yes, чтобы доставить файлы установленной версии.

Повторный запуск install.sh тоже кладёт свежие файлы и сохраняет секреты, трекер-домены, бэкапы, Telegram и флаги поведения, но заново задаёт вопросы мастера (домен панели, ключ, e-mail, таймзона, валюта). Что поменялось в конкретном релизе — docs/RELEASE-NOTES-next.md.

Релизы и каналы

Версии объявляет license-server (фид версий по каналам), а не registry: инстанс ставит только то, что сервер назвал для его канала. Образ всегда закреплён конкретной версией — плавающих тегов в .env нет, docker-compose.yml без INOTRACK_VERSION не стартует.

Что Что это Кому
X.Y.Z релизная версия сначала канал beta, после обкатки — stable
edge-<sha> предрелизная сборка install.sh берёт последнюю, только пока ни stable, ни beta не опубликованы
:stable, :edge плавающие теги tracker-ctl (и tracker) в registry только install.sh, чтобы достать tracker-ctl до активации лицензии; в .env не попадают
tracker-l<id>:X.Y.Z общий X.Y.Z + слой с водяным знаком лицензии лицензии со штамповкой (INOTRACK_IMAGE ставят install.sh и update)

Канал stable не понижается, beta не бывает ниже stable. min_supported каждого релиза — deploy/MIN_SUPPORTED_VERSION (нет значения — X.0.0 того же мажора).

После успешного update tracker-ctl на хосте заменяется бинарём из нового образа (прежний остаётся рядом как tracker-ctl.prev; откат — mv tracker-ctl.prev tracker-ctl).

Водяной знак лицензиата в образе

Каждая лицензия со штамповкой (новые — по умолчанию) получает свой образ registry.inotrack.run/inotrack/tracker-l<id>: в бинарь tracker вписан <licensee_id>:<license_id>:<hmac> (видно в docker run --rm --entrypoint /usr/local/bin/tracker <образ> version и в /version панели как build_stamp, уходит в heartbeat). Чужой штамп в heartbeat license-server помечает как build_stamp_leaked у лицензии-источника. Штампуется только трекер; панель (admin) и tracker-ctl — общие.

Новая лицензия между релизами ставится на общем образе; как только для неё готов персональный образ, tracker-ctl update переводит инстанс на него, а общий образ для этой лицензии закрывается.

Установка до первого релиза (stable/beta пусты) закрепит edge-<sha> с громким предупреждением; как только опубликуют stable, tracker-ctl update (или автообновление) переведёт такой инстанс на неё.

Автообновление

INOTRACK_AUTO_UPDATE в .env (или install.sh --auto-update ...), после правки — tracker-ctl update schedule:

Режим Что делает раз в сутки в INOTRACK_UPDATE_WINDOW (дефолт 04:00, локальное время, + до 15 мин)
off ничего
notify (дефолт) update --check --notify: пишет в журнал и один раз на версию — в Telegram, что вышла новая
install update --yes --backup --notify: бэкап, обновление, при сбое — автооткат; результат в Telegram

Ставится systemd-таймер inotrack-update.timer (журнал: journalctl -u inotrack-update), на хостах без systemd — строка cron (лог backups/update-cron.log). Снять: tracker-ctl update schedule --remove. Telegram — бот из INOTRACK_TELEGRAM_TOKEN и числовой чат INOTRACK_TELEGRAM_CHAT; не заданы — результат только в журнале.

Текущая версия и «доступна версия X» видны в админке: бейдж в сайдбаре и «Настройки → О системе». Ядро сверяется с license-server раз в час.

Ручной откат

Автооткат покрывает сбой самого обновления. Если проблема всплыла позже:

tracker-ctl backup                       # снимок текущего состояния
sed -i 's/^INOTRACK_VERSION=.*/INOTRACK_VERSION=1.1.0/' /opt/inotrack/.env
cd /opt/inotrack && docker compose pull && docker compose up -d

Если между версиями были миграции схемы — откат образа их не отменит: восстановите бэкап, снятый до обновления (tracker-ctl restore --from ..., режим install снимает его сам). На время разбирательства поставьте INOTRACK_AUTO_UPDATE=off и tracker-ctl update schedule, иначе ночью таймер снова обновит инстанс.

Лицензия

tracker-ctl license info       # текущий статус локально сохранённого токена
tracker-ctl license refresh    # принудительная активация/heartbeat к license-server

license info показывает: до какого момента действителен локальный токен, когда была последняя успешная связь с license-server, длину офлайн-грейса (по умолчанию 72 часа) и дедлайн, до которого можно быть офлайн без деградации, включённые фичи и лимиты.

Точный состав того, что уходит license-server'у на каждом heartbeat (и что туда намеренно не входит — офферы, креативы, домены трафика, финансы) — см. FAQ, раздел «Какую телеметрию инстанс отправляет license-server'у?».

Отпечаток инстанса (machine-id) — не теряйте этот каталог

Лицензия привязана к «отпечатку» (fingerprint): sha256 от персистентного machine-id + список трекер-доменов. machine-id — не /etc/machine-id хоста, а отдельный файл рядом с кэшем лицензии, путь — INOTRACK_LICENSE_CACHE_PATH (/var/lib/inotrack/license/state.json по умолчанию, значит machine-id лежит в /var/lib/inotrack/license/).

В docker-compose.yml этот каталог — bind-mount на хостовый путь /var/lib/inotrack/license (единственный из томов ядра НЕ именованный docker volume, как geoip/storage/wal) — специально, чтобы tracker-ctl, запущенный прямо на хосте, и демон в контейнерах (tracker-core, workers) читали один и тот же файл и давали один и тот же отпечаток. Если потерять этот каталог (пересоздать хост без переноса /var/lib/inotrack/license и без tracker-ctl restore), при следующем запуске трекер сгенерирует НОВЫЙ id — license-server увидит это как клонирование лицензии или лишнюю активацию, а не как продолжение той же установки. tracker-ctl backup/restore включают этот каталог автоматически (см. «Бэкап и восстановление» выше, раздел про --new-instance) — ручной перенос нужен только если вы двигаете диск/файлы мимо tracker-ctl (например, rsync каталога установки целиком): в этом случае переносите /var/lib/inotrack/license вручную вместе с остальными данными.

⚠️ Архив бэкапа содержит machine-id — сам по себе секрет и идентификатор установки. Кто получит файл архива, получает отпечаток чужой лицензии. Задайте BACKUP_ENCRYPTION_KEY (см. «Шифрование бэкапов» выше) — без него архив остаётся обычным .tar.gz под правами 0600 (только файловый ACL хоста) как локально, так и при выгрузке в S3 (BACKUP_S3_*) — подпись запросов SigV4 защищает канал передачи и доступ к бакету по ключам, но не содержимое объекта после того, как он туда попал. Даже с шифрованием ограничивайте доступ к BACKUP_LOCAL_DIR и к бакету BACKUP_S3_BUCKET так же строго, как к самой лицензии — шифрование защищает от утечки НОСИТЕЛЯ, а не заменяет контроль доступа к нему.

В deploy/k8s то же самое решено иначе: machine-id — не файл на диске пода (при HPA 2-7 репликах tracker-core обычный PersistentVolume не подходит — ReadWriteOnce примонтируется только к одному поду, остальные зависнут в Pending), а Secret, смонтированный read-only во все поды tracker-core/workers одинаковым содержимым (deploy/k8s/SECRETS.md, inotrack-license.machine_id) — Secret-тома read-only по построению доступны любому числу реплик без требований к StorageClass, а трекер при существующем непустом файле никогда не пытается его перезаписать.

Удаление инстанса

tracker-ctl uninstall               # интерактивное подтверждение, бэкап перед удалением,
                                     # данные (тома Postgres/ClickHouse/Redis/storage) остаются
tracker-ctl uninstall --purge       # + удаляет тома с данными и каталог установки целиком
tracker-ctl uninstall --no-backup   # не делать бэкап перед удалением
tracker-ctl uninstall --yes         # не спрашивать подтверждение

Без --purge инстанс можно поднять обратно тем же .env — данные на диске остаются. --purge удаляет данные безвозвратно. Без --yes команда uninstall (с --purge и без) просит ввести имя инстанса или yes для подтверждения.

Управление пользователями

Первого администратора создаёт установщик из INOTRACK_ADMIN_EMAIL/_PASSWORD (срабатывает один раз, пока таблица users пуста). Владелец (роль admin) заводит остальных сотрудников штатно, приглашением — без правки БД и без рестарта (только роль admin):

# пригласить (кука сессии владельца — см. /docs/quickstart/, приложение «через API», шаг 0)
curl -s -b cookies.txt -X POST "$TRACKER/api/internal/auth/users/invite" \
  -H 'Content-Type: application/json' -d '{"email":"[email protected]","role":"buyer"}'
# в ответе — token, отдаётся ОДИН РАЗ. Ссылка приглашённому: https://<панель>/invite?token=<token>
# (срок жизни 7 дней, приглашённый сам ставит пароль по ссылке — публичный POST /invite/accept)

curl -s -b cookies.txt "$TRACKER/api/internal/auth/users"                       # список
curl -s -b cookies.txt -X PATCH "$TRACKER/api/internal/auth/users/<id>/role" \
  -H 'Content-Type: application/json' -d '{"role":"viewer"}'                    # сменить роль
curl -s -b cookies.txt -X POST "$TRACKER/api/internal/auth/users/<id>/disable"  # отключить
curl -s -b cookies.txt -X POST "$TRACKER/api/internal/auth/users/<id>/enable"   # вернуть доступ

Отключение форсирует релогин немедленно (отзывает все сессии пользователя), а не только запрещает следующий вход. Понизить или отключить последнего активного admin нельзя — 409 conflict: продукт не даёт запереть команду без владельца.

То же самое доступно в панели, в разделе «Пользователи» (/users): список сотрудников, приглашение, смена роли, отключение и возврат доступа, сброс 2FA. Вызовы curl выше — для автоматизации.

Роли: admin — полный доступ и управление командой; buyer видит только свои потоки/источники (владелец потока — flows.owner_user_id); viewer — то же самое, но без права менять.

Администратор потерял 2FA-устройство

Два штатных пути (подробное обоснование — FAQ):

  1. Второй admin сбрасывает 2FA первому, без рестарта: POST /api/internal/auth/users/<id>/2fa/reset (под сессией второго admin, не себе).
  2. Один administrator — аварийная переменная INOTRACK_AUTH_RESET_2FA=email:метка в .env, требует перезапуск процесса (то есть физический доступ к серверу). Метка — любая непустая строка, уникальная для инцидента (дата/номер тикета): без неё следующий обычный рестарт снёс бы 2FA повторно, уже после того как её настроили заново. Сразу после входа настройте 2FA заново и уберите переменную — она не самоочищается.

POST /2fa/disable по-прежнему безусловно 403 для роли admin (2FA для владельца обязательна) — это не то же самое, что пути выше, снять её самому себе паролём или recovery-кодом нельзя, только одним из двух способов выше. У buyer/viewer self-service работает штатно (recovery-код → вход → 2fa/disable паролем → 2fa/setup заново).

Переменные окружения

Все — в /opt/inotrack/.env (права 600). Под сотню переменных в сумме по всем модулям — ниже не полный список, а то, что владельцу инстанса реально нужно знать: что установщик заполняет сам, что стоит проверить под свою конфигурацию, и что трогать не нужно вовсе.

Заполняет установщик — обычно руками не трогать

install.sh интерактивно или флагами (установка) сам пишет в .env: домены, e-mail/пароль первого администратора (INOTRACK_ADMIN_EMAIL/_PASSWORD, читаются один раз при пустой таблице пользователей), лицензионный ключ (INOTRACK_LICENSE_KEY), пароли Postgres/ClickHouse/Redis, JWT-секрет, роль инстанса (INOTRACK_ROLE: central/edge), таймзону и базовую валюту (INOTRACK_TZ, INOTRACK_BASE_CURRENCY), порты (INOTRACK_HTTP_ADDR :8080, INOTRACK_ADMIN_ADDR :8081), REDIS_MAXMEMORY (25% RAM хоста, минимум 1gb — пересчитывается только при первой установке, при переустановке поверх того же каталога уже заданное значение сохраняется, см. «Оценка роста Redis» в руководстве по установке).

⚠️ Одно стоит сверить после установки руками: INOTRACK_PUBLIC_URL — домен, который подставляется в трекинговые ссылки. Если по ошибке указывает на домен панели, а не на трекер-домен, ссылки будут вести не туда при формально «зелёной» установке (см. типичные проблемы в руководстве по установке).

Меняете под свою конфигурацию — если используете соответствующую фичу

  • Планка сложности нового пароля (действует при установке пароля — смена пароля, приём приглашения, бутстрап первого админа; на сам вход не влияет): INOTRACK_PASSWORD_MIN_LEN (дефолт 8 символов), INOTRACK_PASSWORD_ALLOW_COMMON=1 — отключить проверку по списку предсказуемых паролей (сознательное ослабление для тестового стенда/доверенного контура). На чистой установке слабый INOTRACK_ADMIN_PASSWORD не блокирует создание первого администратора (иначе продукт оказался бы заперт с первого запуска для собственного покупателя) — вместо этого при старте пишется ERROR в лог с требованием сменить пароль немедленно после первого входа.
  • Аварийное восстановление доступа администратора, потерявшего 2FA-устройство: INOTRACK_AUTH_RESET_2FA=email:метка-инцидента — см. раздел «Администратор потерял 2FA-устройство» выше и подробности в FAQ. Не задана — ничего не делает при старте, ставится только на время инцидента и убирается сразу после.
  • Ограничение доступа к панели по IP: INOTRACK_ADMIN_IP_ALLOWLIST (IP/CIDR через запятую) — закрывает весь административный контур, включая саму форму входа. Не задана или пуста — ограничения нет (дефолт). Список читается только из окружения процесса и никогда из базы или интерфейса: это сделано намеренно, чтобы ошибка в диапазоне лечилась правкой конфигурации и перезапуском, а не требовала входа в приложение, куда вас уже не пускают. Перед включением проверьте, что ваш собственный адрес входит в список и что он постоянный. Публичный API по Bearer-токенам под это ограничение не попадает намеренно.
  • Работа за Cloudflare/другим прокси: INOTRACK_TRUSTED_PROXIES (CIDR через запятую) — каким адресам доверять X-Forwarded-For/X-Real-IP; INOTRACK_CLOUDFLARE_IPS — то же для CF-Connecting-IP. Без этого клиентский IP берётся из недоверенного источника — дыра для подделки гео и обхода IP-allowlist постбеков (постбеки).
  • Панель на другом домене, чем видит трекер (прокси переписывает Host): изменяющие запросы админского порта по куке сессии принимаются только с того же origin, что Host, или с origin из INOTRACK_PUBLIC_URL — иначе 403 cross_origin (защита от CSRF). INOTRACK_TRUSTED_ORIGINS — дополнительные origin'ы панели через запятую (https://panel.example.com, без пути); ошибка в значении не даёт трекеру стартовать. Со штатным Caddy не нужна.
  • Резервное копирование в облако: BACKUP_S3_* (эндпоинт/бакет/ключи/префикс/адресация, BACKUP_S3_KEEP — сколько последних архивов хранить в бакете), BACKUP_RETENTION_DAYS (дефолт 14), BACKUP_SCHEDULE_CRON (дефолт 0 3 * * *, ставится .env.example при установке; tracker-ctl backup schedule падает с понятной ошибкой, если строка в .env пуста).
  • Обновления: INOTRACK_UPDATE_CHANNEL (stable по умолчанию, beta — если сознательно хотите более свежие сборки), INOTRACK_AUTO_UPDATE (off/notify/install, дефолт notify), INOTRACK_UPDATE_WINDOW (дефолт 04:00), INOTRACK_TELEGRAM_CHAT — чат для уведомлений; см. «Обновление» выше.
  • SQL-песочница отчётов (API): INOTRACK_CH_SANDBOX_PASS — без него ручка /api/v1/reports/sql выключена.
  • Демо-трафик для теста: INOTRACK_SANDBOX=1 — только на не-боевом инстансе.
  • Push-подписки, менеджер доменов, интеграции рекламных кабинетов, крипто-выплаты: у каждого модуля свой ключ шифрования секретов в БД — INOTRACK_PUSH_ENC_KEY, INOTRACK_DOMAINS_ENC_KEY, INOTRACK_INTEG_ENC_KEY, INOTRACK_EXPORT_ENC_KEY, INOTRACK_RANKTRACKER_ENC_KEY, и отдельно INOTRACK_FINANCE_TRONGRID_KEY/_ETHERSCAN_KEY/_BSCSCAN_KEY для приёма крипто-выплат (TRC20/ERC20/BEP20). Без ключа своего модуля соответствующая фича просто не хранит секреты — не ошибка, если вы её не используете. Тем же INOTRACK_INTEG_ENC_KEY шифруется и сессия входа в режиме «Кабинет (веб)» (Unicom24, Pampadu — логин/пароль, cookie Unicom24 или токен сессии Pampadu, таблица network_web_sessions): без ключа трекер не сможет сохранить вход в кабинет партнёрской сети, см. постбеки. У push-сервиса отдельно INOTRACK_PUSH_CONTACT_MAILTO (необязательно) — контактный e-mail в VAPID JWT (RFC 8292), а у менеджера доменов — INOTRACK_DOMAINS_CHECK_INTERVAL_SEC (частота health-check домена; см. FAQ о том, что failover в этом модуле сегодня — мониторинг и Cloudflare-подключение, а не автоматическое переключение реального трафика).
  • Telegram-алерты: INOTRACK_TELEGRAM_TOKEN/_CHAT. Без токена алерты не теряются: все они видны в панели — колокольчик в шапке со счётчиком непрочитанных (лента хранится 90 дней), а администратору панель показывает баннер «Telegram для алертов не настроен». Telegram — один из каналов доставки; без него не работают только команды бота и ежедневная сводка.
  • TTL кэша клика для матчинга постбека: INOTRACK_CLICK_CACHE_TTL (дефолт 30m, ключ Redis click:<clickid>) — 0 выключает кэш (матчинг только через ClickHouse, холоднее). Влияет на объём Redis — см. «Оценка роста Redis» в руководстве по установке.
  • Автообновление баз GeoIP: работает из коробки без единой настройки — по умолчанию (ни одна из переменных ниже не задана) продукт сам качает бесплатную базу DB-IP Lite (лицензия CC BY 4.0, атрибуция уже показана в панели, раздел «Отчёты») раз в INOTRACK_GEOIP_UPDATE_INTERVAL_HOURS (дефолт 24ч), проверяет целостность файла и атомарно подменяет рабочую базу, без перезапуска процесса. Отказ скачивания (сеть недоступна, источник вернул не .mmdb) пишет ERROR в лог и оставляет прежнее/пустое гео — приём кликов не останавливается ни при каких обстоятельствах. Полностью пустая база (первый запуск/автообновление выключено) видна: один WARN при старте, gauge inotrack_geoip_loaded (0/1) и tracker-ctl doctor.
    • INOTRACK_GEOIP_CITY_URL/_CITY_V6_URL/_ASN_URL — переопределить источник (своё зеркало или прямая ссылка на файл платной лицензии). _CITY_V6_URL нужен только источнику с раздельными IPv4/IPv6-файлами (DB-IP); у источника с одним комбинированным City-файлом (в т.ч. MaxMind) не задавайте вовсе.
    • INOTRACK_GEOIP_MAXMIND_LICENSE_KEY — опциональный ключ платной лицензии MaxMind GeoLite2 (точнее DB-IP Lite). Задан и ни один из трёх URL выше не задан явно — резолвер сам строит официальные permalink-ссылки MaxMind и распаковывает их tar.gz. ⚠️ У издателя продукта своего ключа MaxMind нет — этот путь проверен только синтетическими данными в тестах, не живым скачиванием с download.maxmind.com; при использовании обязательно проверьте tracker-ctl doctor и реальный резолв IP после первого запуска. В deploy/k8s/tracker-config.yaml этот ключ пока не заведён (как и INOTRACK_TELEGRAM_TOKEN — внешний credential для меньшинства инстансов); для k8s-установки с платным MaxMind добавьте его в deploy/k8s/externalsecret.yaml/SECRETS.md по аналогии с INOTRACK_LICENSE_KEY.
    • INOTRACK_GEOIP_AUTOUPDATE=off — полностью выключает фоновое скачивание (air-gapped/регулируемые окружения, где исходящий трафик к GitHub/MaxMind нежелателен) — тогда .mmdb кладут в INOTRACK_GEOIP_PATH вручную.
    • INOTRACK_GEOIP_READY_WAIT — сколько /readyz после старта ждёт City-базу (дефолт 60s, 0 — не ждать), чтобы балансировщик не вёл на под клики, которые запишутся с пустым гео. После потолка под объявляется готовым и без базы — WARN в лог и метрика inotrack_geoip_ready_wait_expired=1. Обычно ждать не приходится: образ трекера несёт запас базы, скачанный при сборке (/opt/inotrack/geoip-seed, путь меняет INOTRACK_GEOIP_SEED_PATH), и при старте недостающие файлы копируются из него.

⚠️ Одна переменная требует внимания при первой настройке, даже если антифрод/first_click не кажутся приоритетом: INOTRACK_IP_HASH_SALT — соль хэша IP в антифроде и матчинге идентичности. Не задана — при каждом рестарте процесса генерируется новая случайная, и сопоставление посетителей по IP-хэшу между рестартами (в т.ч. first_click) перестаёт работать. Задайте один раз при установке и не меняйте.

Проверка ссылок офферов (health-check)

Трекер раз в INOTRACK_CAPS_HEALTHCHECK_INTERVAL_MIN минут (дефолт 5) открывает ссылку каждого активного оффера с включённой проверкой. Оффер останавливается автоматически только когда поломку увидел бы и посетитель:

Что вернула проверка Что делает трекер
404, 410, 5xx; страница «оффер остановлен»; истёкший TLS-сертификат; домен не существует Поломка. Автопауза — после INOTRACK_CAPS_HEALTHCHECK_FAILS (дефолт 3) таких проверок подряд, на INOTRACK_CAPS_BROKEN_TTL_MIN минут (дефолт 180). Алерт «оффер упал».
Страница изменилась в размере, сменился конечный домен, редирект-цикл, сертификат на другой домен, прочие 4xx Один алерт на причину, трафик не останавливается. Страница, которая отвечает (код меньше 400), серию неудач обрывает.
401/403/429 и заглушки антибот-защиты, таймаут, сайт недоступен, сертификат неизвестного центра Ничего: такие ответы получает робот, а не посетитель с браузером. Если сайт не отвечает вовсе 6 проверок подряд — один алерт, без остановки трафика.

Оффер на автопаузе проверяется дальше: первый же ответ с кодом меньше 400 без признаков поломки (в том числе «страница изменилась») возвращает его в ротацию сразу, новая подтверждённая неудача продлевает срок, иначе пауза истекает сама. Вечной автопаузы нет. Статус broken, выставленный вручную, проверка не снимает.

«Подряд» — буквально: чередование «502, страница открылась, 502, страница открылась» паузу не ставит.

Размер страницы сравнивается с эталоном — первой успешной проверкой оффера. Когда рекламодатель переделал лендинг, трекер присылает один алерт «изменилось содержимое страницы», а после INOTRACK_CAPS_BASELINE_STABLE_CHECKS проверок подряд (дефолт 3) с одним и тем же новым размером сам принимает новую страницу за эталон (значение меньше 2 не действует). Сменившийся конечный домен сам эталоном не становится — это решает человек: кнопка «Принять как эталон» в карточке оффера (раздел «Health-check ссылки») запоминает текущую страницу. Страницу, которая не отвечает, пуста или похожа на заглушку «оффер остановлен», кнопка не принимает. Если сайт не открылся с central, а в стране гео оффера есть edge-нода, страницу проверит она — эталон будет принят по её ответу.

Кнопка «Проверить сейчас» показывает результат и пишет его в историю, но в счёт неудач подряд не идёт: поставить автопаузу нажатиями нельзя. Вернуть оффер с автопаузы досрочно — можно: успешная ручная проверка снимает её сразу.

Сертификаты НУЦ Минцифры (Russian Trusted Root CA) встроены в клиент проверки — сайты российских банков проверяются без установки корня в систему. На остальные исходящие соединения трекера (постбеки, API сетей) это доверие не распространяется.

Если у оффера задано гео и в этой стране работает edge-нода, неудачная проверка с central перепроверяется с неё, и решение принимается по её ответу: сайт, закрытый для зарубежных адресов, из своей страны открывается. Нода offline или старой версии, не умеющая такие проверки (в панели — бейдж «Устарела»), задачу не получает: сразу применяется результат central. Нода не ответила — через 2 минуты применяется результат central. Обновление ноды — docs/edge-geo.md, «Версия и обновление ноды».

Страна ноды — отдельное поле «Страна» (двухбуквенный код: RU, DE, KZ) в мастере добавления и в карточке ноды. Регион для этого не используется: он задаёт пул GeoDNS, и у московской ноды там EEU. У нод, созданных до появления поля, страна заполнена автоматически, если с двухбуквенного кода начинается регион или имя ноды (ru-moscow-1 → RU); остальным её нужно указать в карточке. Нода без страны задач проверки не получает.

Имя, регион, домен и IP существующей ноды правятся в её карточке кнопкой «Изменить ноду»; очистить заданный регион, домен или IP нельзя. Новый регион попадает в пул Cloudflare только после «Синхронизировать пулы» на странице GeoDNS. Новый домен меняется только в реестре центра: DNS домена на IP ноды и EDGE_DOMAIN на сервере ноды (на него выпускается TLS-сертификат) нужно поменять заранее, иначе региональные ссылки перестанут открываться.

IP обычно заполняет сам heartbeat ноды (адрес, с которого нода пришла в центр), поэтому руками его правят в двух случаях: адреса ещё нет — нода не на связи, а он нужен для сверки DNS домена и для пула GeoDNS у ноды без домена; или в реестре неверный адрес. Конфликта с heartbeat нет: на ручную правку центр сбрасывает кэш адреса и следующий heartbeat сверяет значение сразу, а не через 10 минут. Ручное значение держится до этого heartbeat: если нода выходит в сеть с другого адреса, в реестре снова будет адрес heartbeat (частные адреса и адреса прокси в реестр не пишутся — такой heartbeat ручное значение не затирает). Принимаются IPv4 и IPv6; правка видна в журнале действий (edge_node.update, поле ip).

Если гео у оффера пустое, для проверки берётся страна проверки интеграции, из которой он импортирован: поле «Страна проверки ссылок» в форме интеграции сети (config.probe_geo в API интеграций; код страны, можно несколько через запятую; не код страны — ошибка 400). Не задана — гео сети (Unicom24, Pampadu — RU), а если и его нет — INOTRACK_CAPS_HEALTHCHECK_DEFAULT_GEO (тот же формат; по умолчанию пусто). Порядок: гео оффера, интеграция, сеть, переменная. Настройка интеграции применяется без перезапуска: со следующей проверки оффера (плановой или ручной). Сохранение интеграции заменяет её настройки целиком: клиент API, который не передал probe_geo, снимает страну проверки. После смены страны эталон страницы (конечный домен, размер) остаётся снятым из прежней: у ссылок, зависящих от страны, возможен разовый алерт-подозрение об изменении страницы. Это же значение подставляется в {geo} ссылки при проверке. На клики оно не влияет: гео оффера не меняется, оффер по-прежнему принимает трафик из любой страны. Без страны интеграции, гео сети и переменной оффер с пустым гео проверяется только с central, {geo} = US.

Сайт может отвечать центру нормально, а посетителю из страны гео — заглушкой или 404 (гео-блок). Поэтому, кроме перепроверки неудач, есть плановая проверка: раз в INOTRACK_CAPS_HEALTHCHECK_EDGE_EVERY (по умолчанию 6h; длительность или число часов, 0 — выключено) каждый активный оффер с гео (своим, интеграции, сети или из переменной выше), для страны которого есть нода с возможностью probes, проверяется и с неё, даже если central ответил OK. Офферы распределены по периоду равномерно (у каждого своя фаза), первый период после выкатки плановых проверок не делает. Нода получает за heartbeat не больше INOTRACK_EDGE_PROBES_PERIODIC_PER_HEARTBEAT (по умолчанию 2) плановых задач — после срочных перепроверок. Если ноды нет или её очередь плановых полна, оффер ждёт своего следующего слота: пропущенные проверки не догоняются залпом.

Плановая задача уходит ноде первой страны гео, где нода есть. Остальные страны гео можно проверять тоже: INOTRACK_CAPS_HEALTHCHECK_EDGE_COUNTRIES (по умолчанию 1 — только первая страна; допустимо 1–5) задаёт, сколько стран проверяется за плановый слот. Ответ дополнительной страны виден в истории проверок, в метрике inotrack_edge_plan_country_total{country,result} и в логе; в серию оффера он не идёт. Каждая страна сверх первой — ещё одна плановая задача ноде на оффер за период, в пределах того же лимита на heartbeat.

При значении больше 1 включаются ещё две вещи:

  • алерт «оффер не открывается из KZ» (RU+EN), когда серия провалов дополнительной страны дошла до INOTRACK_CAPS_HEALTHCHECK_FAILS, а основная страна оффер открывает; трафик при этом не останавливается;
  • правило агрегации стран при автопаузе: оффер останавливается целиком, только если он не открывается из всех проверенных стран гео. Открывается хотя бы из одной — автопаузы нет (а уже стоящая снимается живым ответом дополнительной страны с учётом бюджета ноды), уходит алерт «не открывается из RU, но открывается из KZ; трафик продолжается», и решение за человеком: убрать страну из гео оффера или остановить оффер вручную. Пока оффер падает, остальные страны гео перепроверяются срочной очередью каждым тиком, чтобы решение принималось по свежим вердиктам. Счётчик таких решений — inotrack_offer_autopause_held_total (снятия по дополнительной стране: inotrack_offer_autopause_released_country_total).

Подробности, обоснование решения и ограничения — ops/edge-probe-multicountry.md.

Ответ ноды попадает в историю проверок и решается по тем же правилам, но со своей серией:

  • первый «жёсткий» провал с ноды при живом central — алерт-подозрение «из страны гео ссылка не открывается, а с central открывается» (не чаще раза в сутки на оффер), трафик не останавливается; дальше оффер проверяется с ноды каждым тиком, пока серия не оборвётся;
  • INOTRACK_CAPS_HEALTHCHECK_FAILS провалов с ноды подряд — автопауза, как обычно; живая проверка с central такую паузу не снимает — снимает живой ответ ноды (в том числе на ручную проверку, которая для такого оффера тоже идёт с ноды) или истечение срока;
  • живой ответ ноды обрывает серию; нода не ответила за 15 минут — плановая проверка оффера откладывается на час (перепроверку неудач central это не трогает);
  • защита от сбоя самой ноды (её DNS, прокси): автопауза по серии с ноды ставится, только если та же нода открыла какой-то другой оффер уже после начала серии (не раньше чем за час); если по плану с ноды за 2 часа не открылось больше половины офферов (от 5 ответов), её провалы в серию не идут; dns_not_found с ноды в серию не идёт никогда. В логе central — «похоже, сбоит сама нода».

Доверие к данным edge-ноды

Вердикт проверки central считает сам, но сами факты о странице (код ответа, стоп-слово, домен, размер) приходят от ноды, и проверить их нечем. Нода стоит на чужом VPS, поэтому её ответ не может в одиночку остановить или вернуть трафик оффера (ops/edge-probe-trust.md):

  • кворум нод. Автопауза по ответам нод ставится, только если «жёсткий» провал подтвердили не меньше INOTRACK_CAPS_HEALTHCHECK_EDGE_QUORUM (по умолчанию 2) разных нод страны. Ноды различаются по id в реестре, а не по региону (у ru-moscow-1 и ru-moscow-2 регион общий — EEU). Одна нода даёт только алерт-подозрение; задачи серии central раздаёт нодам страны по очереди, поэтому исправная серия кворум собирает. 1 — прежнее поведение (решает одна нода). У инстанса с единственной нодой в стране при значении 2 автопауза по гео-блоку не ставится — приходит только алерт «с central открывается, из страны гео — нет»; если нода одна и этого достаточно, поставьте 1;
  • бюджет ноды. Не больше INOTRACK_CAPS_HEALTHCHECK_EDGE_NODE_BUDGET (по умолчанию 10) новых автопауз и столько же возвратов оффера по ответам одной ноды за час. Продление уже стоящей паузы бюджет не тратит, алерты им не ограничены — только остановка и возврат трафика. Сверх бюджета ответы ноды идут только в историю и метрику, в решения — нет; в логе central — «нода исчерпала часовой бюджет решений». Отложенное решение не теряется: серия провалов остаётся, и в следующем часу оно повторится. 0 — без ограничения;
  • ответ только от ноды-адресата. Задача привязана к id ноды: ответ другой ноды с верным одноразовым числом отбрасывается (foreign_node в метрике);
  • эталон страницы по ответу ноды автоматически не пишется — ни первым OK без эталона, ни серией одинаковых content_changed. Эталон задаёт проверка с central или человек кнопкой «Принять как эталон» (в истории такая запись помечена именем ноды в графе «откуда»);
  • стоп-слово — только из списка оффера (или списка по умолчанию). Слово, которого central не знает, в историю и в текст алерта не попадает, а сам ответ не идёт в решения: вердикт inconclusive, причина edge_untrusted;
  • в алертах данные ноды помечены источником: «по данным edge-ноды <имя>».

Аварийное выключение проверок с нод — INOTRACK_CAPS_HEALTHCHECK_EDGE=0 (и отзыв ноды в панели) — работает как раньше.

INOTRACK_CAPS_HEALTHCHECK_EDGE=0 отключает перепроверку и плановую проверку с нод, INOTRACK_CAPS_HEALTHCHECK_UA задаёт свой User-Agent проверки (по умолчанию — браузерный).

Метрики проверки для Prometheus: inotrack_offer_healthcheck_total{verdict,via} (проверки по вердикту и месту — central или edge), inotrack_offer_healthcheck_duration_seconds (длительность), inotrack_offer_autopaused и inotrack_offer_active (офферы на автопаузе и активные сейчас), inotrack_offer_autopause_total (постановки на автопаузу), inotrack_edge_probe_total{result} (перепроверки через edge-ноды: ok, timeout, muted), inotrack_offer_geo_mismatch_total (алерты «с central открывается, из страны гео — нет»), inotrack_edge_plan_probe_total{result} (плановые проверки с нод: ok, timeout, region_suspect — провал не засчитан из-за подозрения на ноду), inotrack_edge_trust_total{result} (модель доверия к данным нод: quorum_wait — автопауза отложена до подтверждения другой нодой, quorum_met — провал подтверждён кворумом, budget_pause/budget_restore — нода исчерпала часовой бюджет решений, foreign_node — ответ не от ноды-адресата, keyword_untrusted — стоп-слово не из списка оффера, baseline_skipped — эталон по ответу ноды не записан), inotrack_offer_autopause_held_total (снятия по дополнительной стране: inotrack_offer_autopause_released_country_total) (автопауз не поставлено по правилу агрегации стран гео: оффер открывается не из всех стран). Дашборд Grafana «health-check офферов» входит в профиль observability (deploy/grafana/dashboards/inotrack-offer-healthcheck.json).

Общий запасной адрес и живые потоки

INOTRACK_FLOW_NOT_FOUND_URL — куда уходит клик по ссылке несуществующего потока. Туда же попадёт клик включённого потока, если у него не осталось ни одного доступного оффера (пауза, капа, автопауза), а запасного варианта нет. Посетитель при этом уходит не к рекламодателю, поэтому трекер считает такие клики (inotrack_click_global_fallback_total; клики, принятые edge-нодами, central считает в inotrack_edge_click_global_fallback_total) и присылает алерт «Клики потока уходят на общий запасной адрес» — не чаще раза в час на поток (настройка алерта flow_global_fallback). Что сделать: вернуть офферы в ротацию или задать потоку запасной URL, поток или оффер. У авто-потоков, созданных импортом каталога сети, запасного URL нет — задайте его, если ссылки таких потоков стоят на витрине.

«Последний шанс». Если у потока нет ни запасного варианта, ни запасного URL, а оффер снят только автопаузой health-check (проверка могла ошибиться — антибот, гео), клик уходит на этот оффер, а не на общий адрес. Порядок: запасной поток/смартлинк/оффер → запасной URL потока → оффер на автопаузе → общий адрес. Офферы на капе, на ручной паузе, с паузой рекламодателя и со статусом broken, выставленным вручную, так не используются никогда. Из нескольких офферов на автопаузе берётся оффер с наибольшим весом в потоке. Такие клики считаются (inotrack_click_last_resort_total{flow}; клики edge-нод central считает по их heartbeat в inotrack_edge_click_last_resort_total), помечаются в событии клика (clicks.last_resort; в отчётах — группировка и фильтр «Последний шанс», last_resort: 1 — такой клик, 0 — обычный; конверсии — по флагу своего клика) и дают алерт «Клики потока уходят на оффер на автопаузе health-check» — не чаще раза в час на поток (настройка flow_last_resort). Выключить для инстанса — INOTRACK_FLOW_LAST_RESORT_BROKEN=0 (по умолчанию 1); тогда такие клики снова идут на общий адрес. Флаг читает каждый процесс: edge-ноды принимают клики сами, и на них его нужно задать в окружении ноды отдельно. Edge-нода выбирает «последний шанс» по тем же правилам: капу, ручную паузу и паузу рекламодателя видит в снимке central, а в автономном режиме клики на такой оффер расходуют её лиз капы, как обычные (оценка — клики × CR оффера за сутки и постбеки). Нода старше этой функции ведёт такие клики на общий адрес, как раньше.

Алерт smartlink_last_resort сообщает о реальном выборе смартлинком оффера на автопаузе health-check при пустом fallback_url. Central и edge агрегируют клики за минуту; антиспам — раз в час на смартлинк независимо от ноды. Проверьте ссылку оффера, верните его в ротацию или задайте fallback_url/default_offer_id. Флаг INOTRACK_FLOW_LAST_RESORT_BROKEN=0 отключает последний шанс и новые уведомления.

«Последний шанс» смартлинка. То же правило — у смартлинка /s/{token}, у которого под запрос не осталось ни одного доступного оффера: если запасной адрес смартлинка (fallback_url) не задан, клик уходит на его собственный оффер на автопаузе health-check, а не теряется (204). Берутся только офферы самого смартлинка — явные привязки и дефолтный оффер; офферы из общего пула, капа, ручная пауза, пауза рекламодателя, архив и broken, выставленный вручную, так не используются. Если fallback_url задан, поведение прежнее: явный выбор оператора не подменяется. Такие клики считаются (inotrack_smartlink_last_resort_total{smartlink}) и видны в статистике смартлинка как результат «Последний шанс» (smartlink_clicks.result = 'last_resort'); в симуляторе подбора — та же подпись. Выключается тем же флагом инстанса INOTRACK_FLOW_LAST_RESORT_BROKEN=0.

Серверный клик и автопауза. POST /api/v1/clicks трафик не подменяет: если оффер снят только автопаузой health-check, ответ — 409 offer_autopaused (раньше это выглядело как offer_paused или даже offer_capped), клик не создаётся. Витрина решает сама: скрыть оффер, отправить анкету в другую сеть или повторить позже. Анкета — это почти лид, поэтому молча уводить её в оффер, который проверка считает нерабочим, трекер не станет.

Внутренний тюнинг — не трогать без явной причины

Буфер приёма кликов (INOTRACK_CLICK_FLUSH_MS, _SHUTDOWN_FLUSH_MS, _RING_SIZE, _BATCH_SIZE, _OVERFLOW_QUEUE), пути на диске (INOTRACK_WAL_PATH, _STORAGE_PATH, _GEOIP_PATH), уровень логов (INOTRACK_LOG_LEVEL, дефолт info), экспозиция метрик (INOTRACK_METRICS, дефолт 1). Дефолты рассчитаны под продакшен-нагрузку и проверены нагрузочными тестами; меняйте только если tracker-ctl doctor или метрики явно показывают нехватку.

INOTRACK_WEBHOOK_SHUTDOWN_GRACE (дефолт 10s, потолок 15s) — сколько при остановке процесса (раскатка, рестарт) уже начатая доставка вебхука может доработать: за это время ответ получателя успевает записаться, и вебхук не уходит повторно. С момента остановки модуля API (после закрытия HTTP-серверов) новые задания не берутся, не начатые сразу возвращаются в очередь — их заберёт другой процесс. Формат — длительность (5s, 0 — обрывать сразу). Значение входит в срок остановки пода (terminationGracePeriodSeconds), поэтому выше потолка не поднимается. В docker-compose срок остановки контейнера не задан (дефолт Docker — 10 с) и переменная в контейнер не пробрасывается: если процесс убит раньше, чем дождался получателя, задание вернётся в очередь по истечении аренды (10 минут), как при падении.

⚠️ Отдельно — INOTRACK_CLICK_CONFIRM_INSERT (дефолт true): это не только про производительность, а про то, теряются ли клики молча. Соединение с ClickHouse асинхронное (async_insert=1), и без подтверждения сервер может отчитаться «ок», ещё не записав батч физически — при падении ClickHouse ровно в этот момент партия пропадает бесследно, мимо и ClickHouse, и WAL (подробности и измерения — FAQ, раздел про недоступность ClickHouse). =false — сознательный выбор оператора ради пропускной способности выше типичной нагрузки (~500–640 тыс. вставок/с вместо ~130–150 тыс./с), а не что-то, что стоит выключать по умолчанию.

Сложность проверки браузера (guard)

INOTRACK_GUARD_POW_BITS — сложность proof-of-work в челлендже guard, число ведущих нулевых бит (в среднем 2^N попыток в браузере). Необязательная, дефолт 14. Допустимый диапазон — 8–24: значение ниже 8 поднимается до 8, выше 24 опускается до 24, не число — действует дефолт 14; в каждом из этих случаев в лог пишется WARN. Значение читается лениво — при первом челлендже/проверке куки, а не при старте процесса, поэтому WARN появляется в логе только после первого запроса, попавшего под challenge (на инстансе без такого трафика его не будет). Для смены нужен рестарт.

Справочник прочих переменных

Переменные, которых нет в deploy/.env.example: дефолты подходят большинству установок, задавайте их в окружении процесса (docker-compose, манифест k8s), только если нужно поменять поведение. Числа — целые, интервалы без суффикса — в единицах из имени (_SEC, _MS, _MIN), длительности с суффиксом — в формате Go (30s, 5m, 1h). Недопустимое значение (не число, 0 или меньше, где это не оговорено отдельно) — действует дефолт.

Edge-ноды

Переменная По умолчанию Назначение
INOTRACK_EDGE_AUTONOMOUS_AFTER 30s Сколько нода живёт без успешного config/heartbeat до перехода в автономный режим; длительность > 0, иначе дефолт
INOTRACK_EDGE_BLOB_CACHE_MB 2048 Лимит дискового кэша блобов контента (лендов) на ноде, МБ; целое > 0
INOTRACK_EDGE_BLOB_MEM_MB 256 Лимит памяти под блобы лендов на ноде, МБ; при лимите контейнера меньше 1 ГБ возможен OOM-kill.
INOTRACK_EDGE_CENTRAL_URL пусто → INOTRACK_PUBLIC_URL Адрес central, который подставляется в однострочную команду установки и снимок нод; пусто — публичный URL central
INOTRACK_EDGE_CONTROL_TIMEOUT 10s Таймаут обычных управляющих HTTP-запросов агента в central; длительность > 0
INOTRACK_EDGE_CREDENTIAL_PATH /var/lib/inotrack/edge/credential Путь к файлу credential ноды (0600); tracker-ctl читает его оттуда при обновлении ноды
INOTRACK_EDGE_EPC_REFRESH 10m Период перезагрузки EPC офферов из ClickHouse для снимка edge-нод; длительность > 0
INOTRACK_EDGE_EVENT_RETRY_MAX 1m Потолок экспоненциальной паузы между повторами отправки спула событий в central (или Retry-After, если больше); длительность > 0
INOTRACK_EDGE_EVENT_SHIP_INTERVAL 5s Интервал отправки спула событий в central и базовая пауза бэкоффа; длительность > 0
INOTRACK_EDGE_EVENT_SPOOL_MB 256 Лимит дискового спула событий ноды, МБ; целое > 0
INOTRACK_EDGE_HEARTBEAT_SEC 15 Интервал heartbeat, который central выдаёт нодам при регистрации/в конфиге, секунды; целое > 0
INOTRACK_EDGE_HTTP_TIMEOUT_MS 5000 Таймаут HTTP-запроса репликации кликов в central, мс; целое > 0
INOTRACK_EDGE_INGEST_MAX_BODY_BYTES 33554432 (32 МиБ) Лимит сжатого тела запроса приёма реплицированных кликов, байты; целое > 0
INOTRACK_EDGE_INGEST_MAX_DECOMPRESSED_BYTES 268435456 (256 МиБ) Лимит объёма тела приёма кликов после распаковки gzip (защита от zip-бомб), байты; целое > 0
INOTRACK_EDGE_JOIN_TOKEN пусто Одноразовый токен регистрации ноды в central; нужен только до появления credential
INOTRACK_EDGE_LEGACY_TOKEN пусто (legacy-токен принимается, если задан INOTRACK_CENTRAL_TOKEN) 0 — перестать принимать от нод общий legacy INOTRACK_CENTRAL_TOKEN (deprecated), только credential нод
INOTRACK_EDGE_PB_MAX_AGE 168h (7 суток) Максимальный возраст постбека в спуле ноды; старше — отбрасывается без пересылки в central.
INOTRACK_EDGE_PB_REPLAY_INTERVAL 10s Период переигрывания спула постбеков в central; длительность > 0
INOTRACK_EDGE_PB_SPOOL_MAX_BYTES 67108864 (64 МиБ) Лимит дискового спула постбеков ноды, байты; целое > 0
INOTRACK_EDGE_PB_TIMEOUT 5s Таймаут пересылки одного постбека в central; длительность > 0
INOTRACK_EDGE_POLL_SEC 5 Пауза перед повтором long-poll конфига после ошибки, которую central выдаёт нодам, секунды; целое > 0
INOTRACK_EDGE_RECONCILE_DRAIN_TIMEOUT 5m Сколько сверка после восстановления связи ждёт слива спула кликов, прежде чем продолжить; длительность > 0
INOTRACK_EDGE_REVOKE_GRACE 1h Сколько нода после отзыва credential ещё принимает клики (/healthz/edge уже 503, постбеки не пересылаются); 0 — останавливаться сразу.
INOTRACK_EDGE_SEND_BATCH_MAX 2000 Максимум кликов в одном HTTP-запросе репликации в central; целое > 0
INOTRACK_EDGE_SEND_INTERVAL_MS 2000 Интервал отправки спула кликов в central, мс; целое > 0
INOTRACK_EDGE_SHUTDOWN_FLUSH_MS 10000 Сколько при остановке процесса пытаться дослать спул кликов в central, мс; целое > 0
INOTRACK_EDGE_SPOOL_PATH <WAL>/edge.spool (WAL — каталог), иначе <WAL>.edge-spool; без WAL — во временном каталоге Путь к файлу спула репликации кликов
INOTRACK_EDGE_SSH_ALLOW_PRIVATE пусто (выкл.) 1/true/TRUE/yes — разрешить SSH-установку на приватные/внутренние адреса (снимает SSRF-фильтр)
INOTRACK_EDGE_STATE_DIR /var/lib/inotrack/edge Каталог состояния агента ноды (credential, спулы); должен быть доступен на запись uid 65532; tracker-ctl читает его при обновлении ноды.

Трекер

Переменная По умолчанию Назначение
INOTRACK_ADMIN_PUBLIC_URL пусто → INOTRACK_PANEL_URL → https://$PANEL_DOMAIN Публичный базовый URL панели (http/https, / в конце срезается): кнопка «открыть в панели» в Telegram-алертах и хост панели для изоляции загруженного контента (CSP sandbox).
INOTRACK_API_MAX_JSON_BYTES 8388608 (8 МиБ) Лимит тела JSON-запроса к API трекера, байты; нечисло или ≤0 — дефолт. Пакетные операции сохраняют свой лимит 1 МиБ.
INOTRACK_AUTH_REFRESH_IDEM_WINDOW_SEC 20 Окно идемпотентности ротации refresh-токена в Redis: параллельные повторы refresh одного токена получают тот же результат.
INOTRACK_AUTOPICK_INTERVAL_MIN 10 Период фонового пересчёта автоподбора (также запускается по событиям с debounce).
INOTRACK_BOT_FEED_ALLOW_CIDRS пусто CIDR/IP через запятую из частных/CGNAT-сетей, откуда разрешено скачивать фиды; loopback, link-local и прочие служебные адреса не открывает.
INOTRACK_BOT_FEED_AWS_URL встроенный URL AWS Переопределяет URL встроенного фида диапазонов датацентра AWS; пусто — фид выключен.
INOTRACK_BOT_FEED_BINGBOT_URL встроенный URL Bing Переопределяет URL встроенного фида IP-диапазонов Bingbot; пусто — фид выключен.
INOTRACK_BOT_FEED_DIGITALOCEAN_URL встроенный URL DigitalOcean Переопределяет URL встроенного фида диапазонов датацентра DigitalOcean; пусто — фид выключен.
INOTRACK_BOT_FEED_GOOGLEBOT_URL встроенный URL Google Переопределяет URL встроенного фида IP-диапазонов Googlebot; пусто — фид выключен.
INOTRACK_BOT_FEED_HETZNER_URL пусто (статические диапазоны) URL фида диапазонов Hetzner; по умолчанию работают только встроенные статические диапазоны.
INOTRACK_BOT_FEED_OVH_URL пусто (статические диапазоны) URL фида диапазонов OVH; по умолчанию работают только встроенные статические диапазоны.
INOTRACK_BOT_FEED_TOR_EXIT_URL встроенный URL Tor Project Переопределяет URL встроенного списка выходных узлов Tor; пусто — фид выключен.
INOTRACK_BOT_FEED_YANDEXBOT_URL пусто (фид выкл.) URL фида IP-диапазонов YandexBot; по умолчанию фида нет, задание URL включает его.
INOTRACK_BOT_FREQ_IP_MAX 30 (0 — выкл.) Порог кликов с одного IP за окно, сверх которого клик получает сигнал «поведенческий бот».
INOTRACK_BOT_FREQ_IP_WINDOW_SEC 60 Окно (фиксированное, Redis) счётчика кликов с одного IP для поведенческого бот-сигнала.
INOTRACK_BOT_FREQ_SUBNET_MAX 150 (0 — выкл.) Порог кликов с одной подсети за окно, сверх которого клик получает сигнал «поведенческий бот».
INOTRACK_BOT_FREQ_SUBNET_WINDOW_SEC 60 Окно (фиксированное, Redis) счётчика кликов с одной подсети (/24 IPv4, /48 IPv6).
INOTRACK_BOT_RATELIMIT_MAX 50 (0 — выкл.) Максимум запросов клик-пути с одного IP за окно, сверх — 429.
INOTRACK_BOT_RATELIMIT_WINDOW_SEC 10 Окно rate-limit запросов клик-пути по IP (Redis INCR+EXPIRE); оно же Retry-After в ответе 429.
INOTRACK_BOT_RDNS_CACHE_SIZE 65536 Размер локального кэша вердиктов (IP и подсети вместе), записей.
INOTRACK_BOT_RDNS_MAX_INFLIGHT 256 Максимум одновременных фоновых проверок на процесс (и не больше 8 на подсеть).
INOTRACK_BOT_RDNS_NEG_TTL_SEC 3600 Сколько хранится отрицательный вердикт обратной DNS-проверки, секунды.
INOTRACK_BOT_RDNS_TIMEOUT_MS 1000 Бюджет фоновой DNS-проверки, мс; на задержку клика не влияет.
INOTRACK_BOT_RDNS_TTL_SEC 86400 Сколько хранится положительный вердикт обратной DNS-проверки поискового бота, секунды.
INOTRACK_CAPS_REFRESH_SEC 20 Период перечитывания конфигов капов офферов из PG с согласованием пауз и восстановлением упёршихся в кап офферов.
INOTRACK_CENTRAL_IP пусто → самый частый A-IP пула доменов → A-запись хоста INOTRACK_PUBLIC_URL Публичный IPv4 сервера, подсказываемый как A-запись для новых доменов; невалидный IPv4 игнорируется.
INOTRACK_CH_HTTP_ADDR хост первого native-адреса ClickHouse + :8123, иначе localhost:8123 HTTP-эндпоинт ClickHouse (host:port) для выгрузки Parquet.
INOTRACK_CH_HTTP_TLS пусто (http) 1 — ходить к HTTP-эндпоинту ClickHouse экспорта по https.
INOTRACK_CLICK_DRAIN_MULTIPLIER 32 Сколько пачек INOTRACK_CLICK_BATCH_SIZE вычитывается из кольца за один тик сброса.
INOTRACK_CLICK_OVERFLOW_QUEUE 16384 Ёмкость канала переполнения для кликов, не влезших в кольцо (пишутся в WAL пачками); при заполнении клики теряются (счётчик clicksLost).
INOTRACK_CLICK_RING_SIZE 65536 Ёмкость in-memory кольцевого буфера кликов.
INOTRACK_COSTSYNC_MIN_DELAY_MS 1200 Минимальная пауза между последовательными запросами синхронизации расходов к API одного вида интеграции (рекламной площадки).
INOTRACK_GA4_CLIENT_ID пусто → INOTRACK_GSC_CLIENT_ID OAuth2 client ID Google-приложения для обновления токенов интеграции GA4 через https://oauth2.googleapis.com/token.
INOTRACK_GA4_CLIENT_SECRET пусто → INOTRACK_GSC_CLIENT_SECRET OAuth2 client secret Google-приложения для обновления токенов GA4.
INOTRACK_GSC_CLIENT_ID пусто OAuth2 client ID общего Google-приложения: обновляет токены Search Console (и GA4, если не задан отдельный INOTRACK_GA4_CLIENT_ID), а также обслуживает кнопку «Войти через Google» в разделе «Интеграции» панели. Authorized redirect URI в консоли Google — https://<панель>/api/integrations/oauth/<kind>/callback, где <kind> = google_search_console или ga4. Не задано — кнопка отвечает понятной ошибкой, остальные интеграции не затронуты.
INOTRACK_GSC_CLIENT_SECRET пусто OAuth2 client secret общего Google-приложения, см. INOTRACK_GSC_CLIENT_ID.
INOTRACK_GUARD_AUTOBLOCK_INTERVAL_SEC 300 (минимум 60) Период прохода автоблока зон с высокой долей ботов (notify — алерт, auto — блокировка); при нескольких репликах тик выполняет одна (advisory lock).
INOTRACK_GUARD_REFRESH_SEC 30 Период перечитывания правил guard из PG в кэш процесса, секунды; изменения из панели применяются сразу через Redis, тик — страховка.
INOTRACK_K8S_POOL_SERVICE_PORT 8080 Порт Service бэкенда в Ingress пула трекер-доменов.
INOTRACK_MOBILE_PUSH пусто (= relay) Транспорт мобильных пушей: log — только запись в лог процесса; пусто/relay — через релей license-server (без лицензии всё равно лог); прочие значения → warning и релей.
INOTRACK_PG_MAX_CONNS 20 Размер общего пула Postgres процесса, границы 4..100; в k8s tracker-core — 12. При расчёте бюджета соединений учитывайте размер пула и максимальное число процессов tracker-core и workers.
INOTRACK_POSTBACK_RATELIMIT_KNOWN_MAX 6000 Лимит постбеков за окно на пару (IP, токен) для активного токена с IP вне IPAllowlist эндпоинта.
INOTRACK_POSTBACK_RATELIMIT_TRUSTED_MAX 60000 Лимит постбеков за окно на пару (IP, токен), когда токен активен и IP явно в IPAllowlist эндпоинта.
INOTRACK_POSTBACK_RATELIMIT_WINDOW_SEC 60 Фиксированное окно (Redis INCR+EXPIRE) для всех трёх уровней лимита постбеков.
INOTRACK_POSTBACK_INBOUND_LEASE_SEC 600 Аренда очереди приёма и TTL блокировки дедупликации в секундах. Невалидные/неположительные значения дают дефолт. Занижение опасно перехватом живой записи; для e2e и ускоренного восстановления.
INOTRACK_POSTBACK_RATE_LIMIT 600 Лимит запросов /pb/{token} с одного IP за окно для неизвестного/неактивного токена (защита от перебора токенов), сверх — 429.
INOTRACK_PROTECTED_MODULES пусто (выкл.) 1 — вычисления отчётов выполняет защищённый WASM-модуль с license-server; без клиента лицензии отчёты недоступны.
INOTRACK_PROTECTED_REFRESH_MIN 360 Период проверки новой версии защищённого модуля на license-server (подмена без рестарта).
INOTRACK_PUSH_CHRONIC_FAILURE_STREAK 5 (0 — выкл.) Сколько подряд неудачных (не-410) отправок без успеха — и web-push подписка деактивируется очисткой.
INOTRACK_PUSH_CLEANUP_INTERVAL_HOURS 24 часа Период очистки мёртвых web-push подписок (первый проход сразу при старте). Внимание: заданное значение читается как секунды, а не часы (24 — раз в 24 секунды); чтобы получить сутки, задайте 86400 или не задавайте переменную.
INOTRACK_PUSH_CLEANUP_RETENTION_DAYS 90 Сколько дней хранить деактивированную подписку после смерти до удаления строки.
INOTRACK_PUSH_DISPATCH_INTERVAL_SEC 5 Период разбора очереди отправки web-push (пачка 50).
INOTRACK_PUSH_RATELIMIT_MAX 20 (0 — выкл.) Максимум запросов с одного IP за окно к публичным /push/subscribe, /push/unsubscribe, /push/click/{token}, сверх — 429.
INOTRACK_PUSH_RATELIMIT_WINDOW_SEC 60 Фиксированное окно (Redis) лимита публичных push-эндпоинтов; оно же Retry-After.
INOTRACK_PUSH_SCHEDULER_INTERVAL_SEC 60 Период проверки, не пора ли запустить push-кампании по расписанию.
INOTRACK_PUSH_SEND_SUB sub10 Query-параметр (sub-слот), в который при клике по пушу пишется ID отправки.
INOTRACK_PUSH_SOURCE_SUB sub9 Query-параметр (sub-слот), в который при клике по пушу пишется метка источника push/push-tx.
INOTRACK_RANKTRACKER_SCAN_INTERVAL_SEC 300 Период сканирования очереди фраз, готовых к съёму позиций (частота съёма фразы задаётся отдельно).
INOTRACK_TELEGRAM_API_BASE пусто (https://api.telegram.org) Базовый URL Telegram Bot API: прокси с тем же протоколом там, где Telegram заблокирован, или фейковый сервер в E2E.
INOTRACK_YANDEXMETRIKA_CLIENT_ID пусто → INOTRACK_YANDEX_CLIENT_ID Client ID OAuth-приложения Яндекса для обновления токенов Яндекс.Метрики через https://oauth.yandex.ru/token.
INOTRACK_YANDEXMETRIKA_CLIENT_SECRET пусто → INOTRACK_YANDEX_CLIENT_SECRET Client secret OAuth-приложения Яндекса для обновления токенов Яндекс.Метрики.
INOTRACK_YANDEX_CLIENT_ID пусто Client ID общего OAuth-приложения Яндекса: обновляет токены Директа/Метрики/Вебмастера (если не задан свой INOTRACK_YANDEXMETRIKA_CLIENT_ID/INOTRACK_YANDEXWEBMASTER_CLIENT_ID), а также обслуживает кнопку «Войти через Яндекс» в разделе «Интеграции» панели. Authorized redirect URI в настройках приложения на oauth.yandex.ru — https://<панель>/api/integrations/oauth/<kind>/callback, где <kind> = yandex_webmaster, yandex_metrika или yandex_direct. Не задано — кнопка отвечает понятной ошибкой, остальные интеграции не затронуты.
INOTRACK_YANDEX_CLIENT_SECRET пусто Client secret общего OAuth-приложения Яндекса, см. INOTRACK_YANDEX_CLIENT_ID.
INOTRACK_VK_CLIENT_ID пусто Необязательный client ID OAuth-приложения VK Рекламы: обновление токенов через https://ads.vk.ru/api/v2/oauth2/token.json и кнопка «Войти через VK». Redirect URI приложения: https://<панель>/api/integrations/oauth/vk_ads/callback. Без кредов приложения OAuth-подключение VK и обновление токенов недоступны; остальные интеграции работают.
INOTRACK_VK_CLIENT_SECRET пусто Необязательный client secret OAuth-приложения VK Рекламы, см. INOTRACK_VK_CLIENT_ID.

Дальше

Установка — /docs/install/. Честные ответы на вопросы про надёжность — FAQ.

Перенос истории атрибуции

INOTRACK_CH_BACKUP_METRICS_URL — необязательный внутренний HTTP(S) endpoint метрик Prometheus clickhouse-backup (CronJob в namespace инстанса; задаётся в окружении инстанса). Операция читает время успешного завершения бэкапа с таймаутом 5 секунд. Резервные источники: system.backup_log, затем явное RFC3339 backup_verified_at в аудите. Без свежего бэкапа в пределах max_backup_age (по умолчанию 26h) запись не начинается. Сначала выполните запрос с dry_run:true, проверьте plan, recovery_days и backup.fresh, затем повторите его с dry_run:false. Сохраните запрос и ответ; при частичном сбое повторите идентичный запрос для завершения пересборки.

Приложения и ключи

Администратор настраивает ключи инстанса в Настройки → Приложения и ключи (/settings#apps). Здесь доступны OAuth-приложения Google (Search Console, Analytics 4, Google Ads), Яндекса (Директ, Метрика, Вебмастер), VK Рекламы, Telegram для алертов, ключи TronGrid/Etherscan/BscScan и лицензия MaxMind для загрузки GeoIP.

Значение из панели приоритетнее env и применяется без рестарта. Секреты доступны только для записи: после сохранения отображаются статус и маска. Пустое секретное поле не меняет значение; «Очистить: вернуть к env» удаляет переопределение из панели и снова использует env. Для GA4, Метрики и Вебмастера можно задать отдельное приложение; иначе используется общее приложение провайдера. Сначала сохраните изменения, затем проверьте приложение или отправьте тест Telegram.

Google: Google Cloud Console → APIs & Services → Credentials → OAuth client ID (Web application). Яндекс: oauth.yandex.ru, с правами нужных сервисов. VK: ads.vk.com → Настройки → Доступ к API. Скопируйте redirect URI каждого сервиса из панели: https://<панель>/api/integrations/oauth/<kind>/callback. Токен Telegram выдаёт @BotFather; добавьте бота в чат и укажите chat id.

Env остаётся запасным вариантом: INOTRACK_GSC_*, INOTRACK_GA4_*, INOTRACK_YANDEX*, INOTRACK_GOOGLEADS_*, INOTRACK_VK_*, INOTRACK_TELEGRAM_TOKEN, INOTRACK_TELEGRAM_CHAT, INOTRACK_FINANCE_*, INOTRACK_GEOIP_MAXMIND_LICENSE_KEY. Ключи шифрования, DSN, JWT и лицензия самого инстанса по-прежнему задаются только в env; этот раздел их не заменяет.

Исходящий прокси к геоблокированным API

Вариант A: поднимите доверенный HTTP CONNECT-прокси (3proxy/tinyproxy) на RU-ноде с доступом к API. Обязательны авторизация, firewall только с IP трекера и ACL прокси только для api.webmaster.yandex.net:443 и oauth.yandex.ru:443. Не открывайте публичный прокси. При передаче авторизации через недоверенную сеть используйте HTTPS-прокси или защищённый туннель.

Вариант B: WireGuard до подходящей ноды и отдельный маршрут к API либо локальный CONNECT-прокси на ноде через туннель. Проверьте DNS и обновление маршрутов при изменении адресов API. SOCKS5 не поддерживается; используйте HTTP/HTTPS CONNECT.

В «Настройки → Приложения → Исходящий прокси» сохраните URL в секретном поле и список хостов через запятую/пробел. Альтернатива: INOTRACK_EGRESS_PROXY_URL и INOTRACK_EGRESS_PROXY_HOSTS. По умолчанию проксируются только API Вебмастера и OAuth Яндекса; остальные запросы этого транспорта идут напрямую. Панель имеет приоритет над env, секрет хранится зашифрованным, изменения применяются без рестарта в пределах 12 секунд. Пакет netguard для пользовательских сетевых URL сохраняет собственную политику.

Проверка готовности:

  1. Нажмите «Проверить доступ». API POST /api/v1/settings/egress/check доступен только admin; без тела проверяет https://api.webmaster.yandex.net/v4/user. Ответ показывает маску прокси, HTTP-статус и задержку. 401 допустим: проверка идёт без токена. 403/5xx требуют проверки ограничений; транспортная ошибка даёт 502. Редиректы не выполняются. Без прокси проверяется прямое соединение.
  2. В «Интеграции → Поисковые консоли» получите список сайтов с действующим OAuth-токеном. При необходимости переподключите консоль и проверьте обновление токена через oauth.yandex.ru.
  3. Запустите ручной SEO-импорт за период с данными и проверьте строки соответствующего сайта/дней в seo_query_stats. Пустой период не подтверждает импорт.
  4. Зафиксируйте только время, HTTP-статус, число сайтов/импортированных строк. Не логируйте URL с паролем, Proxy-Authorization, Authorization, OAuth-коды и токены.

Локальные тесты проверяют CONNECT-туннель и импорт фикстур через прокси. Они не подтверждают доступ реальной RU-ноды или права реального аккаунта; эти проверки выполняются на инстансе после настройки маршрута.

Поддержка