refactor(proxy-client): переименован интерфейс прокси
- Зачем: - прежнее имя выпячивало Xray, хотя клиент поддерживает также внешний прокси, а два каталога конфигурации были неочевидны. - Что: - пакет, CLI, конфигурация, состояние, тесты и документация переименованы в proxy-client. - машинный конфиг, профиль и два явных примера собраны в одном пользовательском каталоге. - свежая установка защищена от сворачивания каталога конфигурации внутрь репозитория. - Проверка: - bash tests/test_proxy_client_full.sh. - bash tests/test_install.sh. - ручные start, status и xray run -test; незавершённая сетевая приёмка записана в handoff.
This commit is contained in:
1 parent
e5323284a8
commit
6e431d875e
25 files changed
+310
-227
No files matched your search
@@ -0,0 +1,144 @@
|
||||
# 04 — Проверяемая пользовательская установка Xray
|
||||
|
||||
**What to build:** Пользователь без root устанавливает официальный Xray
|
||||
одной явной командой `install` и получает проверенный бинарник
|
||||
зафиксированной версии, без скрытой замены существующей установки, без
|
||||
записи за пределами пользовательских каталогов и без риска для живого
|
||||
managed-экземпляра.
|
||||
|
||||
**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:** done
|
||||
|
||||
## Зафиксированный протокол установки
|
||||
|
||||
- **Только версия 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] `proxy-client/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/proxy-client/token-ledger.md`.
|
||||
Reference in new issue
Block a user