feat(xray-proxy): добавлен безопасный жизненный цикл управляемого Xray
- Зачем:
- stop/status опирались на обход argv и могли счесть чужой или
переиспользованный PID управляемым процессом, а параллельный start
и аварийная гибель супервизора не имели определённого поведения.
- Что:
- введена модель идентичности: PID + starttime (поле 22 /proc/PID/stat),
/proc/PID/exe для ребёнка, канонический путь скрипта для супервизора,
boot_id как граница загрузки; перечитка перед каждым сигналом.
- добавлены lifetime-flock супервизора, три исхода проверки состояния
(здоровая пара / подтверждённый сирота / непроверяемое), отказ при
занятом порте пробным bind и очистка stale только под lock.
- тесты расширены до 49 (blank flock, подмена starttime, чужой exe,
boot_id, SIGKILL супервизора, занятый порт, парсер stat); спека
приведена к модели идентичности; тикет 03 закрыт, леджер дополнен.
- Проверка:
- python3 -m unittest discover -s tests — 49 OK.
- bash tests/test_proxy_launchers.sh — 8 PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f2d303c233
commit
5ace0cfae0
6 files changed
+890
-100
No files matched your search
@@ -306,14 +306,30 @@ CLI предоставляет `install`, `start`, `stop`, `status`, `env` и `r
|
||||
останавливает Xray. Отдельный постоянный журнал и команда `logs` не нужны:
|
||||
достаточно истории tmux.
|
||||
|
||||
Супервизор владеет блокировкой, дочерним процессом и служебным каталогом
|
||||
`~/.local/state/xray-proxy/`. Файл состояния содержит PID и время старта обоих
|
||||
процессов, путь к бинарнику и порт, но не VLESS-ссылку и не её секреты. `status`
|
||||
и `stop` сверяют PID со временем старта и
|
||||
ожидаемым исполняемым файлом, поэтому повторное использование PID не считается
|
||||
управляемым процессом. `stop` посылает SIGTERM супервизору, а тот завершает Xray
|
||||
и очищает состояние. Если супервизор аварийно исчез, `stop` может завершить
|
||||
только дочерний Xray, идентичность которого подтверждена теми же данными.
|
||||
Супервизор на всё время работы удерживает неблокирующую эксклюзивную блокировку
|
||||
`fcntl.flock` в служебном каталоге `~/.local/state/xray-proxy/`. Второй `start`
|
||||
отказывает до запуска процессов и изменения state или сгенерированного
|
||||
конфига. `state.json` нужен для восстановления после сбоя, но не служит
|
||||
блокировкой.
|
||||
|
||||
Файл состояния содержит ровно `supervisor_pid`, `xray_pid`, `xray_path`,
|
||||
`config_path`, `address`, `port`, `supervisor_path`, `supervisor_starttime`,
|
||||
`xray_starttime` и `boot_id`. Время старта — поле 22 `/proc/PID/stat`; поле
|
||||
`comm` сначала отделяется по последней `)`. Для обоих процессов проверяются PID,
|
||||
точное время старта и `boot_id`. У ребёнка дополнительно сверяется канонический
|
||||
`/proc/PID/exe` с `xray_path`. У Python-супервизора операнд скрипта из
|
||||
`/proc/PID/cmdline` разрешается относительно `/proc/PID/cwd` и сравнивается с
|
||||
каноническим `supervisor_path`. Ошибка чтения или разбора означает несовпадение.
|
||||
Непосредственно перед каждым сигналом идентичность читается повторно.
|
||||
|
||||
Проверка состояния различает три исхода. Для здоровой пары `stop` посылает
|
||||
SIGTERM супервизору. Если супервизор исчез, но ребёнок подтверждён, `status`
|
||||
сообщает о сироте с ненулевым кодом, `env` и `run` отказывают, а `stop` посылает
|
||||
сигнал ребёнку напрямую без требования доступности порта и удаляет state и
|
||||
конфиг. При живом, но неподтверждённом PID сигналы не посылаются, файлы
|
||||
сохраняются, а диагностика требует ручной проверки и удаления. Если оба PID
|
||||
мертвы или `boot_id` не совпадает, устаревшие state и конфиг можно безопасно
|
||||
удалить без сигналов.
|
||||
|
||||
У пользователя может быть только один управляемый экземпляр. Повторный `start`
|
||||
не заменяет его другим профилем. Для смены профиля нужны явные `stop` и `start`.
|
||||
|
||||
Reference in new issue
Block a user