- Зачем: - прежнее имя выпячивало Xray, хотя клиент поддерживает также внешний прокси, а два каталога конфигурации были неочевидны. - Что: - пакет, CLI, конфигурация, состояние, тесты и документация переименованы в proxy-client. - машинный конфиг, профиль и два явных примера собраны в одном пользовательском каталоге. - свежая установка защищена от сворачивания каталога конфигурации внутрь репозитория. - Проверка: - bash tests/test_proxy_client_full.sh. - bash tests/test_install.sh. - ручные start, status и xray run -test; незавершённая сетевая приёмка записана в handoff.
11 KiB
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-гарантии не требуются.
Чекбоксы
- Команда
install [--replace]добавлена в парсер и диспетчеризуется ДОload_config(): установка не требует машинного конфига и VLESS-профиля.startи запуск без подкоманды по-прежнему никогда не начинают скачивание. - Защита managed-режима: до любого скачивания и изменения назначения
installберёт существующий start-lock и держит его до концаos.replace. Lock занят — немедленный отказ с подсказкой «сначала stop». При взятом lock допускаются только отсутствующий state или доказуемо устаревший (по классификации 03 — stale); живой, сиротский или непроверяемый state — отказ. - Поведение при существующем назначении
~/.local/bin/xray:- отсутствует → установка;
- обычный исполняемый файл, отвечающий
26.3.27→ no-op без сети (успех, сообщение «уже установлено»); --replace→ принудительная переустановка с полной проверкой;- иной файл (не отвечает 26.3.27, неисполняемый, битый) или симлинк →
отказ без
--replace; с--replaceзаменяется сам путь, симлинк не разыменовывается; - каталог → всегда понятный отказ.
- После установки: проверка
shutil.which("xray")— резолвится ли в установленный~/.local/bin/xray. Нет — ненулевой код с подсказкой добавить~/.local/binв началоPATH; готовая установка при этом НЕ откатывается. - Тестовый шов: чистая функция «архитектура → имя ассета» и подменяемый загрузчик (callable), который тесты заменяют in-process. Пользовательского оверрайда базового URL нет.
- Автоматические тесты (без 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.
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 — вне объёма.- Никаких коммитов.
Отчёт исполнителя
- Изменённые файлы со строкой «зачем» на каждый.
- Команды тестов и счётчики (baseline: 49 OK / 8 PASS — новых станет больше, указать числа).
- Чекбоксы: закрыт / не закрыт и почему.
- Принятые по ходу решения и сомнения, если есть.
Резолюция (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.