Files
dotfiles/.scratch/proxy-client/issues/04-verified-xray-installation.md
T
ddadmin 6e431d875e 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.
2026-07-21 09:31:21 -04:00

11 KiB
Raw Blame History

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 — вне объёма.
  • Никаких коммитов.

Отчёт исполнителя

  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.