271 lines
22 KiB
Markdown
271 lines
22 KiB
Markdown
# Автоматическое сопровождение dev-релизов Xray-core
|
||
|
||
## Задача
|
||
|
||
VPnBot должен получать новые официальные dev-релизы Xray-core без ручного
|
||
переписывания версии, но не имеет права сразу раздавать непроверенный бинарник
|
||
всему парку. Существующий механизм `vpnbot-active-revoke-v3` остаётся без
|
||
изменений: мы по-прежнему накладываем три собственных патча на точный
|
||
официальный исходный код и проверяем, что удаление пользователя закрывает его
|
||
уже открытые соединения, не затрагивая соседнего пользователя.
|
||
|
||
### Процессорные варианты одного proven-релиза
|
||
|
||
Номер версии и оптимизация под процессор являются двумя независимыми
|
||
измерениями одного выпуска. Самым свежим считается первый безопасный тег из
|
||
официальной Atom-ленты XTLS, но один и тот же проверенный исходник собирается в
|
||
двух вариантах для Linux amd64:
|
||
|
||
- `Xray-linux-64.zip` с `GOAMD64=v1` — обязательный совместимый вариант;
|
||
- `Xray-linux-64-v3.zip` с `GOAMD64=v3` — вариант для современных CPU, которым
|
||
виртуальная машина действительно предоставляет весь набор инструкций v3.
|
||
|
||
Манифест выпуска хранит для каждого архива не только SHA-256 и размер, но и
|
||
`goarch`, `goamd64` и точный список обязательных Linux CPU flags. Решение
|
||
принимает узловой updater по `/proc/cpuinfo`, то есть по возможностям, реально
|
||
видимым гостевой системе, а не по маркетинговому названию физического
|
||
процессора. Неполный или неизвестный набор возможностей всегда выбирает v1.
|
||
|
||
До замены рабочего файла updater запускает скачанный бинарник, проверяет
|
||
capability-маркер VPnBot и всю живую конфигурацию. После рестарта он проверяет
|
||
активность службы; при любой ошибке возвращает прежний бинарник, ресурсы и
|
||
конфигурацию. Отсутствие v3-архива в старом proven-релизе также является
|
||
штатным совместимым состоянием: узел берёт v1, а не остаётся без обновления.
|
||
|
||
Новый набор вариантов обязан создавать новую редакцию `-vpnbot.N`, даже если
|
||
официальный тег и три исходных патча не менялись. Для этого release identity
|
||
включает явную версию build profile; изменение только документации не должно
|
||
порождать новый бинарный релиз.
|
||
|
||
Production-пилот обязан установить именно v3 на canary с подтверждёнными
|
||
флагами процессора. Совместимый v1 отдельно проходит реальные процессные тесты
|
||
в CI. Proven публикуется только после обеих проверок и содержит byte-identical
|
||
архивы candidate-релиза.
|
||
|
||
Новая цепочка выпуска состоит из двух разных состояний:
|
||
|
||
1. `candidate` — предварительный релиз Forgejo. Его видит только управляющий
|
||
контур пилота. Обычные узлы используют канал `stable` и игнорируют такие
|
||
релизы благодаря признаку `prerelease=true`.
|
||
2. `proven` — обычный, не предварительный релиз. Он создаётся только из тех же
|
||
байтов, которые уже были установлены и проверены на выделенном production-
|
||
узле. После этого существующие таймеры обновления Xray могут забрать его.
|
||
|
||
## Источники истины
|
||
|
||
- Официальный GitHub `XTLS/Xray-core` владеет исходным тегом и commit.
|
||
- Официальный `releases.atom` владеет порядком опубликованных релизов. Для
|
||
discovery не используются GitHub Releases/Commits API, API-токен или
|
||
локальное зеркало Xray-core.
|
||
- `patches/series` владеет точным набором и SHA-256 наших трёх патчей.
|
||
- Манифест кандидата владеет связью между официальным тегом, официальным
|
||
commit, commit этого репозитория, патчами и контрольными суммами архивов.
|
||
- Отчёт пилота владеет фактом живой проверки конкретного кандидата.
|
||
- Forgejo release с `prerelease=false` является единственным источником
|
||
стабильного бинарника для автообновления узлов.
|
||
|
||
Файл `upstream.env` остаётся проверяемым примером и ручной фиксацией текущего
|
||
релиза. Автоматическая сборка не изменяет Git сама: она создаёт отдельный
|
||
строго проверенный build-env и манифест для найденного официального тега.
|
||
|
||
## Полный рабочий процесс
|
||
|
||
### 1. Обнаружение официального выпуска
|
||
|
||
Forgejo Actions по расписанию и по ручному запуску читает официальный
|
||
`https://github.com/XTLS/Xray-core/releases.atom`. Берётся первая безопасная
|
||
ссылка на опубликованный официальный тег формата `vN.N.N`; feed одинаково
|
||
принимает обычные и официальные prerelease/dev-выпуски, поэтому отдельный
|
||
GitHub-флаг prerelease для доверия не нужен.
|
||
|
||
Затем Actions создаёт временный пустой Git-каталог, выполняет shallow fetch
|
||
ровно этого тега из официального `XTLS/Xray-core.git`, разрешает его в точный
|
||
40-символьный commit и читает из commit-объекта время и `go.mod`. Временный
|
||
каталог удаляется после discovery. Постоянная или полная копия Xray-core в
|
||
Forgejo и на production-хосте не создаётся; ветка `main`, GitHub
|
||
Releases/Commits API и `raw.githubusercontent.com` в выборе версии не
|
||
участвуют.
|
||
|
||
Позднейший `prepare-source.sh` заново получает тот же точный тег и обязан
|
||
доказать совпадение commit перед наложением патчей. Если официальный тег между
|
||
discovery и подготовкой исходника был перемещён, выпуск останавливается.
|
||
|
||
Если для сочетания «официальный commit + SHA-256 набора патчей» уже существует
|
||
`proven`-манифест, запуск завершается как штатный `no-op`. Если существует тот
|
||
же кандидат, повторный запуск проверяет его идентичность и тоже не создаёт
|
||
дубликат.
|
||
|
||
### 2. Подготовка и проверка
|
||
|
||
Официальный исходник загружается по точному тегу и сверяется с ожидаемым
|
||
commit. Затем последовательно проверяются SHA-256 и накладываются ровно три
|
||
патча. Любой конфликт патча, лишний патч, пропавший capability-маркер,
|
||
непроходящий Go-тест или ошибка сборки останавливает выпуск до публикации.
|
||
|
||
Тесты включают unit- и real-process-сценарии для VLESS, Trojan, VMess и
|
||
Shadowsocks. В них пользователь A удаляется при открытом соединении,
|
||
соединение A должно закрыться, а соединение B — продолжить работу.
|
||
|
||
### 3. Воспроизводимая сборка кандидата
|
||
|
||
Три Linux-архива собираются с одинаковым `SOURCE_DATE_EPOCH`, полученным из
|
||
времени официального commit. `geoip.dat` и `geosite.dat` берутся из
|
||
официального Xray-архива того же тега и проверяются по опубликованному
|
||
официальному `.dgst`; подвижная ветка стороннего репозитория больше не влияет
|
||
на результат.
|
||
|
||
В бинарник записывается будущий `proven`-тег. Поэтому кандидат и окончательный
|
||
релиз содержат один и тот же бинарник; продвижение не пересобирает и не меняет
|
||
ни одного байта.
|
||
|
||
Рядом с архивами публикуется `vpnbot-xray-release-manifest.json`. Он содержит:
|
||
|
||
- официальный репозиторий, тег, commit и время commit;
|
||
- commit репозитория патчей;
|
||
- capability `vpnbot-active-revoke-v3`;
|
||
- candidate- и proven-теги;
|
||
- SHA-256 каждого патча и каждого публикуемого файла;
|
||
- версию схемы манифеста.
|
||
|
||
### 4. Публикация candidate
|
||
|
||
Actions сначала создаёт скрытый Forgejo draft с `prerelease=true`, загружает и
|
||
повторно сверяет точные архивы, файлы `.dgst` и манифест и только затем
|
||
публикует candidate последним API-вызовом. Для этого используется короткоживущий
|
||
`FORGEJO_TOKEN`, ограниченный текущим репозиторием. В workflow нет SSH-ключей
|
||
production-узлов и постоянного Forgejo-токена.
|
||
|
||
### 5. Живой production-пилот
|
||
|
||
Root-only таймер на production-хосте опрашивает только candidate-релизы. Он
|
||
берёт первый ещё не продвинутый кандидат, проверяет схему манифеста и SHA-256
|
||
всех загруженных файлов, после чего устанавливает точную версию на явно
|
||
указанный canary-узел штатным `vpnbot-xray-core-updater`.
|
||
|
||
После установки проверяются:
|
||
|
||
- совпадение заявленной версии и capability;
|
||
- валидность существующей конфигурации;
|
||
- активность `vpnbot-xray.service`;
|
||
- изолированный real-process-сценарий на самом canary: отдельный Xray-процесс
|
||
на loopback, два временных VLESS-пользователя, два непрерывных TCP-потока,
|
||
удаление только пользователя A через Xray API, закрытие A не позднее 10
|
||
секунд и продолжение B;
|
||
- отсутствие оставшихся тестовых процессов и временных конфигураций.
|
||
|
||
Изолированный сценарий не добавляет пользователей в боевые inbound и не
|
||
меняет production-конфигурацию. Он запускает уже установленный кандидат тем же
|
||
бинарником, но на случайных loopback-портах.
|
||
|
||
Если установка или проверка не проходит, `proven` не создаётся. При сбое после
|
||
успешной установки управляющий контур возвращает canary на предыдущий точный
|
||
стабильный релиз штатным updater и сохраняет диагностический отчёт.
|
||
|
||
### 6. Продвижение proven
|
||
|
||
Управляющий контур формирует JSON-отчёт пилота с candidate-тегом, хешем
|
||
манифеста, узлом, временем, предыдущей и установленной версиями и результатами
|
||
проверок. Затем через Forgejo API создаётся скрытый draft с proven-тегом.
|
||
|
||
Все архивы и `.dgst` скачиваются из candidate-релиза, повторно сверяются с
|
||
манифестом и без пересборки загружаются в proven-релиз. Манифест и отчёт пилота
|
||
тоже прикладываются. Candidate повторно проверяется после загрузки proven-
|
||
файлов; только затем draft становится видимым стабильным релизом. Если
|
||
proven-тег уже существует, повторный запуск обязан
|
||
доказать его полную идентичность; несовпадение считается конфликтом и
|
||
завершает операцию безопасной ошибкой.
|
||
|
||
### 7. Распространение
|
||
|
||
Обычные узлы остаются на `XRAY_CORE_RELEASE_CHANNEL=stable` и
|
||
`XRAY_CORE_VERSION=latest`. Их существующий updater пропускает Forgejo-
|
||
релизы с `prerelease=true`, поэтому кандидат не может случайно попасть во весь
|
||
парк. После появления proven-релиза штатные таймеры устанавливают его с
|
||
проверкой SHA-256, capability, конфигурации, перезапуском и автоматическим
|
||
rollback при ошибке.
|
||
|
||
## Гонки и повторные запуски
|
||
|
||
- Workflow имеет один `concurrency`-ключ и не отменяет уже начатую сборку.
|
||
- Production-промоутер использует локальную файловую блокировку и не допускает
|
||
два одновременных пилота.
|
||
- Теги вычисляются детерминированно из существующих релизов. Конфликт уже
|
||
занятого тега никогда не обходится перезаписью или удалением.
|
||
- Во всех сетевых ответах проверяются типы, ожидаемые имена, commit и хеши.
|
||
- Между проверкой кандидата и созданием proven повторно загружается release и
|
||
подтверждается неизменность candidate-манифеста.
|
||
- Публикация proven происходит только после завершённого отчёта пилота; draft
|
||
и prerelease не считаются стабильным состоянием.
|
||
|
||
## Сбои и безопасное поведение
|
||
|
||
- GitHub release-feed или официальный Git transport недоступен: выпуск
|
||
откладывается, текущий proven остаётся последним.
|
||
- Feed указывает на небезопасную ссылку, неизвестный XML или тег не формата
|
||
`vN.N.N`: запись игнорируется, а при отсутствии безопасной записи выпуск
|
||
останавливается.
|
||
- Новый тег исчез или перемещён: commit не совпадает, сборка прекращается.
|
||
- Патчи больше не применяются: кандидат не публикуется, это сигнал для ручной
|
||
адаптации патчей, а не повод перейти на чистое upstream-ядро.
|
||
- Сборка или тесты упали: кандидат не публикуется.
|
||
- Candidate опубликован не полностью: промоутер отвергает его по отсутствующим
|
||
файлам или хешам.
|
||
- Canary недоступен: продвижение откладывается без изменения парка.
|
||
- Canary не стартовал: updater возвращает предыдущий бинарник.
|
||
- Живой тест отзыва не прошёл: canary возвращается на предыдущий proven, новый
|
||
stable-релиз не появляется.
|
||
- Forgejo недоступен после пилота: отчёт сохраняется локально, следующий запуск
|
||
повторяет идемпотентное продвижение того же кандидата.
|
||
|
||
## Операторские уведомления
|
||
|
||
Отдельный root-only наблюдатель читает три уже существующих источника истины и
|
||
не вмешивается в выпуск:
|
||
|
||
- последний запуск `candidate.yml` на `main` в Forgejo Actions;
|
||
- опубликованные candidate-манифесты и наличие связанного proven;
|
||
- JSON-состояния production-промоутера, включая
|
||
`failed_rolled_back` и `failed_rollback_failed`.
|
||
|
||
Подтверждённая проблема отправляется оператору в Telegram один раз, затем не
|
||
чаще заданного интервала напоминания. Незавершённый повторный workflow или
|
||
canary имеет состояние `pending` и не создаёт ложного сообщения о
|
||
восстановлении. Восстановление публикуется только после успешного workflow,
|
||
появления exact proven или исчезновения canary-сбоя после успешного
|
||
продвижения. Долговечное состояние доставки отделено от манифеста, proof и
|
||
решения о публикации релиза.
|
||
|
||
Полный контракт дедупликации, безопасности токена и проверки описан в
|
||
[`2026-08-07-release-telegram-alert-plan.md`](2026-08-07-release-telegram-alert-plan.md).
|
||
|
||
## Развёртывание
|
||
|
||
1. Реализовать и протестировать разрешение официального тега, манифест,
|
||
публикацию и идемпотентное продвижение.
|
||
2. Опубликовать текущий код репозитория патчей и дождаться успешного Actions.
|
||
3. Создать repository-scoped Forgejo-токен с `write:repository`, сохранить его
|
||
только в root-only runtime-env production-хоста.
|
||
4. Установить root-only promoter, canary-проверку и systemd timer.
|
||
5. Создать новый candidate для текущего официального dev-тега, выполнить пилот
|
||
на узле `magnus` и убедиться, что proven содержит те же хеши.
|
||
6. Проверить, что стабильный updater видит proven, а candidate игнорирует.
|
||
7. Обновить проектные навыки: будущая ручная работа должна различать
|
||
candidate, proven, поломку rebase и обычное обновление парка.
|
||
8. Установить `vpnbot-xray-release-alert.timer`, отправить один test-message и
|
||
подтвердить, что здоровый снимок даёт три состояния `healthy` без
|
||
аварийного сообщения.
|
||
|
||
## Критерий готовности
|
||
|
||
Система считается завершённой, когда новый официальный dev-тег без изменения
|
||
патчей автоматически проходит цепочку
|
||
`GitHub tag -> patch/test/build -> candidate -> canary -> proven`, а узлы на
|
||
стабильном канале никогда не видят candidate. При любой неоднозначности или
|
||
ошибке цепочка останавливается до proven и сохраняет работающий предыдущий
|
||
релиз. Оператор получает дедуплицированное Telegram-уведомление об этой
|
||
остановке и подтверждение после реального восстановления источника истины.
|
||
|
||
Discovery считается независимым от GitHub API, только если в release-коде нет
|
||
ссылок на `api.github.com` и `raw.githubusercontent.com`, новый тег разрешается
|
||
через `releases.atom` плюс точный Git-fetch, а временный shallow-каталог не
|
||
остаётся источником или хранилищем Xray-core после завершения процесса.
|