- Зачем: Kimi Desktop по умолчанию ходит в сеть напрямую, а прокси нужен как для Codex и Claude. - Что: start-kimi-proxy.ps1 запускает Kimi.exe с параметрами Electron --proxy-server и --proxy-bypass-list, run-скрипт chezmoi создаёт ярлык Kimi Proxy в меню "Пуск"; тест синхронизации списков обхода обобщён на все копии. - Проверка: синтаксис скриптов проверен парсером PowerShell, tests/test_proxy_env_sync.py зелёный, запуск через ярлык проверен вручную.
85 lines
3.4 KiB
Python
85 lines
3.4 KiB
Python
#!/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",
|
|
"chezmoi/dot_local/bin/start-kimi-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(
|
|
len(set(values)), 1, "ProxyBypassList разошёлся между файлами"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|