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.
This commit is contained in:
1 parent
e5323284a8
commit
6e431d875e
25 files changed
+310
-227
No files matched your search
@@ -0,0 +1,87 @@
|
|||||||
|
# Handoff: `proxy-client` переименован, сетевой маршрут требует диагностики
|
||||||
|
|
||||||
|
Обновлён: 2026-07-21 09:23 EDT.
|
||||||
|
|
||||||
|
## Состояние задачи 05
|
||||||
|
|
||||||
|
Автоматическая часть готова. Публичное имя изменено с `xray-proxy` на
|
||||||
|
`proxy-client`, а ручная приёмка дошла до реального сетевого запроса. Xray
|
||||||
|
запускается и принимает локальные соединения, но удалённый маршрут пока не
|
||||||
|
работает. Статус тикета остаётся `ready-for-human`.
|
||||||
|
|
||||||
|
Тикет: `.scratch/proxy-client/issues/05-package-delivery-and-acceptance.md`.
|
||||||
|
|
||||||
|
## Переименование
|
||||||
|
|
||||||
|
- stow-пакет: `proxy-client/`;
|
||||||
|
- CLI: `~/.local/bin/proxy-client`;
|
||||||
|
- единый пользовательский каталог: `~/.config/proxy-client/`;
|
||||||
|
- служебный каталог: `~/.local/state/proxy-client/`;
|
||||||
|
- машинный конфиг и закрытый профиль теперь лежат рядом как `config` и
|
||||||
|
`default.url`;
|
||||||
|
- примеры называются `config.external` и `config.xray`;
|
||||||
|
- имена тестов, спецификации, задач, README и Bash-модуля приведены к
|
||||||
|
`proxy-client`;
|
||||||
|
- `Xray`, бинарник `xray` и значение `type = xray` сохранены как названия
|
||||||
|
конкретного движка.
|
||||||
|
|
||||||
|
Поддержка старой раскладки в `install.sh` намеренно не добавлена: публичное имя
|
||||||
|
изменено до завершения разработки и выпуска. Текущий HOME уже приведён к новой
|
||||||
|
структуре. Свежая установка заранее создаёт настоящий
|
||||||
|
`~/.config/proxy-client`, поэтому Stow не может свернуть его в ссылку на
|
||||||
|
репозиторий.
|
||||||
|
|
||||||
|
## Автоматическая проверка
|
||||||
|
|
||||||
|
`bash tests/test_proxy_client_full.sh` завершился с кодом 0 после
|
||||||
|
переименования:
|
||||||
|
|
||||||
|
- системный Python 3.14: 79 тестов, `OK`, 61,034 с;
|
||||||
|
- Bash-интеграция: 8 проверок, `PASS`; `shellcheck` не установлен;
|
||||||
|
- установка и миграция: `All install tests passed`;
|
||||||
|
- CPython 3.8 через `uv`: 79 тестов, `OK`, 52,446 с.
|
||||||
|
|
||||||
|
Дополнительно прошли синтаксическая проверка shell-файлов и `git diff --check`.
|
||||||
|
|
||||||
|
## Текущий HOME
|
||||||
|
|
||||||
|
Во время разработки текущий HOME приведён к новой структуре:
|
||||||
|
|
||||||
|
- `~/.config/proxy-client` — настоящий каталог;
|
||||||
|
- `config` и `default.url` — обычные файлы с правами `600`;
|
||||||
|
- профиль содержит одну непустую строку, его содержимое не выводилось;
|
||||||
|
- `shell.bash`, `examples` и CLI указывают в новый stow-пакет;
|
||||||
|
- старые `~/.config/xray-proxy`, `~/.config/proxy/config`,
|
||||||
|
`~/.local/bin/xray-proxy` и `~/.local/state/xray-proxy` отсутствуют.
|
||||||
|
|
||||||
|
## Ручная приёмка
|
||||||
|
|
||||||
|
Подтверждено:
|
||||||
|
|
||||||
|
- установлен Xray 26.3.27;
|
||||||
|
- `proxy-client start` успешно проверяет версию, строит JSON и запускает Xray;
|
||||||
|
- `proxy-client status` видит управляемый процесс на `127.0.0.1:12334`;
|
||||||
|
- `xray run -test` сообщает `Configuration OK`;
|
||||||
|
- после проверки процесс штатно остановлен командой `proxy-client stop`.
|
||||||
|
|
||||||
|
Не прошло:
|
||||||
|
|
||||||
|
- встроенное определение внешнего IP;
|
||||||
|
- HTTP через `127.0.0.1:12334`;
|
||||||
|
- SOCKS5h через тот же порт.
|
||||||
|
|
||||||
|
Оба запроса `curl` завершились кодом 35: TLS-соединение неожиданно закрылось во
|
||||||
|
время рукопожатия. Внешний IP и содержимое профиля в вывод не попадали.
|
||||||
|
|
||||||
|
## Следующий шаг
|
||||||
|
|
||||||
|
Диагностировать сетевой маршрут XHTTP/TLS: сравнить профиль с рабочим клиентом,
|
||||||
|
проверить доступность удалённого узла и выяснить, связан ли EOF с преобразованием
|
||||||
|
профиля, параметрами XHTTP/TLS или удалённой стороной. До этого не отмечать
|
||||||
|
ручную HTTP/SOCKS-приёмку выполненной.
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
Переименование и текущий handoff ещё не закоммичены. Не включать посторонние
|
||||||
|
неотслеживаемые `agents/.agents/.skill-lock.json` и
|
||||||
|
`scripts/setup-xray-proxy.sh`.
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Handoff: владение `~/.config` исправлено, задача 05 готова к приёмке
|
|
||||||
|
|
||||||
Обновлён: 2026-07-21 08:18 EDT.
|
|
||||||
|
|
||||||
## Состояние задачи 05
|
|
||||||
|
|
||||||
Автоматическая часть `headless-xray-proxy` зафиксирована коммитом `fd8210d`.
|
|
||||||
Тикет `.scratch/headless-xray-proxy/issues/05-package-delivery-and-acceptance.md`
|
|
||||||
находится в `ready-for-human`; результаты тестов и команды ручной приёмки уже
|
|
||||||
записаны там. Дальше можно продолжать ручную приёмку по этому тикету.
|
|
||||||
|
|
||||||
## Исправленный дефект stow
|
|
||||||
|
|
||||||
Корневая причина была в сворачивании GNU Stow: в пустом домашнем каталоге пакет
|
|
||||||
`zed` создавал общую ссылку `~/.config -> ~/dotfiles/zed/.config`. После этого
|
|
||||||
рабочие настройки приложений и закрытый профиль Xray физически попадали внутрь
|
|
||||||
репозитория.
|
|
||||||
|
|
||||||
`install.sh` теперь:
|
|
||||||
|
|
||||||
- заранее создаёт настоящий каталог `~/.config`, поэтому свежая установка
|
|
||||||
создаёт только ссылку `~/.config/zed`;
|
|
||||||
- распознаёт старую ссылку лишь при точном совпадении с
|
|
||||||
`$DOTFILES_DIR/zed/.config`;
|
|
||||||
- оставляет управляемый каталог `zed` в пакете, а остальные верхнеуровневые
|
|
||||||
элементы переносит в настоящий `~/.config`;
|
|
||||||
- до первого переноса проверяет, что смог полностью перечислить данные;
|
|
||||||
- при штатной ошибке переноса возвращает уже перемещённые элементы назад;
|
|
||||||
- не раскрывает произвольные ссылки `~/.config`.
|
|
||||||
|
|
||||||
В `tests/test_install.sh` добавлены проверки свежей установки, миграции с
|
|
||||||
вымышленным секретом, сохранения прав `600`, отсутствия секрета в выводе,
|
|
||||||
повторного запуска и неприкосновенности произвольной ссылки.
|
|
||||||
|
|
||||||
## Проверка и живое состояние
|
|
||||||
|
|
||||||
Полный набор `bash tests/test_xray_proxy_full.sh` прошёл на системном Python и
|
|
||||||
Python 3.8 через `uv`: по 79 Python-тестов, 8 проверок командной оболочки и тесты
|
|
||||||
установщика. Синтаксис сценариев командной оболочки и `git diff --check` также
|
|
||||||
прошли.
|
|
||||||
|
|
||||||
Миграция применена к текущему домашнему каталогу:
|
|
||||||
|
|
||||||
- `~/.config` — настоящий каталог;
|
|
||||||
- `~/.config/zed` указывает на пакет Zed;
|
|
||||||
- в `zed/.config` остался только каталог `zed`;
|
|
||||||
- рабочие каталоги приложений находятся в `~/.config`;
|
|
||||||
- `~/.config/xray-proxy/default.url` — обычный файл вне репозитория, сохранил
|
|
||||||
права `600` и одну непустую строку. Содержимое профиля не выводилось.
|
|
||||||
|
|
||||||
## Текущее состояние Git
|
|
||||||
|
|
||||||
Не включать в коммиты посторонние неотслеживаемые
|
|
||||||
`agents/.agents/.skill-lock.json` и `scripts/setup-xray-proxy.sh`.
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -93,7 +93,7 @@ Ubuntu / РЕД ОС / Astra / Alt.
|
|||||||
и state не тронуты;
|
и state не тронуты;
|
||||||
- `shutil.which`-проверка: отсутствие каталога в PATH и конкурирующий
|
- `shutil.which`-проверка: отсутствие каталога в PATH и конкурирующий
|
||||||
более ранний `xray` в PATH.
|
более ранний `xray` в PATH.
|
||||||
- [x] `xray-proxy/README.md` обновлён: одна поддержанная версия 26.3.27
|
- [x] `proxy-client/README.md` обновлён: одна поддержанная версия 26.3.27
|
||||||
(упоминания 25.12.8 удалены), использование `install [--replace]`,
|
(упоминания 25.12.8 удалены), использование `install [--replace]`,
|
||||||
маппинг x86_64/aarch64, SHA-256-проверка, поведение PATH, отказ при
|
маппинг x86_64/aarch64, SHA-256-проверка, поведение PATH, отказ при
|
||||||
живом managed-экземпляре.
|
живом managed-экземпляре.
|
||||||
@@ -141,4 +141,4 @@ TASK: 3 NIT) → триаж (FIXED 7, REJECTED 0) → узкие перепро
|
|||||||
`allowInsecure` — 25.12.8, опция удалена в 26.2.6; CLI их не ставит.
|
`allowInsecure` — 25.12.8, опция удалена в 26.2.6; CLI их не ставит.
|
||||||
|
|
||||||
Тесты: 79 OK (baseline 49, +30) + 8 PASS shell-проверок. Затраты — в
|
Тесты: 79 OK (baseline 49, +30) + 8 PASS shell-проверок. Затраты — в
|
||||||
`.scratch/headless-xray-proxy/token-ledger.md`.
|
`.scratch/proxy-client/token-ledger.md`.
|
||||||
+30
-15
@@ -1,6 +1,6 @@
|
|||||||
# 05 — Доставка пакета и итоговая приёмка
|
# 05 — Доставка пакета и итоговая приёмка
|
||||||
|
|
||||||
**What to build:** Поставка `xray-proxy` проверена целиком: корневой обзор
|
**What to build:** Поставка `proxy-client` проверена целиком: корневой обзор
|
||||||
соответствует готовым возможностям, автоматические проверки воспроизводимы, а
|
соответствует готовым возможностям, автоматические проверки воспроизводимы, а
|
||||||
ручная приёмка на доступных системах подготовлена для пользователя.
|
ручная приёмка на доступных системах подготовлена для пользователя.
|
||||||
|
|
||||||
@@ -13,13 +13,13 @@
|
|||||||
Тикеты 03/04/06 уже доставили CLI, Bash-модуль, установку и жизненный цикл.
|
Тикеты 03/04/06 уже доставили CLI, Bash-модуль, установку и жизненный цикл.
|
||||||
Задача 05 их проверяет, но не переписывает. Источники истины:
|
Задача 05 их проверяет, но не переписывает. Источники истины:
|
||||||
|
|
||||||
- [`xray-proxy/README.md`](../../../xray-proxy/README.md) — пользовательские
|
- [`proxy-client/README.md`](../../../proxy-client/README.md) — пользовательские
|
||||||
команды, настройка и ограничения;
|
команды, настройка и ограничения;
|
||||||
- [исходный дизайн](../../../docs/specs/2026-07-15-headless-xray-proxy-design.md)
|
- [исходный дизайн](../../../docs/specs/2026-07-15-proxy-client-design.md)
|
||||||
— границы и принятые решения.
|
— границы и принятые решения.
|
||||||
|
|
||||||
- [x] Корневой `README.md` кратко описывает внешний Hiddify и управляемый Xray,
|
- [x] Корневой `README.md` кратко описывает внешний Hiddify и управляемый Xray,
|
||||||
показывает `stow xray-proxy` и ссылается на полную инструкцию.
|
показывает `stow proxy-client` и ссылается на полную инструкцию.
|
||||||
- [x] Пакет подключается через stow, ручные ссылки или копирование; общий
|
- [x] Пакет подключается через stow, ручные ссылки или копирование; общий
|
||||||
установщик использует тот же пакет.
|
установщик использует тот же пакет.
|
||||||
- [x] Инструкция покрывает две семьи VLESS, Xray 26.3.27, отсутствие systemd,
|
- [x] Инструкция покрывает две семьи VLESS, Xray 26.3.27, отсутствие systemd,
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
## Автоматическая проверка
|
## Автоматическая проверка
|
||||||
|
|
||||||
- [x] `tests/test_xray_proxy_full.sh` запускает весь Python-набор под системным
|
- [x] `tests/test_proxy_client_full.sh` запускает весь Python-набор под системным
|
||||||
`python3` и CPython 3.8 через `uv`, затем Bash-тесты и проверку установки.
|
`python3` и CPython 3.8 через `uv`, затем Bash-тесты и проверку установки.
|
||||||
- [x] Тестовый Xray остаётся отдельным исполняемым файлом и получает доступ к
|
- [x] Тестовый Xray остаётся отдельным исполняемым файлом и получает доступ к
|
||||||
библиотекам исходного Python. Это устраняет ложные падения переносимой сборки
|
библиотекам исходного Python. Это устраняет ложные падения переносимой сборки
|
||||||
@@ -66,13 +66,13 @@
|
|||||||
|
|
||||||
### 2026-07-21 — автоматическая проверка
|
### 2026-07-21 — автоматическая проверка
|
||||||
|
|
||||||
`bash tests/test_xray_proxy_full.sh` завершился с кодом 0:
|
`bash tests/test_proxy_client_full.sh` завершился с кодом 0:
|
||||||
|
|
||||||
- системный Python 3.14: 79 тестов, `OK`, 57,276 с;
|
- системный Python 3.14: 79 тестов, `OK`, 61,034 с;
|
||||||
- Bash-интеграция: 8 проверок, `PASS`; `shellcheck` отсутствует и явно пропущен;
|
- Bash-интеграция: 8 проверок, `PASS`; `shellcheck` отсутствует и явно пропущен;
|
||||||
- установка: `All install tests passed`; в `/tmp` не осталось каталогов
|
- установка: `All install tests passed`; в `/tmp` не осталось каталогов
|
||||||
`dotfiles-install-test.*`;
|
`dotfiles-install-test.*`;
|
||||||
- CPython 3.8.20 через `uv`: 79 тестов, `OK`, 51,412 с.
|
- CPython 3.8 через `uv`: 79 тестов, `OK`, 52,446 с.
|
||||||
|
|
||||||
### 2026-07-21 — команды ручной приёмки
|
### 2026-07-21 — команды ручной приёмки
|
||||||
|
|
||||||
@@ -82,22 +82,37 @@
|
|||||||
```bash
|
```bash
|
||||||
python3 --version
|
python3 --version
|
||||||
cd ~/dotfiles
|
cd ~/dotfiles
|
||||||
stow xray-proxy
|
stow proxy-client
|
||||||
source ~/.config/xray-proxy/shell.bash
|
source ~/.config/proxy-client/shell.bash
|
||||||
xray-proxy install
|
proxy-client install
|
||||||
xray-proxy start
|
proxy-client start
|
||||||
```
|
```
|
||||||
|
|
||||||
Во второй панели, пока `start` работает:
|
Во второй панели, пока `start` работает:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xray-proxy status
|
proxy-client status
|
||||||
xray run -test -c "${XDG_STATE_HOME:-$HOME/.local/state}/xray-proxy/client.json"
|
xray run -test -c "${XDG_STATE_HOME:-$HOME/.local/state}/proxy-client/client.json"
|
||||||
curl --proxy http://127.0.0.1:12334 https://api.ipify.org
|
curl --proxy http://127.0.0.1:12334 https://api.ipify.org
|
||||||
curl --proxy socks5h://127.0.0.1:12334 https://api.ipify.org
|
curl --proxy socks5h://127.0.0.1:12334 https://api.ipify.org
|
||||||
xray-proxy stop
|
proxy-client stop
|
||||||
```
|
```
|
||||||
|
|
||||||
Если настроен другой локальный адрес или порт, те же значения подставляются в
|
Если настроен другой локальный адрес или порт, те же значения подставляются в
|
||||||
обе команды `curl`. В отчёт записываются только версии, коды возврата и итог
|
обе команды `curl`. В отчёт записываются только версии, коды возврата и итог
|
||||||
проверки HTTP/SOCKS.
|
проверки HTTP/SOCKS.
|
||||||
|
|
||||||
|
### 2026-07-21 — переименование и частичная ручная приёмка
|
||||||
|
|
||||||
|
Публичный интерфейс переименован в `proxy-client`, а машинный конфиг и закрытый
|
||||||
|
профиль объединены в `~/.config/proxy-client/`. В ходе разработки текущий HOME
|
||||||
|
приведён к новой структуре; старые пользовательские пути отсутствуют, права
|
||||||
|
файлов `600` сохранены. Поддержка прежнего имени в установщик не добавлялась,
|
||||||
|
поскольку выпуск ещё не состоялся.
|
||||||
|
|
||||||
|
Xray 26.3.27 установился и запустился, `status` подтвердил процесс на
|
||||||
|
`127.0.0.1:12334`, а `xray run -test` завершился сообщением
|
||||||
|
`Configuration OK`. HTTP и SOCKS5h через локальный вход не прошли: оба запроса
|
||||||
|
`curl` завершились кодом 35 с неожиданным закрытием TLS-соединения во время
|
||||||
|
рукопожатия. После проверки Xray штатно остановлен. Пункты ручной приёмки пока
|
||||||
|
не отмечены.
|
||||||
+5
-5
@@ -22,9 +22,9 @@ Non-gating замечания ревью приняты как риски мод
|
|||||||
1. Этот файл целиком, затем `/tmp/xray-simplify-Zeo5/06-simplify/sweep.md` —
|
1. Этот файл целиком, затем `/tmp/xray-simplify-Zeo5/06-simplify/sweep.md` —
|
||||||
там точный line-level blast radius каждого пункта (секция 2 — срезы A/B,
|
там точный line-level blast radius каждого пункта (секция 2 — срезы A/B,
|
||||||
секция 3 — SIMP-N). Номера строк — ориентир, сверяй с фактическим кодом.
|
секция 3 — SIMP-N). Номера строк — ориентир, сверяй с фактическим кодом.
|
||||||
2. `docs/specs/2026-07-15-headless-xray-proxy-design.md` — исходная спека.
|
2. `docs/specs/2026-07-15-proxy-client-design.md` — исходная спека.
|
||||||
3. `xray-proxy/.local/bin/xray-proxy`, `xray-proxy/.config/xray-proxy/shell.bash`,
|
3. `proxy-client/.local/bin/proxy-client`, `proxy-client/.config/proxy-client/shell.bash`,
|
||||||
`tests/test_xray_proxy.py`, `tests/test_xray_proxy_managed.py`.
|
`tests/test_proxy_client.py`, `tests/test_proxy_client_managed.py`.
|
||||||
|
|
||||||
Команды проверки (обе обязательны):
|
Команды проверки (обе обязательны):
|
||||||
|
|
||||||
@@ -88,11 +88,11 @@ Non-gating замечания ревью приняты как риски мод
|
|||||||
|
|
||||||
- [x] `write_private_json`: простая запись файла с правами 0600 внутри
|
- [x] `write_private_json`: простая запись файла с правами 0600 внутри
|
||||||
уже-0700 каталога; O_EXCL/fsync/`os.replace`/PID-суффиксы удалены.
|
уже-0700 каталога; O_EXCL/fsync/`os.replace`/PID-суффиксы удалены.
|
||||||
- [x] Валидация `PROXY_PREFLIGHT_TIMEOUT` и `XRAY_PROXY_STOP_TIMEOUT` — один
|
- [x] Валидация `PROXY_PREFLIGHT_TIMEOUT` и `PROXY_CLIENT_STOP_TIMEOUT` — один
|
||||||
guard «`float()` успешен и > 0, иначе понятная ошибка»; `math` не
|
guard «`float()` успешен и > 0, иначе понятная ошибка»; `math` не
|
||||||
импортируется; тесты экстремальных значений (nan/inf/1e-7/1e308)
|
импортируется; тесты экстремальных значений (nan/inf/1e-7/1e308)
|
||||||
удалены. Для `PROXY_PREFLIGHT_TIMEOUT` оставить один тест «плохое
|
удалены. Для `PROXY_PREFLIGHT_TIMEOUT` оставить один тест «плохое
|
||||||
значение → понятный отказ»; для `XRAY_PROXY_STOP_TIMEOUT` отдельного
|
значение → понятный отказ»; для `PROXY_CLIENT_STOP_TIMEOUT` отдельного
|
||||||
теста нет и добавлять его не нужно — достаточно guard'а.
|
теста нет и добавлять его не нужно — достаточно guard'а.
|
||||||
- [x] `check_reachable` использует `socket.create_connection(timeout=...)`
|
- [x] `check_reachable` использует `socket.create_connection(timeout=...)`
|
||||||
без SIGALRM/setitimer-машинерии. Принятый риск (записан здесь):
|
без SIGALRM/setitimer-машинерии. Принятый риск (записан здесь):
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# Токен-леджер проекта headless-xray-proxy
|
# Токен-леджер проекта proxy-client
|
||||||
|
|
||||||
Затраты по этапам конвейера, для калибровки эффорта и планирования лимитов
|
Затраты по этапам конвейера, для калибровки эффорта и планирования лимитов
|
||||||
(5-часовые / недельные окна подписок). Прямое сравнение корректно внутри
|
(5-часовые / недельные окна подписок). Прямое сравнение корректно внутри
|
||||||
@@ -48,7 +48,7 @@ Chezmoi использует отдельный source dir `chezmoi/` внутр
|
|||||||
| Пакет | Что делает |
|
| Пакет | Что делает |
|
||||||
|-------|------------|
|
|-------|------------|
|
||||||
| `bash` | Кастомный prompt, общие функции, подгрузка секретов |
|
| `bash` | Кастомный prompt, общие функции, подгрузка секретов |
|
||||||
| `xray-proxy` | Переносимый CLI и Bash-команды для явного запуска через прокси |
|
| `proxy-client` | Переносимый CLI и Bash-команды для явного запуска через прокси |
|
||||||
| `tmux` | Два префикса (`Ctrl+b`, `Ctrl+Space`), vi-режим, split по `\|` / `-` |
|
| `tmux` | Два префикса (`Ctrl+b`, `Ctrl+Space`), vi-режим, split по `\|` / `-` |
|
||||||
| `vim` | Минимальный .vimrc без плагинов, 2 пробела, leader = `Space` |
|
| `vim` | Минимальный .vimrc без плагинов, 2 пробела, leader = `Space` |
|
||||||
| `zed` | Конфигурация Zed редактора |
|
| `zed` | Конфигурация Zed редактора |
|
||||||
@@ -95,13 +95,14 @@ export ZAI_API_KEY="..." # https://z.ai
|
|||||||
|
|
||||||
Файл подгружается при старте bash и VS Code Server. Не трекается в git.
|
Файл подгружается при старте bash и VS Code Server. Не трекается в git.
|
||||||
|
|
||||||
### Прокси (`xray-proxy`)
|
### Прокси (`proxy-client`)
|
||||||
|
|
||||||
Linux/Bash-команды `proxy_on/off`, `pclaude`, `pcodex` и `myip` находятся в
|
Linux/Bash-команды `proxy_on/off`, `pclaude`, `pcodex` и `myip` находятся в
|
||||||
самостоятельном пакете [`xray-proxy`](xray-proxy/README.md). Он поддерживает
|
самостоятельном пакете [`proxy-client`](proxy-client/README.md). Он поддерживает
|
||||||
явно выбранный внешний Hiddify или управляемый пользовательский Xray 26.3.27 с
|
явно выбранный внешний Hiddify или управляемый пользовательский Xray 26.3.27 с
|
||||||
профилями VLESS TCP/RAW + REALITY и XHTTP + TLS. Для отдельного подключения
|
профилями VLESS TCP/RAW + REALITY и XHTTP + TLS. Для отдельного подключения
|
||||||
выполните `stow xray-proxy`. Команды `xray-proxy install/start/stop/status`
|
сначала выполните `mkdir -p ~/.config/proxy-client`, затем
|
||||||
|
`stow proxy-client`. Команды `proxy-client install/start/stop/status`
|
||||||
работают без systemd, автозапуска и автоматического перехода между прокси. Без
|
работают без systemd, автозапуска и автоматического перехода между прокси. Без
|
||||||
конфига сохраняется внешний Hiddify `hproxy.local:12334`.
|
конфига сохраняется внешний Hiddify `hproxy.local:12334`.
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ echo '127.0.0.1 hproxy.local' | sudo tee -a /etc/hosts
|
|||||||
|
|
||||||
**Windows pwsh** — `proxy_on` / `proxy_off` / `myip` работают аналогично bash-версиям.
|
**Windows pwsh** — `proxy_on` / `proxy_off` / `myip` работают аналогично bash-версиям.
|
||||||
|
|
||||||
**`pclaude` / `pcodex`** — явный запуск Claude/Codex CLI через прокси (bash и pwsh). Bash-версия делегирует проверку и запуск `xray-proxy run`; Windows-версия сохраняет прежний адрес `hproxy.local:12334`. Таймаут — `PROXY_PREFLIGHT_TIMEOUT`, по умолчанию 2 c. Если прокси недоступен, клиент не запускается. Proxy-переменные видны только дочернему процессу и не остаются в сессии. Голые `claude`/`codex` не перекрываются и идут напрямую — для прокси нужен явный `p`-префикс.
|
**`pclaude` / `pcodex`** — явный запуск Claude/Codex CLI через прокси (bash и pwsh). Bash-версия делегирует проверку и запуск `proxy-client run`; Windows-версия сохраняет прежний адрес `hproxy.local:12334`. Таймаут — `PROXY_PREFLIGHT_TIMEOUT`, по умолчанию 2 c. Если прокси недоступен, клиент не запускается. Proxy-переменные видны только дочернему процессу и не остаются в сессии. Голые `claude`/`codex` не перекрываются и идут напрямую — для прокси нужен явный `p`-префикс.
|
||||||
|
|
||||||
**Codex GUI на Windows** — chezmoi создает Start Menu ярлык `Codex Proxy`, который запускает Codex Windows App с proxy-переменными:
|
**Codex GUI на Windows** — chezmoi создает Start Menu ярлык `Codex Proxy`, который запускает Codex Windows App с proxy-переменными:
|
||||||
|
|
||||||
@@ -188,7 +189,7 @@ NO_PROXY=hproxy.local,localhost,127.0.0.1,::1,[::1],.concept-software.ru
|
|||||||
|
|
||||||
## 📖 Справочник
|
## 📖 Справочник
|
||||||
|
|
||||||
### Bash-функции (`~/.config/xray-proxy/shell.bash`)
|
### Bash-функции (`~/.config/proxy-client/shell.bash`)
|
||||||
|
|
||||||
| Функция | Описание |
|
| Функция | Описание |
|
||||||
|---------|----------|
|
|---------|----------|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ esac
|
|||||||
|
|
||||||
# Файлы из этого репозитория (stow кладет их в $HOME).
|
# Файлы из этого репозитория (stow кладет их в $HOME).
|
||||||
[ -f ~/.bash_functions ] && source ~/.bash_functions
|
[ -f ~/.bash_functions ] && source ~/.bash_functions
|
||||||
[ -f ~/.config/xray-proxy/shell.bash ] && source ~/.config/xray-proxy/shell.bash
|
[ -f ~/.config/proxy-client/shell.bash ] && source ~/.config/proxy-client/shell.bash
|
||||||
[ -f ~/.bash_prompt ] && source ~/.bash_prompt
|
[ -f ~/.bash_prompt ] && source ~/.bash_prompt
|
||||||
|
|
||||||
# Локальные секреты (не хранить в git/stow).
|
# Локальные секреты (не хранить в git/stow).
|
||||||
|
|||||||
+37
-26
@@ -1,8 +1,8 @@
|
|||||||
# Headless Xray-прокси для агентских CLI
|
# `proxy-client` для агентских CLI
|
||||||
|
|
||||||
Дата: 2026-07-15
|
Дата: 2026-07-15
|
||||||
|
|
||||||
Статус: дизайн утверждён, реализация не начата
|
Статус: реализация готова к ручной приёмке; публичное имя уточнено 2026-07-21
|
||||||
|
|
||||||
## Проблема
|
## Проблема
|
||||||
|
|
||||||
@@ -93,9 +93,12 @@ Bash нужен только там, где необходимо изменит
|
|||||||
|
|
||||||
### Границы компонентов
|
### Границы компонентов
|
||||||
|
|
||||||
`xray-proxy` — один исполняемый файл на Python 3.8+ без внешних библиотек. Он
|
`proxy-client` — один исполняемый файл на Python 3.8+ без внешних библиотек. Он
|
||||||
отвечает за установку Xray, запуск и остановку процесса, диагностику,
|
отвечает за установку Xray, запуск и остановку процесса, диагностику,
|
||||||
преобразование VLESS и запуск произвольной дочерней команды через прокси.
|
преобразование VLESS и запуск произвольной дочерней команды через прокси.
|
||||||
|
Нейтральное публичное имя описывает клиентскую прокси-подсистему целиком:
|
||||||
|
внешний прокси и управляемый Xray. Название `xray` остаётся у конкретного
|
||||||
|
движка и значения `type = xray`, где оно действительно важно.
|
||||||
|
|
||||||
Переносимый `shell.bash` — единственный владелец `proxy_on`, `proxy_off`,
|
Переносимый `shell.bash` — единственный владелец `proxy_on`, `proxy_off`,
|
||||||
`pclaude`, `pcodex`, `myip` и внутренних proxy-помощников. При реализации они
|
`pclaude`, `pcodex`, `myip` и внутренних proxy-помощников. При реализации они
|
||||||
@@ -105,18 +108,18 @@ Bash нужен только там, где необходимо изменит
|
|||||||
машинный конфиг самостоятельно:
|
машинный конфиг самостоятельно:
|
||||||
|
|
||||||
- `pclaude` и `pcodex` делегируют запуск форме
|
- `pclaude` и `pcodex` делегируют запуск форме
|
||||||
`xray-proxy run -- COMMAND ARG...`, где `--` отделяет параметры клиента;
|
`proxy-client run -- COMMAND ARG...`, где `--` отделяет параметры клиента;
|
||||||
- `proxy_on` получает от `xray-proxy env` проверенный набор proxy-переменных и
|
- `proxy_on` получает от `proxy-client env` проверенный набор proxy-переменных и
|
||||||
применяет его к текущей Bash-сессии;
|
применяет его к текущей Bash-сессии;
|
||||||
- `proxy_off` очищает верхний и нижний регистр proxy-переменных.
|
- `proxy_off` очищает верхний и нижний регистр proxy-переменных.
|
||||||
|
|
||||||
Общий `bash/.bash_functions` и модуль Xray не зависят друг от друга. В составе
|
Общий `bash/.bash_functions` и модуль клиента не зависят друг от друга. В составе
|
||||||
dotfiles `.bash_custom` подключает их двумя независимыми строками. Без
|
dotfiles `.bash_custom` подключает их двумя независимыми строками. Без
|
||||||
остальных dotfiles пользователь добавляет только один `source` модуля Xray в
|
остальных dotfiles пользователь добавляет только один `source` модуля клиента в
|
||||||
`.bashrc`. Для неинтерактивных сценариев и других оболочек остаётся прямой
|
`.bashrc`. Для неинтерактивных сценариев и других оболочек остаётся прямой
|
||||||
`xray-proxy run`.
|
`proxy-client run`.
|
||||||
|
|
||||||
`xray-proxy env` сначала выполняет ту же проверку доступности, что и `run`, а
|
`proxy-client env` сначала выполняет ту же проверку доступности, что и `run`, а
|
||||||
затем печатает восемь строк `KEY=value`: HTTP, HTTPS, ALL и NO_PROXY в верхнем
|
затем печатает восемь строк `KEY=value`: HTTP, HTTPS, ALL и NO_PROXY в верхнем
|
||||||
и нижнем регистре. `shell.bash` сначала целиком получает успешный ответ, затем
|
и нижнем регистре. `shell.bash` сначала целиком получает успешный ответ, затем
|
||||||
разбирает строки по первому `=`, принимает только известные ключи и лишь после
|
разбирает строки по первому `=`, принимает только известные ключи и лишь после
|
||||||
@@ -131,17 +134,17 @@ dotfiles `.bash_custom` подключает их двумя независим
|
|||||||
набора переменных, fail-fast перед `pclaude`/`pcodex` и изоляции окружения
|
набора переменных, fail-fast перед `pclaude`/`pcodex` и изоляции окружения
|
||||||
дочернего процесса. Автоматический запрос внешнего IP из `proxy_on` и
|
дочернего процесса. Автоматический запрос внешнего IP из `proxy_on` и
|
||||||
`proxy_off` удаляется: он замедляет переключение и смешивает изменение
|
`proxy_off` удаляется: он замедляет переключение и смешивает изменение
|
||||||
окружения с диагностикой. Для проверки остаются `myip` и `xray-proxy status`.
|
окружения с диагностикой. Для проверки остаются `myip` и `proxy-client status`.
|
||||||
|
|
||||||
### Два независимых файла настройки
|
### Два независимых файла настройки
|
||||||
|
|
||||||
Машинный файл `~/.config/proxy/config` хранит несекретное описание активного
|
Машинный файл `~/.config/proxy-client/config` хранит несекретное описание
|
||||||
прокси: явный тип `xray` или `external`, отображаемое имя, адрес, порт и
|
активного прокси: явный тип `xray` или `external`, отображаемое имя, адрес, порт
|
||||||
добавку к `NO_PROXY`. Формат — INI из стандартной библиотеки Python. Если файла
|
и добавку к `NO_PROXY`. Формат — INI из стандартной библиотеки Python. Если
|
||||||
нет, прокси-подсистема сохраняет прежний внешний адрес
|
файла нет, прокси-подсистема сохраняет прежний внешний адрес
|
||||||
`hproxy.local:12334` ради обратной совместимости.
|
`hproxy.local:12334` ради обратной совместимости.
|
||||||
|
|
||||||
Секретный `~/.config/xray-proxy/default.url` содержит одну VLESS-ссылку.
|
Секретный `~/.config/proxy-client/default.url` содержит одну VLESS-ссылку.
|
||||||
Команда запуска принимает другой файл через `-f`. Профили можно хранить в
|
Команда запуска принимает другой файл через `-f`. Профили можно хранить в
|
||||||
нескольких файлах, но активным у пользователя бывает только один.
|
нескольких файлах, но активным у пользователя бывает только один.
|
||||||
CLI следует за симлинком, но проверяет права конечного файла: доступ группы или
|
CLI следует за симлинком, но проверяет права конечного файла: доступ группы или
|
||||||
@@ -156,8 +159,8 @@ CLI следует за симлинком, но проверяет права
|
|||||||
### Явная семантика прокси
|
### Явная семантика прокси
|
||||||
|
|
||||||
Тип `xray` означает, что `pclaude` и `pcodex` требуют запущенный процесс,
|
Тип `xray` означает, что `pclaude` и `pcodex` требуют запущенный процесс,
|
||||||
которым управляет `xray-proxy`. Если его нет, агент не запускается, а сообщение
|
которым управляет `proxy-client`. Если его нет, агент не запускается, а сообщение
|
||||||
предлагает выполнить `xray-proxy start`.
|
предлагает выполнить `proxy-client start`.
|
||||||
|
|
||||||
Тип `external` означает внешний процесс, например Hiddify. Проверяется только
|
Тип `external` означает внешний процесс, например Hiddify. Проверяется только
|
||||||
доступность явно заданного адреса и порта. Ни один тип не переходит на другой
|
доступность явно заданного адреса и порта. Ни один тип не переходит на другой
|
||||||
@@ -264,7 +267,7 @@ Hiddify Manager может добавлять `allowInsecure=true` и `insecure=
|
|||||||
|
|
||||||
### Установка Xray
|
### Установка Xray
|
||||||
|
|
||||||
`xray-proxy install` — отдельная явная операция. Она устанавливает Xray в
|
`proxy-client install` — отдельная явная операция. Она устанавливает Xray в
|
||||||
`~/.local/bin`, скачивает официальный архив и файл контрольной суммы, затем
|
`~/.local/bin`, скачивает официальный архив и файл контрольной суммы, затем
|
||||||
проверяет SHA-256. Поддерживается только зафиксированный релиз 26.3.27;
|
проверяет SHA-256. Поддерживается только зафиксированный релиз 26.3.27;
|
||||||
параметра версии нет. Официальные релизы публикуют необходимые файлы `.dgst`:
|
параметра версии нет. Официальные релизы публикуют необходимые файлы `.dgst`:
|
||||||
@@ -301,7 +304,7 @@ Start-lock берётся до проверки назначения и удер
|
|||||||
из содержимого VLESS-ссылки.
|
из содержимого VLESS-ссылки.
|
||||||
|
|
||||||
`start` никогда не скачивает бинарник. Если `xray` отсутствует в `PATH`, команда
|
`start` никогда не скачивает бинарник. Если `xray` отсутствует в `PATH`, команда
|
||||||
завершается с подсказкой выполнить `xray-proxy install`.
|
завершается с подсказкой выполнить `proxy-client install`.
|
||||||
|
|
||||||
### Жизненный цикл
|
### Жизненный цикл
|
||||||
|
|
||||||
@@ -317,7 +320,7 @@ CLI предоставляет `install`, `start`, `stop`, `status`, `env` и `r
|
|||||||
достаточно истории tmux.
|
достаточно истории tmux.
|
||||||
|
|
||||||
Супервизор на всё время работы удерживает неблокирующую эксклюзивную блокировку
|
Супервизор на всё время работы удерживает неблокирующую эксклюзивную блокировку
|
||||||
`fcntl.flock` в служебном каталоге `~/.local/state/xray-proxy/`. Второй `start`
|
`fcntl.flock` в служебном каталоге `~/.local/state/proxy-client/`. Второй `start`
|
||||||
отказывает до запуска процессов и изменения state или сгенерированного
|
отказывает до запуска процессов и изменения state или сгенерированного
|
||||||
конфига. `state.json` нужен для восстановления после сбоя, но не служит
|
конфига. `state.json` нужен для восстановления после сбоя, но не служит
|
||||||
блокировкой.
|
блокировкой.
|
||||||
@@ -364,12 +367,14 @@ UUID, ключи, short ID и другие уникальные секретны
|
|||||||
|
|
||||||
### Доставка
|
### Доставка
|
||||||
|
|
||||||
Компоненты образуют отдельный stow-совместимый каталог `xray-proxy/`. Он
|
Компоненты образуют отдельный stow-совместимый каталог `proxy-client/`. Он
|
||||||
доставляет CLI, Bash-модуль и два шаблона: машинного INI и файла VLESS-ссылки.
|
доставляет CLI, Bash-модуль и три шаблона: машинного INI для внешнего прокси,
|
||||||
|
машинного INI для Xray и файла VLESS-ссылки.
|
||||||
Реальные пользовательские файлы не управляются stow и не входят в Git.
|
Реальные пользовательские файлы не управляются stow и не входят в Git.
|
||||||
CLI раскладывается как `~/.local/bin/xray-proxy`, а модуль — как
|
CLI раскладывается как `~/.local/bin/proxy-client`. Машинный конфиг, профиль,
|
||||||
`~/.config/xray-proxy/shell.bash`. Шаблоны лежат рядом с модулем в подкаталоге
|
модуль `shell.bash` и каталог `examples/` находятся вместе в
|
||||||
`examples/` и не подменяют настоящие пользовательские файлы.
|
`~/.config/proxy-client/`. Шаблоны не подменяют настоящие пользовательские
|
||||||
|
файлы.
|
||||||
|
|
||||||
В корне этого каталога находится самостоятельный `README.md`. Он является
|
В корне этого каталога находится самостоятельный `README.md`. Он является
|
||||||
основной инструкцией клиента и исключается из раскладки stow. README остаётся
|
основной инструкцией клиента и исключается из раскладки stow. README остаётся
|
||||||
@@ -391,6 +396,12 @@ README репозитория только кратко описывает по
|
|||||||
Python 3.11, что исключило бы штатный Python 3.10 в Ubuntu 22.04.
|
Python 3.11, что исключило бы штатный Python 3.10 в Ubuntu 22.04.
|
||||||
- Bash оставлен отдельным тонким модулем, потому что дочерний Python-процесс не
|
- Bash оставлен отдельным тонким модулем, потому что дочерний Python-процесс не
|
||||||
может изменить окружение родительской shell-сессии.
|
может изменить окружение родительской shell-сессии.
|
||||||
|
- Выбрано публичное имя `proxy-client`, потому что подсистема обслуживает и
|
||||||
|
внешний прокси, и управляемый Xray. `xray-proxy` отклонено как имя,
|
||||||
|
выпячивающее одну реализацию, а `proxyctl` — как менее понятное пользователю.
|
||||||
|
- Все пользовательские файлы собраны в `~/.config/proxy-client/`. Разделение
|
||||||
|
между общим машинным конфигом и Xray-профилем остаётся логическим, но не
|
||||||
|
требует помнить два каталога.
|
||||||
- Машинный прокси и VLESS-ссылка разделены: первый файл описывает локальную
|
- Машинный прокси и VLESS-ссылка разделены: первый файл описывает локальную
|
||||||
политику shell, второй содержит секрет и меняется независимо.
|
политику shell, второй содержит секрет и меняется независимо.
|
||||||
- Выбран один порт, потому что SOCKS-вход Xray совместим с HTTP и не требует
|
- Выбран один порт, потому что SOCKS-вход Xray совместим с HTTP и не требует
|
||||||
@@ -459,7 +470,7 @@ README репозитория только кратко описывает по
|
|||||||
|
|
||||||
## Влияние на документацию
|
## Влияние на документацию
|
||||||
|
|
||||||
Основная инструкция создаётся как `xray-proxy/README.md` одновременно с
|
Основная инструкция находится в `proxy-client/README.md` рядом с
|
||||||
реализацией. Она должна описывать:
|
реализацией. Она должна описывать:
|
||||||
|
|
||||||
- различие внешнего Hiddify и управляемого Xray;
|
- различие внешнего Hiddify и управляемого Xray;
|
||||||
+5
-1
@@ -302,7 +302,7 @@ if ! command -v "$STOW_BIN" &>/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PACKAGES=(bash tmux vim zed vscode agents codex gemini xray-proxy)
|
PACKAGES=(bash tmux vim zed vscode agents codex gemini proxy-client)
|
||||||
AVAILABLE_PACKAGES=()
|
AVAILABLE_PACKAGES=()
|
||||||
|
|
||||||
info "Применяю stow пакеты..."
|
info "Применяю stow пакеты..."
|
||||||
@@ -320,6 +320,10 @@ migrate_zed_config_stow_symlink
|
|||||||
if [ ! -L "$HOME/.config" ]; then
|
if [ ! -L "$HOME/.config" ]; then
|
||||||
mkdir -p "$HOME/.config"
|
mkdir -p "$HOME/.config"
|
||||||
fi
|
fi
|
||||||
|
if [ -d "$HOME/.config" ] && [ ! -L "$HOME/.config" ] \
|
||||||
|
&& [ ! -L "$HOME/.config/proxy-client" ]; then
|
||||||
|
mkdir -p "$HOME/.config/proxy-client"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then
|
if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then
|
||||||
stow_package "${AVAILABLE_PACKAGES[@]}"
|
stow_package "${AVAILABLE_PACKAGES[@]}"
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1,6 @@
|
|||||||
|
[proxy]
|
||||||
|
type = xray
|
||||||
|
name = Local Xray
|
||||||
|
address = 127.0.0.1
|
||||||
|
port = 12334
|
||||||
|
no_proxy_extra = .example.internal,git.example.internal
|
||||||
File renamed without changes.
+6
-6
@@ -1,8 +1,8 @@
|
|||||||
# Переносимый Bash-интерфейс xray-proxy. Файл можно подключать отдельно от
|
# Переносимый Bash-интерфейс proxy-client. Файл можно подключать отдельно от
|
||||||
# остальных dotfiles: source ~/.config/xray-proxy/shell.bash
|
# остальных dotfiles: source ~/.config/proxy-client/shell.bash
|
||||||
|
|
||||||
_proxy_cli() {
|
_proxy_cli() {
|
||||||
"${XRAY_PROXY_BIN:-xray-proxy}" "$@"
|
"${PROXY_CLIENT_BIN:-proxy-client}" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
_proxy_apply_env_file() {
|
_proxy_apply_env_file() {
|
||||||
@@ -15,7 +15,7 @@ _proxy_apply_env_file() {
|
|||||||
|
|
||||||
while IFS= read -r line || [ -n "$line" ]; do
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
if [[ "$line" != *=* ]]; then
|
if [[ "$line" != *=* ]]; then
|
||||||
echo "Ошибка: xray-proxy env вернул строку без '='" >&2
|
echo "Ошибка: proxy-client env вернул строку без '='" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
key="${line%%=*}"
|
key="${line%%=*}"
|
||||||
@@ -23,7 +23,7 @@ _proxy_apply_env_file() {
|
|||||||
case "$key" in
|
case "$key" in
|
||||||
HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY|http_proxy|https_proxy|all_proxy|no_proxy) ;;
|
HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY|http_proxy|https_proxy|all_proxy|no_proxy) ;;
|
||||||
*)
|
*)
|
||||||
echo "Ошибка: xray-proxy env вернул неизвестный ключ: $key" >&2
|
echo "Ошибка: proxy-client env вернул неизвестный ключ: $key" >&2
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -42,7 +42,7 @@ proxy_on() {
|
|||||||
local output_file old_umask
|
local output_file old_umask
|
||||||
old_umask="$(umask)"
|
old_umask="$(umask)"
|
||||||
umask 077
|
umask 077
|
||||||
if ! output_file="$(mktemp "${TMPDIR:-/tmp}/xray-proxy-env.XXXXXX")"; then
|
if ! output_file="$(mktemp "${TMPDIR:-/tmp}/proxy-client-env.XXXXXX")"; then
|
||||||
umask "$old_umask"
|
umask "$old_umask"
|
||||||
echo "Ошибка: не удалось создать временный файл для proxy-env" >&2
|
echo "Ошибка: не удалось создать временный файл для proxy-env" >&2
|
||||||
return 1
|
return 1
|
||||||
@@ -3,7 +3,7 @@ import sys
|
|||||||
|
|
||||||
if sys.version_info < (3, 8):
|
if sys.version_info < (3, 8):
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
"Ошибка: xray-proxy требует Python 3.8 или новее; обнаружен Python {}.{}.\n".format(
|
"Ошибка: proxy-client требует Python 3.8 или новее; обнаружен Python {}.{}.\n".format(
|
||||||
sys.version_info[0], sys.version_info[1]
|
sys.version_info[0], sys.version_info[1]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -125,22 +125,22 @@ class VlessProfile:
|
|||||||
def config_path():
|
def config_path():
|
||||||
config_home = os.environ.get("XDG_CONFIG_HOME")
|
config_home = os.environ.get("XDG_CONFIG_HOME")
|
||||||
if config_home:
|
if config_home:
|
||||||
return Path(config_home).expanduser() / "proxy/config"
|
return Path(config_home).expanduser() / "proxy-client/config"
|
||||||
return Path.home() / ".config/proxy/config"
|
return Path.home() / ".config/proxy-client/config"
|
||||||
|
|
||||||
|
|
||||||
def xray_config_home():
|
def proxy_client_config_home():
|
||||||
config_home = os.environ.get("XDG_CONFIG_HOME")
|
config_home = os.environ.get("XDG_CONFIG_HOME")
|
||||||
if config_home:
|
if config_home:
|
||||||
return Path(config_home).expanduser() / "xray-proxy"
|
return Path(config_home).expanduser() / "proxy-client"
|
||||||
return Path.home() / ".config/xray-proxy"
|
return Path.home() / ".config/proxy-client"
|
||||||
|
|
||||||
|
|
||||||
def state_home():
|
def state_home():
|
||||||
state_root = os.environ.get("XDG_STATE_HOME")
|
state_root = os.environ.get("XDG_STATE_HOME")
|
||||||
if state_root:
|
if state_root:
|
||||||
return Path(state_root).expanduser() / "xray-proxy"
|
return Path(state_root).expanduser() / "proxy-client"
|
||||||
return Path.home() / ".local/state/xray-proxy"
|
return Path.home() / ".local/state/proxy-client"
|
||||||
|
|
||||||
|
|
||||||
def state_path():
|
def state_path():
|
||||||
@@ -574,7 +574,7 @@ def build_xray_config(profile, local_address, local_port):
|
|||||||
def resolve_xray():
|
def resolve_xray():
|
||||||
found = shutil.which("xray")
|
found = shutil.which("xray")
|
||||||
if found is None:
|
if found is None:
|
||||||
raise ProxyError("xray не найден в PATH; сначала выполните xray-proxy install")
|
raise ProxyError("xray не найден в PATH; сначала выполните proxy-client install")
|
||||||
try:
|
try:
|
||||||
return str(Path(found).resolve(strict=True))
|
return str(Path(found).resolve(strict=True))
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
@@ -765,18 +765,18 @@ def require_install_safe_managed_state():
|
|||||||
return
|
return
|
||||||
except OSError:
|
except OSError:
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"состояние managed-режима не удалось проверить; сначала выполните xray-proxy stop"
|
"состояние managed-режима не удалось проверить; сначала выполните proxy-client stop"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
state = read_managed_state()
|
state = read_managed_state()
|
||||||
outcome = managed_state_outcome(state)
|
outcome = managed_state_outcome(state)
|
||||||
except ProxyError:
|
except ProxyError:
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"состояние managed-режима не подтверждено; сначала выполните xray-proxy stop"
|
"состояние managed-режима не подтверждено; сначала выполните proxy-client stop"
|
||||||
)
|
)
|
||||||
if outcome != "stale":
|
if outcome != "stale":
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"managed Xray работает или не подтверждён; сначала выполните xray-proxy stop"
|
"managed Xray работает или не подтверждён; сначала выполните proxy-client stop"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -785,7 +785,7 @@ def acquire_install_lock():
|
|||||||
return acquire_start_lock()
|
return acquire_start_lock()
|
||||||
except StartLockBusyError:
|
except StartLockBusyError:
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"другая операция удерживает блокировку; сначала выполните xray-proxy stop"
|
"другая операция удерживает блокировку; сначала выполните proxy-client stop"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -991,7 +991,7 @@ def read_managed_state():
|
|||||||
raise ProxyError("состояние управляемого Xray не подтверждено")
|
raise ProxyError("состояние управляемого Xray не подтверждено")
|
||||||
value = json.loads(path.read_text(encoding="utf-8"))
|
value = json.loads(path.read_text(encoding="utf-8"))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise ProxyError("управляемый Xray не запущен; выполните xray-proxy start")
|
raise ProxyError("управляемый Xray не запущен; выполните proxy-client start")
|
||||||
except ProxyError:
|
except ProxyError:
|
||||||
raise
|
raise
|
||||||
except (OSError, ValueError, TypeError):
|
except (OSError, ValueError, TypeError):
|
||||||
@@ -1123,7 +1123,7 @@ def verify_managed_state(config=None, require_port=True):
|
|||||||
finally:
|
finally:
|
||||||
lock.close()
|
lock.close()
|
||||||
if outcome == "orphan":
|
if outcome == "orphan":
|
||||||
raise ProxyError("обнаружен Xray-сирота; выполните xray-proxy stop")
|
raise ProxyError("обнаружен Xray-сирота; выполните proxy-client stop")
|
||||||
if outcome != "healthy":
|
if outcome != "healthy":
|
||||||
raise unverifiable_state_error()
|
raise unverifiable_state_error()
|
||||||
if config is not None and (
|
if config is not None and (
|
||||||
@@ -1200,7 +1200,7 @@ def diagnose_ip(config):
|
|||||||
connection.request(
|
connection.request(
|
||||||
"GET",
|
"GET",
|
||||||
"http://ifconfig.me/ip",
|
"http://ifconfig.me/ip",
|
||||||
headers={"Host": "ifconfig.me", "User-Agent": "xray-proxy/1"},
|
headers={"Host": "ifconfig.me", "User-Agent": "proxy-client/1"},
|
||||||
)
|
)
|
||||||
response = connection.getresponse()
|
response = connection.getresponse()
|
||||||
address = response.read(256).decode("ascii", "replace").strip()
|
address = response.read(256).decode("ascii", "replace").strip()
|
||||||
@@ -1259,7 +1259,7 @@ def command_env(config):
|
|||||||
|
|
||||||
def command_run(config, remainder):
|
def command_run(config, remainder):
|
||||||
if not remainder or remainder[0] != "--" or len(remainder) == 1:
|
if not remainder or remainder[0] != "--" or len(remainder) == 1:
|
||||||
raise ProxyError("использование: xray-proxy run -- COMMAND ARG...")
|
raise ProxyError("использование: proxy-client run -- COMMAND ARG...")
|
||||||
command = remainder[1:]
|
command = remainder[1:]
|
||||||
if shutil.which(command[0]) is None:
|
if shutil.which(command[0]) is None:
|
||||||
sys.stderr.write("Ошибка: команда не найдена в PATH: {}\n".format(command[0]))
|
sys.stderr.write("Ошибка: команда не найдена в PATH: {}\n".format(command[0]))
|
||||||
@@ -1319,7 +1319,7 @@ def command_start_locked(config, profile_file, address):
|
|||||||
if outcome == "stale":
|
if outcome == "stale":
|
||||||
cleanup_stale_files()
|
cleanup_stale_files()
|
||||||
elif outcome == "orphan":
|
elif outcome == "orphan":
|
||||||
raise ProxyError("обнаружен Xray-сирота; сначала выполните xray-proxy stop")
|
raise ProxyError("обнаружен Xray-сирота; сначала выполните proxy-client stop")
|
||||||
elif outcome == "healthy":
|
elif outcome == "healthy":
|
||||||
raise ProxyError("управляемый Xray уже запущен")
|
raise ProxyError("управляемый Xray уже запущен")
|
||||||
else:
|
else:
|
||||||
@@ -1327,7 +1327,11 @@ def command_start_locked(config, profile_file, address):
|
|||||||
|
|
||||||
check_port_available(address, config.port)
|
check_port_available(address, config.port)
|
||||||
|
|
||||||
profile_path = Path(profile_file) if profile_file else xray_config_home() / "default.url"
|
profile_path = (
|
||||||
|
Path(profile_file)
|
||||||
|
if profile_file
|
||||||
|
else proxy_client_config_home() / "default.url"
|
||||||
|
)
|
||||||
raw_profile = read_profile(profile_path)
|
raw_profile = read_profile(profile_path)
|
||||||
executable = resolve_xray()
|
executable = resolve_xray()
|
||||||
version = xray_version(executable)
|
version = xray_version(executable)
|
||||||
@@ -1432,13 +1436,13 @@ def command_start_locked(config, profile_file, address):
|
|||||||
|
|
||||||
|
|
||||||
def stop_timeout():
|
def stop_timeout():
|
||||||
raw = os.environ.get("XRAY_PROXY_STOP_TIMEOUT", "5")
|
raw = os.environ.get("PROXY_CLIENT_STOP_TIMEOUT", "5")
|
||||||
try:
|
try:
|
||||||
value = float(raw)
|
value = float(raw)
|
||||||
if not value > 0:
|
if not value > 0:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ProxyError("XRAY_PROXY_STOP_TIMEOUT должен быть положительным числом секунд")
|
raise ProxyError("PROXY_CLIENT_STOP_TIMEOUT должен быть положительным числом секунд")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -1498,7 +1502,7 @@ def command_stop():
|
|||||||
|
|
||||||
def build_parser():
|
def build_parser():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="xray-proxy",
|
prog="proxy-client",
|
||||||
description="Безопасный запуск команд через явно настроенный прокси.",
|
description="Безопасный запуск команд через явно настроенный прокси.",
|
||||||
)
|
)
|
||||||
subparsers = parser.add_subparsers(dest="subcommand")
|
subparsers = parser.add_subparsers(dest="subcommand")
|
||||||
@@ -1521,7 +1525,7 @@ def main():
|
|||||||
arguments = parser.parse_args()
|
arguments = parser.parse_args()
|
||||||
if arguments.subcommand is None:
|
if arguments.subcommand is None:
|
||||||
parser.print_usage(sys.stderr)
|
parser.print_usage(sys.stderr)
|
||||||
sys.stderr.write("xray-proxy: ошибка: укажите явную подкоманду\n")
|
sys.stderr.write("proxy-client: ошибка: укажите явную подкоманду\n")
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
|||||||
# xray-proxy
|
# proxy-client
|
||||||
|
|
||||||
Переносимый Linux/Bash-интерфейс для безопасного запуска команд через явно
|
Переносимый Linux/Bash-интерфейс для безопасного запуска команд через явно
|
||||||
настроенный внешний прокси или через управляемый пользовательский Xray.
|
настроенный внешний прокси или через управляемый пользовательский Xray.
|
||||||
@@ -13,8 +13,9 @@ CLI требует Python 3.8+ и использует только станда
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/dotfiles
|
cd ~/dotfiles
|
||||||
stow xray-proxy
|
mkdir -p ~/.config/proxy-client
|
||||||
source ~/.config/xray-proxy/shell.bash
|
stow proxy-client
|
||||||
|
source ~/.config/proxy-client/shell.bash
|
||||||
```
|
```
|
||||||
|
|
||||||
Полный `./install.sh` выполняет тот же `stow` вместе с остальными пакетами, а
|
Полный `./install.sh` выполняет тот же `stow` вместе с остальными пакетами, а
|
||||||
@@ -23,11 +24,11 @@ source ~/.config/xray-proxy/shell.bash
|
|||||||
Без остальных dotfiles можно создать две ссылки:
|
Без остальных dotfiles можно создать две ссылки:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.local/bin ~/.config/xray-proxy
|
mkdir -p ~/.local/bin ~/.config/proxy-client
|
||||||
ln -s /path/to/xray-proxy/.local/bin/xray-proxy ~/.local/bin/xray-proxy
|
ln -s /path/to/proxy-client/.local/bin/proxy-client ~/.local/bin/proxy-client
|
||||||
ln -s /path/to/xray-proxy/.config/xray-proxy/shell.bash \
|
ln -s /path/to/proxy-client/.config/proxy-client/shell.bash \
|
||||||
~/.config/xray-proxy/shell.bash
|
~/.config/proxy-client/shell.bash
|
||||||
printf '%s\n' 'source ~/.config/xray-proxy/shell.bash' >> ~/.bashrc
|
printf '%s\n' 'source ~/.config/proxy-client/shell.bash' >> ~/.bashrc
|
||||||
```
|
```
|
||||||
|
|
||||||
Вместо ссылок файлы можно скопировать в те же пути. Исполняемому файлу нужен
|
Вместо ссылок файлы можно скопировать в те же пути. Исполняемому файлу нужен
|
||||||
@@ -35,11 +36,13 @@ printf '%s\n' 'source ~/.config/xray-proxy/shell.bash' >> ~/.bashrc
|
|||||||
|
|
||||||
## Машинный конфиг
|
## Машинный конфиг
|
||||||
|
|
||||||
Скопируйте пример и отредактируйте его:
|
Скопируйте пример нужного режима и отредактируйте его:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.config/proxy
|
mkdir -p ~/.config/proxy-client
|
||||||
cp ~/.config/xray-proxy/examples/config ~/.config/proxy/config
|
cp ~/.config/proxy-client/examples/config.xray ~/.config/proxy-client/config
|
||||||
|
# или для внешнего Hiddify:
|
||||||
|
cp ~/.config/proxy-client/examples/config.external ~/.config/proxy-client/config
|
||||||
```
|
```
|
||||||
|
|
||||||
Формат строгий: одна секция `[proxy]`, пять обязательных ключей без повторов и
|
Формат строгий: одна секция `[proxy]`, пять обязательных ключей без повторов и
|
||||||
@@ -78,23 +81,23 @@ no_proxy_extra = .example.internal,git.example.internal
|
|||||||
его одной настоящей VLESS-ссылкой и закройте права конечного файла:
|
его одной настоящей VLESS-ссылкой и закройте права конечного файла:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp ~/.config/xray-proxy/examples/default.url ~/.config/xray-proxy/default.url
|
cp ~/.config/proxy-client/examples/default.url ~/.config/proxy-client/default.url
|
||||||
chmod 600 ~/.config/xray-proxy/default.url
|
chmod 600 ~/.config/proxy-client/default.url
|
||||||
```
|
```
|
||||||
|
|
||||||
Файл должен содержать ровно одну непустую строку. Можно передать другой файл
|
Файл должен содержать ровно одну непустую строку. Можно передать другой файл
|
||||||
через `xray-proxy start -f /path/to/profile.url`. Если это симлинк, проверяются
|
через `proxy-client start -f /path/to/profile.url`. Если это симлинк, проверяются
|
||||||
права конечного файла.
|
права конечного файла.
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xray-proxy install [--replace]
|
proxy-client install [--replace]
|
||||||
xray-proxy status
|
proxy-client status
|
||||||
xray-proxy start [-f PROFILE]
|
proxy-client start [-f PROFILE]
|
||||||
xray-proxy stop
|
proxy-client stop
|
||||||
xray-proxy env
|
proxy-client env
|
||||||
xray-proxy run -- COMMAND ARG...
|
proxy-client run -- COMMAND ARG...
|
||||||
proxy_on
|
proxy_on
|
||||||
proxy_off
|
proxy_off
|
||||||
pclaude [ARG...]
|
pclaude [ARG...]
|
||||||
@@ -120,7 +123,7 @@ myip
|
|||||||
но проверенная установка сохраняется. Родительский симлинк принимается, только
|
но проверенная установка сохраняется. Родительский симлинк принимается, только
|
||||||
если разрешённый каталог остаётся внутри `HOME`. Во время живого или
|
если разрешённый каталог остаётся внутри `HOME`. Во время живого или
|
||||||
неподтверждённого managed-экземпляра установка запрещена: сначала выполните
|
неподтверждённого managed-экземпляра установка запрещена: сначала выполните
|
||||||
`xray-proxy stop`.
|
`proxy-client stop`.
|
||||||
|
|
||||||
Для `external` команды `status`, `env` и `run` проверяют настроенный адрес и
|
Для `external` команды `status`, `env` и `run` проверяют настроенный адрес и
|
||||||
порт. Для `xray` они дополнительно требуют закрытое состояние именно того
|
порт. Для `xray` они дополнительно требуют закрытое состояние именно того
|
||||||
@@ -167,6 +170,6 @@ CLI такие версии не поддерживает и не устанав
|
|||||||
|
|
||||||
Сводка скрывает UUID и ключи. Содержимое XHTTP `path` не показывается — только
|
Сводка скрывает UUID и ключи. Содержимое XHTTP `path` не показывается — только
|
||||||
его длина. Сгенерированный JSON и состояние лежат в закрытом каталоге
|
его длина. Сгенерированный JSON и состояние лежат в закрытом каталоге
|
||||||
`$XDG_STATE_HOME/xray-proxy` либо `~/.local/state/xray-proxy` и удаляются после
|
`$XDG_STATE_HOME/proxy-client` либо `~/.local/state/proxy-client` и удаляются после
|
||||||
штатной остановки. Сбой диагностики внешнего IP через локальный HTTP-прокси
|
штатной остановки. Сбой диагностики внешнего IP через локальный HTTP-прокси
|
||||||
показывается как предупреждение и не останавливает уже готовый Xray.
|
показывается как предупреждение и не останавливает уже готовый Xray.
|
||||||
+22
-16
@@ -38,7 +38,7 @@ setup_fixture() {
|
|||||||
# stow конфликтами, поэтому в тестовое дерево не попадает.
|
# stow конфликтами, поэтому в тестовое дерево не попадает.
|
||||||
git -C "$REPO_ROOT" ls-files -z --cached -- \
|
git -C "$REPO_ROOT" ls-files -z --cached -- \
|
||||||
install.sh bash tmux vim zed vscode agents opencode codex gemini \
|
install.sh bash tmux vim zed vscode agents opencode codex gemini \
|
||||||
claude xray-proxy scripts \
|
claude proxy-client scripts \
|
||||||
| tar -C "$REPO_ROOT" --null -T - -cf - \
|
| tar -C "$REPO_ROOT" --null -T - -cf - \
|
||||||
| tar -xf - -C "$repo_root"
|
| tar -xf - -C "$repo_root"
|
||||||
|
|
||||||
@@ -97,8 +97,13 @@ test_fresh_install_uses_home_target() {
|
|||||||
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
||||||
|
|
||||||
[ -L "$home_dir/.bash_custom" ] || fail "Expected .bash_custom symlink in HOME"
|
[ -L "$home_dir/.bash_custom" ] || fail "Expected .bash_custom symlink in HOME"
|
||||||
[ -x "$home_dir/.local/bin/xray-proxy" ] || fail "Expected executable xray-proxy CLI in HOME"
|
[ -x "$home_dir/.local/bin/proxy-client" ] || fail "Expected executable proxy-client CLI in HOME"
|
||||||
[ -f "$home_dir/.config/xray-proxy/shell.bash" ] || fail "Expected xray-proxy shell module"
|
[ -d "$home_dir/.config/proxy-client" ] || fail "Expected proxy-client config directory"
|
||||||
|
[ ! -L "$home_dir/.config/proxy-client" ] || fail "Expected proxy-client config to stay outside the repository"
|
||||||
|
[ -f "$home_dir/.config/proxy-client/shell.bash" ] || fail "Expected proxy-client shell module"
|
||||||
|
[ -f "$home_dir/.config/proxy-client/examples/config.external" ] || fail "Expected external proxy config example"
|
||||||
|
[ -f "$home_dir/.config/proxy-client/examples/config.xray" ] || fail "Expected managed Xray config example"
|
||||||
|
assert_not_exists "$home_dir/.config/proxy-client/examples/config"
|
||||||
[ ! -e "$home_dir/README.md" ] || fail "Package README must not be stowed into HOME"
|
[ ! -e "$home_dir/README.md" ] || fail "Package README must not be stowed into HOME"
|
||||||
[ -L "$home_dir/.claude/skills" ] || fail "Expected ~/.claude/skills symlink"
|
[ -L "$home_dir/.claude/skills" ] || fail "Expected ~/.claude/skills symlink"
|
||||||
assert_file_contains "$home_dir/.bashrc" "[ -f ~/.bash_custom ] && source ~/.bash_custom"
|
assert_file_contains "$home_dir/.bashrc" "[ -f ~/.bash_custom ] && source ~/.bash_custom"
|
||||||
@@ -113,7 +118,7 @@ test_fresh_install_does_not_fold_config_into_zed() {
|
|||||||
mkdir -p "$home_dir"
|
mkdir -p "$home_dir"
|
||||||
|
|
||||||
# Неполный клон: zed остаётся единственным stow-пакетом с каталогом .config.
|
# Неполный клон: zed остаётся единственным stow-пакетом с каталогом .config.
|
||||||
mv "$repo_root/xray-proxy" "$fixture/xray-proxy-disabled"
|
mv "$repo_root/proxy-client" "$fixture/proxy-client-disabled"
|
||||||
|
|
||||||
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
||||||
|
|
||||||
@@ -131,10 +136,10 @@ test_migrates_legacy_zed_config_symlink() {
|
|||||||
repo_root="$fixture/repo"
|
repo_root="$fixture/repo"
|
||||||
home_dir="$fixture/home"
|
home_dir="$fixture/home"
|
||||||
legacy_config="$repo_root/zed/.config"
|
legacy_config="$repo_root/zed/.config"
|
||||||
mkdir -p "$home_dir" "$legacy_config/btop" "$legacy_config/xray-proxy"
|
mkdir -p "$home_dir" "$legacy_config/btop" "$legacy_config/proxy-client"
|
||||||
printf '%s\n' 'btop settings' > "$legacy_config/btop/btop.conf"
|
printf '%s\n' 'btop settings' > "$legacy_config/btop/btop.conf"
|
||||||
printf '%s\n' 'VLESS_PROFILE_SENTINEL' > "$legacy_config/xray-proxy/default.url"
|
printf '%s\n' 'VLESS_PROFILE_SENTINEL' > "$legacy_config/proxy-client/default.url"
|
||||||
chmod 600 "$legacy_config/xray-proxy/default.url"
|
chmod 600 "$legacy_config/proxy-client/default.url"
|
||||||
ln -s "$legacy_config" "$home_dir/.config"
|
ln -s "$legacy_config" "$home_dir/.config"
|
||||||
|
|
||||||
if ! output="$(
|
if ! output="$(
|
||||||
@@ -149,19 +154,19 @@ test_migrates_legacy_zed_config_symlink() {
|
|||||||
[ -L "$home_dir/.config/zed" ] || fail "Expected managed ~/.config/zed symlink"
|
[ -L "$home_dir/.config/zed" ] || fail "Expected managed ~/.config/zed symlink"
|
||||||
[ -d "$home_dir/.config/btop" ] || fail "Expected btop config to be migrated"
|
[ -d "$home_dir/.config/btop" ] || fail "Expected btop config to be migrated"
|
||||||
[ ! -L "$home_dir/.config/btop" ] || fail "Expected btop config outside the repository"
|
[ ! -L "$home_dir/.config/btop" ] || fail "Expected btop config outside the repository"
|
||||||
[ -f "$home_dir/.config/xray-proxy/default.url" ] || fail "Expected profile to be migrated"
|
[ -f "$home_dir/.config/proxy-client/default.url" ] || fail "Expected profile to be migrated"
|
||||||
[ ! -L "$home_dir/.config/xray-proxy/default.url" ] || fail "Expected profile outside the repository"
|
[ ! -L "$home_dir/.config/proxy-client/default.url" ] || fail "Expected profile outside the repository"
|
||||||
mode="$(stat -c '%a' "$home_dir/.config/xray-proxy/default.url")"
|
mode="$(stat -c '%a' "$home_dir/.config/proxy-client/default.url")"
|
||||||
[ "$mode" = "600" ] || fail "Expected profile mode 600, got $mode"
|
[ "$mode" = "600" ] || fail "Expected profile mode 600, got $mode"
|
||||||
assert_file_contains "$home_dir/.config/xray-proxy/default.url" "VLESS_PROFILE_SENTINEL"
|
assert_file_contains "$home_dir/.config/proxy-client/default.url" "VLESS_PROFILE_SENTINEL"
|
||||||
assert_not_exists "$legacy_config/btop"
|
assert_not_exists "$legacy_config/btop"
|
||||||
assert_not_exists "$legacy_config/xray-proxy"
|
assert_not_exists "$legacy_config/proxy-client"
|
||||||
if printf '%s' "$output" | grep -Fq 'VLESS_PROFILE_SENTINEL'; then
|
if printf '%s' "$output" | grep -Fq 'VLESS_PROFILE_SENTINEL'; then
|
||||||
fail "Secret profile leaked into install output"
|
fail "Secret profile leaked into install output"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
||||||
assert_file_contains "$home_dir/.config/xray-proxy/default.url" "VLESS_PROFILE_SENTINEL"
|
assert_file_contains "$home_dir/.config/proxy-client/default.url" "VLESS_PROFILE_SENTINEL"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_stops_legacy_migration_when_listing_fails() {
|
test_stops_legacy_migration_when_listing_fails() {
|
||||||
@@ -170,8 +175,8 @@ test_stops_legacy_migration_when_listing_fails() {
|
|||||||
repo_root="$fixture/repo"
|
repo_root="$fixture/repo"
|
||||||
home_dir="$fixture/home"
|
home_dir="$fixture/home"
|
||||||
legacy_config="$repo_root/zed/.config"
|
legacy_config="$repo_root/zed/.config"
|
||||||
mkdir -p "$home_dir" "$legacy_config/xray-proxy"
|
mkdir -p "$home_dir" "$legacy_config/proxy-client"
|
||||||
printf '%s\n' 'VLESS_PROFILE_SENTINEL' > "$legacy_config/xray-proxy/default.url"
|
printf '%s\n' 'VLESS_PROFILE_SENTINEL' > "$legacy_config/proxy-client/default.url"
|
||||||
ln -s "$legacy_config" "$home_dir/.config"
|
ln -s "$legacy_config" "$home_dir/.config"
|
||||||
|
|
||||||
cat > "$fixture/bin/find" <<'EOF'
|
cat > "$fixture/bin/find" <<'EOF'
|
||||||
@@ -190,7 +195,7 @@ EOF
|
|||||||
target="$(readlink -f "$home_dir/.config")"
|
target="$(readlink -f "$home_dir/.config")"
|
||||||
expected="$(readlink -f "$legacy_config")"
|
expected="$(readlink -f "$legacy_config")"
|
||||||
[ "$target" = "$expected" ] || fail "Legacy ~/.config target changed after listing failure"
|
[ "$target" = "$expected" ] || fail "Legacy ~/.config target changed after listing failure"
|
||||||
assert_file_contains "$legacy_config/xray-proxy/default.url" "VLESS_PROFILE_SENTINEL"
|
assert_file_contains "$legacy_config/proxy-client/default.url" "VLESS_PROFILE_SENTINEL"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_does_not_migrate_unrelated_config_symlink() {
|
test_does_not_migrate_unrelated_config_symlink() {
|
||||||
@@ -213,6 +218,7 @@ test_does_not_migrate_unrelated_config_symlink() {
|
|||||||
expected="$(readlink -f "$unrelated_config")"
|
expected="$(readlink -f "$unrelated_config")"
|
||||||
[ "$target" = "$expected" ] || fail "Unrelated ~/.config symlink target changed"
|
[ "$target" = "$expected" ] || fail "Unrelated ~/.config symlink target changed"
|
||||||
assert_file_contains "$unrelated_config/local.conf" "keep me"
|
assert_file_contains "$unrelated_config/local.conf" "keep me"
|
||||||
|
assert_not_exists "$unrelated_config/proxy-client"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_settings_local_is_symlinked_to_repo() {
|
test_settings_local_is_symlinked_to_repo() {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
CLI = REPO_ROOT / "xray-proxy/.local/bin/xray-proxy"
|
CLI = REPO_ROOT / "proxy-client/.local/bin/proxy-client"
|
||||||
|
|
||||||
|
|
||||||
class FakeUrlResponse:
|
class FakeUrlResponse:
|
||||||
@@ -71,7 +71,7 @@ SUCCESSFUL_SOCKET_SETUP = checking_socket_setup()
|
|||||||
REFUSED_SOCKET_SETUP = checking_socket_setup("refused")
|
REFUSED_SOCKET_SETUP = checking_socket_setup("refused")
|
||||||
|
|
||||||
|
|
||||||
class XrayProxyCliTest(unittest.TestCase):
|
class ProxyClientCliTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temp_dir = tempfile.TemporaryDirectory()
|
self.temp_dir = tempfile.TemporaryDirectory()
|
||||||
self.config_home = Path(self.temp_dir.name)
|
self.config_home = Path(self.temp_dir.name)
|
||||||
@@ -90,7 +90,7 @@ class XrayProxyCliTest(unittest.TestCase):
|
|||||||
address="127.0.0.1",
|
address="127.0.0.1",
|
||||||
no_proxy_extra=".example.test",
|
no_proxy_extra=".example.test",
|
||||||
):
|
):
|
||||||
config_dir = self.config_home / "proxy"
|
config_dir = self.config_home / "proxy-client"
|
||||||
config_dir.mkdir(exist_ok=True)
|
config_dir.mkdir(exist_ok=True)
|
||||||
(config_dir / "config").write_text(
|
(config_dir / "config").write_text(
|
||||||
"[proxy]\n"
|
"[proxy]\n"
|
||||||
@@ -105,7 +105,7 @@ class XrayProxyCliTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def write_raw_config(self, content):
|
def write_raw_config(self, content):
|
||||||
config_dir = self.config_home / "proxy"
|
config_dir = self.config_home / "proxy-client"
|
||||||
config_dir.mkdir(exist_ok=True)
|
config_dir.mkdir(exist_ok=True)
|
||||||
path = config_dir / "config"
|
path = config_dir / "config"
|
||||||
if isinstance(content, bytes):
|
if isinstance(content, bytes):
|
||||||
@@ -167,7 +167,7 @@ class XrayProxyCliTest(unittest.TestCase):
|
|||||||
self.assertEqual(2, result.returncode)
|
self.assertEqual(2, result.returncode)
|
||||||
self.assertEqual("", result.stdout)
|
self.assertEqual("", result.stdout)
|
||||||
self.assertIn("укажите явную подкоманду", result.stderr)
|
self.assertIn("укажите явную подкоманду", result.stderr)
|
||||||
self.assertFalse((self.config_home / "proxy").exists())
|
self.assertFalse((self.config_home / "proxy-client").exists())
|
||||||
|
|
||||||
def test_old_python_version_gets_clear_error_before_main_program(self):
|
def test_old_python_version_gets_clear_error_before_main_program(self):
|
||||||
code = (
|
code = (
|
||||||
@@ -269,7 +269,7 @@ class XrayProxyCliTest(unittest.TestCase):
|
|||||||
self.assertEqual(7654321, wait_for_starttime(child))
|
self.assertEqual(7654321, wait_for_starttime(child))
|
||||||
|
|
||||||
def test_broken_config_symlink_is_not_treated_as_missing_config(self):
|
def test_broken_config_symlink_is_not_treated_as_missing_config(self):
|
||||||
config_dir = self.config_home / "proxy"
|
config_dir = self.config_home / "proxy-client"
|
||||||
config_dir.mkdir()
|
config_dir.mkdir()
|
||||||
(config_dir / "config").symlink_to(self.config_home / "missing-config")
|
(config_dir / "config").symlink_to(self.config_home / "missing-config")
|
||||||
result = self.run_cli("start")
|
result = self.run_cli("start")
|
||||||
@@ -651,7 +651,7 @@ class XrayInstallTest(unittest.TestCase):
|
|||||||
|
|
||||||
def tree_snapshot_without_install_lock(self):
|
def tree_snapshot_without_install_lock(self):
|
||||||
snapshot = self.tree_snapshot()
|
snapshot = self.tree_snapshot()
|
||||||
for path in ("state/xray-proxy/lock", "state/xray-proxy", "state"):
|
for path in ("state/proxy-client/lock", "state/proxy-client", "state"):
|
||||||
snapshot.pop(path, None)
|
snapshot.pop(path, None)
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
@@ -662,7 +662,7 @@ class XrayInstallTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def assert_install_lock_released(self):
|
def assert_install_lock_released(self):
|
||||||
lock_path = self.state_home / "xray-proxy/lock"
|
lock_path = self.state_home / "proxy-client/lock"
|
||||||
with lock_path.open("r+", encoding="ascii") as lock:
|
with lock_path.open("r+", encoding="ascii") as lock:
|
||||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
|
||||||
@@ -692,8 +692,8 @@ class XrayInstallTest(unittest.TestCase):
|
|||||||
"home/.local/bin",
|
"home/.local/bin",
|
||||||
"home/.local/bin/xray",
|
"home/.local/bin/xray",
|
||||||
"state",
|
"state",
|
||||||
"state/xray-proxy",
|
"state/proxy-client",
|
||||||
"state/xray-proxy/lock",
|
"state/proxy-client/lock",
|
||||||
},
|
},
|
||||||
set(self.tree_snapshot()),
|
set(self.tree_snapshot()),
|
||||||
)
|
)
|
||||||
@@ -983,7 +983,7 @@ class XrayInstallTest(unittest.TestCase):
|
|||||||
downloader = mock.Mock(side_effect=AssertionError("network used"))
|
downloader = mock.Mock(side_effect=AssertionError("network used"))
|
||||||
guard = mock.Mock(
|
guard = mock.Mock(
|
||||||
side_effect=self.module["ProxyError"](
|
side_effect=self.module["ProxyError"](
|
||||||
"managed Xray работает; сначала выполните xray-proxy stop"
|
"managed Xray работает; сначала выполните proxy-client stop"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
globals_ = self.module["command_install"].__globals__
|
globals_ = self.module["command_install"].__globals__
|
||||||
@@ -1129,14 +1129,14 @@ class XrayInstallTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(sentinel, self.destination.read_bytes())
|
self.assertEqual(sentinel, self.destination.read_bytes())
|
||||||
self.assert_no_staging()
|
self.assert_no_staging()
|
||||||
lock_path = self.state_home / "xray-proxy/lock"
|
lock_path = self.state_home / "proxy-client/lock"
|
||||||
with lock_path.open("r+", encoding="ascii") as lock:
|
with lock_path.open("r+", encoding="ascii") as lock:
|
||||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
self.assertEqual(before, self.tree_snapshot_without_install_lock())
|
self.assertEqual(before, self.tree_snapshot_without_install_lock())
|
||||||
|
|
||||||
def test_start_lock_is_held_during_download_and_through_replace(self):
|
def test_start_lock_is_held_during_download_and_through_replace(self):
|
||||||
downloader, _calls = self.downloader()
|
downloader, _calls = self.downloader()
|
||||||
lock_path = self.state_home / "xray-proxy/lock"
|
lock_path = self.state_home / "proxy-client/lock"
|
||||||
|
|
||||||
def assert_lock_held():
|
def assert_lock_held():
|
||||||
with lock_path.open("r+", encoding="ascii") as competing:
|
with lock_path.open("r+", encoding="ascii") as competing:
|
||||||
@@ -1213,7 +1213,7 @@ class XrayInstallTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_install_dispatches_before_machine_config_loading(self):
|
def test_install_dispatches_before_machine_config_loading(self):
|
||||||
self.write_destination()
|
self.write_destination()
|
||||||
config = self.root / "config/proxy"
|
config = self.root / "config/proxy-client"
|
||||||
config.mkdir(parents=True)
|
config.mkdir(parents=True)
|
||||||
(config / "config").write_text("not an ini\n", encoding="ascii")
|
(config / "config").write_text("not an ini\n", encoding="ascii")
|
||||||
environment = os.environ.copy()
|
environment = os.environ.copy()
|
||||||
File renamed without changes.
@@ -15,7 +15,7 @@ from urllib.parse import quote
|
|||||||
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
CLI = REPO_ROOT / "xray-proxy/.local/bin/xray-proxy"
|
CLI = REPO_ROOT / "proxy-client/.local/bin/proxy-client"
|
||||||
UUID = "11111111-2222-4333-8444-555555555555"
|
UUID = "11111111-2222-4333-8444-555555555555"
|
||||||
PBK = "PUBLIC_KEY_SENTINEL_7f8a"
|
PBK = "PUBLIC_KEY_SENTINEL_7f8a"
|
||||||
SID = "SHORT_ID_SENTINEL_9b3c"
|
SID = "SHORT_ID_SENTINEL_9b3c"
|
||||||
@@ -185,7 +185,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
"FAKE_XRAY_LOG": str(self.log),
|
"FAKE_XRAY_LOG": str(self.log),
|
||||||
"FAKE_XRAY_VERSION": "Xray 26.3.27 (fake)",
|
"FAKE_XRAY_VERSION": "Xray 26.3.27 (fake)",
|
||||||
"PROXY_PREFLIGHT_TIMEOUT": "0.5",
|
"PROXY_PREFLIGHT_TIMEOUT": "0.5",
|
||||||
"XRAY_PROXY_STOP_TIMEOUT": "2",
|
"PROXY_CLIENT_STOP_TIMEOUT": "2",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.port = self.free_port()
|
self.port = self.free_port()
|
||||||
@@ -201,7 +201,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
return listener.getsockname()[1]
|
return listener.getsockname()[1]
|
||||||
|
|
||||||
def write_machine_config(self, proxy_type):
|
def write_machine_config(self, proxy_type):
|
||||||
directory = self.config_home / "proxy"
|
directory = self.config_home / "proxy-client"
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
(directory / "config").write_text(
|
(directory / "config").write_text(
|
||||||
"[proxy]\n"
|
"[proxy]\n"
|
||||||
@@ -217,7 +217,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
if explicit:
|
if explicit:
|
||||||
path = self.root / "chosen.url"
|
path = self.root / "chosen.url"
|
||||||
else:
|
else:
|
||||||
path = self.config_home / "xray-proxy/default.url"
|
path = self.config_home / "proxy-client/default.url"
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(url + "\n", encoding="utf-8")
|
path.write_text(url + "\n", encoding="utf-8")
|
||||||
path.chmod(0o600)
|
path.chmod(0o600)
|
||||||
@@ -257,7 +257,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def wait_for_state(self, process):
|
def wait_for_state(self, process):
|
||||||
state = self.state_home / "xray-proxy/state.json"
|
state = self.state_home / "proxy-client/state.json"
|
||||||
deadline = time.monotonic() + 5
|
deadline = time.monotonic() + 5
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
if state.exists():
|
if state.exists():
|
||||||
@@ -276,7 +276,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
return [json.loads(line) for line in self.log.read_text().splitlines()]
|
return [json.loads(line) for line in self.log.read_text().splitlines()]
|
||||||
|
|
||||||
def managed_state_path(self):
|
def managed_state_path(self):
|
||||||
return self.state_home / "xray-proxy/state.json"
|
return self.state_home / "proxy-client/state.json"
|
||||||
|
|
||||||
def read_state(self):
|
def read_state(self):
|
||||||
return json.loads(self.managed_state_path().read_text(encoding="utf-8"))
|
return json.loads(self.managed_state_path().read_text(encoding="utf-8"))
|
||||||
@@ -358,8 +358,8 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.assertIn("ip-probe", kinds)
|
self.assertIn("ip-probe", kinds)
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
self.assertEqual(str(self.xray.resolve()), entry["executable"])
|
self.assertEqual(str(self.xray.resolve()), entry["executable"])
|
||||||
self.assertFalse((self.state_home / "xray-proxy/state.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/state.json").exists())
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
self.assert_no_secrets(stdout + stderr + status.stdout + env.stdout, profile_text)
|
self.assert_no_secrets(stdout + stderr + status.stdout + env.stdout, profile_text)
|
||||||
self.log.unlink()
|
self.log.unlink()
|
||||||
self.port = self.free_port()
|
self.port = self.free_port()
|
||||||
@@ -395,9 +395,9 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
profile_text = self.tcp_profile()
|
profile_text = self.tcp_profile()
|
||||||
self.write_profile(profile_text)
|
self.write_profile(profile_text)
|
||||||
process = self.start()
|
process = self.start()
|
||||||
state_path = self.state_home / "xray-proxy/state.json"
|
state_path = self.state_home / "proxy-client/state.json"
|
||||||
config_path = self.state_home / "xray-proxy/client.json"
|
config_path = self.state_home / "proxy-client/client.json"
|
||||||
lock_path = self.state_home / "xray-proxy/lock"
|
lock_path = self.state_home / "proxy-client/lock"
|
||||||
state = json.loads(state_path.read_text(encoding="utf-8"))
|
state = json.loads(state_path.read_text(encoding="utf-8"))
|
||||||
config = json.loads(config_path.read_text(encoding="utf-8"))
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
||||||
self.assertEqual(0o600, state_path.stat().st_mode & 0o777)
|
self.assertEqual(0o600, state_path.stat().st_mode & 0o777)
|
||||||
@@ -456,7 +456,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
def test_held_lock_rejects_start_without_changing_managed_files(self):
|
def test_held_lock_rejects_start_without_changing_managed_files(self):
|
||||||
profile_text = self.tcp_profile()
|
profile_text = self.tcp_profile()
|
||||||
self.write_profile(profile_text)
|
self.write_profile(profile_text)
|
||||||
state_dir = self.state_home / "xray-proxy"
|
state_dir = self.state_home / "proxy-client"
|
||||||
state_dir.mkdir(parents=True, mode=0o700)
|
state_dir.mkdir(parents=True, mode=0o700)
|
||||||
state_path = state_dir / "state.json"
|
state_path = state_dir / "state.json"
|
||||||
config_path = state_dir / "client.json"
|
config_path = state_dir / "client.json"
|
||||||
@@ -496,7 +496,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.stop_and_collect(process)
|
self.stop_and_collect(process)
|
||||||
|
|
||||||
def test_stale_cleanup_waits_for_start_lock_and_preserves_files(self):
|
def test_stale_cleanup_waits_for_start_lock_and_preserves_files(self):
|
||||||
state_dir = self.state_home / "xray-proxy"
|
state_dir = self.state_home / "proxy-client"
|
||||||
state_dir.mkdir(parents=True, mode=0o700)
|
state_dir.mkdir(parents=True, mode=0o700)
|
||||||
state = {
|
state = {
|
||||||
"supervisor_pid": 999999998,
|
"supervisor_pid": 999999998,
|
||||||
@@ -586,7 +586,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.assertNotEqual(0, status.returncode)
|
self.assertNotEqual(0, status.returncode)
|
||||||
self.assertIn("устаревшее", status.stderr)
|
self.assertIn("устаревшее", status.stderr)
|
||||||
self.assertFalse(self.managed_state_path().exists())
|
self.assertFalse(self.managed_state_path().exists())
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
self.assertTrue(Path("/proc/{}".format(state["xray_pid"])).exists())
|
self.assertTrue(Path("/proc/{}".format(state["xray_pid"])).exists())
|
||||||
|
|
||||||
os.kill(state["xray_pid"], signal.SIGKILL)
|
os.kill(state["xray_pid"], signal.SIGKILL)
|
||||||
@@ -594,7 +594,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.assert_no_secrets(stdout + stderr, profile_text)
|
self.assert_no_secrets(stdout + stderr, profile_text)
|
||||||
|
|
||||||
def test_dead_pids_allow_stale_state_and_config_cleanup(self):
|
def test_dead_pids_allow_stale_state_and_config_cleanup(self):
|
||||||
state_dir = self.state_home / "xray-proxy"
|
state_dir = self.state_home / "proxy-client"
|
||||||
state_dir.mkdir(parents=True, mode=0o700)
|
state_dir.mkdir(parents=True, mode=0o700)
|
||||||
state = {
|
state = {
|
||||||
"supervisor_pid": 999999998,
|
"supervisor_pid": 999999998,
|
||||||
@@ -638,7 +638,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
len([entry for entry in self.log_entries() if entry["kind"] == "run"]),
|
len([entry for entry in self.log_entries() if entry["kind"] == "run"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
lock_path = self.state_home / "xray-proxy/lock"
|
lock_path = self.state_home / "proxy-client/lock"
|
||||||
with lock_path.open("r+", encoding="ascii") as lock:
|
with lock_path.open("r+", encoding="ascii") as lock:
|
||||||
with self.assertRaises(OSError):
|
with self.assertRaises(OSError):
|
||||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
@@ -662,7 +662,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.assertEqual(0, stopped.returncode, stopped.stderr)
|
self.assertEqual(0, stopped.returncode, stopped.stderr)
|
||||||
self.assertIn("сирот", stopped.stdout)
|
self.assertIn("сирот", stopped.stdout)
|
||||||
self.assertFalse(self.managed_state_path().exists())
|
self.assertFalse(self.managed_state_path().exists())
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
stdout, stderr = process.communicate(timeout=2)
|
stdout, stderr = process.communicate(timeout=2)
|
||||||
self.assert_no_secrets(stdout + stderr + stopped.stdout, profile_text)
|
self.assert_no_secrets(stdout + stderr + stopped.stdout, profile_text)
|
||||||
|
|
||||||
@@ -677,7 +677,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
def test_unverifiable_live_child_preserves_state_and_config(self):
|
def test_unverifiable_live_child_preserves_state_and_config(self):
|
||||||
profile_text = self.tcp_profile()
|
profile_text = self.tcp_profile()
|
||||||
self.write_profile(profile_text)
|
self.write_profile(profile_text)
|
||||||
self.environment["XRAY_PROXY_STOP_TIMEOUT"] = "0.2"
|
self.environment["PROXY_CLIENT_STOP_TIMEOUT"] = "0.2"
|
||||||
process = self.start()
|
process = self.start()
|
||||||
state = self.read_state()
|
state = self.read_state()
|
||||||
self.xray.unlink()
|
self.xray.unlink()
|
||||||
@@ -685,7 +685,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
os.kill(state["supervisor_pid"], signal.SIGTERM)
|
os.kill(state["supervisor_pid"], signal.SIGTERM)
|
||||||
process.wait(timeout=3)
|
process.wait(timeout=3)
|
||||||
state_exists = self.managed_state_path().exists()
|
state_exists = self.managed_state_path().exists()
|
||||||
config_path = self.state_home / "xray-proxy/client.json"
|
config_path = self.state_home / "proxy-client/client.json"
|
||||||
config_exists = config_path.exists()
|
config_exists = config_path.exists()
|
||||||
child_alive = Path("/proc/{}".format(state["xray_pid"])).exists()
|
child_alive = Path("/proc/{}".format(state["xray_pid"])).exists()
|
||||||
if child_alive:
|
if child_alive:
|
||||||
@@ -715,11 +715,11 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
process.wait(timeout=2)
|
process.wait(timeout=2)
|
||||||
|
|
||||||
stopped = self.run_cli(
|
stopped = self.run_cli(
|
||||||
"stop", environment={"XRAY_PROXY_STOP_TIMEOUT": "0.2"}
|
"stop", environment={"PROXY_CLIENT_STOP_TIMEOUT": "0.2"}
|
||||||
)
|
)
|
||||||
self.assertEqual(0, stopped.returncode, stopped.stderr)
|
self.assertEqual(0, stopped.returncode, stopped.stderr)
|
||||||
self.assertFalse(self.managed_state_path().exists())
|
self.assertFalse(self.managed_state_path().exists())
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
signals = [
|
signals = [
|
||||||
entry["signum"]
|
entry["signum"]
|
||||||
for entry in self.log_entries()
|
for entry in self.log_entries()
|
||||||
@@ -745,7 +745,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.assertIn("занят", result.stderr)
|
self.assertIn("занят", result.stderr)
|
||||||
self.assertEqual([], self.log_entries())
|
self.assertEqual([], self.log_entries())
|
||||||
self.assertFalse(self.managed_state_path().exists())
|
self.assertFalse(self.managed_state_path().exists())
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
self.assert_no_secrets(result.stdout + result.stderr, profile_text)
|
self.assert_no_secrets(result.stdout + result.stderr, profile_text)
|
||||||
|
|
||||||
def test_versions_and_profile_failures_stop_before_test_or_run_without_leaks(self):
|
def test_versions_and_profile_failures_stop_before_test_or_run_without_leaks(self):
|
||||||
@@ -930,8 +930,8 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.write_profile(profile_text)
|
self.write_profile(profile_text)
|
||||||
process = self.start()
|
process = self.start()
|
||||||
stdout, stderr = self.stop_and_collect(process)
|
stdout, stderr = self.stop_and_collect(process)
|
||||||
self.assertFalse((self.state_home / "xray-proxy/state.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/state.json").exists())
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
self.assert_no_secrets(stdout + stderr, profile_text)
|
self.assert_no_secrets(stdout + stderr, profile_text)
|
||||||
self.log.unlink()
|
self.log.unlink()
|
||||||
self.port = self.free_port()
|
self.port = self.free_port()
|
||||||
@@ -969,12 +969,12 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.assertNotEqual(0, result.returncode)
|
self.assertNotEqual(0, result.returncode)
|
||||||
self.assertEqual(["version", "test"], [entry["kind"] for entry in self.log_entries()])
|
self.assertEqual(["version", "test"], [entry["kind"] for entry in self.log_entries()])
|
||||||
self.assert_no_secrets(result.stdout + result.stderr, profile_text)
|
self.assert_no_secrets(result.stdout + result.stderr, profile_text)
|
||||||
self.assertFalse((self.state_home / "xray-proxy/client.json").exists())
|
self.assertFalse((self.state_home / "proxy-client/client.json").exists())
|
||||||
|
|
||||||
def test_xray_start_requires_loopback_before_selecting_binary(self):
|
def test_xray_start_requires_loopback_before_selecting_binary(self):
|
||||||
profile_text = self.tcp_profile()
|
profile_text = self.tcp_profile()
|
||||||
self.write_profile(profile_text)
|
self.write_profile(profile_text)
|
||||||
config_path = self.config_home / "proxy/config"
|
config_path = self.config_home / "proxy-client/config"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
config_path.read_text(encoding="utf-8").replace(
|
config_path.read_text(encoding="utf-8").replace(
|
||||||
"address = 127.0.0.1", "address = 192.0.2.10"
|
"address = 127.0.0.1", "address = 192.0.2.10"
|
||||||
@@ -991,7 +991,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
listener = socket.socket()
|
listener = socket.socket()
|
||||||
listener.bind(("127.0.0.1", self.port))
|
listener.bind(("127.0.0.1", self.port))
|
||||||
listener.listen(1)
|
listener.listen(1)
|
||||||
state_dir = self.state_home / "xray-proxy"
|
state_dir = self.state_home / "proxy-client"
|
||||||
state_dir.mkdir(parents=True)
|
state_dir.mkdir(parents=True)
|
||||||
state = {
|
state = {
|
||||||
"supervisor_pid": os.getpid(),
|
"supervisor_pid": os.getpid(),
|
||||||
@@ -1021,7 +1021,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
listener = socket.socket()
|
listener = socket.socket()
|
||||||
listener.bind(("127.0.0.1", foreign_port))
|
listener.bind(("127.0.0.1", foreign_port))
|
||||||
listener.listen(1)
|
listener.listen(1)
|
||||||
config_path = self.config_home / "proxy/config"
|
config_path = self.config_home / "proxy-client/config"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
config_path.read_text(encoding="utf-8").replace(
|
config_path.read_text(encoding="utf-8").replace(
|
||||||
"port = {}".format(managed_port), "port = {}".format(foreign_port)
|
"port = {}".format(managed_port), "port = {}".format(foreign_port)
|
||||||
@@ -1056,7 +1056,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
self.write_profile(profile_text)
|
self.write_profile(profile_text)
|
||||||
process = self.start()
|
process = self.start()
|
||||||
state = json.loads(
|
state = json.loads(
|
||||||
(self.state_home / "xray-proxy/state.json").read_text(
|
(self.state_home / "proxy-client/state.json").read_text(
|
||||||
encoding="utf-8"
|
encoding="utf-8"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1077,12 +1077,12 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
def test_absolute_relative_and_path_symlink_cli_invocations_are_manageable(self):
|
def test_absolute_relative_and_path_symlink_cli_invocations_are_manageable(self):
|
||||||
proxy_bin = self.root / "proxy-bin"
|
proxy_bin = self.root / "proxy-bin"
|
||||||
proxy_bin.mkdir()
|
proxy_bin.mkdir()
|
||||||
(proxy_bin / "xray-proxy").symlink_to(CLI)
|
(proxy_bin / "proxy-client").symlink_to(CLI)
|
||||||
invocations = (
|
invocations = (
|
||||||
([str(CLI)], self.root, self.environment),
|
([str(CLI)], self.root, self.environment),
|
||||||
([os.path.relpath(str(CLI), str(self.root))], self.root, self.environment),
|
([os.path.relpath(str(CLI), str(self.root))], self.root, self.environment),
|
||||||
(
|
(
|
||||||
["xray-proxy"],
|
["proxy-client"],
|
||||||
self.root,
|
self.root,
|
||||||
dict(
|
dict(
|
||||||
self.environment,
|
self.environment,
|
||||||
@@ -1106,7 +1106,7 @@ class ManagedXrayCliTest(unittest.TestCase):
|
|||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
env=environment,
|
env=environment,
|
||||||
)
|
)
|
||||||
state_path = self.state_home / "xray-proxy/state.json"
|
state_path = self.state_home / "proxy-client/state.json"
|
||||||
deadline = time.monotonic() + 3
|
deadline = time.monotonic() + 3
|
||||||
while time.monotonic() < deadline and not state_path.exists():
|
while time.monotonic() < deadline and not state_path.exists():
|
||||||
if process.poll() is not None:
|
if process.poll() is not None:
|
||||||
@@ -4,7 +4,7 @@ set -euo pipefail
|
|||||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
FUNCTIONS_FILE="$REPO_ROOT/bash/.bash_functions"
|
FUNCTIONS_FILE="$REPO_ROOT/bash/.bash_functions"
|
||||||
CUSTOM_FILE="$REPO_ROOT/bash/.bash_custom"
|
CUSTOM_FILE="$REPO_ROOT/bash/.bash_custom"
|
||||||
SHELL_MODULE="$REPO_ROOT/xray-proxy/.config/xray-proxy/shell.bash"
|
SHELL_MODULE="$REPO_ROOT/proxy-client/.config/proxy-client/shell.bash"
|
||||||
|
|
||||||
pass_count=0
|
pass_count=0
|
||||||
|
|
||||||
@@ -52,10 +52,10 @@ assert_five_proxy_commands() {
|
|||||||
# Обычная .bash_custom независимо подключает общие функции и proxy-модуль.
|
# Обычная .bash_custom независимо подключает общие функции и proxy-модуль.
|
||||||
(
|
(
|
||||||
fixture="$(mktemp -d /tmp/proxy-bash-custom-test.XXXXXX)"
|
fixture="$(mktemp -d /tmp/proxy-bash-custom-test.XXXXXX)"
|
||||||
mkdir -p "$fixture/.config/xray-proxy"
|
mkdir -p "$fixture/.config/proxy-client"
|
||||||
ln -s "$CUSTOM_FILE" "$fixture/.bash_custom"
|
ln -s "$CUSTOM_FILE" "$fixture/.bash_custom"
|
||||||
ln -s "$FUNCTIONS_FILE" "$fixture/.bash_functions"
|
ln -s "$FUNCTIONS_FILE" "$fixture/.bash_functions"
|
||||||
ln -s "$SHELL_MODULE" "$fixture/.config/xray-proxy/shell.bash"
|
ln -s "$SHELL_MODULE" "$fixture/.config/proxy-client/shell.bash"
|
||||||
output="$(
|
output="$(
|
||||||
HOME="$fixture" bash --noprofile --norc -ic '
|
HOME="$fixture" bash --noprofile --norc -ic '
|
||||||
source "$HOME/.bash_custom"
|
source "$HOME/.bash_custom"
|
||||||
|
|||||||
Reference in new issue
Block a user