feat(xray-proxy): добавлена проверяемая установка Xray командой install
- Зачем:
- пользователь без root должен получать проверенный бинарник Xray 26.3.27
одной явной командой, без риска для живого managed-экземпляра.
- Что:
- команда install [--replace]: пин 26.3.27, маппинг x86_64/aarch64,
скачивание urllib + проверка SHA2-256 из официального четырёхзаписного
.dgst, staging внутри ~/.local/bin и один атомарный os.replace;
- границы размеров (64 MiB архив/бинарник, 4 KiB .dgst, счёт limit+1,
сверка Content-Length) и guard managed-режима через start-lock;
- типизированная StartLockBusyError, offline-тесты реального загрузчика
через подмену urlopen, снапшоты дерева ФС в отказных путях;
- README/спека обновлены, включая справку о последней версии с
allowInsecure (25.12.8, удалена в 26.2.6); тикет 04 закрыт, леджер
дополнен итогами эксперимента.
- Проверка:
- PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests — 79 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
90cc8456cd
commit
0432c4ae8a
7 files changed
+1266
-43
No files matched your search
@@ -1,28 +1,144 @@
|
||||
# 04 — Проверяемая пользовательская установка Xray
|
||||
|
||||
**What to build:** Пользователь без root устанавливает официальный Xray одной
|
||||
явной командой и получает проверенный бинарник нужной версии без скрытой замены
|
||||
существующей установки или записи за пределами пользовательских каталогов.
|
||||
**What to build:** Пользователь без root устанавливает официальный Xray
|
||||
одной явной командой `install` и получает проверенный бинарник
|
||||
зафиксированной версии, без скрытой замены существующей установки, без
|
||||
записи за пределами пользовательских каталогов и без риска для живого
|
||||
managed-экземпляра.
|
||||
|
||||
**Blocked by:** 01 — Внешний прокси и переносимый Bash-интерфейс.
|
||||
**Prerequisites:** 01, 03, 06 — все done. Наследуемые решения: одна
|
||||
поддержанная версия Xray 26.3.27 (06); идентичность managed-процессов по
|
||||
`/proc/PID/exe` и start-lock (03); Python 3.8+ stdlib-only, переносимость
|
||||
Ubuntu / РЕД ОС / Astra / Alt.
|
||||
|
||||
**Status:** ready-for-agent
|
||||
**Status:** done
|
||||
|
||||
- [ ] `install` поддерживает Linux x86_64 и aarch64, по умолчанию выбирает
|
||||
зафиксированную версию 26.3.27 и принимает другую только через явный параметр
|
||||
версии, не объявляя произвольный релиз поддержанным.
|
||||
- [ ] Архив и файл контрольной суммы скачиваются из официального релиза, после
|
||||
чего SHA-256 проверяется до распаковки и установки; несовпадение прекращает
|
||||
операцию без изменения установленного бинарника.
|
||||
- [ ] Работа ведётся во временном пользовательском каталоге, извлечённый бинарник
|
||||
сначала проходит проверку `version`, а затем атомарно устанавливается в
|
||||
пользовательский каталог исполняемых файлов.
|
||||
- [ ] Существующий бинарник другой версии не перезаписывается без `--replace`;
|
||||
ошибка, отказ или прерывание не оставляют частично установленный файл.
|
||||
- [ ] После установки `command -v xray` обязан указывать на установленный файл;
|
||||
иначе команда завершается с понятной подсказкой исправить `PATH`.
|
||||
- [ ] Установщик не использует sudo, systemd или системные каталоги, а команды
|
||||
`start` и запуск без подкоманды никогда не начинают скачивание.
|
||||
- [ ] Автоматические тесты проверяют обе архитектуры, корректную и повреждённую
|
||||
контрольную сумму, существующую версию, `--replace`, атомарность и отсутствие
|
||||
записи вне изолированного пользовательского каталога.
|
||||
## Зафиксированный протокол установки
|
||||
|
||||
- **Только версия 26.3.27** — та же, что принимает `VERSION_LINE`.
|
||||
Никакого параметра версии: будущий апгрейд — отдельный тикет, меняющий
|
||||
пин скачивания, ворота рантайма, фикстуры и документацию вместе.
|
||||
- **Источник:** `https://github.com/XTLS/Xray-core/releases/download/v26.3.27/`.
|
||||
Маппинг архитектур (чистая функция, по `platform.machine()`):
|
||||
`x86_64`/`amd64` → `Xray-linux-64.zip`; `aarch64`/`arm64` →
|
||||
`Xray-linux-arm64-v8a.zip`; прочее — понятный отказ.
|
||||
- **Проверка:** скачивается `<архив>.dgst`. Фактический формат официальных
|
||||
файлов — четыре записи `MD5=`, `SHA1=`, `SHA2-256=`, `SHA2-512=`
|
||||
(исправлено 2026-07-20 по находке CODE-ревью: однострочного `SHA256=`
|
||||
в релизах нет). Принимается ровно одна запись `SHA2-256= <64 hex в
|
||||
нижнем регистре>`; дубликаты, uppercase, отсутствие записи или мусор —
|
||||
отказ. Хэш архива считается `hashlib.sha256`; несовпадение или
|
||||
неожиданный формат — отказ до распаковки, установленный бинарник не
|
||||
тронут.
|
||||
- **Границы размеров:** скачивание и распаковка ограничены
|
||||
зафиксированными лимитами (архив, `.dgst`, извлекаемый бинарник);
|
||||
фактически прочитанные байты считаются с limit+1; несоответствие
|
||||
объявленному `Content-Length` или превышение лимита — отказ без
|
||||
исчерпания диска/памяти.
|
||||
- **Инструменты:** `urllib.request` для скачивания, `zipfile` для
|
||||
извлечения члена `xray`. Явно исключены: tar, GPG-подписи, curl, wget,
|
||||
пакетные менеджеры, sudo, systemd, системные каталоги.
|
||||
- **Атомарность:** приватный staging-каталог создаётся ВНУТРИ
|
||||
`~/.local/bin` (одна файловая система — `os.replace` без EXDEV);
|
||||
скачивание и распаковка туда; кандидату ставится режим 0755; кандидат
|
||||
проходит проверку `version` (те же ворота 26.3.27); затем один
|
||||
`os.replace` в `~/.local/bin/xray`. До этого вызова существующий файл
|
||||
назначения не изменяется. Staging убирается при любой ошибке и
|
||||
прерывании (best-effort); fsync/power-loss-гарантии не требуются.
|
||||
|
||||
## Чекбоксы
|
||||
|
||||
- [x] Команда `install [--replace]` добавлена в парсер и диспетчеризуется
|
||||
ДО `load_config()`: установка не требует машинного конфига и
|
||||
VLESS-профиля. `start` и запуск без подкоманды по-прежнему никогда не
|
||||
начинают скачивание.
|
||||
- [x] Защита managed-режима: до любого скачивания и изменения назначения
|
||||
`install` берёт существующий start-lock и держит его до конца
|
||||
`os.replace`. Lock занят — немедленный отказ с подсказкой «сначала
|
||||
stop». При взятом lock допускаются только отсутствующий state или
|
||||
доказуемо устаревший (по классификации 03 — stale); живой, сиротский
|
||||
или непроверяемый state — отказ.
|
||||
- [x] Поведение при существующем назначении `~/.local/bin/xray`:
|
||||
- отсутствует → установка;
|
||||
- обычный исполняемый файл, отвечающий `26.3.27` → no-op без сети
|
||||
(успех, сообщение «уже установлено»);
|
||||
- `--replace` → принудительная переустановка с полной проверкой;
|
||||
- иной файл (не отвечает 26.3.27, неисполняемый, битый) или симлинк →
|
||||
отказ без `--replace`; с `--replace` заменяется сам путь, симлинк не
|
||||
разыменовывается;
|
||||
- каталог → всегда понятный отказ.
|
||||
- [x] После установки: проверка `shutil.which("xray")` — резолвится ли в
|
||||
установленный `~/.local/bin/xray`. Нет — ненулевой код с подсказкой
|
||||
добавить `~/.local/bin` в начало `PATH`; готовая установка при этом НЕ
|
||||
откатывается.
|
||||
- [x] Тестовый шов: чистая функция «архитектура → имя ассета» и
|
||||
подменяемый загрузчик (callable), который тесты заменяют in-process.
|
||||
Пользовательского оверрайда базового URL нет.
|
||||
- [x] Автоматические тесты (без root, без сети; временные HOME/PATH/state):
|
||||
- маппинг обеих архитектур (patch значения архитектуры) и отказ на
|
||||
неподдержанной;
|
||||
- корректный `.dgst` → установка; повреждённый хэш и неожиданный формат
|
||||
`.dgst` → отказ до распаковки, назначение не тронуто;
|
||||
- фикстуры: локальный ZIP с фейковым исполняемым, печатающим
|
||||
`Xray 26.3.27 ...`, и парные валидный/битый `.dgst`;
|
||||
- существующая корректная установка → no-op без обращения к загрузчику;
|
||||
`--replace` → переустановка; симлинк/каталог/битый файл — по таблице
|
||||
выше;
|
||||
- атомарность: инъецированная ошибка после скачивания / при распаковке /
|
||||
при проверке `version` → назначение не изменено, staging убран;
|
||||
- запись только в пределах staging/назначения и lock-файла: сверка
|
||||
полного дерева временной ФС до/после;
|
||||
- защита managed: при живом managed-экземпляре (реальная фикстура 03)
|
||||
`install --replace` отказывает с подсказкой «сначала stop», бинарник
|
||||
и state не тронуты;
|
||||
- `shutil.which`-проверка: отсутствие каталога в PATH и конкурирующий
|
||||
более ранний `xray` в PATH.
|
||||
- [x] `xray-proxy/README.md` обновлён: одна поддержанная версия 26.3.27
|
||||
(упоминания 25.12.8 удалены), использование `install [--replace]`,
|
||||
маппинг x86_64/aarch64, SHA-256-проверка, поведение PATH, отказ при
|
||||
живом managed-экземпляре.
|
||||
|
||||
## Сохранить, не трогать
|
||||
|
||||
- Существующее поведение `resolve_xray`, ворот `VERSION_LINE`,
|
||||
managed-режима и всех 49 зелёных тестов + 8 shell-проверок.
|
||||
- Секреты: install не касается профилей и не выводит их; общее правило
|
||||
«VLESS/UUID/ключи никогда в stdout/stderr/argv» остаётся.
|
||||
- `bash/.bash_functions`, установщик dotfiles — вне объёма.
|
||||
- Никаких коммитов.
|
||||
|
||||
## Отчёт исполнителя
|
||||
|
||||
1. Изменённые файлы со строкой «зачем» на каждый.
|
||||
2. Команды тестов и счётчики (baseline: 49 OK / 8 PASS — новых станет
|
||||
больше, указать числа).
|
||||
3. Чекбоксы: закрыт / не закрыт и почему.
|
||||
4. Принятые по ходу решения и сомнения, если есть.
|
||||
|
||||
## Резолюция (2026-07-20)
|
||||
|
||||
Конвейер пройден целиком: постановка → Codex spec-review (high, 5 MAJOR +
|
||||
3 MINOR + 1 NIT, переписана) → реализация (Codex, medium — эксперимент) →
|
||||
саморевью (found 6, fixed 6) → два слепых ревью (CODE: 2 MAJOR + 2 MINOR;
|
||||
TASK: 3 NIT) → триаж (FIXED 7, REJECTED 0) → узкие перепроверки
|
||||
(оба FIXES_CONFIRMED) → приёмка оркестратором.
|
||||
|
||||
Ключевое:
|
||||
|
||||
- Команда `install [--replace]`: пин 26.3.27, маппинг архитектур чистой
|
||||
функцией, `urllib.request` + `zipfile`, staging внутри `~/.local/bin`,
|
||||
один `os.replace`, `version`-ворота кандидата, guard по start-lock.
|
||||
- CODE-1 (MAJOR) оказался дефектом постановки: spec-review зафиксировал
|
||||
несуществующий однострочный формат `SHA256=`; реальные `.dgst` — четыре
|
||||
записи. Протокол в тикете исправлен (см. выше), парсер строгий: ровно
|
||||
одна `SHA2-256= <64 hex в нижнем регистре>`.
|
||||
- Границы размеров: 64 MiB архив/бинарник, 4 KiB `.dgst`, счёт limit+1,
|
||||
сверка Content-Length; offline-покрытие реального `download_file` через
|
||||
подмену `urllib.request.urlopen`.
|
||||
- `StartLockBusyError` вместо парсинга текста ошибки; снапшоты дерева ФС
|
||||
в тестах отказных путей; PATH-тест конкурирующего бинарника.
|
||||
- README дополнён (по просьбе пользователя): последняя версия с
|
||||
`allowInsecure` — 25.12.8, опция удалена в 26.2.6; CLI их не ставит.
|
||||
|
||||
Тесты: 79 OK (baseline 49, +30) + 8 PASS shell-проверок. Затраты — в
|
||||
`.scratch/headless-xray-proxy/token-ledger.md`.
|
||||
@@ -39,6 +39,15 @@ _Заполняется по ходу; сравнивать построчно
|
||||
| 03: перепроверка TASK-1..3 | Claude Opus (резюм субагента) | default | инкремент ~7.4k (суммарно 80 560) | 8 tool uses, 1.7 мин; FIXES_CONFIRMED |
|
||||
| 03: перепроверка CODE-1..5 | Codex-ревьюер (resume) | medium | инкремент: свежих in ~112k, out ~3.2k | 20 команд; FIXES_CONFIRMED |
|
||||
|
||||
| 04: ревью постановки | Codex gpt-5.6-sol | high | in 2 469 937 (кэш 2 352 896 → свежих ~117k), out 17 051 (reasoning 10 128) | 54 команды; NEEDS_REWORK: 5 MAJOR + 3 MINOR + 1 NIT |
|
||||
| 04: реализация | Codex gpt-5.6-sol | **medium** | in 2 941 682 (кэш 2 828 288 → свежих ~113k), out 18 521 (reasoning 5 278) | 50 команд; +~600 строк; 63 OK (+14) + 8 PASS |
|
||||
| 04: саморевью | Codex (resume) | medium | инкремент: свежих in ~50k, out ~17.2k (reasoning ~9.2k) | 38 команд; found 6 fixed 6; 72 OK + 8 PASS |
|
||||
| 04: слепое ревью «по задаче» | Claude Opus (субагент) | default | 53 463 суммарно | 18 tool uses, 5.7 мин; 3 NIT |
|
||||
| 04: слепое ревью «по коду» | Codex gpt-5.6-sol (свежий тред) | high | in 1 774 005 (кэш 1 676 288 → свежих ~98k), out 10 402 (reasoning 5 481) | 46 команд; 2 MAJOR + 2 MINOR (CODE-1 — дефект постановки: реальный формат .dgst) |
|
||||
| 04: триаж/починка | Codex (resume) | medium | инкремент: свежих in ~237k, out ~58.9k (reasoning ~24k) | FIXED 7, REJECTED 0; 79 OK (+30 к baseline) + 8 PASS |
|
||||
| 04: перепроверка TASK-1..3 | Claude Opus (резюм субагента) | default | инкремент ~7.7k (суммарно 61 135) | 6 tool uses, 1.7 мин; FIXES_CONFIRMED |
|
||||
| 04: перепроверка CODE-1..4 | Codex-ревьюер (resume) | medium | инкремент: свежих in ~207k, out ~12.5k (reasoning ~6.3k) | FIXES_CONFIRMED |
|
||||
|
||||
Итог 03 (реализация на MEDIUM): Codex — свежий input ~636k + output ~77k
|
||||
(ревью постановки 82k/16k, реализация ~248k/29.5k, саморевью 53k/11.6k,
|
||||
CODE-ревью 85k/12.8k, триаж 55.5k/16.9k, перепроверка 112k/3.2k);
|
||||
@@ -49,6 +58,25 @@ Claude-доля — 80.5k суммарно (TASK-ревью + перепрове
|
||||
|
||||
\* usage первого (убитого) хода не был зафиксирован (нет `turn.completed`); цифры — из resume-хода, по масштабу покрывают весь тред.
|
||||
|
||||
Итог 04 (реализация на MEDIUM): Codex — свежий input ~822k + output ~135k
|
||||
(ревью постановки 117k/17k, реализация 113k/18.5k, саморевью 50k/17.2k,
|
||||
CODE-ревью 98k/10.4k, триаж 237k/58.9k, перепроверка 207k/12.5k);
|
||||
Claude-доля — 61k суммарно (TASK-ревью + перепроверка). Слепые ревью
|
||||
нашли 2 MAJOR + 2 MINOR + 3 NIT, причём CODE-1 (MAJOR) — дефект
|
||||
постановки (spec-review на high выдумал однострочный `SHA256=`), т.е.
|
||||
на счету реализации-medium фактически 1 MAJOR (границы размеров).
|
||||
Примечательно: 04-medium вышла ДОРОЖЕ 06-high по свежим токенам
|
||||
(~822k+135k против ~735k+97k) — экономию реализации съели дорогой триаж
|
||||
(7 находок, включая переделку под исправленный протокол) и перепроверки.
|
||||
|
||||
Сводка эксперимента (свежий input + output Codex; MAJOR от слепых ревью):
|
||||
|
||||
| Задача | Effort реализации | Codex токены | Claude | MAJOR |
|
||||
|---|---|---|---|---|
|
||||
| 06 | high (baseline) | ~735k + 97k | 175k | 1 |
|
||||
| 03 | medium | ~636k + 77k | 80.5k | 4 (гонки) |
|
||||
| 04 | medium | ~822k + 135k | 61k | 2 (1 — вина постановки) |
|
||||
|
||||
## Наблюдения
|
||||
|
||||
- Кэш у Codex покрывает ~93% входа ревью-этапа: длинные треды с большим
|
||||
|
||||
Reference in new issue
Block a user