fix(proxy): выровнены списки исключений во всех профилях

- Зачем:
  - копии списка в профилях разошлись: в PowerShell и VS Code не хватало
    ::1 и [::1], из-за чего один и тот же хост то шёл мимо прокси, то нет.
- Что:
  - profile.ps1 и server-env-setup приведены к выдаче proxy-client.
  - добавлен tests/test_proxy_env_sync.py — сверяет каждую копию с эталоном
    DEFAULT_NO_PROXY_EXTRA и списки обхода Windows между собой.
  - в файлах-копиях и README указан эталон и тест, который ловит расхождение.
- Проверка:
  - python3 tests/test_proxy_env_sync.py (2 OK, на подложенном расхождении падает).
  - python3 tests/test_proxy_client.py (54 OK), bash tests/test_proxy_launchers.sh (8 PASS).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dmitry DementievandClaude Opus 5 committed 2026-07-26 22:33:36 +03:00
1 parent 0b300d2e26
commit 80db31aa57
5 files changed
+97 -3

No files matched your search

+6
View File
@@ -122,6 +122,12 @@ Windows не меняется. Если Codex уже запущен, его ну
Если Claude уже запущен обычным способом, его нужно полностью закрыть перед
запуском через `Claude Proxy`.
Список хостов, которые идут мимо прокси, продублирован в профилях Codex,
PowerShell и VS Code: одного общего файла у Windows и WSL нет. Эталон —
`DEFAULT_NO_PROXY_EXTRA` в `proxy-client/.local/bin/proxy-client`; меняя его,
поправь и копии. Расхождение ловит `tests/test_proxy_env_sync.py` — раньше
копии разъезжались, и один и тот же хост то шёл мимо прокси, то нет.
### Git baseline
На всех платформах фиксируются настройки для предсказуемого поведения:
+5 -2
View File
@@ -75,12 +75,15 @@ function Get-PublicIp {
catch { return $null }
}
# Единый источник правды для прокси-эндпоинта.
# Единый источник правды для прокси-эндпоинта в PowerShell.
# Список NoProxy обязан совпадать с тем, что выдаёт proxy-client
# (см. DEFAULT_NO_PROXY_EXTRA в proxy-client/.local/bin/proxy-client);
# расхождение ловит tests/test_proxy_env_sync.py.
function Get-ProxyEndpoint {
@{
ProxyHost = 'hproxy.local'
Port = 12334
NoProxy = 'hproxy.local,localhost,127.0.0.1,.concept-software.ru,dementev.space,.dementev.space'
NoProxy = 'hproxy.local,localhost,127.0.0.1,::1,[::1],.concept-software.ru,dementev.space,.dementev.space'
}
}
@@ -4,6 +4,8 @@ $ProxyHostName = 'hproxy.local'
$ProxyPort = 12334
$HttpProxy = "http://${ProxyHostName}:${ProxyPort}"
$SocksProxy = "socks5://${ProxyHostName}:${ProxyPort}"
# Список обязан совпадать с выдачей proxy-client (DEFAULT_NO_PROXY_EXTRA);
# расхождение ловит tests/test_proxy_env_sync.py.
$NoProxy = "${ProxyHostName},localhost,127.0.0.1,::1,[::1],.concept-software.ru,dementev.space,.dementev.space"
$ProxyBypassList = "${ProxyHostName};localhost;127.0.0.1;[::1];*.concept-software.ru;dementev.space;*.dementev.space"
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Список исключений прокси лежит в нескольких профилях (Bash, PowerShell,
Codex, VS Code). Раньше копии расходились, и один и тот же хост то шёл мимо
прокси, то нет. Тест сверяет каждую копию с эталоном из proxy-client."""
import re
import runpy
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
CLI = REPO_ROOT / "proxy-client/.local/bin/proxy-client"
# Где список продублирован: файл -> (регулярное выражение, начало строки,
# которое в файле подставляется переменной и потому не попадает в захват).
COPIES = (
("chezmoi/dot_codex/dot_env", re.compile(r"^NO_PROXY=(.+)$", re.M), ""),
("chezmoi/dot_codex/dot_env", re.compile(r"^no_proxy=(.+)$", re.M), ""),
(
"chezmoi/dot_config/powershell/profile.ps1",
re.compile(r"^\s*NoProxy\s*=\s*'(.+)'\s*$", re.M),
"",
),
(
"chezmoi/dot_local/bin/start-claude-proxy.ps1",
re.compile(r'^\$NoProxy = "\$\{ProxyHostName\},(.+)"\s*$', re.M),
"hproxy.local,",
),
(
"vscode/.vscode-server/server-env-setup",
re.compile(r'^\s*export NO_PROXY="(.+)"\s*$', re.M),
"",
),
)
# Списки обхода для Windows: тот же смысл, другой синтаксис (точка с запятой,
# поддомены звёздочкой), поэтому сверяем их только между собой.
BYPASS_FILES = (
"chezmoi/dot_local/bin/start-claude-proxy.ps1",
"chezmoi/dot_local/bin/start-codex-proxy.ps1",
)
BYPASS_RE = re.compile(r'^\$ProxyBypassList = "\$\{ProxyHostName\};(.+)"\s*$', re.M)
def expected_no_proxy():
"""Эталон — то, что proxy-client выдаёт в режиме по умолчанию."""
module = runpy.run_path(str(CLI))
items = [module["DEFAULT_ADDRESS"], "localhost", "127.0.0.1", "::1", "[::1]"]
items.extend(module["DEFAULT_NO_PROXY_EXTRA"])
return ",".join(dict.fromkeys(items))
class NoProxySyncTest(unittest.TestCase):
def test_copies_match_proxy_client(self):
expected = expected_no_proxy()
for relative_path, pattern, prefix in COPIES:
text = (REPO_ROOT / relative_path).read_text(encoding="utf-8")
match = pattern.search(text)
self.assertIsNotNone(
match, "{}: не найден список исключений".format(relative_path)
)
self.assertEqual(
expected,
prefix + match.group(1),
"{}: список разошёлся".format(relative_path),
)
def test_windows_bypass_lists_match_each_other(self):
values = []
for relative_path in BYPASS_FILES:
text = (REPO_ROOT / relative_path).read_text(encoding="utf-8")
match = BYPASS_RE.search(text)
self.assertIsNotNone(
match, "{}: не найден ProxyBypassList".format(relative_path)
)
values.append(match.group(1))
self.assertEqual(values[0], values[1], "ProxyBypassList разошёлся")
if __name__ == "__main__":
unittest.main()
+3 -1
View File
@@ -25,7 +25,9 @@ if getent hosts hproxy.local >/dev/null 2>&1; then
export HTTP_PROXY="$proxy_url"
export HTTPS_PROXY="$proxy_url"
export ALL_PROXY="socks5://hproxy.local:12334"
export NO_PROXY="hproxy.local,localhost,127.0.0.1,.concept-software.ru,dementev.space,.dementev.space"
# Список обязан совпадать с выдачей proxy-client (DEFAULT_NO_PROXY_EXTRA);
# расхождение ловит tests/test_proxy_env_sync.py.
export NO_PROXY="hproxy.local,localhost,127.0.0.1,::1,[::1],.concept-software.ru,dementev.space,.dementev.space"
# Дублируем в нижнем регистре для лучшей совместимости с инструментами
export http_proxy="$HTTP_PROXY"