Mirror Manager — источник правды по зеркалам riobet.com: какие домены есть, доступны ли они, заблокированы ли РКН, и когда нужно поднять новое зеркало вместо заблокированного.
status/weight заводятся и меняются вручную операторами прямо здесь. is_blocked ставится либо вручную через кнопку RKN-BAN, либо автоматически через uwuaff check — но только в сторону блокировки: снять is_blocked может исключительно оператор кнопкой Un-ban, автоматика этого никогда не делает.
На основе is_blocked сервис сам решает, нужно ли выпускать новое зеркало (см. Groups), и запускает для этого пайплайн automirrors-crypt в GitLab.
Дальше по цепочке: отдельный проект seo-frontend-nginx раз в час опрашивает mirrors-service (GET /api/v1/get-mirrors) и сам переключает is_active/redirect_to в своём inventory.yml, разворачивая nginx-конфиги на реальных серверах. Mirror Manager ничего не деплоит сам — он только решает и хранит статус.
| Колонка | Что значит | Кто проставляет |
|---|---|---|
| status | available / unavailable — включено ли зеркало в принципе. Это главный переключатель "в ротации / нет". | Только вручную — через Edit или кнопку Enable/Disable. |
| Active | Реально ли зеркало сейчас может отдавать трафик = status = available И НЕ is_blocked. Это то, что практически совпадает с is_active в inventory.yml у seo-frontend-nginx. |
Пересчитывается сервисом при каждом RKN-BAN/ручном изменении статуса. Отдельно не редактируется. |
| Blocked | Заблокирован ли домен по РКН. | Кнопкой RKN-BAN (оператор), либо автоматически — uwuaff check сам жмёт RKN-BAN, если находит новую блокировку. Un-ban снимает блок независимо от того, кто его поставил. |
| 📌 (значок у статуса) | Manual override — статус зафиксирован вручную оператором. Снимается кнопкой Un-pin. | Ставится автоматически, когда вы реально меняете status через Edit или кнопку Enable/Disable (просто пересохранение формы без смены статуса пин не ставит). |
| uwuaff | Чисто информационная колонка — свой собственный статус блокировки домена по данным api.uwuaff.com (см. uwuaff check). Никак не влияет на Blocked/is_blocked и автоматику Groups — это делает только RKN-BAN. |
Автоматически, по расписанию (раз в uwuaff_check_interval_seconds, Settings), либо вручную кнопкой "Check now" в Settings. |
| Kuma | Есть ли у домена монитор в Uptime Kuma (см. Kuma check). automirrors-crypt создаёт монитор при провижене; mirrors-service только удаляет его, когда зеркало становится неактивным. | Реактивно (сразу при деактивации зеркала) и по расписанию (раз в kuma_check_interval_seconds, Settings), либо вручную кнопкой "Check now". |
seo-frontend-nginx (или запустить его пайплайн вручную) — он увидит status=unavailable, выберет живого соседа по группе (riobet40x/riobet_1000) как redirect_to и задеплоит редирект.Ручной приоритет (меньше — важнее). Заводится и меняется вручную, ничем автоматически не перезаписывается.
| Значение | Смысл |
|---|---|
-9 | Основное зеркало |
-5 | В очереди / запасное |
-1 | Fallback, последний вариант |
Group — это политика, по которой mirrors-service сам решает, когда запускать пайплайн automirrors-crypt и выпускать новый домен. Оценивается сразу после каждого RKN-BAN.
Пример: группа riobet40x, target_pool_size = 8. Если из доменов с тегом riobet40x доступно (available, не заблокировано, не disabled) меньше 8 — заказывается разница (deficit), с учётом уже выпускаемых (provisioning, чтобы не заказать дважды). Новые домены берут следующий свободный номер после максимального когда-либо использованного в диапазоне number_min–number_max — номера не переиспользуются.
Пример: цепочка riobet2000. "Головой" считается домен с максимальным номером среди немаркированных как provisioning. Пока голова не заблокирована — ничего не происходит. Как только голову блокируют — заказывается следующий по номеру домен, а голова становится predecessor (на неё ставится redirect_to в inventory.yml при онбординге).
range_exhausted — если number_max исчерпан, а зеркало всё ещё нужно, автоматика останавливается и шлёт алерт в Slack; поднимите number_max в Groups.
Кнопка Preview plan в Groups показывает точно то же решение, что приняла бы автоматика прямо сейчас — без побочных эффектов (ничего не создаёт, пайплайн не запускает). Полезно проверить перед тем, как включать enabled или прописывать GitLab-токен.
Зеркала выпускаются по одному на всю систему, а не по одному на группу: два одновременных пайплайна automirrors-crypt будут драться за один и тот же свободный IP при выборе (pick_free_ip()), поэтому пока хоть одно зеркало в статусе provisioning — ни одна другая группа ничего не заказывает. В Preview plan это видно как причина вида "another mirror is currently provisioning in a different group".
Соответственно, если блокировки пришли пачкой (например, uwuaff нашёл сразу пять), то преемник закажется только для первой группы, а остальные отложатся. Дальше отложенная очередь разгружается сама, по одному, тремя способами:
provisioning: seo-frontend-nginx пометил его available после своей health-проверки, оператор удалил зависшую запись, или зафиксировал её через Set Active. Это основной путь: следующее зеркало уходит в выпуск сразу же, без ожидания.pool_sweep_interval_seconds (по умолчанию 300с = 5 минут). Это сейф-нет для случаев, которые реактивный хук увидеть не может: пайплайн умер на середине и зеркало так и не стало available, или запрос в GitLab вообще не ушёл. Без него очередь встала бы до следующего бана.POST /api/v1/groups/evaluate), если нужно слить очередь немедленно.Любой из трёх запускает максимум один пайплайн за раз — сколько бы групп ни ждало. Зеркало, зависшее в provisioning дольше часа, подсвечивается на главной оранжевым и счётчиком в шапке: это как раз тот случай, когда очередь стоит из-за него и запись нужно поправить или удалить руками.
Кнопка в таблице Mirrors (напротив каждого зеркала). В отличие от Disable/Enable, она трогает не status, а is_blocked — а редирект на стороне seo-frontend-nginx ориентируется только на status. Поэтому зеркало продолжает отдаваться в выдаче как обычно, а Groups оцениваются немедленно — если зеркало было головой цепочки, преемник закажется прямо сейчас.
Тот же самый эффект (is_blocked=true + немедленная оценка Groups) может сработать автоматически — см. uwuaff check ниже, который жмёт эту кнопку сам, когда находит новую блокировку. Разница видна только в block_type (manual vs uwuaff) и в колонке Trigger на странице Runs (manual vs scheduled).
issued: riobet1003.com), и пишет запись в job rkn_ban на странице Runs.issued: none. Это нормально: очередь разгрузится сама, как только текущее зеркало встанет (см. "Выпуск строго по одному").Требования те же, что и для автовыпуска: зеркало должно быть протегировано mirror_tag нужной Group, а сама Group — существовать и быть enabled. Если группы нет или тега нет — кнопка отработает, но в Runs будет issued: none.
Раз в uwuaff_check_interval_seconds (по умолчанию 900с = 15 минут) сервис опрашивает GET api.uwuaff.com/v1/domains — это тот же uwuaff, куда seo-frontend-nginx регистрирует каждый домен (register-mirror-domain.yml/trigger-frontend-deploy.yml) и который сам гоняет собственные проверки на блокировку (RKN, прокси и т.д.). Результат по каждому известному домену сохраняется в колонку uwuaff.
Затем, если uwuaff нашёл новую блокировку (is_active: false) у зеркала, чьи теги пересекаются с mirror_tag какой-либо включённой Group, и которое ещё не is_blocked здесь — сервис сам жмёт RKN-BAN за оператора (block_type = 'uwuaff'): выставляет is_blocked=true и сразу оценивает Groups, заказывая преемника, если нужно. Зеркала вне какой-либо включённой Group авто-баном не трогаются — это касается и зеркал вообще без тегов, и зеркал с тегами, не связанными ни с одной Group (например, тег api у API/LB-хостов) — им просто некому заказать преемника, колонка uwuaff у них остаётся чисто информационной.
Важно про направление: это одностороннее действие — только на блокировку, никогда обратно. Ничего в uwuaff-чеке само не снимает is_blocked; это по-прежнему делает только оператор кнопкой Un-ban. Так сделано намеренно: раньше в проекте уже был похожий автоматический чек (по другому сервису, dump.allury.tech), который писал is_blocked в обе стороны — и молча снимал ручные RKN-BAN'ы, если сам не находил блокировку. Именно эта реверсия и была проблемой, от неё отказались; новый uwuaff-чек её не повторяет — он может только поставить блок, никогда не снять.
Побочный эффект одностороннего дизайна: если оператор нажмёт Un-ban на зеркале, которое uwuaff всё ещё считает заблокированным, следующий плановый чек (до 15 минут) снова поставит is_blocked=true. Чтобы бан не возвращался — либо дождитесь, пока uwuaff сам перестанет считать домен заблокированным, либо временно выключите чек в Settings.
is_active: false в его ответе). Наведите курсор — во всплывающей подсказке будет время проверки и текст причины от uwuaff (например, номер решения РКН).Каждый авто-бан пишет отдельную запись в job rkn_ban (trigger = scheduled) на странице Runs — как и ручной RKN-BAN, только с другим trigger. Сам опрос api.uwuaff.com пишется в job uwuaff_check.
Включается/выключается и настраивается интервал в Settings, там же кнопка Check now для ручного запуска и статус последнего прогона. Учётные данные (UWUAFF_TOKEN_URL/UWUAFF_CLIENT_ID/UWUAFF_CLIENT_SECRET/UWUAFF_API/UWUAFF_API_TOKEN) задаются переменными окружения контейнера — теми же GitLab CI/CD переменными, что уже использует seo-frontend-nginx, в UI не вводятся.
Когда automirrors-crypt поднимает новое зеркало, его собственная стадия kuma_monitor заводит HTTP-монитор в Uptime Kuma (https://{домен}/health). mirrors-service сам мониторы никогда не создаёт — только удаляет, и только когда зеркало становится неактивным.
Как срабатывает удаление: реактивно, сразу в момент, когда у зеркала is_active становится false — неважно из-за чего: ручной Disable, RKN-BAN (включая авто-бан от uwuaff), деактивация соседей при Set Active в chain, webhook /webhook/blocked. Плюс раз в kuma_check_interval_seconds (по умолчанию 900с = 15 минут) — плановая сверка: обновляет колонку Kuma для всех зеркал и подчищает мониторы, которые реактивная проверка могла пропустить (например, зеркала, ставшие неактивными ещё до появления этой функции).
Одностороннее действие, как и uwuaff-чек: только удаление, никогда не создаёт монитор обратно, даже если зеркало снова становится активным (Un-ban, повторный Enable). Если нужен монитор обратно — заводится вручную в самой Kuma или через новый прогон automirrors-crypt.
Включается/выключается и настраивается интервал в Settings, там же кнопка Check now. Учётные данные (KUMA_URL/KUMA_USER/KUMA_PASS) — переменные окружения контейнера, в UI не вводятся.
Enable/Disable и Set Active — два разных, независимых переключателя, специально разделены:
⛓ Groups, mode=chain). Жёстко фиксирует: это зеркало — available, а все остальные зеркала с тем же тегом (та же цепочка) — unavailable. Гарантирует инвариант "ровно один активный на цепочку" — все затронутые зеркала получают 📌 (manual override).Дальше это конвертируется в реальный редирект как обычно: sync_mirrors.py в seo-frontend-nginx видит, что "неактивные" зеркала группы теперь unavailable, и сам подставляет им redirect_to на активное — никаких изменений в seo-frontend-nginx для этого не потребовалось.
| Раздел | За что отвечает |
|---|---|
| Slack alerts | Куда слать: успешный/неуспешный запуск automirrors-crypt, исчерпание диапазона номеров группы. |
| automirrors-crypt | GitLab base URL / Project ID / Pipeline trigger token — которыми mirrors-service стучится в GitLab API, чтобы запустить пайплайн выпуска нового зеркала. Пока Project ID или токен не заполнены — автоматика не запускает пайплайны, только шлёт Slack-алерт вида "нужно перевыпустить вручную". |
| uwuaff domain-block check | Включить/выключить автоматический опрос api.uwuaff.com и задать интервал (см. выше), плюс статус/ошибка последнего прогона и кнопка Check now. |
| Uptime Kuma monitor cleanup | Включить/выключить автоматическую уборку мониторов и задать интервал (см. выше), плюс статус/ошибка последнего прогона и кнопка Check now. |
| Pool/chain sweep | Плановая переоценка Groups — сейф-нет, разгружающий отложенную очередь выпуска (см. "Выпуск строго по одному"), плюс статус/ошибка последнего прогона и кнопка Evaluate now для немедленного запуска. Выпускает максимум одно зеркало за прогон. |
| seo-frontend-nginx sync | Плановый запуск sync_mirrors-джобы в seo-frontend-nginx (по умолчанию раз в сутки) — тот же самый прогон, что и кнопка Sync now/ручной POST /seo-sync/run. Заведено 2026-09-17 после того, как собственный CI Schedule этой джобы в seo-frontend-nginx оказался ненадёжным — 24 хоста разъехались по is_active/redirect_to, один не трогался с июльской миграции. |
is_blocked=true, немедленно оцениваются все включённые Groups — если что-то нужно, тут же уходит запрос в GitLab на запуск automirrors-crypt.uwuaff_check_interval_seconds он опрашивает uwuaff check, обновляет колонку uwuaff, и при новой блокировке сам жмёт RKN-BAN (см. выше) — но только в сторону блокировки, никогда не снимает её.kuma_check_interval_seconds подчищает мониторы в Kuma, а раз в pool_sweep_interval_seconds переоценивает Groups заново — чтобы отложенная очередь выпуска (см. "Выпуск строго по одному") не стояла в ожидании следующего бана.provisioning — именно так следующее зеркало из очереди уходит в выпуск сразу же, а не по таймеру.seo_sync_interval_seconds (по умолчанию раз в сутки) планировщик отдельно дёргает sync_mirrors-джобу в seo-frontend-nginx — она сверяет is_active/redirect_to в inventory.yml с текущими статусами здесь и передеплоивает всё, что разошлось. Не связана с выпуском новых зеркал — это отдельный, более медленный контур на редиректы уже существующих доменов.Если внешняя система узнала о блокировке, можно дёрнуть эндпоинт напрямую — статус обновится сразу, не дожидаясь ручного RKN-BAN:
curl -X POST https://mirrors-service.rioorg.com/webhook/blocked \
-H "Content-Type: application/json" \
-d '{"domain": "riobet403.com"}'
Это не ставит 📌 manual override.
Полная история запусков — не только "последний раз", а каждый прогон отдельной строкой:
| Job | Что это |
|---|---|
| rkn_ban | Нажатие кнопки RKN-BAN — форсированный is_blocked=true и последующая оценка Groups. |
| automirrors_trigger | Одна попытка запустить пайплайн automirrors-crypt для конкретного домена/группы — сюда же попадает ошибка "не достучались до GitLab CI" (сеть/таймаут) и ошибка неверного статуса ответа GitLab (например, протухший токен). |
| seo_sync_trigger | Запуск ТОЛЬКО sync_mirrors-джобы в seo-frontend-nginx (см. ниже) — manual (кнопка "Sync now" в Settings) или scheduled (раз в seo_sync_interval_seconds). |
| uwuaff_check | Опрос api.uwuaff.com — scheduled (по интервалу) или manual (кнопка "Check now" в Settings). Информационный, ничего не выпускает. |
| kuma_check | Сверка и уборка мониторов в Uptime Kuma — scheduled или manual (кнопка "Check now"). |
| pool_sweep | Плановая переоценка Groups (см. "Выпуск строго по одному"). Пишется только когда выпускать было нечего (nothing due) или когда сам прогон упал — если зеркало реально заказано, это уже видно отдельной строкой automirrors_trigger, дублировать не нужно. |
Для каждой строки: время старта, длительность, ok/error, деталь (для automirrors_trigger — какой домен/группа), колонка Pipeline (ссылка на пайплайн в GitLab + его статус сразу после создания — created/pending) и полный текст ошибки по наведению.
Важно про статус пайплайна: Pipeline Trigger Token в GitLab умеет только запускать пайплайн, но не может потом прочитать его финальный результат (success/failed) — для этого нужен отдельный токен с правом чтения API, которого здесь нет. Поэтому колонка Pipeline показывает только начальный статус создания; реальный результат (прошёл ли деплой) смотрите по ссылке в самом GitLab.
Кнопка на странице Runs запускает исключительно джобу sync_mirrors в проекте seo-frontend-nginx через её собственный Pipeline Trigger Token. Данные берутся ровно те, что прямо сейчас лежат в mirrors-service (наш status/is_active — источник правды), включая любые ручные 📌-изменения.
Что делает эта джоба на стороне seo-frontend-nginx: читает GET /api/v1/get-mirrors отсюда, для каждого известного хоста сравнивает с текущим inventory.yml, и если статус разошёлся — проставляет is_active: true/false (и redirect_to для заблокированных, подбирая живого соседа по группе riobet40x/riobet_1000 — только среди хостов, которые сам знает, см. ниже), коммитит и деплоит nginx-конфиги на изменившихся хостах.
На странице Runs — две отдельные кнопки, специально разделённые, чтобы не катить вслепую:
SYNC_MIRRORS_CHECK=true: джоба посчитает изменения и выведет их в лог + артефакт inventory.diff, но ничего не закоммитит/не запушит/не задеплоит. Диф смотрите по ссылке на пайплайн, в логе джобы sync_mirrors или в артефактах.inventory.yml, пушит и деплоит nginx-конфиги на изменившихся хостах.Чтобы кнопки заработали, нужно:
seo-frontend-nginx/.gitlab-ci.yml у джобы sync_mirrors должно быть правило if: '$CI_PIPELINE_SOURCE == "trigger"' — без него пайплайн от Trigger Token запустится, но выполнит ноль джоб.Важно про выбор redirect_to: цель для редиректа выбирается только среди хостов группы (riobet40x/riobet_1000), которые реально зарегистрированы в mirrors-service и сейчас доступны. Хост, которого mirrors-service вообще не знает (например, добавлен в inventory.yml вручную и никогда не заводился здесь), никогда не будет выбран как цель — раньше это было не так, и именно из-за этого редиректы иногда улетали на несуществующее/неотслеживаемое зеркало вместо реально живого соседа.
| Симптом | Что делать |
|---|---|
automirrors_trigger: "could not reach GitLab CI at ..." |
Сеть/доступность GitLab с сервера mirrors-service, либо неверный automirrors_gitlab_base_url в Settings. |
automirrors_trigger / seo_sync_trigger: "POST /trigger/pipeline -> 404" |
Почти всегда — неверный Project ID (сверьте с Settings → General → Project ID у нужного проекта в GitLab, вверху страницы) либо Pipeline trigger token принадлежит другому проекту. Project ID можно указать и числом, и полным путём вида devops/seo-frontend-nginx — оба варианта поддерживаются. |
automirrors_trigger / seo_sync_trigger: "POST /trigger/pipeline -> 401/403" |
Токен отозван/просрочен — создайте новый в GitLab → тот проект → Settings → CI/CD → Pipeline trigger tokens (показывается только один раз при создании) и вставьте в Settings mirrors-service. |
Group показывает range_exhausted |
Увеличьте Number max у этой группы в Groups. |
Зеркало не появляется в seo-frontend-nginx/inventory.yml |
Это отдельный шаг — либо автоматический онбординг (при выпуске через automirrors-crypt с заданными MIRROR_GROUP/PREDECESSOR_DOMAIN), либо ручное добавление хоста в inventory.yml. |
| "Trigger seo-frontend-nginx sync" вернул 400 "not configured in Settings" | Заполните Project ID и Pipeline trigger token в Settings → seo-frontend-nginx. |
uwuaff_check: "UWUAFF_TOKEN_URL/UWUAFF_CLIENT_ID/UWUAFF_CLIENT_SECRET/UWUAFF_API_TOKEN not configured" |
Эти переменные — env vars контейнера, не поля в Settings. Проверьте, что в GitLab CI/CD Variables проекта/группы mirrors-service заданы UWUAFF_API/UWUAFF_TOKEN_URL/UWUAFF_CLIENT_ID/UWUAFF_CLIENT_SECRET/UWUAFF_API_TOKEN (те же, что уже использует seo-frontend-nginx), и передаются в docker-stack-prod.yml. |
Колонка uwuaff всегда показывает n/a |
Домен не найден в списке api.uwuaff.com/v1/domains — либо он ещё не зарегистрирован там (см. register-mirror-domain.yml/trigger-frontend-deploy.yml в seo-frontend-nginx), либо url_mirror здесь не совпадает с доменом в uwuaff. |
Кнопка отработала (Pipeline: created), но inventory.yml не поменялся |
Скорее всего в seo-frontend-nginx/.gitlab-ci.yml у джобы sync_mirrors нет правила if: '$CI_PIPELINE_SOURCE == "trigger"' — пайплайн создался, но выполнил ноль джоб. Проверьте по ссылке на пайплайн, была ли там вообще джоба sync_mirrors. |