fix(proxy-client): замечания слепого ревью

- state.json снова хранит адрес и порт: при смене конфига у работающего
  Xray status/env/run отказывают и советуют перезапустить start.
- Ошибки разбора ссылки называют файл профиля.
- Тесты: отказ start при чужой версии Xray, ключи REALITY не попадают
  в вывод, Xray узнаётся после замены файла; машинный конфиг проверяется
  через CLI; тест пути по умолчанию не зависит от доступности Hiddify.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ddadminandClaude Opus 5.5 committed 2026-09-28 10:38:35 -04:00
1 parent 5ccc75419d
commit 09f57aa1e7
3 files changed
+168 -56

No files matched your search

+27 -8
View File
@@ -215,7 +215,7 @@ def check_reachable(config):
def check_proxy(config):
if config.proxy_type == "xray":
require_supervised_xray()
require_supervised_xray(config)
check_reachable(config)
@@ -262,7 +262,7 @@ def write_private_json(path, value):
path.chmod(0o600)
def read_profile(path):
def load_profile(path):
# Права проверяем у конечного файла: у симлинка они ничего не значат.
resolved = path.expanduser().resolve(strict=True)
if resolved.stat().st_mode & 0o077:
@@ -277,7 +277,10 @@ def read_profile(path):
raise ProxyError(
"файл профиля {} должен содержать ровно одну непустую VLESS-ссылку".format(resolved)
)
return lines[0]
try:
return parse_vless(lines[0])
except ProxyError as error:
raise ProxyError("файл профиля {}: {}".format(resolved, error))
def parse_query(raw_query):
@@ -581,7 +584,8 @@ def read_state():
path = state_path()
try:
state = json.loads(path.read_text(encoding="utf-8"))
return {key: state[key] for key in ("supervisor_pid", "xray_pid", "xray_path")}
keys = ("supervisor_pid", "xray_pid", "xray_path", "address", "port")
return {key: state[key] for key in keys}
except FileNotFoundError:
raise ProxyError("управляемый Xray не запущен; выполните proxy-client start")
except (ValueError, TypeError, KeyError):
@@ -634,12 +638,21 @@ def managed_xray_running():
return False
def require_supervised_xray():
if not supervisor_running(managed_state()):
def require_supervised_xray(config):
state = managed_state()
if not supervisor_running(state):
raise ProxyError(
"Xray работает без proxy-client start (его процесс завершён); "
"выполните proxy-client stop"
)
if (config.address, config.port) != (state["address"], state["port"]):
raise ProxyError(
"Xray запущен на {}:{}, а {} указывает {}:{}; чтобы применить конфиг, "
"выполните proxy-client stop, затем proxy-client start".format(
state["address"], state["port"], config_home() / "config",
config.address, config.port,
)
)
def wait_until(condition, timeout):
@@ -809,7 +822,7 @@ def run_managed_xray(config, profile_file):
remove_state_files()
check_port_available(ipaddress.ip_address(config.address), config.port)
profile_path = Path(profile_file) if profile_file else config_home() / "default.url"
profile = parse_vless(read_profile(profile_path))
profile = load_profile(profile_path)
executable = resolve_xray()
check_xray_version(executable)
write_private_json(
@@ -826,7 +839,13 @@ def run_managed_xray(config, profile_file):
child = subprocess.Popen([executable, "run", "-c", str(generated_config_path())])
write_private_json(
state_path(),
{"supervisor_pid": os.getpid(), "xray_pid": child.pid, "xray_path": executable},
{
"supervisor_pid": os.getpid(),
"xray_pid": child.pid,
"xray_path": executable,
"address": config.address,
"port": config.port,
},
)
if not wait_until(
lambda: port_open(config.address, config.port), max(1.0, preflight_timeout())