fix(install): предотвращено сворачивание каталога .local

- Зачем:
  - свежая установка могла превратить ~/.local в ссылку на пакет и записать Xray в репозиторий.
- Что:
  - перед Stow теперь гарантируются настоящие ~/.local/bin и ~/.config/proxy-client без раскрытия посторонних ссылок.
  - добавлены регрессионные проверки свежего HOME и посторонней ссылки ~/.local.
  - README перестроены вокруг установки, выбора режима и ежедневного использования.
- Проверка:
  - bash tests/test_install.sh.
  - bash tests/test_proxy_launchers.sh.
  - bash -n install.sh tests/test_install.sh.
  - git diff --cached --check.
This commit is contained in:
ddadmin committed 2026-07-21 11:15:27 -04:00
1 parent bca90c1d21
commit 9c2e134fc1
5 files changed
+341 -201

No files matched your search

+288 -138
View File
@@ -1,175 +1,325 @@
# proxy-client
Переносимый Linux/Bash-интерфейс для безопасного запуска команд через явно
настроенный внешний прокси или через управляемый пользовательский Xray.
`proxy-client` — переносимый Linux/Bash-интерфейс для запуска команд через
внешний прокси или через управляемый пользовательский Xray. Он добавляет команды
`proxy_on`, `proxy_off`, `pclaude` и `pcodex`, но не подменяет обычные `claude`
и `codex`.
CLI требует Python 3.8+ и использует только стандартную библиотеку. Он ничего не
устанавливает и не запускает без явной подкоманды. `start` использует первый
`xray` из `PATH`; скачивания и установки при запуске нет.
CLI требует Python 3.8+ и использует только стандартную библиотеку. Он не
запускает Xray и не переключает режим прокси без явной команды пользователя.
## Подключение
## Установка
В составе dotfiles:
### В составе dotfiles
Полная установка подключает пакет и автоматически загружает Bash-функции в
новых интерактивных сессиях:
```bash
cd ~/dotfiles
mkdir -p ~/.config/proxy-client
./install.sh
```
Установщик заранее создаёт `~/.local/bin` и `~/.config/proxy-client`, чтобы Stow
размещал ссылки внутри них, а не превращал сами каталоги в ссылки на
репозиторий.
После установки откройте новый Bash или выполните:
```bash
source ~/.bash_custom
```
### Только пакет `proxy-client`
```bash
cd ~/dotfiles
mkdir -p ~/.local/bin ~/.config/proxy-client
stow proxy-client
source ~/.config/proxy-client/shell.bash
```
Полный `./install.sh` выполняет тот же `stow` вместе с остальными пакетами, а
`.bash_custom` подключает модуль автоматически.
Если Bash-пакет dotfiles не установлен, добавьте подключение модуля в
`~/.bashrc`:
Без остальных dotfiles можно создать две ссылки:
```bash
printf '%s\n' 'source ~/.config/proxy-client/shell.bash' >> ~/.bashrc
```
## Как выбирается прокси
Режим определяется только машинным файлом
`~/.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-шлюза меняется при перезапуске. Интерактивный
`./install.sh` предлагает добавить обновление `hproxy.local` в `/etc/wsl.conf`.
То же можно настроить вручную:
```ini
[boot]
command = "/path/to/dotfiles/scripts/setup-hproxy.sh"
```
После изменения выполните `wsl --shutdown` из Windows. Обновить адрес без
перезапуска WSL можно так:
```bash
sudo ~/dotfiles/scripts/setup-hproxy.sh
# или после загрузки Bash-функций:
update_hproxy
```
В NAT mode Hiddify должен слушать адрес, доступный через Windows-шлюз, а не
только `127.0.0.1`.
В [WSL 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
```
Альтернатива — создать `config.external` с `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.
`stop` завершает управляемые процессы и удаляет созданные служебные файлы. Он
работает и после изменения машинного типа на `external`.
## Диагностика
Начните с проверки выбранного режима:
```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
```
## Справочник
### Ручное подключение без Stow
Можно создать ссылки на CLI, Bash-модуль и примеры либо скопировать те же файлы:
```bash
mkdir -p ~/.local/bin ~/.config/proxy-client
ln -s /path/to/proxy-client/.local/bin/proxy-client ~/.local/bin/proxy-client
ln -s /path/to/proxy-client/.config/proxy-client/shell.bash \
~/.config/proxy-client/shell.bash
printf '%s\n' 'source ~/.config/proxy-client/shell.bash' >> ~/.bashrc
ln -s /path/to/proxy-client/.config/proxy-client/examples \
~/.config/proxy-client/examples
```
Вместо ссылок файлы можно скопировать в те же пути. Исполняемому файлу нужен
режим `chmod 755`. Каталог `~/.local/bin` должен входить в `PATH`.
Исполняемому файлу нужен режим `755`, а `~/.local/bin` должен входить в `PATH`.
## Машинный конфиг
### Машинный конфиг
Скопируйте пример нужного режима и отредактируйте его:
Формат строгий: одна секция `[proxy]` и пять обязательных ключей без повторов и
неизвестных полей:
```bash
mkdir -p ~/.config/proxy-client
cp ~/.config/proxy-client/examples/config.xray ~/.config/proxy-client/config
# или для внешнего Hiddify:
cp ~/.config/proxy-client/examples/config.external ~/.config/proxy-client/config
```
- `type` — `external` или `xray`;
- `name` — имя для диагностики;
- `address` — IPv4, IPv6 без квадратных скобок или ASCII DNS-имя без схемы и
порта;
- `port` — TCP-порт локального входа;
- `no_proxy_extra` — дополнительные IP и домены через запятую, значение может
быть пустым.
Формат строгий: одна секция `[proxy]`, пять обязательных ключей без повторов и
неизвестных полей.
```ini
[proxy]
type = external
name = Hiddify
address = hproxy.local
port = 12334
no_proxy_extra = .example.internal,git.example.internal
```
`address` — IPv4, IPv6 без квадратных скобок или ASCII DNS-имя без схемы и
порта. `no_proxy_extra` — список через запятую; значение может быть пустым.
Поддержка CIDR в `NO_PROXY` различается между программами, поэтому надёжнее
указывать точные IP и домены.
указывать точные IP и домены. Без машинного файла внешний режим дополнительно
включает `.concept-software.ru` в `NO_PROXY`. Для `type = xray` адрес обязан
быть адресом обратной петли, например `127.0.0.1` или `::1`.
Если файла нет, используется прежний внешний Hiddify на
`hproxy.local:12334`, включая `.concept-software.ru` в `NO_PROXY`. Это не
переключает тип прокси автоматически.
### Поддерживаемые профили
Для управляемого Xray задайте явный тип, loopback IP и нужный локальный порт:
```ini
[proxy]
type = xray
name = Local Xray
address = 127.0.0.1
port = 12334
no_proxy_extra = .example.internal,git.example.internal
```
Секретный профиль хранится отдельно. Скопируйте синтетический пример, замените
его одной настоящей VLESS-ссылкой и закройте права конечного файла:
```bash
cp ~/.config/proxy-client/examples/default.url ~/.config/proxy-client/default.url
chmod 600 ~/.config/proxy-client/default.url
```
Файл должен содержать ровно одну непустую строку. Можно передать другой файл
через `proxy-client start -f /path/to/profile.url`. Если это симлинк, проверяются
права конечного файла.
## Команды
```bash
proxy-client install [--replace]
proxy-client status
proxy-client start [-f PROFILE]
proxy-client stop
proxy-client env
proxy-client run -- COMMAND ARG...
proxy_on
proxy_off
pclaude [ARG...]
pcodex [ARG...]
myip
```
`install` без root устанавливает единственную поддержанную версию Xray
`26.3.27` в `~/.local/bin/xray`. Для `x86_64`/`amd64` используется официальный
`Xray-linux-64.zip`, для `aarch64`/`arm64` — `Xray-linux-arm64-v8a.zip`.
Остальные архитектуры отклоняются. CLI скачивает рядом файл `.dgst`, проверяет
единственную запись `SHA2-256= <64 lowercase hex>` в официальном
четырёхстрочном формате, извлекает `xray` в закрытый временный каталог внутри
`~/.local/bin`, проверяет `xray version` и только затем атомарно меняет
назначение. Дубликаты, uppercase, пропущенные и лишние записи отклоняются.
Архив и извлечённый бинарник ограничены 64 MiB, `.dgst` — 4 KiB; объявленный
`Content-Length` должен точно совпадать с фактически прочитанным размером.
Существующая корректная версия `26.3.27` даёт успешный no-op без сети. Другой,
битый, неисполняемый файл или симлинк не заменяется без `--replace`; каталог не
заменяется никогда. Если после установки `PATH` выбирает не этот файл, команда
завершается с ошибкой и предлагает поставить `~/.local/bin` в начало `PATH`,
но проверенная установка сохраняется. Родительский симлинк принимается, только
если разрешённый каталог остаётся внутри `HOME`. Во время живого или
неподтверждённого managed-экземпляра установка запрещена: сначала выполните
`proxy-client stop`.
Для `external` команды `status`, `env` и `run` проверяют настроенный адрес и
порт. Для `xray` они дополнительно требуют закрытое состояние именно того
супервизора и дочернего Xray, который запустил CLI. Один открытый порт или чужой
PID не принимается. `run` никогда не запускает Xray скрыто.
`start` поддерживает только Xray `26.3.27`, сначала выполняет
`xray version` и `xray run -test`, затем остаётся на переднем плане. Используйте
отдельную панель tmux. После готовности один loopback-порт принимает HTTP и
SOCKS. `stop` ограниченно ждёт завершения супервизора и Xray, очищает служебные
файлы и возвращает исходный `start` в терминал. Команда `stop` работает и после
явного переключения машинного типа на `external`. CLI не создаёт systemd-службу,
не запускает Xray автоматически и не переключается между типами прокси.
`run` передаёт восемь proxy-переменных только дочернему процессу и сохраняет
аргументы и код возврата. `proxy_on` атомарно применяет те же восемь переменных
к текущей Bash-сессии; `proxy_off` удаляет их.
Таймаут проверки задаётся положительным числом секунд:
```bash
export PROXY_PREFLIGHT_TIMEOUT=2
```
Значение по умолчанию — 2 секунды. `env` при успехе печатает только восемь строк
`KEY=value`; для обычной диагностики используйте `status` или `myip`.
## Поддерживаемые профили
Поддерживаются только VLESS TCP/RAW с REALITY и VLESS XHTTP с TLS. Неизвестные,
Поддерживаются VLESS TCP/RAW с REALITY и VLESS XHTTP с TLS. Неизвестные,
повторные и несовместимые query-параметры отклоняются. XHTTP поддерживает ALPN
`http/1.1`, `h2` и `h3`; `extra` должен быть JSON-объектом без повторных ключей.
Xray 26.3.27 использует `pcs` и `vcn`, а устаревшие `allowInsecure` и
`insecure` не переносит. Истинное legacy-значение даёт предупреждение, но
обычная проверка сертификата остаётся включённой. Автоматического перехода
между версиями или типами прокси нет.
обычная проверка сертификата остаётся включённой.
Для справки при ручном конфигурировании: последняя версия Xray, где
`allowInsecure` ещё применяется, —
Последняя версия 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).
CLI такие версии не поддерживает и не устанавливает.
`proxy-client` эти версии не поддерживает и не устанавливает.
Сводка скрывает UUID и ключи. Содержимое XHTTP `path` не показывается — только
его длина. Сгенерированный JSON и состояние лежат в закрытом каталоге
`$XDG_STATE_HOME/proxy-client` либо `~/.local/state/proxy-client` и удаляются после
штатной остановки. Сбой диагностики внешнего IP через локальный HTTP-прокси
показывается как предупреждение и не останавливает уже готовый Xray.
### Проверяемая установка 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, ограничения
размера и фактическую версию бинарника, а затем атомарно заменяет назначение.
Корректная уже установленная версия даёт no-op без сети. Другой файл или симлинк
не заменяется без `--replace`; каталог не заменяется никогда.
`start` использует первый `xray` из `PATH` и принимает только версию 26.3.27.
Если `PATH` выбирает другой бинарник, команда сообщает об ошибке. Во время
живого или неподтверждённого управляемого процесса установка запрещена.
### Файлы и безопасность
| Путь | Назначение |
|------|------------|
| `~/.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`.
Служебный каталог закрыт для других пользователей и очищается после штатной
остановки Xray. Сбой определения внешнего IP показывается как предупреждение и
не останавливает уже готовый процесс.