Симптом: ci.yml красный с 01.10.2026 (patch failed: proxy/vless/inbound/inbound.go:597 на свежем XTLS), и этого никто не видел. Наблюдатель следил только за candidate.yml, а тот зелёный, пока официальный релиз не вышел. Когда XTLS выпустит релиз, candidate упадёт, и тревога придёт на дни позже, чем можно было узнать. Исправление: оценщик workflow обобщён на таблицу WorkflowWatch (имя условия, заголовок, последствие, действие). Кроме patch_pipeline (candidate.yml) появилось условие patch_ci (ci.yml, VPNBOT_XRAY_ALERT_CI_WORKFLOW_ID): красный последний запуск main — тревога с действием «адаптировать патч», зелёный — восстановление. Старое состояние без patch_ci читается без миграции. Тесты: красный CI — отдельная проблема при зелёном candidate; зелёный CI восстанавливает; collect_conditions читает оба workflow. Claude-Session: 6557d466-b637-49c9-9419-7a5fa7cc1ff7
165 lines
13 KiB
Markdown
165 lines
13 KiB
Markdown
# VPnBot Xray patches
|
||
|
||
Этот репозиторий хранит только наши изменения для Xray-core. Полной копии
|
||
чужого проекта здесь нет: `scripts/prepare-source.sh` получает точный тег из
|
||
официального [`XTLS/Xray-core`](https://github.com/XTLS/Xray-core), проверяет
|
||
его commit и последовательно накладывает три файла из `patches/`.
|
||
|
||
Патчи добавляют capability-маркер `vpnbot-active-revoke-v3`. Он означает, что
|
||
после удаления пользователя через Xray API закрываются уже установленные
|
||
VLESS, Trojan, VMess и Shadowsocks-сессии этого пользователя. Обычный
|
||
официальный Xray запрещает только новые подключения; поэтому до принятия этого
|
||
механизма upstream-проектом VPnBot не может безопасно устанавливать чистый
|
||
официальный бинарник на платные узлы.
|
||
|
||
Второй маркер `vpnbot-live-user-audit-v1` означает, что команда
|
||
`xray api vpnbot-audit-users` читает всех пользователей всех inbound через
|
||
один процесс и одно локальное gRPC-соединение. Команда делает два полных
|
||
прохода и сверяет каждый список с `GetInboundUsersCount`; узловой помощник
|
||
сравнивает оба прохода и по-прежнему безопасно отказывает при любой
|
||
неоднозначности. На старых узлах без маркера остаётся совместимый, но более
|
||
дорогой аудит отдельными командами.
|
||
|
||
## Граница источников
|
||
|
||
- чужой исходный код загружается только из официального GitHub XTLS;
|
||
- Forgejo хранит наши патчи, проверки, сборочные сценарии и готовые релизы;
|
||
- тег и commit upstream закреплены в `upstream.env` для ручной сборки, а
|
||
автоматический выпуск создаёт отдельный build-env с теми же строгими
|
||
проверками;
|
||
- SHA-256 каждого патча закреплён в `patches/series`;
|
||
- Actions заново применяет патчи к официальному исходнику, запускает профильные
|
||
тесты и собирает три Linux-архива, которые используются VPnBot.
|
||
|
||
## Автоматические dev-релизы
|
||
|
||
Workflow `Follow official Xray dev releases` каждые шесть часов читает
|
||
официальный `XTLS/Xray-core/releases.atom`, после чего временным shallow
|
||
Git-fetch получает ровно найденный тег, его commit, время и `go.mod`.
|
||
GitHub API, API-токен, подвижная ветка `main` и постоянная локальная копия
|
||
Xray-core для discovery не используются. Официальный prerelease/dev-тег для
|
||
него является нормальным входом, но не становится стабильным релизом VPnBot
|
||
сразу.
|
||
|
||
Цепочка разделена на два состояния:
|
||
|
||
1. Actions разрешает тег в точный официальный commit, применяет патчи, запускает
|
||
unit- и real-process-тесты, собирает архивы и публикует Forgejo prerelease с
|
||
суффиксом `-candidate.1`.
|
||
2. Root-only таймер на production-хосте устанавливает точный кандидат на
|
||
выделенный canary-узел, проверяет живую конфигурацию и запускает изолированный
|
||
VLESS-сценарий с двумя непрерывными потоками. После удаления пользователя A
|
||
поток A должен закрыться не позднее 10 секунд, а поток B — продолжить
|
||
работу. Тот же canary до и после удаления проверяет двухпроходную команду
|
||
live-аудита и не сохраняет пользовательские идентификаторы в proof-файле.
|
||
3. Только после этого создаётся обычный `proven`-релиз. Его файлы копируются из
|
||
candidate без пересборки и поэтому имеют ровно те же SHA-256.
|
||
|
||
Стабильные Xray-updater-таймеры на узлах используют канал `stable`: они
|
||
игнорируют `prerelease=true` и видят только proven-релизы. При конфликте патча,
|
||
падении теста, недоступном canary или неуспешном отзыве новый stable-релиз не
|
||
создаётся.
|
||
|
||
Каждый candidate содержит `vpnbot-xray-release-manifest.json` с официальным
|
||
тегом/commit, SHA-256 патчей и архивов. Proven дополнительно содержит
|
||
`vpnbot-xray-pilot-proof.json`, то есть машинно проверяемый отчёт живого пилота.
|
||
Для Linux amd64 один выпуск содержит два архива из одного и того же исходника:
|
||
совместимый `Xray-linux-64.zip` (`GOAMD64=v1`) и оптимизированный
|
||
`Xray-linux-64-v3.zip` (`GOAMD64=v3`). Манифест схемы 3 закрепляет требования
|
||
к процессору, узловой updater выбирает вариант по фактически видимым гостевой
|
||
системе CPU flags, а production-canary обязан доказать именно v3-вариант.
|
||
Схема 3 дополнительно закрепляет обязательный `vpnbot-live-user-audit-v1`,
|
||
чтобы старый бинарник нельзя было ошибочно продвинуть как новый выпуск с
|
||
однопроцессным аудитом.
|
||
Полная модель состояний, гонок и rollback описана в
|
||
[`docs/2026-08-07-automatic-dev-release-plan.md`](docs/2026-08-07-automatic-dev-release-plan.md).
|
||
|
||
Production-компонент устанавливается от root:
|
||
|
||
```bash
|
||
scripts/install_promoter.sh
|
||
```
|
||
|
||
Перед включением таймера нужен runtime-файл
|
||
`/etc/vpnbot-xray-release-promoter.env` по примеру из `systemd/` и root-only
|
||
Forgejo-токен с областью `write:repository`. Токен, SSH-ключ и состояние пилота
|
||
не хранятся в Git.
|
||
|
||
Тот же установщик добавляет независимый наблюдатель
|
||
`vpnbot-xray-release-alert.timer`. Он раз в пять минут проверяет последний
|
||
`candidate.yml`, возраст опубликованных candidate без proven и root-only
|
||
состояние canary. Оператор получает Telegram-сообщение при безопасной остановке
|
||
выпуска, редкое напоминание для продолжающейся аварии и отдельное сообщение о
|
||
подтверждённом восстановлении. Переходный retry workflow или canary не считается
|
||
восстановлением.
|
||
|
||
Forgejo может показывать публичные Actions-запуски в интерфейсе, но возвращать
|
||
пустой `workflow_runs` через публичный API. В таком случае наблюдатель читает
|
||
страницу именно `candidate.yml`, выбирает последний запуск `main` и извлекает
|
||
только встроенное структурированное JSON-состояние страницы запуска. Он не
|
||
угадывает результат по цвету иконки или локализованному тексту. Незавершённый
|
||
запуск старше общего `stall`-интервала становится отдельной аварией runner,
|
||
потому что бесконечное `waiting` иначе оставляло бы сторож молчащим.
|
||
|
||
Наблюдатель следит и за `ci.yml` (`VPNBOT_XRAY_ALERT_CI_WORKFLOW_ID`): красный
|
||
CI на свежем официальном исходнике — отдельное условие `patch_ci`. Оно значит,
|
||
что следующий релиз XTLS не соберётся в candidate, и узнаём мы об этом за дни
|
||
до `candidate.yml` (с 01.10.2026 CI был красным незаметно).
|
||
|
||
Недоступный источник не означает вердикта о выпуске. Ответы 5xx и 429 и
|
||
сетевой обрыв после повторов `release_pipeline.request_bytes` поднимает как
|
||
`SourceUnavailableError` с кодом `source_server_error`, `source_rate_limited`
|
||
или `source_unreachable`. Ответ 4xx и битое тело остаются обычным
|
||
`PipelineError`: это поломка контракта. В такой прогон наблюдатель не оценивает
|
||
workflow, candidate и canary, поэтому открытый инцидент не закрывается и новый
|
||
не открывается. Вместо этого он ведёт условие `release_source`: молчит, пока
|
||
недоступность короче `VPNBOT_XRAY_ALERT_SOURCE_OUTAGE_SECONDS` (по умолчанию
|
||
1800 с), затем шлёт одну тревогу, а потом сообщение о восстановлении.
|
||
Промоутер при недоступном источнике на шаге выбора кандидата (до любых
|
||
изменений) откладывает прогон и завершается с кодом 0. Повод для этого —
|
||
ночная остановка Forgejo на ~90 секунд ради консистентного бэкапа: 28–29.09.2026
|
||
она роняла оба юнита ответом 502.
|
||
|
||
Для наблюдателя нужен `/etc/vpnbot-xray-release-alert.env` по отдельному примеру
|
||
из `systemd/`. В нём нет токена Telegram: скрипт читает только
|
||
`VPNBOT_BOT_TOKEN` из существующего root-only runtime-env VPnBot и
|
||
отправляет обычный HTTPS на `api.telegram.org`. Своей маршрутизации у него
|
||
нет: путь к Telegram принадлежит хосту. Telegram-фронт node manager
|
||
сопоставляет имя с loopback-адресом и склеивает соединение со здоровым реле
|
||
или прямым адресом. До 03.10.2026 скрипт держал копию политики бота (реле,
|
||
резервные IPv4, семейство адресов), а 24.09 не держал никакой и падал с
|
||
`ENETUNREACH`. Отказ доставки печатается typed-кодом:
|
||
`telegram_connect_failed` гарантирует, что запрос не ушёл;
|
||
`telegram_delivery_ambiguous` — запрос ушёл без ответа и в этом запуске
|
||
не повторяется. Долговечное
|
||
состояние дедупликации хранится в
|
||
`/var/lib/vpnbot-xray-release-alert/state.json` и никак не участвует в решении о
|
||
публикации proven.
|
||
|
||
Режимы ручной проверки после установки:
|
||
|
||
```bash
|
||
sudo -n /usr/local/libexec/vpnbot-xray-release-promoter/release_alert_monitor.py --print-status
|
||
sudo -n /usr/local/libexec/vpnbot-xray-release-promoter/release_alert_monitor.py --send-test
|
||
```
|
||
|
||
Первый вызов ничего не отправляет и не меняет incident-state. Второй отправляет
|
||
одно явно помеченное тестовое сообщение и тоже не создаёт ложную аварию.
|
||
|
||
## Локальная проверка
|
||
|
||
```bash
|
||
scripts/test-patches.sh
|
||
scripts/build-release.sh ./release-assets
|
||
```
|
||
|
||
Для подготовки исходного дерева без сборки:
|
||
|
||
```bash
|
||
scripts/prepare-source.sh ./xray-with-vpnbot-patches
|
||
```
|
||
|
||
Каталоги назначения должны отсутствовать или быть пустыми.
|
||
|
||
Исходный Xray-core и изменённые файлы распространяются по Mozilla Public
|
||
License 2.0. Полный текст лицензии находится в `LICENSE`.
|