feat(chatgpt): добавлен отдельный запуск через прокси

- Зачем:
  - приложение должно явно работать через прокси и сохранять настройку после обновлений.
- Что:
  - добавлены отдельный ярлык ChatGPT Proxy и запускатель на основе proxy-client.
  - исключено смешение прямого и проксированного режимов существующего процесса.
  - установка, документация и автоматические проверки дополнены новым Stow-пакетом.
- Проверка:
  - bash tests/test_chatgpt_proxy_launcher.sh.
  - bash tests/test_proxy_launchers.sh.
  - bash tests/test_install.sh.
This commit is contained in:
ddadmin committed 2026-08-17 01:09:59 +03:00
1 parent 039cf29b95
commit cb600bcaa8
8 files changed
+462 -3

No files matched your search

+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LAUNCHER="$REPO_ROOT/chatgpt/.local/bin/start-chatgpt-proxy"
DESKTOP_FILE="$REPO_ROOT/chatgpt/.local/share/applications/chatgpt-proxy.desktop"
SHELL_MODULE="$REPO_ROOT/proxy-client/.config/proxy-client/shell.bash"
TEST_ROOT="$(mktemp -d /tmp/chatgpt-proxy-launcher-test.XXXXXX)"
trap 'rm -rf "$TEST_ROOT"' EXIT
pass_count=0
fail() {
echo "FAIL: $*" >&2
exit 1
}
ok() {
pass_count=$((pass_count + 1))
echo "ok - $*"
}
assert_contains() {
local file="$1" expected="$2" message="$3"
grep -Fq -- "$expected" "$file" || fail "$message"
}
assert_not_exists() {
local path="$1" message="$2"
[ ! -e "$path" ] || fail "$message"
}
setup_fixture() {
local fixture="$1"
mkdir -p "$fixture/bin" "$fixture/home" "$fixture/proc" "$fixture/results"
cat > "$fixture/bin/pgrep" <<'EOF'
#!/usr/bin/env bash
if [ "${FAKE_PGREP_STATUS:-1}" = 0 ]; then
printf '%s\n' "${FAKE_PGREP_PIDS:-4242}"
fi
exit "${FAKE_PGREP_STATUS:-1}"
EOF
cat > "$fixture/bin/notify-send" <<'EOF'
#!/usr/bin/env bash
printf '<%s>\n' "$@" >> "$RESULTS_DIR/notifications"
EOF
cat > "$fixture/bin/proxy-client" <<'EOF'
#!/usr/bin/env bash
printf 'called\n' >> "$RESULTS_DIR/proxy-calls"
if [ "${FAKE_PROXY_FAIL:-0}" = 1 ]; then
echo 'тестовый прокси недоступен' >&2
exit 1
fi
[ "${1:-}" = env ] || exit 99
cat <<'ENV'
HTTP_PROXY=http://proxy.test:1234
HTTPS_PROXY=http://proxy.test:1234
ALL_PROXY=socks5://proxy.test:1234
NO_PROXY=proxy.test,localhost,127.0.0.1,::1,[::1],.example.test
http_proxy=http://proxy.test:1234
https_proxy=http://proxy.test:1234
all_proxy=socks5://proxy.test:1234
no_proxy=proxy.test,localhost,127.0.0.1,::1,[::1],.example.test
ENV
EOF
cat > "$fixture/bin/chatgpt" <<'EOF'
#!/usr/bin/env bash
printf '<%s>\n' "$@" > "$RESULTS_DIR/chatgpt-args"
printf 'HTTP_PROXY=%s\nALL_PROXY=%s\nNO_PROXY=%s\n' \
"$HTTP_PROXY" "$ALL_PROXY" "$NO_PROXY" > "$RESULTS_DIR/chatgpt-env"
EOF
chmod +x "$fixture/bin/pgrep" "$fixture/bin/notify-send" \
"$fixture/bin/proxy-client" "$fixture/bin/chatgpt"
}
write_process() {
local fixture="$1" pid="$2"
shift 2
mkdir -p "$fixture/proc/$pid"
printf '%s\0' "$@" > "$fixture/proc/$pid/cmdline"
}
run_launcher() {
local fixture="$1"
shift
HOME="$fixture/home" \
PATH="$fixture/bin:/usr/bin:/bin" \
RESULTS_DIR="$fixture/results" \
PROXY_CLIENT_BIN="$fixture/bin/proxy-client" \
PROXY_CLIENT_SHELL="$SHELL_MODULE" \
CHATGPT_BIN="$fixture/bin/chatgpt" \
PROC_ROOT="$fixture/proc" \
"$LAUNCHER" "$@"
}
# Успешный запуск передаёт и окружение, и явные параметры Chromium.
fixture="$TEST_ROOT/success"
setup_fixture "$fixture"
FAKE_PGREP_STATUS=1 run_launcher "$fixture" 'chatgpt://example/path'
assert_contains "$fixture/results/chatgpt-args" \
'<--proxy-server=http://proxy.test:1234>' \
'не передан сервер Chromium'
assert_contains "$fixture/results/chatgpt-args" \
'<--proxy-bypass-list=proxy.test;localhost;127.0.0.1;::1;[::1];*.example.test>' \
'неверно преобразован список обхода Chromium'
assert_contains "$fixture/results/chatgpt-args" '<chatgpt://example/path>' \
'пользовательский аргумент потерян'
assert_contains "$fixture/results/chatgpt-env" \
'ALL_PROXY=socks5://proxy.test:1234' \
'proxy-окружение не унаследовано'
assert_not_exists "$fixture/results/notifications" \
'успешный запуск показал ошибку'
ok 'успешный запуск применяет proxy-client к Chromium и окружению'
# Уже работающий через тот же прокси экземпляр получает повторный запрос:
# Electron поднимает его существующее окно вместо ошибки «уже запущен».
fixture="$TEST_ROOT/running-proxied"
setup_fixture "$fixture"
write_process "$fixture" 4242 \
'/usr/lib/chatgpt/ChatGPT --proxy-server=http://proxy.test:1234 --proxy-bypass-list=proxy.test;localhost;127.0.0.1;::1;[::1];*.example.test'
FAKE_PGREP_STATUS=0 run_launcher "$fixture" 'chatgpt://existing'
assert_contains "$fixture/results/chatgpt-args" '<chatgpt://existing>' \
'повторный запрос не передан прокси-экземпляру'
assert_not_exists "$fixture/results/notifications" \
'работающий прокси-экземпляр ошибочно объявлен конфликтом'
ok 'повторный прокси-запуск открывает существующий экземпляр'
# Прямой экземпляр блокируется с точной инструкцией: встроенный Restart
# сохранит прямой режим, поэтому приложение нужно завершить через системный лоток.
fixture="$TEST_ROOT/running-direct"
setup_fixture "$fixture"
write_process "$fixture" 4242 /usr/lib/chatgpt/ChatGPT
set +e
FAKE_PGREP_STATUS=0 run_launcher "$fixture" >/dev/null 2>&1
status=$?
set -e
[ "$status" -eq 1 ] || fail "ожидался код 1 при прямом ChatGPT, получен $status"
assert_not_exists "$fixture/results/chatgpt-args" \
'прямой ChatGPT получил повторный запуск'
assert_contains "$fixture/results/notifications" 'без прокси' \
'уведомление не различает прямой экземпляр'
assert_contains "$fixture/results/notifications" 'системном лотке' \
'нет инструкции для полного завершения прямого экземпляра'
ok 'прямой экземпляр блокируется с точной инструкцией'
# Недоступный прокси не приводит к прямому запуску.
fixture="$TEST_ROOT/unavailable"
setup_fixture "$fixture"
set +e
FAKE_PGREP_STATUS=1 FAKE_PROXY_FAIL=1 run_launcher "$fixture" >/dev/null 2>&1
status=$?
set -e
[ "$status" -eq 1 ] || fail "ожидался код 1 при ошибке прокси, получен $status"
assert_not_exists "$fixture/results/chatgpt-args" \
'ChatGPT запущен напрямую после ошибки прокси'
assert_contains "$fixture/results/notifications" 'proxy-client status' \
'нет диагностической подсказки при ошибке прокси'
ok 'ошибка прокси запрещает прямой запуск'
# Ярлык остаётся отдельным и не перехватывает штатные обработчики ChatGPT.
assert_contains "$DESKTOP_FILE" 'Name=ChatGPT Proxy' \
'неверное имя ярлыка'
assert_contains "$DESKTOP_FILE" 'Exec=start-chatgpt-proxy %U' \
'ярлык не вызывает управляемый запускатель'
if grep -q '^MimeType=' "$DESKTOP_FILE"; then
fail 'прокси-ярлык не должен перехватывать штатные MIME-обработчики'
fi
[ "$(basename "$DESKTOP_FILE")" = chatgpt-proxy.desktop ] \
|| fail 'прокси-ярлык подменяет системный chatgpt.desktop'
ok 'ChatGPT Proxy остаётся отдельным пользовательским ярлыком'
if command -v desktop-file-validate >/dev/null 2>&1; then
desktop-file-validate "$DESKTOP_FILE" >/dev/null \
|| fail 'desktop-file-validate нашёл ошибку'
ok 'desktop-файл корректен'
else
echo 'skip - desktop-file-validate не установлен'
fi
if command -v shellcheck >/dev/null 2>&1; then
shellcheck -s bash "$LAUNCHER" "$0" \
|| fail 'shellcheck нашёл проблемы'
ok 'shellcheck чист'
else
echo 'skip - shellcheck не установлен'
fi
echo "PASS: $pass_count проверок пройдено"
+10 -2
View File
@@ -36,9 +36,9 @@ setup_fixture() {
# дерева. Локальные данные инструментов внутри пакетов (vscode/.vscode-server,
# zed/.config/opencode и т.п.) весят гигабайты, переполняют /tmp и ломают
# stow конфликтами, поэтому в тестовое дерево не попадает.
git -C "$REPO_ROOT" ls-files -z --cached -- \
git -C "$REPO_ROOT" ls-files -z --cached --others --exclude-standard -- \
install.sh bash tmux vim zed kitty wezterm vscode agents opencode codex gemini \
claude proxy-client scripts \
claude proxy-client chatgpt scripts \
| tar -C "$REPO_ROOT" --null -T - -cf - \
| tar -xf - -C "$repo_root"
@@ -102,6 +102,14 @@ test_fresh_install_uses_home_target() {
[ -d "$home_dir/.local/bin" ] || fail "Expected ~/.local/bin directory"
[ ! -L "$home_dir/.local/bin" ] || fail "Expected ~/.local/bin to stay a real directory"
[ -x "$home_dir/.local/bin/proxy-client" ] || fail "Expected executable proxy-client CLI in HOME"
[ -x "$home_dir/.local/bin/start-chatgpt-proxy" ] || fail "Expected ChatGPT proxy launcher in HOME"
[ -f "$home_dir/.local/share/applications/chatgpt-proxy.desktop" ] \
|| fail "Expected ChatGPT Proxy desktop entry in HOME"
[ -d "$home_dir/.local/share" ] || fail "Expected ~/.local/share directory"
[ ! -L "$home_dir/.local/share" ] || fail "Expected ~/.local/share to stay a real directory"
[ -d "$home_dir/.local/share/applications" ] || fail "Expected applications directory"
[ ! -L "$home_dir/.local/share/applications" ] \
|| fail "Expected applications directory to stay real"
[ -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"
+1
View File
@@ -13,6 +13,7 @@ export PYTHONDONTWRITEBYTECODE=1
python3 -m unittest discover -s tests
bash tests/test_proxy_launchers.sh
bash tests/test_chatgpt_proxy_launcher.sh
bash tests/test_proxy_client_install.sh
bash tests/test_install.sh
uv run --python 3.8 --no-project python -m unittest discover -s tests