From 86d63ab2aac5b9b99da3c48f3f094dcc62860477 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 21 Jul 2026 21:42:33 +0300 Subject: [PATCH 1/5] =?UTF-8?q?docs(proxy-client):=20=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20?= =?UTF-8?q?=D1=81=D0=B0=D0=BC=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=83=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=89=D0=B8=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - отдельный пакет должен устанавливаться без общего dotfiles и обязательного stow. - Что: - зафиксирована единая раскладка симлинков через stow или ln. - отделены файлы пакета от машинного конфига и закрытого профиля. - добавлены правила конфликтов, настройки Bash и проверки установки. - Проверка: - git diff --cached --check. --- docs/specs/2026-07-15-proxy-client-design.md | 114 +++++++++++++++++-- 1 file changed, 102 insertions(+), 12 deletions(-) diff --git a/docs/specs/2026-07-15-proxy-client-design.md b/docs/specs/2026-07-15-proxy-client-design.md index 0345263..e4b3a4a 100644 --- a/docs/specs/2026-07-15-proxy-client-design.md +++ b/docs/specs/2026-07-15-proxy-client-design.md @@ -2,7 +2,8 @@ Дата: 2026-07-15 -Статус: реализация готова к ручной приёмке; публичное имя уточнено 2026-07-21 +Статус: основная реализация принята вручную; самостоятельный установщик +спроектирован 2026-07-21, но ещё не реализован ## Проблема @@ -48,8 +49,9 @@ proxy-переменных совместима с локальным Xray бе JSON Xray. - Сохранить совместимость Bash-функций с внешним Hiddify. - Позволить настраивать машинный `NO_PROXY` без правки файлов репозитория. -- Поддержать выборочную установку после `git clone`: через stow, ручные - симлинки или копирование. +- Поддержать самостоятельную установку из отдельно скопированного каталога + `proxy-client/` без зависимости от общего установщика dotfiles и без + обязательного stow. ## Не входит в объём @@ -89,6 +91,22 @@ Bash нужен только там, где необходимо изменит требует кода, но не решает основную боль: безопасный запуск `pclaude` и `pcodex` из точечной ссылки Hiddify. +### Доставка отдельного пакета + +Рассмотрены четыре способа подключить отдельно скопированный каталог: + +- оставить только инструкции для stow и ручных симлинков — просто, но сохраняет + легко забываемые шаги; +- копировать файлы в домашний каталог — не требует сохранять исходный каталог, + но усложняет обновления и расходится с моделью dotfiles; +- требовать stow — даёт привычное управление ссылками, но добавляет + необязательную зависимость на удалённых серверах; +- дать пакету собственный установщик, который использует stow при наличии и + создаёт те же симлинки напрямую без него. + +Выбран последний вариант. Он сохраняет один результат установки при разном +составе машины и не требует второй копии обновляемых файлов. + ## Рекомендуемое решение ### Границы компонентов @@ -376,15 +394,50 @@ CLI раскладывается как `~/.local/bin/proxy-client`. Машин `~/.config/proxy-client/`. Шаблоны не подменяют настоящие пользовательские файлы. -В корне этого каталога находится самостоятельный `README.md`. Он является -основной инструкцией клиента и исключается из раскладки stow. README остаётся -рядом с кодом при выборочном клонировании или копировании каталога. Корневой -README репозитория только кратко описывает подсистему и ссылается на него. +В корне этого каталога находятся самостоятельный `README.md` и пакетный +`install.sh`. Оба файла относятся к исходному каталогу и исключаются из +раскладки stow. README остаётся рядом с кодом при выборочном клонировании или +копировании каталога. Корневой README репозитория только кратко описывает +подсистему и ссылается на него. -После клонирования репозитория пакет можно подключить отдельно от общего -`install.sh`. README также должен описывать эквивалентные ручные симлинки и -копирование файлов. Полный `install.sh` подключает этот же stow-пакет вместе с -остальными dotfiles и не имеет отдельной реализации. +Пакетный установщик связывает отдельно скопированный каталог с домашним +каталогом и не зависит от положения или наличия остальных dotfiles. Если stow +доступен, установщик использует его; если нет — создаёт тот же набор симлинков +напрямую. Выбранный механизм явно показывается пользователю, но не меняет +результат. Исходный каталог должен оставаться на прежнем пути всё время +использования ссылок. + +Установщик управляет только тремя публичными артефактами: CLI, `shell.bash` и +каталогом шаблонов. Машинный `config` и закрытый `default.url` не входят в +раскладку stow, никогда не создаются, не заменяются и не связываются +установщиком. Их наличие в исходном каталоге также не должно расширять набор +управляемых путей. + +Домашние `~/.local/bin` и `~/.config/proxy-client` остаются настоящими +каталогами. Stow не должен сворачивать их или их родителей в одну ссылку на +исходное дерево: ссылками становятся только три перечисленных артефакта. Это +одновременно сохраняет место для машинных файлов и делает результат stow +совпадающим с прямой раскладкой через `ln -s`. + +Перед записью установщик проверяет весь набор назначений. Правильная ссылка на +текущий исходный каталог считается уже установленной; любой другой файл, +каталог или симлинк считается конфликтом. При конфликте установщик ничего не +меняет и сообщает все пути, которые пользователь должен разобрать вручную. +Принудительной замены и автоматических резервных копий нет. + +По умолчанию установщик идемпотентно подключает Bash-модуль в `~/.bashrc` и при +необходимости добавляет `~/.local/bin` в `PATH`. Уже существующее подключение +через dotfiles не дублируется. Управляемый блок помечается, повторно не +добавляется и может быть полностью отключён параметром `--no-shell-setup`. + +Установщик не выбирает режим прокси, не создаёт конфигурацию, не читает VLESS, +не устанавливает Xray и не проверяет сеть. После раскладки он проверяет только +локальные требования и исправность CLI и Bash-модуля, затем печатает краткую +подсказку для текущей shell-сессии и направляет пользователя в README за +настройкой режима. Недоступный прокси не делает установку неуспешной. + +Полный корневой `install.sh` продолжает подключать тот же stow-пакет вместе с +остальными dotfiles и не получает отдельной реализации этой логики. ## Решения и отклонённые альтернативы @@ -412,6 +465,14 @@ README репозитория только кратко описывает по автоматическое распределение портов отложены до появления реальной нужды. - Официальный системный установщик отклонён: он требует root и создаёт systemd-службы. Пользовательская установка соответствует выбранной модели. +- Копирование файлов пакета отклонено: оно создаёт отдельные экземпляры, + усложняет обновление и нарушает общую с dotfiles модель симлинков. +- Обязательный stow отклонён как лишняя зависимость. Прямые симлинки дают тот же + результат на машине без stow, а автоматический выбор механизма не меняет + публичное поведение пакета. +- Автоматическая настройка режима, установка Xray и проверка сети отклонены для + установщика пакета. Эти операции зависят от машины, могут требовать секретов + или сети и уже имеют отдельные явные команды и инструкции. - Готовые JSON и полный импорт Hiddify отклонены как основной интерфейс: точечная VLESS-ссылка проще для пользователя и ограничивает область преобразования. @@ -428,6 +489,15 @@ README репозитория только кратко описывает по истинное значение сопровождается предупреждением. - Старые дистрибутивы могут иметь Python ниже 3.8. CLI проверяет версию до основной работы и сообщает обнаруженную и требуемую версии. +- Симлинки зависят от постоянного пути исходного каталога. Установщик явно + сообщает эту зависимость и при повторном запуске принимает только ссылки на + текущий источник. +- Изменение `~/.bashrc` может конфликтовать с пользовательским способом загрузки + оболочки. Установщик распознаёт собственный блок и известное подключение из + dotfiles, не дублирует их и предоставляет `--no-shell-setup`. +- Частичная установка может оставить неоднозначное состояние. Все конфликты + назначений проверяются до изменений; проверки охватывают отсутствие новых + ссылок и правок shell при отказе. - `NO_PROXY` реализуется программами неодинаково. README рекомендует точные IP и домены, а CIDR описывает как совместимость best-effort. - Разные пользователи делят сетевое пространство имён. Настраиваемый порт и @@ -463,6 +533,22 @@ README репозитория только кратко описывает по - Регрессионные Bash-тесты подтверждают прежнее поведение Hiddify при отсутствии машинного конфига, оба регистра proxy-переменных, настраиваемый `NO_PROXY` и отсутствие автоматического запроса внешнего IP в `proxy_on`/`proxy_off`. +- Проверки самостоятельной установки покрывают путь со stow и без него, + одинаковый набор итоговых ссылок, запуск не из каталога пакета и каталог с + пробелами в пути. +- В обоих путях домашние каталоги остаются обычными каталогами, а пакетные + `README.md` и `install.sh` не появляются в домашнем каталоге через stow. +- Повторный запуск не меняет уже правильную установку. Конфликт любого + назначения обнаруживается до записи; чужие файлы, каталоги и ссылки остаются + неизменными. +- Машинный `config` и закрытый `default.url` не управляются установщиком даже + при их наличии в исходном каталоге или домашнем каталоге. +- Настройка Bash проверяется для нового `~/.bashrc`, уже помеченного блока, + подключения из полного dotfiles и режима `--no-shell-setup`. Добавление + `~/.local/bin` не дублируется. +- Локальная проверка установки не обращается к сети и не требует доступного + прокси. Неисправный или отсутствующий машинный конфиг не отменяет успешную + раскладку пакета. - Ручная проверка выполняется минимум на Ubuntu с Python 3.10 и на одной целевой удалённой системе. РЕД ОС, Astra Linux и Alt Linux проверяются по мере доступности: недоступная среда явно записывается как неподтверждённая, а @@ -474,7 +560,11 @@ README репозитория только кратко описывает по реализацией. Она должна описывать: - различие внешнего Hiddify и управляемого Xray; -- выборочная установка через stow, ручные симлинки и копирование; +- самостоятельный `install.sh`, его автоматический выбор stow или прямых + симлинков, требование сохранять исходный каталог и режим + `--no-shell-setup`; +- границу между управляемыми файлами пакета и создаваемыми вручную машинным + конфигом и закрытым профилем; - создание машинного INI из шаблона и отдельного файла VLESS-ссылки; - команды управления, `proxy_on/off`, `pclaude/pcodex`; - настройка `NO_PROXY`, ограничения CIDR и диагностика; From 43404d374ff4d9cff44b2932013cd3d23d113d6b Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 21 Jul 2026 21:57:13 +0300 Subject: [PATCH 2/5] =?UTF-8?q?docs(proxy-client):=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=82=D0=B8=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=20=D1=81=D0=B0=D0=BC=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D0=B9=20=D1=83=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - согласованный дизайн нужно передать агенту одной готовой вертикальной задачей. - Что: - опубликован тикет самостоятельного установщика без блокирующих зависимостей. - зафиксированы критерии безопасности, совместимости, проверки и документации. - Проверка: - git diff --cached --check. --- .../issues/01-standalone-install.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .scratch/proxy-client-installer/issues/01-standalone-install.md diff --git a/.scratch/proxy-client-installer/issues/01-standalone-install.md b/.scratch/proxy-client-installer/issues/01-standalone-install.md new file mode 100644 index 0000000..2f5c191 --- /dev/null +++ b/.scratch/proxy-client-installer/issues/01-standalone-install.md @@ -0,0 +1,42 @@ +# 01 — Самостоятельная установка proxy-client из отдельного каталога + +**What to build:** пользователь может отдельно скопировать каталог +`proxy-client`, запустить пакетный `install.sh` без общего dotfiles и получить +готовые симлинки, подключённый Bash-модуль и краткую подсказку о дальнейшей +настройке. Установка даёт одинаковый результат со stow и без него, безопасно +повторяется, не трогает машинные файлы и не требует доступной сети или прокси. + +**Blocked by:** None — can start immediately. + +**Status:** ready-for-agent + +- [ ] При наличии stow установщик использует его, а без stow создаёт напрямую + тот же набор симлинков и явно сообщает выбранный способ. +- [ ] Управляются только CLI, Bash-модуль и шаблоны; машинный конфиг и закрытый + профиль не создаются, не заменяются и не связываются даже при наличии в + исходном каталоге. +- [ ] Домашние каталоги остаются обычными каталогами; пакетные README и + установщик не раскладываются в домашний каталог. +- [ ] До любых изменений проверяется весь набор назначений. Любой чужой файл, + каталог или симлинк приводит к отказу без частичной установки и без + автоматической замены или резервной копии; пользователю показывается полный + список конфликтующих путей. +- [ ] Повторный запуск для уже правильных ссылок ничего не меняет и завершается + успешно, а исходный каталог может находиться в произвольном пути, включая + путь с пробелами. +- [ ] По умолчанию установщик идемпотентно подключает Bash-модуль и при + необходимости добавляет пользовательский каталог исполняемых файлов в + `PATH`, не дублируя подключение из полного dotfiles. +- [ ] Параметр `--no-shell-setup` полностью отключает изменение настроек Bash. +- [ ] После раскладки проверяется только локальная исправность CLI и + Bash-модуля; установка не выбирает режим, не устанавливает Xray, не читает + VLESS, не проверяет сеть и не требует существующего корректного конфига. +- [ ] Финальный вывод кратко объясняет, как применить изменения в текущей + shell-сессии, предупреждает о постоянном пути исходного каталога и направляет + к настройке режимов в основной инструкции, не дублируя её. +- [ ] Автоматические проверки покрывают оба способа создания ссылок, повторный + запуск, все виды конфликтов, защиту машинных файлов, настройку Bash и режим + без её изменения. +- [ ] Инструкция пакета описывает новый установщик, зависимость симлинков от + постоянного пути исходного каталога и дальнейшую ручную настройку; общий + установщик dotfiles продолжает работать по прежнему контракту. From f679515a8fe22d8e6c254487acd2b63d9dbc7190 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 21 Jul 2026 22:22:04 +0300 Subject: [PATCH 3/5] =?UTF-8?q?feat(proxy-client):=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=81=D0=B0=D0=BC=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=8F=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D1=89=D0=B8=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - пакет должен устанавливаться отдельно от dotfiles без ручной раскладки и опасных замен. - Что: - добавлен безопасный установщик со Stow и прямыми симлинками. - добавлены идемпотентная настройка Bash и защита машинных файлов. - обновлены инструкция и полный набор интеграционных проверок. - Проверка: - env XDG_STATE_HOME=<временный каталог> bash tests/test_proxy_client_full.sh. --- proxy-client/.stow-local-ignore | 3 + proxy-client/README.md | 35 ++- proxy-client/install.sh | 216 ++++++++++++++++ tests/test_proxy_client_full.sh | 1 + tests/test_proxy_client_install.sh | 402 +++++++++++++++++++++++++++++ 5 files changed, 645 insertions(+), 12 deletions(-) create mode 100755 proxy-client/install.sh create mode 100755 tests/test_proxy_client_install.sh diff --git a/proxy-client/.stow-local-ignore b/proxy-client/.stow-local-ignore index 8d287b5..9d84037 100644 --- a/proxy-client/.stow-local-ignore +++ b/proxy-client/.stow-local-ignore @@ -1 +1,4 @@ ^/README\.md$ +^/install\.sh$ +^/\.config/proxy-client/config$ +^/\.config/proxy-client/default\.url$ diff --git a/proxy-client/README.md b/proxy-client/README.md index 6c26514..50933cd 100644 --- a/proxy-client/README.md +++ b/proxy-client/README.md @@ -32,20 +32,31 @@ source ~/.bash_custom ### Только пакет `proxy-client` -```bash -cd ~/dotfiles -mkdir -p ~/.local/bin ~/.config/proxy-client -stow proxy-client -source ~/.config/proxy-client/shell.bash -``` - -Если Bash-пакет dotfiles не установлен, добавьте подключение модуля в -`~/.bashrc`: +Каталог можно скопировать отдельно от остальных dotfiles и запустить его +собственный установщик из любого рабочего каталога: ```bash -printf '%s\n' 'source ~/.config/proxy-client/shell.bash' >> ~/.bashrc +~/src/proxy-client/install.sh ``` +При наличии Stow установщик использует его, иначе создаёт те же три симлинка +напрямую: CLI, Bash-модуль и каталог шаблонов. Исходный каталог после установки +нельзя перемещать: домашние ссылки зависят от его постоянного пути. + +По умолчанию установщик добавляет в `~/.bashrc` помеченный блок для +`~/.local/bin` и Bash-модуля. Чтобы оставить настройки оболочки без изменений: + +```bash +~/src/proxy-client/install.sh --no-shell-setup +``` + +Установщик не создаёт машинный `config` и закрытый `default.url`, не +устанавливает Xray и не проверяет сеть. После установки примените изменения +через `source ~/.bashrc`; с `--no-shell-setup` подключите модуль напрямую: +`source ~/.config/proxy-client/shell.bash`. Затем выберите режим по разделу +[«Настройка режима»](#настройка-режима). Без машинного конфига продолжает +действовать режим Hiddify по умолчанию. + ## Как выбирается прокси Режим определяется только машинным файлом @@ -247,9 +258,9 @@ export PROXY_PREFLIGHT_TIMEOUT=5 ## Справочник -### Ручное подключение без Stow +### Ручное подключение без установщика -Можно создать ссылки на CLI, Bash-модуль и примеры либо скопировать те же файлы: +Если пакетный установщик запустить нельзя, создайте те же ссылки вручную: ```bash mkdir -p ~/.local/bin ~/.config/proxy-client diff --git a/proxy-client/install.sh b/proxy-client/install.sh new file mode 100755 index 0000000..e36dda6 --- /dev/null +++ b/proxy-client/install.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +set -euo pipefail + +PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +STOW_BIN="${STOW_BIN:-stow}" +SETUP_SHELL=1 + +usage() { + cat <<'EOF' +Использование: ./install.sh [--no-shell-setup] + + --no-shell-setup не изменять ~/.bashrc +EOF +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --no-shell-setup) + SETUP_SHELL=0 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Ошибка: неизвестный параметр: $1" >&2 + usage >&2 + exit 2 + ;; + esac + shift +done + +SOURCES=( + "$PACKAGE_DIR/.local/bin/proxy-client" + "$PACKAGE_DIR/.config/proxy-client/shell.bash" + "$PACKAGE_DIR/.config/proxy-client/examples" +) +TARGETS=( + "$HOME/.local/bin/proxy-client" + "$HOME/.config/proxy-client/shell.bash" + "$HOME/.config/proxy-client/examples" +) +STOW_IGNORE_PATTERNS=( + '^\.local/bin/proxy-client$' + '^\.config/proxy-client/shell\.bash$' + '^\.config/proxy-client/examples$' +) + +source_errors=() +[ -x "${SOURCES[0]}" ] || source_errors+=("${SOURCES[0]}") +[ -f "${SOURCES[1]}" ] || source_errors+=("${SOURCES[1]}") +[ -d "${SOURCES[2]}" ] || source_errors+=("${SOURCES[2]}") +if [ "${#source_errors[@]}" -gt 0 ]; then + echo "Ошибка: отсутствуют обязательные файлы исходного пакета:" >&2 + printf ' %s\n' "${source_errors[@]}" >&2 + exit 1 +fi + +"${SOURCES[0]}" --help >/dev/null +bash -n "${SOURCES[1]}" + +SHELL_BLOCK_START='# >>> proxy-client >>>' +SHELL_BLOCK_END='# <<< proxy-client <<<' +SHELL_SETUP_NEEDED=0 +BASH_CUSTOM_HAS_MODULE=0 +SHELL_PATH_NEEDED=1 +SHELL_SOURCE_NEEDED=1 +SHELL_BLOCK_CONFLICT=0 +if [ "$SETUP_SHELL" -eq 1 ]; then + if [ -f "$HOME/.bash_custom" ] \ + && grep -Fq 'proxy-client/shell.bash' "$HOME/.bash_custom"; then + BASH_CUSTOM_HAS_MODULE=1 + SHELL_SOURCE_NEEDED=0 + fi + + bashrc_start_count=0 + bashrc_end_count=0 + if [ -f "$HOME/.bashrc" ]; then + bashrc_start_count="$( + grep -Fxc "$SHELL_BLOCK_START" "$HOME/.bashrc" || true + )" + bashrc_end_count="$( + grep -Fxc "$SHELL_BLOCK_END" "$HOME/.bashrc" || true + )" + if grep -Fq 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.bashrc"; then + SHELL_PATH_NEEDED=0 + fi + fi + + if [ "$bashrc_start_count" -eq 0 ] && [ "$bashrc_end_count" -eq 0 ]; then + if [ "$SHELL_PATH_NEEDED" -eq 1 ] \ + || [ "$SHELL_SOURCE_NEEDED" -eq 1 ]; then + SHELL_SETUP_NEEDED=1 + fi + elif [ "$bashrc_start_count" -eq 1 ] \ + && [ "$bashrc_end_count" -eq 1 ] \ + && [ "$SHELL_PATH_NEEDED" -eq 0 ] \ + && { [ "$BASH_CUSTOM_HAS_MODULE" -eq 1 ] \ + || grep -Fq 'proxy-client/shell.bash' "$HOME/.bashrc"; }; then + : + else + SHELL_BLOCK_CONFLICT=1 + fi +fi + +conflicts=() +missing_targets=0 +stow_ignore_args=() +PARENT_DIRS=( + "$HOME/.local" + "$HOME/.local/bin" + "$HOME/.config" + "$HOME/.config/proxy-client" +) +for parent_dir in "${PARENT_DIRS[@]}"; do + if { [ -e "$parent_dir" ] || [ -L "$parent_dir" ]; } \ + && { [ ! -d "$parent_dir" ] || [ -L "$parent_dir" ]; }; then + conflicts+=("$parent_dir") + fi +done + +for index in "${!TARGETS[@]}"; do + target="${TARGETS[$index]}" + source_path="${SOURCES[$index]}" + if [ -e "$target" ] || [ -L "$target" ]; then + if [ ! -L "$target" ] \ + || [ "$(readlink -f "$target" 2>/dev/null || true)" != "$source_path" ]; then + conflicts+=("$target") + else + stow_ignore_args+=("--ignore=${STOW_IGNORE_PATTERNS[$index]}") + fi + else + missing_targets=$((missing_targets + 1)) + fi +done + +if [ "$SHELL_SETUP_NEEDED" -eq 1 ] \ + && { [ -e "$HOME/.bashrc" ] || [ -L "$HOME/.bashrc" ]; } \ + && [ ! -f "$HOME/.bashrc" ]; then + conflicts+=("$HOME/.bashrc") +fi +if [ "$SHELL_BLOCK_CONFLICT" -eq 1 ]; then + conflicts+=("$HOME/.bashrc") +fi + +if [ "${#conflicts[@]}" -gt 0 ]; then + echo "Ошибка: найдены конфликтующие пути:" >&2 + printf ' %s\n' "${conflicts[@]}" >&2 + echo "Разберите их вручную и повторите установку." >&2 + exit 1 +fi + +mkdir -p "$HOME/.local/bin" "$HOME/.config/proxy-client" + +if command -v "$STOW_BIN" >/dev/null 2>&1; then + echo "Установка с помощью Stow." + if [ "$missing_targets" -gt 0 ]; then + "$STOW_BIN" --dir="$(dirname "$PACKAGE_DIR")" --target="$HOME" \ + "${stow_ignore_args[@]}" \ + "$(basename "$PACKAGE_DIR")" + fi +else + echo "Установка без Stow: создаю символические ссылки напрямую." + for index in "${!TARGETS[@]}"; do + target="${TARGETS[$index]}" + source_path="${SOURCES[$index]}" + if [ ! -L "$target" ]; then + ln -s "$source_path" "$target" + fi + done +fi + +"$HOME/.local/bin/proxy-client" --help >/dev/null +bash -n "$HOME/.config/proxy-client/shell.bash" + +if [ "$SHELL_SETUP_NEEDED" -eq 1 ]; then + cat >> "$HOME/.bashrc" <<'EOF' + +# >>> proxy-client >>> +EOF + if [ "$SHELL_PATH_NEEDED" -eq 1 ]; then + cat >> "$HOME/.bashrc" <<'EOF' +case ":$PATH:" in + *":$HOME/.local/bin:"*) ;; + *) + export PATH="$HOME/.local/bin:$PATH" + ;; +esac +EOF + fi + if [ "$SHELL_SOURCE_NEEDED" -eq 1 ]; then + cat >> "$HOME/.bashrc" <<'EOF' +[ -f "$HOME/.config/proxy-client/shell.bash" ] && source "$HOME/.config/proxy-client/shell.bash" +EOF + fi + cat >> "$HOME/.bashrc" <<'EOF' +# <<< proxy-client <<< +EOF +elif [ "$SETUP_SHELL" -eq 0 ]; then + echo "Настройка Bash пропущена (--no-shell-setup)." +fi + +if [ "$SETUP_SHELL" -eq 1 ]; then + RELOAD_COMMAND='source ~/.bashrc' +else + RELOAD_COMMAND='source ~/.config/proxy-client/shell.bash' +fi + +cat <&2 + exit 1 +} + +assert_link_points_to() { + local link_path="$1" expected_path="$2" actual_path + [ -L "$link_path" ] || fail "Expected symlink: $link_path" + actual_path="$(readlink -f "$link_path")" + [ "$actual_path" = "$expected_path" ] \ + || fail "$link_path points to $actual_path, expected $expected_path" +} + +assert_not_exists() { + local path="$1" + [ ! -e "$path" ] && [ ! -L "$path" ] || fail "Path should not exist: $path" +} + +setup_package() { + local fixture_root="$1" package_dir="$fixture_root/source with spaces/proxy-client" + mkdir -p \ + "$package_dir/.local/bin" \ + "$package_dir/.config/proxy-client" + cp -a \ + "$REPO_ROOT/proxy-client/.local/bin/proxy-client" \ + "$package_dir/.local/bin/proxy-client" + cp -a \ + "$REPO_ROOT/proxy-client/.config/proxy-client/shell.bash" \ + "$REPO_ROOT/proxy-client/.config/proxy-client/examples" \ + "$package_dir/.config/proxy-client/" + cp -a \ + "$REPO_ROOT/proxy-client/.stow-local-ignore" \ + "$REPO_ROOT/proxy-client/README.md" \ + "$REPO_ROOT/proxy-client/install.sh" \ + "$package_dir/" + printf '%s\n' 'MACHINE_CONFIG_SENTINEL' > "$package_dir/.config/proxy-client/config" + printf '%s\n' 'PRIVATE_PROFILE_SENTINEL' > "$package_dir/.config/proxy-client/default.url" + printf '%s\n' "$package_dir" +} + +run_package_install() { + local package_dir="$1" home_dir="$2" stow_bin="$3" + shift 3 + ( + cd "$TEST_TMP_ROOT" + HOME="$home_dir" STOW_BIN="$stow_bin" \ + "$package_dir/install.sh" "$@" + ) +} + +test_installs_same_public_artifacts_without_stow() { + local fixture_root package_dir home_dir output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + + output="$( + run_package_install "$package_dir" "$home_dir" missing-stow --no-shell-setup + )" + + printf '%s' "$output" | grep -Fq 'без Stow' \ + || fail "Expected direct-symlink method in output" + printf '%s' "$output" | grep -Fq \ + 'source ~/.config/proxy-client/shell.bash' \ + || fail "Expected direct shell-module hint with --no-shell-setup" + [ -d "$home_dir/.local/bin" ] && [ ! -L "$home_dir/.local/bin" ] \ + || fail "Expected ~/.local/bin to be a real directory" + [ -d "$home_dir/.config/proxy-client" ] \ + && [ ! -L "$home_dir/.config/proxy-client" ] \ + || fail "Expected proxy-client config home to be a real directory" + assert_link_points_to \ + "$home_dir/.local/bin/proxy-client" \ + "$package_dir/.local/bin/proxy-client" + assert_link_points_to \ + "$home_dir/.config/proxy-client/shell.bash" \ + "$package_dir/.config/proxy-client/shell.bash" + assert_link_points_to \ + "$home_dir/.config/proxy-client/examples" \ + "$package_dir/.config/proxy-client/examples" + assert_not_exists "$home_dir/.config/proxy-client/config" + assert_not_exists "$home_dir/.config/proxy-client/default.url" + assert_not_exists "$home_dir/README.md" + assert_not_exists "$home_dir/install.sh" + assert_not_exists "$home_dir/.bashrc" +} + +test_configures_bash_once_by_default() { + local fixture_root package_dir home_dir output marker_count path_count source_count + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + + run_package_install "$package_dir" "$home_dir" missing-stow >/dev/null + output="$(run_package_install "$package_dir" "$home_dir" missing-stow)" + + marker_count="$(grep -Fxc '# >>> proxy-client >>>' "$home_dir/.bashrc")" + path_count="$(grep -Fc 'export PATH="$HOME/.local/bin:$PATH"' "$home_dir/.bashrc")" + source_count="$( + grep -Fc \ + '[ -f "$HOME/.config/proxy-client/shell.bash" ] && source "$HOME/.config/proxy-client/shell.bash"' \ + "$home_dir/.bashrc" + )" + [ "$marker_count" -eq 1 ] || fail "Expected one managed Bash block" + [ "$path_count" -eq 1 ] || fail "Expected one ~/.local/bin PATH line" + [ "$source_count" -eq 1 ] || fail "Expected one shell module source line" + printf '%s' "$output" | grep -Fq 'source ~/.bashrc' \ + || fail "Expected current-session reload hint" +} + +test_installs_same_public_artifacts_with_stow() { + local fixture_root package_dir home_dir output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + + output="$(run_package_install "$package_dir" "$home_dir" stow --no-shell-setup)" + + printf '%s' "$output" | grep -Fq 'с помощью Stow' \ + || fail "Expected Stow method in output" + [ -d "$home_dir/.local/bin" ] && [ ! -L "$home_dir/.local/bin" ] \ + || fail "Expected ~/.local/bin to be a real directory" + [ -d "$home_dir/.config/proxy-client" ] \ + && [ ! -L "$home_dir/.config/proxy-client" ] \ + || fail "Expected proxy-client config home to be a real directory" + assert_link_points_to \ + "$home_dir/.local/bin/proxy-client" \ + "$package_dir/.local/bin/proxy-client" + assert_link_points_to \ + "$home_dir/.config/proxy-client/shell.bash" \ + "$package_dir/.config/proxy-client/shell.bash" + assert_link_points_to \ + "$home_dir/.config/proxy-client/examples" \ + "$package_dir/.config/proxy-client/examples" + assert_not_exists "$home_dir/.config/proxy-client/config" + assert_not_exists "$home_dir/.config/proxy-client/default.url" + assert_not_exists "$home_dir/README.md" + assert_not_exists "$home_dir/install.sh" +} + +test_rejects_linked_parent_directories_before_changes() { + local fixture_root package_dir home_dir external_local external_config output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + external_local="$fixture_root/external-local" + external_config="$fixture_root/external-config" + mkdir -p "$home_dir" "$external_local" "$external_config" + ln -s "$external_local" "$home_dir/.local" + ln -s "$external_config" "$home_dir/.config" + + if output="$( + run_package_install "$package_dir" "$home_dir" missing-stow 2>&1 + )"; then + fail "Expected linked parent directories to be rejected" + fi + + printf '%s' "$output" | grep -Fq "$home_dir/.local" \ + || fail "Expected ~/.local conflict in output" + printf '%s' "$output" | grep -Fq "$home_dir/.config" \ + || fail "Expected ~/.config conflict in output" + assert_not_exists "$external_local/bin" + assert_not_exists "$external_config/proxy-client" + assert_not_exists "$home_dir/.bashrc" +} + +test_reports_all_target_conflicts_without_partial_install() { + local fixture_root package_dir home_dir other_target output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + other_target="$fixture_root/other-target" + mkdir -p "$home_dir/.local/bin" "$home_dir/.config/proxy-client/shell.bash" + printf '%s\n' 'KEEP_CLI' > "$home_dir/.local/bin/proxy-client" + printf '%s\n' 'KEEP_TARGET' > "$other_target" + ln -s "$other_target" "$home_dir/.config/proxy-client/examples" + + if output="$( + run_package_install "$package_dir" "$home_dir" missing-stow 2>&1 + )"; then + fail "Expected all target conflicts to be rejected" + fi + + printf '%s' "$output" | grep -Fq "$home_dir/.local/bin/proxy-client" \ + || fail "Expected CLI conflict in output" + printf '%s' "$output" | grep -Fq "$home_dir/.config/proxy-client/shell.bash" \ + || fail "Expected shell module conflict in output" + printf '%s' "$output" | grep -Fq "$home_dir/.config/proxy-client/examples" \ + || fail "Expected examples conflict in output" + grep -Fq 'KEEP_CLI' "$home_dir/.local/bin/proxy-client" \ + || fail "Expected conflicting CLI file to stay unchanged" + [ -d "$home_dir/.config/proxy-client/shell.bash" ] \ + || fail "Expected conflicting shell directory to stay unchanged" + assert_link_points_to "$home_dir/.config/proxy-client/examples" "$other_target" + assert_not_exists "$home_dir/.bashrc" +} + +test_rejects_broken_link_without_creating_other_targets() { + local fixture_root package_dir home_dir output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir/.local/bin" + ln -s "$fixture_root/missing-cli" "$home_dir/.local/bin/proxy-client" + + if output="$( + run_package_install "$package_dir" "$home_dir" missing-stow 2>&1 + )"; then + fail "Expected broken target link to be rejected" + fi + + printf '%s' "$output" | grep -Fq "$home_dir/.local/bin/proxy-client" \ + || fail "Expected broken CLI link in output" + [ -L "$home_dir/.local/bin/proxy-client" ] \ + || fail "Expected broken CLI link to stay unchanged" + assert_not_exists "$home_dir/.config" + assert_not_exists "$home_dir/.bashrc" +} + +test_preserves_machine_files_with_both_methods() { + local stow_bin fixture_root package_dir home_dir + for stow_bin in missing-stow stow; do + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir/.config/proxy-client" + printf '%s\n' 'HOME_CONFIG_SENTINEL' \ + > "$home_dir/.config/proxy-client/config" + printf '%s\n' 'HOME_PROFILE_SENTINEL' \ + > "$home_dir/.config/proxy-client/default.url" + + run_package_install "$package_dir" "$home_dir" "$stow_bin" \ + --no-shell-setup >/dev/null + + [ ! -L "$home_dir/.config/proxy-client/config" ] \ + || fail "Machine config must not become a symlink" + [ ! -L "$home_dir/.config/proxy-client/default.url" ] \ + || fail "Private profile must not become a symlink" + grep -Fq 'HOME_CONFIG_SENTINEL' "$home_dir/.config/proxy-client/config" \ + || fail "Machine config changed" + grep -Fq 'HOME_PROFILE_SENTINEL' "$home_dir/.config/proxy-client/default.url" \ + || fail "Private profile changed" + done +} + +test_accepts_existing_links_when_method_changes() { + local fixture_root package_dir home_dir + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + + run_package_install "$package_dir" "$home_dir" missing-stow \ + --no-shell-setup >/dev/null + run_package_install "$package_dir" "$home_dir" stow \ + --no-shell-setup >/dev/null + + assert_link_points_to \ + "$home_dir/.local/bin/proxy-client" \ + "$package_dir/.local/bin/proxy-client" + assert_link_points_to \ + "$home_dir/.config/proxy-client/shell.bash" \ + "$package_dir/.config/proxy-client/shell.bash" + assert_link_points_to \ + "$home_dir/.config/proxy-client/examples" \ + "$package_dir/.config/proxy-client/examples" +} + +test_stow_completes_partial_correct_installation() { + local fixture_root package_dir home_dir + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + + run_package_install "$package_dir" "$home_dir" missing-stow \ + --no-shell-setup >/dev/null + rm "$home_dir/.config/proxy-client/shell.bash" + + run_package_install "$package_dir" "$home_dir" stow \ + --no-shell-setup >/dev/null + + assert_link_points_to \ + "$home_dir/.local/bin/proxy-client" \ + "$package_dir/.local/bin/proxy-client" + assert_link_points_to \ + "$home_dir/.config/proxy-client/shell.bash" \ + "$package_dir/.config/proxy-client/shell.bash" + assert_link_points_to \ + "$home_dir/.config/proxy-client/examples" \ + "$package_dir/.config/proxy-client/examples" +} + +test_does_not_duplicate_full_dotfiles_shell_setup() { + local fixture_root package_dir home_dir source_count + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + printf '%s\n' \ + '[ -f ~/.config/proxy-client/shell.bash ] && source ~/.config/proxy-client/shell.bash' \ + > "$home_dir/.bash_custom" + printf '%s\n' '[ -f ~/.bash_custom ] && source ~/.bash_custom' \ + > "$home_dir/.bashrc" + + run_package_install "$package_dir" "$home_dir" missing-stow >/dev/null + + grep -Fq '# >>> proxy-client >>>' "$home_dir/.bashrc" \ + || fail "Expected managed PATH block with full dotfiles" + grep -Fq 'export PATH="$HOME/.local/bin:$PATH"' "$home_dir/.bashrc" \ + || fail "Expected ~/.local/bin setup with full dotfiles" + source_count="$(grep -Fc 'proxy-client/shell.bash' "$home_dir/.bashrc" || true)" + [ "$source_count" -eq 0 ] \ + || fail "Standalone block duplicated full dotfiles module source" +} + +test_rejects_damaged_managed_bash_block_before_changes() { + local fixture_root package_dir home_dir output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + printf '%s\n' '# >>> proxy-client >>>' > "$home_dir/.bashrc" + + if output="$( + run_package_install "$package_dir" "$home_dir" missing-stow 2>&1 + )"; then + fail "Expected damaged managed Bash block to be rejected" + fi + + printf '%s' "$output" | grep -Fq "$home_dir/.bashrc" \ + || fail "Expected ~/.bashrc conflict in output" + [ "$(wc -l < "$home_dir/.bashrc")" -eq 1 ] \ + || fail "Expected damaged Bash block to stay unchanged" + assert_not_exists "$home_dir/.local" + assert_not_exists "$home_dir/.config" +} + +test_does_not_duplicate_existing_local_bin_path() { + local fixture_root package_dir home_dir path_count source_count + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + printf '%s\n' 'export PATH="$HOME/.local/bin:$PATH"' > "$home_dir/.bashrc" + + run_package_install "$package_dir" "$home_dir" missing-stow >/dev/null + + path_count="$(grep -Fc 'export PATH="$HOME/.local/bin:$PATH"' "$home_dir/.bashrc")" + source_count="$(grep -Fc 'proxy-client/shell.bash' "$home_dir/.bashrc")" + [ "$path_count" -eq 1 ] || fail "Existing ~/.local/bin PATH line was duplicated" + [ "$source_count" -eq 1 ] || fail "Expected shell module source in managed block" +} + +test_rejects_incomplete_source_before_changes() { + local fixture_root package_dir home_dir output + fixture_root="$(mktemp -d "$TEST_TMP_ROOT/fixture.XXXXXX")" + package_dir="$(setup_package "$fixture_root")" + home_dir="$fixture_root/home" + mkdir -p "$home_dir" + rm "$package_dir/.config/proxy-client/shell.bash" + + if output="$( + run_package_install "$package_dir" "$home_dir" missing-stow 2>&1 + )"; then + fail "Expected incomplete source package to be rejected" + fi + + printf '%s' "$output" | grep -Fq 'исходного пакета' \ + || fail "Expected source package error" + assert_not_exists "$home_dir/.local" + assert_not_exists "$home_dir/.config" + assert_not_exists "$home_dir/.bashrc" +} + +main() { + test_installs_same_public_artifacts_without_stow + test_configures_bash_once_by_default + test_installs_same_public_artifacts_with_stow + test_rejects_linked_parent_directories_before_changes + test_reports_all_target_conflicts_without_partial_install + test_rejects_broken_link_without_creating_other_targets + test_preserves_machine_files_with_both_methods + test_accepts_existing_links_when_method_changes + test_stow_completes_partial_correct_installation + test_does_not_duplicate_full_dotfiles_shell_setup + test_rejects_damaged_managed_bash_block_before_changes + test_does_not_duplicate_existing_local_bin_path + test_rejects_incomplete_source_before_changes + echo "All standalone proxy-client install tests passed" +} + +main "$@" From 623ed8f8717c077195baa31c70ab142eac6d0305 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 21 Jul 2026 22:45:28 +0300 Subject: [PATCH 4/5] =?UTF-8?q?fix(proxy-client):=20=D1=81=D0=BE=D0=B3?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=B8=D0=BD?= =?UTF-8?q?=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=86=D0=B8=D0=B8=20=D1=81=D0=B0?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - README смешивал пакетный и корневой установщики и оставлял неполные шаги для отдельных сценариев. - Что: - выстроен цельный путь установки и настройки для standalone и полного dotfiles. - дополнена подсказка --no-shell-setup и добавлена регрессионная проверка. - закрыты критерии приёмки тикета и указана дата завершения. - Проверка: - bash tests/test_proxy_client_install.sh. - bash tests/test_install.sh. --- .../issues/01-standalone-install.md | 24 +-- proxy-client/README.md | 140 ++++++++++++------ proxy-client/install.sh | 2 +- tests/test_proxy_client_install.sh | 4 +- 4 files changed, 109 insertions(+), 61 deletions(-) diff --git a/.scratch/proxy-client-installer/issues/01-standalone-install.md b/.scratch/proxy-client-installer/issues/01-standalone-install.md index 2f5c191..676556c 100644 --- a/.scratch/proxy-client-installer/issues/01-standalone-install.md +++ b/.scratch/proxy-client-installer/issues/01-standalone-install.md @@ -10,33 +10,35 @@ **Status:** ready-for-agent -- [ ] При наличии stow установщик использует его, а без stow создаёт напрямую +**Completed:** 2026-07-21 + +- [x] При наличии stow установщик использует его, а без stow создаёт напрямую тот же набор симлинков и явно сообщает выбранный способ. -- [ ] Управляются только CLI, Bash-модуль и шаблоны; машинный конфиг и закрытый +- [x] Управляются только CLI, Bash-модуль и шаблоны; машинный конфиг и закрытый профиль не создаются, не заменяются и не связываются даже при наличии в исходном каталоге. -- [ ] Домашние каталоги остаются обычными каталогами; пакетные README и +- [x] Домашние каталоги остаются обычными каталогами; пакетные README и установщик не раскладываются в домашний каталог. -- [ ] До любых изменений проверяется весь набор назначений. Любой чужой файл, +- [x] До любых изменений проверяется весь набор назначений. Любой чужой файл, каталог или симлинк приводит к отказу без частичной установки и без автоматической замены или резервной копии; пользователю показывается полный список конфликтующих путей. -- [ ] Повторный запуск для уже правильных ссылок ничего не меняет и завершается +- [x] Повторный запуск для уже правильных ссылок ничего не меняет и завершается успешно, а исходный каталог может находиться в произвольном пути, включая путь с пробелами. -- [ ] По умолчанию установщик идемпотентно подключает Bash-модуль и при +- [x] По умолчанию установщик идемпотентно подключает Bash-модуль и при необходимости добавляет пользовательский каталог исполняемых файлов в `PATH`, не дублируя подключение из полного dotfiles. -- [ ] Параметр `--no-shell-setup` полностью отключает изменение настроек Bash. -- [ ] После раскладки проверяется только локальная исправность CLI и +- [x] Параметр `--no-shell-setup` полностью отключает изменение настроек Bash. +- [x] После раскладки проверяется только локальная исправность CLI и Bash-модуля; установка не выбирает режим, не устанавливает Xray, не читает VLESS, не проверяет сеть и не требует существующего корректного конфига. -- [ ] Финальный вывод кратко объясняет, как применить изменения в текущей +- [x] Финальный вывод кратко объясняет, как применить изменения в текущей shell-сессии, предупреждает о постоянном пути исходного каталога и направляет к настройке режимов в основной инструкции, не дублируя её. -- [ ] Автоматические проверки покрывают оба способа создания ссылок, повторный +- [x] Автоматические проверки покрывают оба способа создания ссылок, повторный запуск, все виды конфликтов, защиту машинных файлов, настройку Bash и режим без её изменения. -- [ ] Инструкция пакета описывает новый установщик, зависимость симлинков от +- [x] Инструкция пакета описывает новый установщик, зависимость симлинков от постоянного пути исходного каталога и дальнейшую ручную настройку; общий установщик dotfiles продолжает работать по прежнему контракту. diff --git a/proxy-client/README.md b/proxy-client/README.md index 50933cd..4058a08 100644 --- a/proxy-client/README.md +++ b/proxy-client/README.md @@ -10,30 +10,10 @@ CLI требует Python 3.8+ и использует только станда ## Установка -### В составе dotfiles - -Полная установка подключает пакет и автоматически загружает Bash-функции в -новых интерактивных сессиях: - -```bash -cd ~/dotfiles -./install.sh -``` - -Установщик заранее создаёт `~/.local/bin` и `~/.config/proxy-client`, чтобы Stow -размещал ссылки внутри них, а не превращал сами каталоги в ссылки на -репозиторий. - -После установки откройте новый Bash или выполните: - -```bash -source ~/.bash_custom -``` - ### Только пакет `proxy-client` -Каталог можно скопировать отдельно от остальных dotfiles и запустить его -собственный установщик из любого рабочего каталога: +Скопируйте каталог в постоянное место и запустите пакетный установщик. Текущий +рабочий каталог не важен: ```bash ~/src/proxy-client/install.sh @@ -44,18 +24,54 @@ source ~/.bash_custom нельзя перемещать: домашние ссылки зависят от его постоянного пути. По умолчанию установщик добавляет в `~/.bashrc` помеченный блок для -`~/.local/bin` и Bash-модуля. Чтобы оставить настройки оболочки без изменений: +`~/.local/bin` и Bash-модуля. После установки откройте новый Bash или примените +блок в текущей сессии: + +```bash +source ~/.bashrc +``` + +Чтобы оставить `~/.bashrc` без изменений, запустите: ```bash ~/src/proxy-client/install.sh --no-shell-setup ``` +В этом случае подготовьте только текущую сессию вручную: + +```bash +export PATH="$HOME/.local/bin:$PATH" +source ~/.config/proxy-client/shell.bash +``` + +Для следующих сессий добавьте эти строки в подходящий Bash-конфиг сами. + Установщик не создаёт машинный `config` и закрытый `default.url`, не -устанавливает Xray и не проверяет сеть. После установки примените изменения -через `source ~/.bashrc`; с `--no-shell-setup` подключите модуль напрямую: -`source ~/.config/proxy-client/shell.bash`. Затем выберите режим по разделу -[«Настройка режима»](#настройка-режима). Без машинного конфига продолжает -действовать режим Hiddify по умолчанию. +устанавливает Xray и не проверяет сеть. После подключения команд выберите режим +по разделу [«Настройка режима»](#настройка-режима). Без машинного конфига +действует режим Hiddify по умолчанию. + +### В составе dotfiles + +Корневой установщик dotfiles подключает тот же пакет через Stow и загружает +Bash-функции в новых интерактивных сессиях: + +```bash +cd ~/dotfiles +./install.sh +``` + +Он заранее создаёт `~/.local/bin` и `~/.config/proxy-client`, чтобы Stow +размещал ссылки внутри них, а не превращал сами каталоги в ссылки на +репозиторий. После установки откройте новый Bash или выполните: + +```bash +export PATH="$HOME/.local/bin:$PATH" +source ~/.bash_custom +``` + +Если `~/.local/bin` отсутствует в `PATH` и в новых сессиях, добавьте ту же строку +`export` в свой Bash-конфиг: корневой установщик dotfiles его не изменяет. ## Как выбирается прокси @@ -91,28 +107,46 @@ proxy-client status pclaude --version ``` -В WSL NAT mode IP Windows-шлюза меняется при перезапуске. Интерактивный -`./install.sh` предлагает добавить обновление `hproxy.local` в `/etc/wsl.conf`. -То же можно настроить вручную: +#### WSL NAT mode -```ini -[boot] -command = "/path/to/dotfiles/scripts/setup-hproxy.sh" +IP Windows-шлюза меняется при перезапуске WSL, поэтому `hproxy.local` нужно +обновлять. В отдельно скопированном пакете системных помощников нет. Узнайте +адрес шлюза: + +```bash +ip route show default | awk 'NR == 1 {print $3}' ``` -После изменения выполните `wsl --shutdown` из Windows. Обновить адрес без -перезапуска WSL можно так: +Затем откройте `/etc/hosts` через `sudoedit` и оставьте одну отдельную строку с +полученным адресом: + +```bash +sudoedit /etc/hosts +``` + +```text +<адрес-шлюза> hproxy.local +``` + +Не удаляйте другие имена на той же строке: при необходимости перенесите их на +отдельную строку. Повторяйте обновление после перезапуска WSL. + +С полным dotfiles запустите корневой `~/dotfiles/install.sh` интерактивно: он +предложит автоматическое обновление при старте WSL. Обновить адрес сразу можно +так: ```bash sudo ~/dotfiles/scripts/setup-hproxy.sh -# или после загрузки Bash-функций: +# после настройки автозапуска и загрузки общих Bash-функций: update_hproxy ``` В NAT mode Hiddify должен слушать адрес, доступный через Windows-шлюз, а не только `127.0.0.1`. -В [WSL mirrored mode](https://learn.microsoft.com/windows/wsl/networking#mirrored-mode-networking) +#### WSL mirrored mode + +В [mirrored mode](https://learn.microsoft.com/windows/wsl/networking#mirrored-mode-networking) (`networkingMode=mirrored`) Windows-сервисы доступны из WSL через `127.0.0.1`. Чтобы сохранить адрес по умолчанию, добавьте алиас в Windows hosts: @@ -121,9 +155,9 @@ update_hproxy 127.0.0.1 hproxy.local ``` -Альтернатива — создать `config.external` с `address = 127.0.0.1`. В обоих -случаях проверьте результат через `getent hosts hproxy.local` или -`proxy-client status`. +Альтернатива — настройте внешний режим по следующему разделу и укажите +`address = 127.0.0.1`. Алиас проверяйте через `getent hosts hproxy.local`, а +активный конфиг — через `proxy-client status`. ### Другой внешний прокси @@ -169,7 +203,7 @@ chmod 600 ~/.config/proxy-client/default.url proxy-client install ``` -Запустите Xray в отдельной панели tmux: +Запустите Xray в отдельном терминале или панели tmux: ```bash proxy-client start @@ -260,18 +294,30 @@ export PROXY_PREFLIGHT_TIMEOUT=5 ### Ручное подключение без установщика -Если пакетный установщик запустить нельзя, создайте те же ссылки вручную: +Если пакетный установщик запустить нельзя, выберите постоянный абсолютный путь +к исходному каталогу и создайте те же ссылки вручную. Команды предполагают, что +назначения ещё не заняты: ```bash mkdir -p ~/.local/bin ~/.config/proxy-client -ln -s /path/to/proxy-client/.local/bin/proxy-client ~/.local/bin/proxy-client -ln -s /path/to/proxy-client/.config/proxy-client/shell.bash \ +ln -s /absolute/path/to/proxy-client/.local/bin/proxy-client \ + ~/.local/bin/proxy-client +ln -s /absolute/path/to/proxy-client/.config/proxy-client/shell.bash \ ~/.config/proxy-client/shell.bash -ln -s /path/to/proxy-client/.config/proxy-client/examples \ +ln -s /absolute/path/to/proxy-client/.config/proxy-client/examples \ ~/.config/proxy-client/examples +chmod 755 /absolute/path/to/proxy-client/.local/bin/proxy-client ``` -Исполняемому файлу нужен режим `755`, а `~/.local/bin` должен входить в `PATH`. +Подготовьте текущую Bash-сессию: + +```bash +export PATH="$HOME/.local/bin:$PATH" +source ~/.config/proxy-client/shell.bash +``` + +Для следующих сессий добавьте эти две строки в свой Bash-конфиг. Исходный +каталог после этого нельзя перемещать: созданные ссылки зависят от его пути. ### Машинный конфиг diff --git a/proxy-client/install.sh b/proxy-client/install.sh index e36dda6..133858b 100755 --- a/proxy-client/install.sh +++ b/proxy-client/install.sh @@ -204,7 +204,7 @@ fi if [ "$SETUP_SHELL" -eq 1 ]; then RELOAD_COMMAND='source ~/.bashrc' else - RELOAD_COMMAND='source ~/.config/proxy-client/shell.bash' + RELOAD_COMMAND='export PATH="$HOME/.local/bin:$PATH"; source ~/.config/proxy-client/shell.bash' fi cat < Date: Tue, 21 Jul 2026 22:47:16 +0300 Subject: [PATCH 5/5] =?UTF-8?q?chore(scratch):=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B8=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=20=D1=81=D0=BB=D0=B8=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=B2=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - рабочая зона ветки не должна переноситься в master вместе с продуктовым кодом. - Что: - удалён завершённый тикет самостоятельной установки proxy-client. - удалён служебный файл .scratch/.gitkeep. - Проверка: - git diff --cached --name-status. --- .scratch/.gitkeep | 0 .../issues/01-standalone-install.md | 44 ------------------- 2 files changed, 44 deletions(-) delete mode 100644 .scratch/.gitkeep delete mode 100644 .scratch/proxy-client-installer/issues/01-standalone-install.md diff --git a/.scratch/.gitkeep b/.scratch/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.scratch/proxy-client-installer/issues/01-standalone-install.md b/.scratch/proxy-client-installer/issues/01-standalone-install.md deleted file mode 100644 index 676556c..0000000 --- a/.scratch/proxy-client-installer/issues/01-standalone-install.md +++ /dev/null @@ -1,44 +0,0 @@ -# 01 — Самостоятельная установка proxy-client из отдельного каталога - -**What to build:** пользователь может отдельно скопировать каталог -`proxy-client`, запустить пакетный `install.sh` без общего dotfiles и получить -готовые симлинки, подключённый Bash-модуль и краткую подсказку о дальнейшей -настройке. Установка даёт одинаковый результат со stow и без него, безопасно -повторяется, не трогает машинные файлы и не требует доступной сети или прокси. - -**Blocked by:** None — can start immediately. - -**Status:** ready-for-agent - -**Completed:** 2026-07-21 - -- [x] При наличии stow установщик использует его, а без stow создаёт напрямую - тот же набор симлинков и явно сообщает выбранный способ. -- [x] Управляются только CLI, Bash-модуль и шаблоны; машинный конфиг и закрытый - профиль не создаются, не заменяются и не связываются даже при наличии в - исходном каталоге. -- [x] Домашние каталоги остаются обычными каталогами; пакетные README и - установщик не раскладываются в домашний каталог. -- [x] До любых изменений проверяется весь набор назначений. Любой чужой файл, - каталог или симлинк приводит к отказу без частичной установки и без - автоматической замены или резервной копии; пользователю показывается полный - список конфликтующих путей. -- [x] Повторный запуск для уже правильных ссылок ничего не меняет и завершается - успешно, а исходный каталог может находиться в произвольном пути, включая - путь с пробелами. -- [x] По умолчанию установщик идемпотентно подключает Bash-модуль и при - необходимости добавляет пользовательский каталог исполняемых файлов в - `PATH`, не дублируя подключение из полного dotfiles. -- [x] Параметр `--no-shell-setup` полностью отключает изменение настроек Bash. -- [x] После раскладки проверяется только локальная исправность CLI и - Bash-модуля; установка не выбирает режим, не устанавливает Xray, не читает - VLESS, не проверяет сеть и не требует существующего корректного конфига. -- [x] Финальный вывод кратко объясняет, как применить изменения в текущей - shell-сессии, предупреждает о постоянном пути исходного каталога и направляет - к настройке режимов в основной инструкции, не дублируя её. -- [x] Автоматические проверки покрывают оба способа создания ссылок, повторный - запуск, все виды конфликтов, защиту машинных файлов, настройку Bash и режим - без её изменения. -- [x] Инструкция пакета описывает новый установщик, зависимость симлинков от - постоянного пути исходного каталога и дальнейшую ручную настройку; общий - установщик dotfiles продолжает работать по прежнему контракту.