Спецификация 603 → 220 строк: без описаний снятых защит и без пересказа кода и README; остались задача, границы, решения с причинами и риски. В README раздел «Изменение кода» ведёт к спецификации и тестам. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
419 lines
20 KiB
Markdown
419 lines
20 KiB
Markdown
# proxy-client
|
||
|
||
`proxy-client` — переносимый Linux/Bash-интерфейс для запуска команд через
|
||
внешний прокси или через управляемый пользовательский Xray. Он добавляет команды
|
||
`proxy_on`, `proxy_off`, `pclaude` и `pcodex`, но не подменяет обычные `claude`
|
||
и `codex`.
|
||
|
||
CLI требует Python 3.8+ и использует только стандартную библиотеку. Он не
|
||
запускает Xray и не переключает режим прокси без явной команды пользователя.
|
||
|
||
## Установка
|
||
|
||
### Только пакет `proxy-client`
|
||
|
||
Скопируйте каталог в постоянное место и запустите пакетный установщик. Текущий
|
||
рабочий каталог не важен:
|
||
|
||
```bash
|
||
~/src/proxy-client/install.sh
|
||
```
|
||
|
||
Установщик создаёт три ссылки на исходный каталог: CLI, Bash-модуль и каталог
|
||
шаблонов. Поэтому исходный каталог после установки нельзя перемещать. Если
|
||
какой-то из путей уже занят чужим файлом или ссылкой, установщик ничего не
|
||
меняет и перечисляет все такие пути. Повторный запуск безопасен.
|
||
|
||
По умолчанию установщик дописывает в `~/.bashrc` блок между строками
|
||
`# >>> proxy-client >>>` и `# <<< proxy-client <<<`: он добавляет
|
||
`~/.local/bin` в `PATH` и подключает Bash-модуль. Если блок уже есть, файл не
|
||
меняется. После установки откройте новый Bash или примените блок в текущей
|
||
сессии:
|
||
|
||
```bash
|
||
source ~/.bashrc
|
||
```
|
||
|
||
Чтобы оставить `~/.bashrc` без изменений, запустите:
|
||
|
||
```bash
|
||
~/src/proxy-client/install.sh --no-shell-setup
|
||
```
|
||
|
||
В этом случае подготовьте только текущую сессию вручную:
|
||
|
||
```bash
|
||
export PATH="$HOME/.local/bin:$PATH"
|
||
source ~/.config/proxy-client/shell.bash
|
||
```
|
||
|
||
Для следующих сессий добавьте эти строки в подходящий Bash-конфиг сами.
|
||
|
||
Установщик не создаёт машинный `config` и закрытый `default.url`, не
|
||
устанавливает Xray и не проверяет сеть. После подключения команд выберите режим
|
||
по разделу [«Настройка режима»](#настройка-режима). Без машинного конфига
|
||
действует режим Hiddify по умолчанию.
|
||
|
||
### В составе dotfiles
|
||
|
||
Корневой установщик dotfiles подключает тот же пакет через Stow и загружает
|
||
Bash-функции в новых интерактивных сессиях:
|
||
|
||
```bash
|
||
cd ~/dotfiles
|
||
./install.sh
|
||
```
|
||
|
||
Он заранее создаёт `~/.local/bin` и `~/.config/proxy-client`, чтобы Stow
|
||
размещал ссылки внутри них, а не превращал сами каталоги в ссылки на
|
||
репозиторий. После установки откройте новый Bash или выполните:
|
||
|
||
```bash
|
||
export PATH="$HOME/.local/bin:$PATH"
|
||
source ~/.bash_custom
|
||
```
|
||
|
||
Если `~/.local/bin` отсутствует в `PATH` и в новых сессиях, добавьте ту же строку
|
||
`export` в свой Bash-конфиг: корневой установщик dotfiles его не изменяет.
|
||
|
||
## Как выбирается прокси
|
||
|
||
Режим определяется только машинным файлом
|
||
`~/.config/proxy-client/config`:
|
||
|
||
| Машинный конфиг | Используется | Что запускать |
|
||
|-----------------|-------------|---------------|
|
||
| Файла нет | Hiddify на `hproxy.local:12334` | Ничего |
|
||
| `type = external` | Указанный внешний прокси | Ничего |
|
||
| `type = xray` | Управляемый локальный Xray | `proxy-client start` |
|
||
|
||
Доступность процесса не меняет выбранный режим. Если в конфиге указан Xray, но
|
||
он не запущен, `status`, `env`, `run`, `proxy_on`, `pclaude` и `pcodex`
|
||
завершатся с ошибкой. Автоматического перехода на Hiddify или другой прокси нет.
|
||
|
||
Текущий выбор и доступность проверяет команда:
|
||
|
||
```bash
|
||
proxy-client status
|
||
```
|
||
|
||
## Настройка режима
|
||
|
||
### Hiddify в Windows, команды в WSL
|
||
|
||
Это режим по умолчанию. Если Hiddify доступен из WSL как
|
||
`hproxy.local:12334`, создавать `config` не нужно:
|
||
|
||
```bash
|
||
getent hosts hproxy.local
|
||
proxy-client status
|
||
pclaude --version
|
||
```
|
||
|
||
#### WSL NAT mode
|
||
|
||
IP Windows-шлюза меняется при перезапуске WSL, поэтому `hproxy.local` нужно
|
||
обновлять. В отдельно скопированном пакете системных помощников нет. Узнайте
|
||
адрес шлюза:
|
||
|
||
```bash
|
||
ip route show default | awk 'NR == 1 {print $3}'
|
||
```
|
||
|
||
Затем откройте `/etc/hosts` через `sudoedit` и оставьте одну отдельную строку с
|
||
полученным адресом:
|
||
|
||
```bash
|
||
sudoedit /etc/hosts
|
||
```
|
||
|
||
```text
|
||
<адрес-шлюза> hproxy.local
|
||
```
|
||
|
||
Не удаляйте другие имена на той же строке: при необходимости перенесите их на
|
||
отдельную строку. Повторяйте обновление после перезапуска WSL.
|
||
|
||
С полным dotfiles запустите корневой `~/dotfiles/install.sh` интерактивно: он
|
||
предложит автоматическое обновление при старте WSL. Обновить адрес сразу можно
|
||
так:
|
||
|
||
```bash
|
||
sudo ~/dotfiles/scripts/setup-hproxy.sh
|
||
# после настройки автозапуска и загрузки общих Bash-функций:
|
||
update_hproxy
|
||
```
|
||
|
||
В NAT mode Hiddify должен слушать адрес, доступный через Windows-шлюз, а не
|
||
только `127.0.0.1`.
|
||
|
||
#### WSL mirrored mode
|
||
|
||
В [mirrored mode](https://learn.microsoft.com/windows/wsl/networking#mirrored-mode-networking)
|
||
(`networkingMode=mirrored`) Windows-сервисы доступны из WSL через `127.0.0.1`.
|
||
Чтобы сохранить адрес по умолчанию, добавьте алиас в Windows hosts:
|
||
|
||
```text
|
||
# C:\Windows\System32\drivers\etc\hosts
|
||
127.0.0.1 hproxy.local
|
||
```
|
||
|
||
Альтернатива — настройте внешний режим по следующему разделу и укажите
|
||
`address = 127.0.0.1`. Алиас проверяйте через `getent hosts hproxy.local`, а
|
||
активный конфиг — через `proxy-client status`.
|
||
|
||
### Другой внешний прокси
|
||
|
||
Скопируйте пример и измените адрес, порт и локальные исключения:
|
||
|
||
```bash
|
||
cp ~/.config/proxy-client/examples/config.external \
|
||
~/.config/proxy-client/config
|
||
```
|
||
|
||
```ini
|
||
[proxy]
|
||
type = external
|
||
name = Office proxy
|
||
address = proxy.example.internal
|
||
port = 12334
|
||
no_proxy_extra = .example.internal,git.example.internal
|
||
```
|
||
|
||
Для внешнего прокси на той же Linux-машине можно указать `127.0.0.1` напрямую.
|
||
|
||
### Локальный Xray
|
||
|
||
Создайте машинный конфиг:
|
||
|
||
```bash
|
||
cp ~/.config/proxy-client/examples/config.xray \
|
||
~/.config/proxy-client/config
|
||
```
|
||
|
||
Закрытый профиль должен содержать ровно одну VLESS-ссылку:
|
||
|
||
```bash
|
||
cp ~/.config/proxy-client/examples/default.url \
|
||
~/.config/proxy-client/default.url
|
||
$EDITOR ~/.config/proxy-client/default.url
|
||
chmod 600 ~/.config/proxy-client/default.url
|
||
```
|
||
|
||
Установите проверенную версию Xray без root:
|
||
|
||
```bash
|
||
proxy-client install
|
||
```
|
||
|
||
Запустите Xray в отдельном терминале или панели tmux:
|
||
|
||
```bash
|
||
proxy-client start
|
||
```
|
||
|
||
Другой закрытый профиль можно выбрать без замены `default.url`:
|
||
|
||
```bash
|
||
proxy-client start -f ~/.config/proxy-client/work.url
|
||
```
|
||
|
||
Для переключения профиля сначала выполните `proxy-client stop`. Все файлы с
|
||
настоящими VLESS-ссылками должны иметь права `600` и не должны попадать в Git.
|
||
|
||
## Ежедневное использование
|
||
|
||
| Задача | Команда | Область действия |
|
||
|--------|---------|------------------|
|
||
| Запустить Claude через прокси | `pclaude [ARG...]` | Только дочерний процесс |
|
||
| Запустить Codex через прокси | `pcodex [ARG...]` | Только дочерний процесс |
|
||
| Запустить другую команду | `proxy-client run -- COMMAND ARG...` | Только дочерний процесс |
|
||
| Включить прокси | `proxy_on` | Текущая Bash-сессия |
|
||
| Выключить прокси | `proxy_off` | Текущая Bash-сессия |
|
||
| Проверить прокси | `proxy-client status` | Без изменения окружения |
|
||
| Получить proxy-переменные | `proxy-client env` | Машинный вывод в stdout |
|
||
| Показать внешний IP | `myip` | С учётом текущего окружения |
|
||
|
||
`run`, `pclaude` и `pcodex` передают дочернему процессу восемь вариантов
|
||
`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY` и `NO_PROXY`, включая имена в нижнем
|
||
регистре. После завершения команды переменные не остаются в оболочке.
|
||
|
||
`proxy_on` применяет те же переменные к текущей Bash-сессии только после
|
||
успешной проверки прокси. `proxy_off` удаляет их. Обычные `claude` и `codex` не
|
||
обёрнуты и не выполняют предварительную проверку, но после `proxy_on` наследуют
|
||
прокси-переменные, как и любой другой дочерний процесс.
|
||
|
||
### Жизненный цикл Xray
|
||
|
||
```bash
|
||
proxy-client install [--replace]
|
||
proxy-client start [-f PROFILE]
|
||
proxy-client status
|
||
proxy-client stop
|
||
```
|
||
|
||
`start` сначала проверяет версию и конфигурацию Xray, затем остаётся на переднем
|
||
плане. Systemd, фоновый автозапуск и скрытый запуск из `run` не используются.
|
||
Один loopback-порт принимает HTTP CONNECT и SOCKS5.
|
||
|
||
Остановить Xray можно двумя способами: `Ctrl+C` в окне `start` или
|
||
`proxy-client stop` из любого терминала. Оба гасят Xray и удаляют служебные
|
||
файлы. Если процесс `start` был убит (например, закрыли окно tmux), Xray может
|
||
остаться работать: `status` и `env` тогда сообщат об ошибке, а `stop` добьёт его
|
||
и уберёт файлы. `stop` работает и после изменения машинного типа на `external`.
|
||
|
||
Второй `start`, пока первый работает, завершится ошибкой.
|
||
|
||
## Диагностика
|
||
|
||
Начните с проверки выбранного режима:
|
||
|
||
```bash
|
||
proxy-client status
|
||
```
|
||
|
||
Для внешнего Hiddify в WSL проверьте имя и порт:
|
||
|
||
```bash
|
||
getent hosts hproxy.local
|
||
curl --proxy http://hproxy.local:12334 https://example.com
|
||
curl --proxy socks5h://hproxy.local:12334 https://example.com
|
||
```
|
||
|
||
Для запущенного Xray можно дополнительно проверить созданный JSON:
|
||
|
||
```bash
|
||
xray run -test -c \
|
||
"${XDG_STATE_HOME:-$HOME/.local/state}/proxy-client/client.json"
|
||
```
|
||
|
||
Если выбран `type = xray`, но процесс не запущен, запустите его отдельно —
|
||
`proxy_on` и `run` этого не делают. Профиль XHTTP с ALPN `h3` требует доступного
|
||
UDP-маршрута до сервера; автоматического перехода на H2 нет.
|
||
|
||
Таймаут предварительной проверки по умолчанию равен двум секундам. Его можно
|
||
изменить положительным числом:
|
||
|
||
```bash
|
||
export PROXY_PREFLIGHT_TIMEOUT=5
|
||
```
|
||
|
||
## Справочник
|
||
|
||
### Ручное подключение без установщика
|
||
|
||
Если пакетный установщик запустить нельзя, выберите постоянный абсолютный путь
|
||
к исходному каталогу и создайте те же ссылки вручную. Команды предполагают, что
|
||
назначения ещё не заняты:
|
||
|
||
```bash
|
||
mkdir -p ~/.local/bin ~/.config/proxy-client
|
||
ln -s /absolute/path/to/proxy-client/.local/bin/proxy-client \
|
||
~/.local/bin/proxy-client
|
||
ln -s /absolute/path/to/proxy-client/.config/proxy-client/shell.bash \
|
||
~/.config/proxy-client/shell.bash
|
||
ln -s /absolute/path/to/proxy-client/.config/proxy-client/examples \
|
||
~/.config/proxy-client/examples
|
||
chmod 755 /absolute/path/to/proxy-client/.local/bin/proxy-client
|
||
```
|
||
|
||
Подготовьте текущую Bash-сессию:
|
||
|
||
```bash
|
||
export PATH="$HOME/.local/bin:$PATH"
|
||
source ~/.config/proxy-client/shell.bash
|
||
```
|
||
|
||
Для следующих сессий добавьте эти две строки в свой Bash-конфиг. Исходный
|
||
каталог после этого нельзя перемещать: созданные ссылки зависят от его пути.
|
||
|
||
### Машинный конфиг
|
||
|
||
Формат строгий: одна секция `[proxy]` и пять обязательных ключей без повторов и
|
||
неизвестных полей:
|
||
|
||
- `type` — `external` или `xray`;
|
||
- `name` — имя для диагностики;
|
||
- `address` — IPv4, IPv6 без квадратных скобок или ASCII DNS-имя без схемы и
|
||
порта;
|
||
- `port` — TCP-порт локального входа;
|
||
- `no_proxy_extra` — IP и домены через запятую, значение может быть пустым.
|
||
Список **замещает** встроенный `DEFAULT_NO_PROXY_EXTRA` целиком, а не
|
||
дополняет его: домены, нужные постоянно, придётся перечислить здесь заново.
|
||
|
||
Поддержка CIDR в `NO_PROXY` различается между программами, поэтому надёжнее
|
||
указывать точные IP и домены. Без машинного файла внешний режим дополнительно
|
||
включает `.concept-software.ru`, `dementev.space` и `.dementev.space` в
|
||
`NO_PROXY`. Для `type = xray` адрес обязан
|
||
быть адресом обратной петли, например `127.0.0.1` или `::1`.
|
||
|
||
### Поддерживаемые профили
|
||
|
||
Поддерживаются VLESS TCP/RAW с REALITY и VLESS XHTTP с TLS. Неизвестные,
|
||
повторные и несовместимые query-параметры отклоняются. XHTTP поддерживает ALPN
|
||
`http/1.1`, `h2` и `h3`; `extra` должен быть JSON-объектом, его содержимое
|
||
проверяет `xray run -test`.
|
||
|
||
Кроме `encryption=none`, принимается VLESS-шифрование
|
||
`encryption=mlkem768x25519plus.…` из новых ссылок Hiddify; остальную часть
|
||
значения проверяет сам Xray. Только с таким шифрованием XHTTP допускает
|
||
`flow=xtls-rprx-vision`. Косая черта перед параметрами (`…:443/?type=…`)
|
||
допустима, другой путь в ссылке — нет.
|
||
|
||
Xray 26.3.27 использует `pcs` и `vcn`. Устаревшие `allowInsecure` и `insecure`
|
||
принимаются с любым значением, но не переносятся: при значении `1` или `true`
|
||
выводится предупреждение, а сертификат проверяется обычным способом.
|
||
|
||
Последняя версия Xray, где `allowInsecure` ещё применяется, —
|
||
[25.12.8](https://github.com/XTLS/Xray-core/releases/tag/v25.12.8); опция
|
||
удалена в [26.2.6](https://github.com/XTLS/Xray-core/releases/tag/v26.2.6).
|
||
`proxy-client` эти версии не поддерживает и не устанавливает.
|
||
|
||
### Проверяемая установка Xray
|
||
|
||
`proxy-client install` устанавливает Xray 26.3.27 в `~/.local/bin/xray`. Для
|
||
`x86_64`/`amd64` используется `Xray-linux-64.zip`, для `aarch64`/`arm64` —
|
||
`Xray-linux-arm64-v8a.zip`; остальные архитектуры отклоняются.
|
||
|
||
CLI скачивает официальный архив и файл `.dgst`, сверяет SHA-256 и фактическую
|
||
версию бинарника, а затем атомарно заменяет назначение. При любой ошибке старый
|
||
файл остаётся на месте. Если нужная версия уже установлена, сеть не нужна и
|
||
ничего не меняется. Другой файл или симлинк не заменяется без `--replace`
|
||
(симлинк заменяется сам, файл по ссылке не трогается); каталог не заменяется
|
||
никогда. Если после установки `PATH` находит другой `xray`, `install` сообщает об
|
||
ошибке и подсказывает команду.
|
||
|
||
Устанавливать можно и при работающем Xray: он продолжит работу со старым файлом,
|
||
а `install` напомнит перезапустить `start`.
|
||
|
||
`start` использует первый `xray` из `PATH` и принимает только версию 26.3.27.
|
||
|
||
### Файлы и безопасность
|
||
|
||
| Путь | Назначение |
|
||
|------|------------|
|
||
| `~/.config/proxy-client/config` | Несекретный машинный выбор режима |
|
||
| `~/.config/proxy-client/default.url` | Закрытая VLESS-ссылка, права `600` |
|
||
| `~/.config/proxy-client/examples/` | Синтетические примеры без секретов |
|
||
| `$XDG_STATE_HOME/proxy-client/` или `~/.local/state/proxy-client/` | Состояние и сгенерированный JSON |
|
||
|
||
Сводка скрывает UUID и ключи, а для XHTTP показывает только длину `path`.
|
||
Если файл профиля доступен другим пользователям, `start` не запустится и
|
||
покажет готовую команду `chmod 600` для конечного файла (у симлинка — для файла,
|
||
на который он указывает). Служебный каталог закрыт для других пользователей
|
||
(`700`, файлы в нём — `600`) и очищается после остановки Xray. Сбой
|
||
определения внешнего IP показывается как предупреждение и не останавливает уже
|
||
готовый процесс.
|
||
|
||
## Изменение кода
|
||
|
||
Перед правкой proxy-client прочитайте
|
||
[спецификацию](../docs/specs/2026-07-15-proxy-client-design.md): там сказано,
|
||
от чего клиент защищается, от чего нет и почему, и где живут тесты.
|
||
|
||
Все тесты пакета (из корня dotfiles):
|
||
|
||
```bash
|
||
tests/test_proxy_client_full.sh
|
||
```
|