diff --git a/README.md b/README.md index d0b47e3..ab34a8a 100644 --- a/README.md +++ b/README.md @@ -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 На всех платформах фиксируются настройки для предсказуемого поведения: diff --git a/chezmoi/dot_config/powershell/profile.ps1 b/chezmoi/dot_config/powershell/profile.ps1 index 96a418a..d9002db 100644 --- a/chezmoi/dot_config/powershell/profile.ps1 +++ b/chezmoi/dot_config/powershell/profile.ps1 @@ -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' } } diff --git a/chezmoi/dot_local/bin/start-claude-proxy.ps1 b/chezmoi/dot_local/bin/start-claude-proxy.ps1 index 615f716..650b6e1 100644 --- a/chezmoi/dot_local/bin/start-claude-proxy.ps1 +++ b/chezmoi/dot_local/bin/start-claude-proxy.ps1 @@ -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" diff --git a/tests/test_proxy_env_sync.py b/tests/test_proxy_env_sync.py new file mode 100644 index 0000000..eda142f --- /dev/null +++ b/tests/test_proxy_env_sync.py @@ -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() diff --git a/vscode/.vscode-server/server-env-setup b/vscode/.vscode-server/server-env-setup index 73b7596..a2407d8 100755 --- a/vscode/.vscode-server/server-env-setup +++ b/vscode/.vscode-server/server-env-setup @@ -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"