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 уже запущен обычным способом, его нужно полностью закрыть перед
запуском через `Claude Proxy`. запуском через `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 ### Git baseline
На всех платформах фиксируются настройки для предсказуемого поведения: На всех платформах фиксируются настройки для предсказуемого поведения:
+5 -2
View File
@@ -75,12 +75,15 @@ function Get-PublicIp {
catch { return $null } 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 { function Get-ProxyEndpoint {
@{ @{
ProxyHost = 'hproxy.local' ProxyHost = 'hproxy.local'
Port = 12334 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 $ProxyPort = 12334
$HttpProxy = "http://${ProxyHostName}:${ProxyPort}" $HttpProxy = "http://${ProxyHostName}:${ProxyPort}"
$SocksProxy = "socks5://${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" $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" $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 HTTP_PROXY="$proxy_url"
export HTTPS_PROXY="$proxy_url" export HTTPS_PROXY="$proxy_url"
export ALL_PROXY="socks5://hproxy.local:12334" 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" export http_proxy="$HTTP_PROXY"