Зачем: профиль XHTTP с x_padding_bytes и пустым host не запускался. Что: Xray обновлён до 26.10.10; добавлены параметры TLS, REALITY и XHTTP, значения по умолчанию и преобразование старых названий сессии XHTTP. Обновлены документация и регрессионные тесты. Проверка: 59 тестов на Python 3.14 и 3.8, Bash-функции и установщик; 16 тестов разбора после уточнения проверки SHA-256. Настоящий Xray проверил конфигурацию и выполнил HTTPS-запрос через профиль с HTTP 200. Пользователь подтвердил работу proxy-client start.
228 lines
16 KiB
Markdown
228 lines
16 KiB
Markdown
# `proxy-client` для агентских CLI
|
||
|
||
Дата: 2026-07-15
|
||
|
||
Статус: реализовано; 2026-09-28 упрощено по принципу ниже
|
||
|
||
Точное поведение, форматы, сообщения и команды описаны в коде,
|
||
`proxy-client --help` и [`proxy-client/README.md`](../../proxy-client/README.md).
|
||
Здесь — то, чего по коду не видно: задача, границы, выбор и его причины, риски.
|
||
|
||
## Принцип: личный инструмент
|
||
|
||
`proxy-client` пользуются владелец и несколько друзей на своих машинах. Код
|
||
защищается от реальных ошибок и опасностей:
|
||
|
||
- скачанный Xray проверяется по SHA-256 и заменяется атомарно;
|
||
- секреты профиля не попадают в вывод, служебные файлы закрыты правами 600/700;
|
||
- локальный порт Xray слушает только loopback;
|
||
- чужие файлы не перезаписываются;
|
||
- сигнал уходит только процессам из `state.json`: Xray — если его
|
||
`/proc/PID/exe` совпадает с запущенным файлом, супервизору — если Xray
|
||
подтверждён и в командной строке супервизора есть `proxy-client`.
|
||
|
||
Пользователь и его собственные файлы — не противник. Сообщение об ошибке
|
||
называет файл полным путём и подсказывает готовую команду.
|
||
|
||
Новая защита появляется вместе со сценарием, который реально случается у
|
||
пользователя, и этот сценарий записывается рядом с ней. Так же проверяется и
|
||
прежний код: в 2026-09-28 убраны защиты без такого сценария — повторное
|
||
использование PID, подложный `state.json`, zip-бомба в проверенном архиве,
|
||
гонка `status` с параллельным `start`.
|
||
|
||
Тесты проверяют поведение через команду `proxy-client`, разбор ссылки
|
||
(`parse_vless` + `build_xray_config`), установку Xray с подставным
|
||
скачивателем, функции Bash с поддельным `PROXY_CLIENT_BIN` и установщик пакета
|
||
во временном HOME.
|
||
|
||
## Проблема
|
||
|
||
На удалённых Linux-серверах нет GUI-клиента Hiddify. Перед запуском Claude Code
|
||
или Codex приходится вручную запускать Xray и выставлять proxy-переменные.
|
||
Переменные легко забыть, и агент стартует без прокси.
|
||
|
||
Нужны привычные команды `pclaude` и `pcodex`, которые не запускают агента,
|
||
если явно настроенный прокси недоступен. Решение работает и в составе этого
|
||
репозитория, и отдельно от остальных dotfiles.
|
||
|
||
## Контекст
|
||
|
||
Прежний дизайн
|
||
[`pclaude` / `pcodex`](../superpowers/specs/2026-05-19-proxy-cli-launchers-design.md)
|
||
задаёт договор: proxy-переменные видны только дочернему процессу, аргументы и
|
||
код возврата сохраняются, недоступный прокси — отказ до запуска агента. Эта
|
||
спецификация заменяет его Linux-часть; Windows-часть не меняется.
|
||
|
||
Xray принимает конфигурационные файлы, а не `vless://`-ссылки. Его SOCKS-вход
|
||
принимает SOCKS и HTTP на одном порту, поэтому прежняя схема proxy-переменных
|
||
подходит локальному Xray без второго порта. См.
|
||
[командную строку Xray](https://xtls.github.io/en/document/command.html) и
|
||
[SOCKS-вход](https://xtls.github.io/en/config/inbounds/socks.html).
|
||
|
||
## Цели
|
||
|
||
- Управляемый пользовательский Xray на Linux-сервере без systemd и root.
|
||
- Явные `pclaude` и `pcodex`, которые нельзя случайно запустить через другой
|
||
прокси или без прокси.
|
||
- Одна VLESS-ссылка Hiddify превращается в клиентский JSON Xray.
|
||
- Внешний Hiddify работает, как раньше.
|
||
- Машинный `NO_PROXY` настраивается без правки файлов репозитория.
|
||
- Каталог `proxy-client/` можно скопировать отдельно и поставить своим
|
||
установщиком, без остальных dotfiles и без Stow.
|
||
|
||
## Не входит в объём
|
||
|
||
- Windows, PowerShell, Windows Codex launcher и VS Code Server.
|
||
- systemd, автозапуск, sudo, firewall, TUN и системная маршрутизация.
|
||
- Автоматический выбор между Xray и Hiddify или запасной прокси.
|
||
- Несколько одновременных профилей у одного Unix-пользователя.
|
||
- Импорт подписок или полного JSON-экспорта Hiddify.
|
||
- Протоколы за пределами двух поддержанных семейств VLESS.
|
||
- pip, venv и сторонние Python-библиотеки.
|
||
- Сетевая изоляция голых `claude` и `codex`: намерение выражают команды с
|
||
префиксом `p`.
|
||
|
||
## Рассмотренные варианты
|
||
|
||
- **Только Bash.** URL-кодирование, JSON, контрольные суммы и управление
|
||
процессом делают Bash хрупким и плохо тестируемым.
|
||
- **Bash-контроллер и Python-преобразователь.** Граница между двумя языками
|
||
усложняет ошибки, тесты и единый источник настроек.
|
||
- **Один Python CLI и тонкий Bash-модуль.** Выбран. Python отвечает за данные,
|
||
установку Xray, процессы и запуск дочерних команд. Bash нужен только там, где
|
||
меняется окружение текущей shell-сессии.
|
||
- **Без менеджера**: готовые JSON и Xray вручную в tmux. Почти нет кода, но не
|
||
решает главного — безопасного `pclaude`/`pcodex` из ссылки Hiddify.
|
||
|
||
Для отдельно скопированного пакета рассматривались: только инструкция для
|
||
ручных ссылок (легко забыть шаги), копирование файлов в `$HOME` (сложно
|
||
обновлять), обязательный Stow (лишняя зависимость на серверах) и свой
|
||
установщик. Выбран свой установщик, который ставит прямые ссылки: один способ,
|
||
нет второй копии файлов.
|
||
|
||
## Решение
|
||
|
||
### Компоненты
|
||
|
||
`proxy-client` — один исполняемый файл на Python 3.8+ без внешних библиотек:
|
||
установка Xray, запуск и остановка, проверка доступности, разбор VLESS, запуск
|
||
дочерней команды через прокси. Команды: `install`, `start`, `stop`, `status`,
|
||
`env`, `run`. Без подкоманды ничего не делается.
|
||
|
||
`shell.bash` — единственный владелец `proxy_on`, `proxy_off`, `pclaude`,
|
||
`pcodex` и `myip`; в `bash/.bash_functions` их нет, чтобы не зависеть от
|
||
порядка `source`. Модуль сам конфиг не читает:
|
||
|
||
- `pclaude` и `pcodex` вызывают `proxy-client run -- COMMAND ARG...`;
|
||
- `proxy_on` берёт готовые переменные из `proxy-client env` и применяет их,
|
||
только если команда завершилась успешно; `eval` не используется;
|
||
- `proxy_off` очищает переменные в обоих регистрах.
|
||
|
||
`proxy_on`/`proxy_off` больше не запрашивают внешний IP: это замедляло
|
||
переключение. Для проверки есть `myip` и `proxy-client status`.
|
||
|
||
### Настройки
|
||
|
||
Два файла в `~/.config/proxy-client/`:
|
||
|
||
- `config` — несекретный машинный INI: тип `xray` или `external`, имя, адрес,
|
||
порт, добавка к `NO_PROXY`. Если файла нет, используется прежний Hiddify на
|
||
`hproxy.local:12334`.
|
||
- `default.url` — секретная VLESS-ссылка с правами 600; `start -f` берёт другой
|
||
файл.
|
||
|
||
Они разделены, потому что первый описывает политику машины, а второй — секрет,
|
||
который меняется независимо. Выбран INI, а не TOML: `tomllib` появился только
|
||
в Python 3.11.
|
||
|
||
### Явный тип прокси
|
||
|
||
`xray` значит: нужен Xray, запущенный через `proxy-client start`. Если его нет,
|
||
агент не запускается. `external` значит: проверяется только доступность адреса
|
||
и порта. Ни один тип не переходит на другой сам — предсказуемость канала важнее
|
||
удобства.
|
||
|
||
`start` работает только при `type=xray` и loopback-адресе в конфиге. Поэтому
|
||
нельзя поднять локальный Xray, оставив `pclaude` направленным во внешний
|
||
Hiddify. `stop` от типа не зависит: после переключения на `external` старый
|
||
Xray всё равно можно остановить.
|
||
|
||
Один порт на HTTP и SOCKS, по умолчанию `12334`; порт настраивается, чтобы
|
||
разные пользователи одной машины не мешали друг другу.
|
||
|
||
### VLESS-ссылки
|
||
|
||
Поддержаны две семьи: TCP/RAW с REALITY и XHTTP с TLS. Неизвестный или
|
||
повторный query-параметр — отказ: он может менять смысл соединения, и его
|
||
нужно добавить в разбор явно. Остальное проверяет `xray run -test`.
|
||
|
||
Разбор следует [спецификации ссылок Xray](https://github.com/XTLS/Xray-core/discussions/716):
|
||
поддерживает `ech`, `pqv`, `spx`, `fm`, необязательные поля и значения по
|
||
умолчанию. Дополнительные параметры XHTTP переносятся внутрь `extra`, чтобы
|
||
ядро их не потеряло. Старые `sessionPlacement`/`sessionKey` преобразуются в
|
||
`sessionIDPlacement`/`sessionIDKey`, в том числе для отдельного скачивания;
|
||
противоречивые значения отклоняются. Полный список — в README пакета.
|
||
|
||
Hiddify Manager добавляет `allowInsecure`/`insecure` даже к профилям с
|
||
нормальным сертификатом
|
||
([Hiddify-Manager#5438](https://github.com/hiddify/Hiddify-Manager/issues/5438)),
|
||
а Xray [26.2.6](https://github.com/XTLS/Xray-core/releases/tag/v26.2.6) эту
|
||
опцию удалил. Поэтому поля принимаются и игнорируются с предупреждением;
|
||
TLS-проверку задают `pcs` и `vcn`.
|
||
|
||
Сводка перед запуском не показывает UUID, ключи REALITY и содержимое XHTTP
|
||
`path` (там бывает токен). Ссылка не передаётся аргументом командной строки.
|
||
|
||
### Установка Xray
|
||
|
||
`proxy-client install` ставит в `~/.local/bin/xray` официальный архив одного
|
||
зафиксированного релиза (сейчас 26.10.10), проверяет SHA-256 по `.dgst` и
|
||
заменяет файл атомарно. Официальный установщик Xray не подходит: он требует
|
||
root и создаёт systemd-службы. `start` принимает только ту же версию и никогда
|
||
ничего не скачивает.
|
||
|
||
### Жизненный цикл
|
||
|
||
`start` остаётся на переднем плане как небольшой Python-супервизор с одним
|
||
дочерним Xray; ожидаемое место — панель tmux. Отдельный журнал и команда `logs`
|
||
не нужны: хватает истории tmux. Второй `start` отсекает блокировка `flock`.
|
||
`state.json` хранит PID супервизора и Xray, чтобы `stop` работал из любого
|
||
терминала и мог добить Xray, если супервизор убит.
|
||
|
||
Перед запуском обязательны разбор ссылки, `xray run -test` и открытый порт
|
||
после старта. Проверка внешнего IP — только диагностика: её сбой даёт
|
||
предупреждение.
|
||
|
||
`run` никогда не запускает Xray, поэтому у `pclaude` и `pcodex` нет скрытого
|
||
автозапуска.
|
||
|
||
### Доставка
|
||
|
||
Каталог `proxy-client/` раскладывается в `$HOME` ссылками: CLI в
|
||
`~/.local/bin/`, `shell.bash` и `examples/` в `~/.config/proxy-client/`.
|
||
Машинный `config` и `default.url` пользователь создаёт сам; в Git они не
|
||
входят.
|
||
|
||
- Отдельно: `proxy-client/install.sh` ставит эти три ссылки и по умолчанию
|
||
дописывает блок в `~/.bashrc`. Чужие файлы не трогает. Режим прокси, Xray и
|
||
сеть не настраивает — это делают отдельные команды по README.
|
||
- В составе dotfiles: корневой `install.sh` подключает тот же пакет через Stow,
|
||
а `.bash_custom` загружает `shell.bash`.
|
||
|
||
## Риски
|
||
|
||
- Формат ссылок Hiddify расширяется. Строгий отказ на неизвестном параметре
|
||
лучше частично неверного JSON, но новые ссылки могут потребовать правки
|
||
разбора.
|
||
- Открытый порт не доказывает, что маршрут работает. Проверка внешнего IP
|
||
отделена от обязательных проверок.
|
||
- Loopback без аутентификации открыт другим пользователям того же узла.
|
||
Пользователи машины считаются доверенными.
|
||
- `NO_PROXY` программы понимают по-разному, особенно CIDR. README советует
|
||
точные IP и домены.
|
||
- Ссылки установки зависят от пути исходного каталога: переносить его нельзя.
|
||
- Поддержка РЕД ОС, Astra Linux и Alt Linux заявляется только после проверки
|
||
версии Python и прогона `tests/test_proxy_client_full.sh` на них.
|
||
- Шаблоны в `examples/` содержат только вымышленные значения; настоящие ссылки,
|
||
UUID и ключи в репозиторий не попадают.
|