refactor(proxy-client): введён явный контекст запуска
Выбор путей, прав профиля, вывода Xray и подсказки остановки перенесён из глобальных переменных в неизменяемый RuntimeContext. Команды и управление процессом получают контекст явно. Проверка: прежние 72 теста без изменений прошли на Python 3.8 и 3.14; оба shell-набора прошли.
This commit is contained in:
1 parent
384b4f215f
commit
45938cde98
2 files changed
+118
-111
No files matched your search
@@ -115,7 +115,10 @@ Xray нужен другой порт. Установка службы не ме
|
|||||||
|
|
||||||
Применяем `codebase-design`: выбор профиля и все его пути должны определяться
|
Применяем `codebase-design`: выбор профиля и все его пути должны определяться
|
||||||
в одном месте, чтобы команды не повторяли правила размещения файлов.
|
в одном месте, чтобы команды не повторяли правила размещения файлов.
|
||||||
Управление процессом использует уже выбранные настройки и расположение файлов.
|
Пути, правила доступа к профилю, вывод Xray и подсказка остановки собраны
|
||||||
|
в неизменяемый контекст запуска. Он создаётся при выборе пользовательского
|
||||||
|
или системного режима и явно передаётся функциям. Управление процессом
|
||||||
|
использует готовые значения, без глобального переключателя режима.
|
||||||
Системная установка скрывает размещение, права и взаимодействие с systemd
|
Системная установка скрывает размещение, права и взаимодействие с systemd
|
||||||
за небольшим интерфейсом; не копирует разбор VLESS и управление Xray.
|
за небольшим интерфейсом; не копирует разбор VLESS и управление Xray.
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import urllib.request
|
|||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
from urllib.parse import parse_qsl, unquote, urlsplit
|
from urllib.parse import parse_qsl, unquote, urlsplit
|
||||||
|
|
||||||
|
|
||||||
@@ -120,36 +122,56 @@ class VlessProfile:
|
|||||||
return self.query["security"]
|
return self.query["security"]
|
||||||
|
|
||||||
|
|
||||||
SELECTED_PROFILE = "default"
|
@dataclass(frozen=True)
|
||||||
SYSTEM_MODE = False
|
class RuntimeContext:
|
||||||
SYSTEM_CONFIG = Path("/etc/proxy-client")
|
"""Размещение и правила выбранного запуска; создаётся до выполнения команды."""
|
||||||
SYSTEM_RUNTIME = Path("/run/proxy-client")
|
|
||||||
SYSTEM_XRAY = Path("/usr/local/lib/proxy-client/xray")
|
config_dir: Path
|
||||||
|
state_dir: Path
|
||||||
|
xray_path: Optional[Path]
|
||||||
|
profile_forbidden_mode: int
|
||||||
|
profile_repair: str
|
||||||
|
xray_output: Optional[int]
|
||||||
|
stop_hint: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_path(self):
|
||||||
|
return self.state_dir / "state.json"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generated_config_path(self):
|
||||||
|
return self.state_dir / "client.json"
|
||||||
|
|
||||||
|
|
||||||
def config_home():
|
def user_context(profile="default"):
|
||||||
if SYSTEM_MODE:
|
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,63}", profile):
|
||||||
return SYSTEM_CONFIG
|
raise ProxyError("недопустимое имя профиля")
|
||||||
root = os.environ.get("XDG_CONFIG_HOME")
|
config_root = os.environ.get("XDG_CONFIG_HOME")
|
||||||
base = (Path(root).expanduser() if root else Path.home() / ".config") / "proxy-client"
|
state_root = os.environ.get("XDG_STATE_HOME")
|
||||||
return base if SELECTED_PROFILE == "default" else base / "profiles" / SELECTED_PROFILE
|
config_dir = (Path(config_root).expanduser() if config_root else Path.home() / ".config") / "proxy-client"
|
||||||
|
state_dir = (Path(state_root).expanduser() if state_root else Path.home() / ".local/state") / "proxy-client"
|
||||||
|
stop_hint = "Ctrl+C или proxy-client stop"
|
||||||
|
if profile != "default":
|
||||||
|
config_dir = config_dir / "profiles" / profile
|
||||||
|
state_dir = state_dir / "profiles" / profile
|
||||||
|
if not (config_dir / "config").is_file():
|
||||||
|
raise ProxyError("неизвестный профиль: {}".format(profile))
|
||||||
|
stop_hint = "Ctrl+C или proxy-client --profile {} stop".format(profile)
|
||||||
|
return RuntimeContext(config_dir, state_dir, None, 0o077,
|
||||||
|
"chmod 600 {0}", None, stop_hint)
|
||||||
|
|
||||||
|
|
||||||
def state_home():
|
def service_context(arguments):
|
||||||
if SYSTEM_MODE:
|
config_dir = Path(arguments.config_dir)
|
||||||
return SYSTEM_RUNTIME
|
state_dir = Path(arguments.runtime_dir)
|
||||||
root = os.environ.get("XDG_STATE_HOME")
|
xray_path = Path(arguments.xray)
|
||||||
base = Path(root).expanduser() if root else Path.home() / ".local/state"
|
if not all(path.is_absolute() for path in (config_dir, state_dir, xray_path)):
|
||||||
base = base / "proxy-client"
|
raise ProxyError("системные пути должны быть абсолютными")
|
||||||
return base if SELECTED_PROFILE == "default" else base / "profiles" / SELECTED_PROFILE
|
if not (config_dir / "config").is_file():
|
||||||
|
raise ProxyError("нет системного config")
|
||||||
|
return RuntimeContext(config_dir, state_dir, xray_path, 0o037,
|
||||||
def state_path():
|
"sudo chown root:proxy-client {0}; sudo chmod 640 {0}",
|
||||||
return state_home() / "state.json"
|
subprocess.DEVNULL, "sudo systemctl stop proxy-client")
|
||||||
|
|
||||||
|
|
||||||
def generated_config_path():
|
|
||||||
return state_home() / "client.json"
|
|
||||||
|
|
||||||
|
|
||||||
def validate_address(value):
|
def validate_address(value):
|
||||||
@@ -170,8 +192,8 @@ def validate_address(value):
|
|||||||
raise ProxyError("поле address должно быть IP-адресом или простым ASCII-именем")
|
raise ProxyError("поле address должно быть IP-адресом или простым ASCII-именем")
|
||||||
|
|
||||||
|
|
||||||
def load_config():
|
def load_config(context):
|
||||||
path = config_home() / "config"
|
path = context.config_dir / "config"
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
if path.is_symlink():
|
if path.is_symlink():
|
||||||
raise ProxyError("машинный конфиг {} ссылается на отсутствующий файл".format(path))
|
raise ProxyError("машинный конфиг {} ссылается на отсутствующий файл".format(path))
|
||||||
@@ -238,9 +260,9 @@ def check_reachable(config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_proxy(config):
|
def check_proxy(config, context):
|
||||||
if config.proxy_type == "xray":
|
if config.proxy_type == "xray":
|
||||||
require_supervised_xray(config)
|
require_supervised_xray(config, context)
|
||||||
check_reachable(config)
|
check_reachable(config)
|
||||||
|
|
||||||
|
|
||||||
@@ -287,13 +309,12 @@ def write_private_json(path, value):
|
|||||||
path.chmod(0o600)
|
path.chmod(0o600)
|
||||||
|
|
||||||
|
|
||||||
def load_profile(path):
|
def load_profile(path, context):
|
||||||
# Права проверяем у конечного файла: у симлинка они ничего не значат.
|
# Права проверяем у конечного файла: у симлинка они ничего не значат.
|
||||||
resolved = path.expanduser().resolve(strict=True)
|
resolved = path.expanduser().resolve(strict=True)
|
||||||
if resolved.stat().st_mode & (0o037 if SYSTEM_MODE else 0o077):
|
if resolved.stat().st_mode & context.profile_forbidden_mode:
|
||||||
target = shlex.quote(str(resolved))
|
target = shlex.quote(str(resolved))
|
||||||
repair = ("sudo chown root:proxy-client {0}; sudo chmod 640 {0}".format(target)
|
repair = context.profile_repair.format(target)
|
||||||
if SYSTEM_MODE else "chmod 600 {}".format(target))
|
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"файл профиля {} доступен другим пользователям; выполните: {}".format(
|
"файл профиля {} доступен другим пользователям; выполните: {}".format(
|
||||||
resolved, repair
|
resolved, repair
|
||||||
@@ -547,8 +568,8 @@ def build_xray_config(profile, local_address, local_port):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def resolve_xray():
|
def resolve_xray(context):
|
||||||
found = str(SYSTEM_XRAY) if SYSTEM_MODE else shutil.which("xray")
|
found = str(context.xray_path) if context.xray_path is not None else shutil.which("xray")
|
||||||
if found is None:
|
if found is None:
|
||||||
raise ProxyError("xray не найден в PATH; установите его: proxy-client install")
|
raise ProxyError("xray не найден в PATH; установите его: proxy-client install")
|
||||||
return str(Path(found).resolve(strict=True))
|
return str(Path(found).resolve(strict=True))
|
||||||
@@ -623,7 +644,8 @@ def install_xray(destination, downloader):
|
|||||||
shutil.rmtree(str(staging), ignore_errors=True)
|
shutil.rmtree(str(staging), ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def command_install(replace=False, downloader=download_file):
|
def command_install(replace=False, downloader=download_file, context=None):
|
||||||
|
context = context if context is not None else user_context()
|
||||||
destination = Path.home() / ".local/bin/xray"
|
destination = Path.home() / ".local/bin/xray"
|
||||||
if destination.is_dir():
|
if destination.is_dir():
|
||||||
raise ProxyError("назначение {} является каталогом".format(destination))
|
raise ProxyError("назначение {} является каталогом".format(destination))
|
||||||
@@ -640,7 +662,7 @@ def command_install(replace=False, downloader=download_file):
|
|||||||
else:
|
else:
|
||||||
install_xray(destination, downloader)
|
install_xray(destination, downloader)
|
||||||
print("Xray {} установлен: {}".format(XRAY_VERSION, destination))
|
print("Xray {} установлен: {}".format(XRAY_VERSION, destination))
|
||||||
if managed_xray_running():
|
if managed_xray_running(context):
|
||||||
print("Работающий Xray остался прежним; перезапустите proxy-client start")
|
print("Работающий Xray остался прежним; перезапустите proxy-client start")
|
||||||
found = shutil.which("xray")
|
found = shutil.which("xray")
|
||||||
if found is None or Path(found).resolve() != destination.resolve():
|
if found is None or Path(found).resolve() != destination.resolve():
|
||||||
@@ -667,8 +689,8 @@ def run_xray_test(path, config_path):
|
|||||||
raise ProxyError("созданная конфигурация не прошла xray run -test")
|
raise ProxyError("созданная конфигурация не прошла xray run -test")
|
||||||
|
|
||||||
|
|
||||||
def read_state():
|
def read_state(context):
|
||||||
path = state_path()
|
path = context.state_path
|
||||||
try:
|
try:
|
||||||
state = json.loads(path.read_text(encoding="utf-8"))
|
state = json.loads(path.read_text(encoding="utf-8"))
|
||||||
keys = ("supervisor_pid", "xray_pid", "xray_path", "address", "port")
|
keys = ("supervisor_pid", "xray_pid", "xray_path", "address", "port")
|
||||||
@@ -701,32 +723,32 @@ def supervisor_running(state):
|
|||||||
return b"proxy-client" in cmdline
|
return b"proxy-client" in cmdline
|
||||||
|
|
||||||
|
|
||||||
def remove_state_files():
|
def remove_state_files(context):
|
||||||
for path in (state_path(), generated_config_path()):
|
for path in (context.state_path, context.generated_config_path):
|
||||||
try:
|
try:
|
||||||
path.unlink()
|
path.unlink()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def managed_state():
|
def managed_state(context):
|
||||||
"""Состояние работающего Xray; устаревшие служебные файлы удаляет."""
|
"""Состояние работающего Xray; устаревшие служебные файлы удаляет."""
|
||||||
state = read_state()
|
state = read_state(context)
|
||||||
if not xray_running(state):
|
if not xray_running(state):
|
||||||
remove_state_files()
|
remove_state_files(context)
|
||||||
raise ProxyError("управляемый Xray не запущен; устаревшие служебные файлы удалены")
|
raise ProxyError("управляемый Xray не запущен; устаревшие служебные файлы удалены")
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
def managed_xray_running():
|
def managed_xray_running(context):
|
||||||
try:
|
try:
|
||||||
return xray_running(read_state())
|
return xray_running(read_state(context))
|
||||||
except ProxyError:
|
except ProxyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def require_supervised_xray(config):
|
def require_supervised_xray(config, context):
|
||||||
state = managed_state()
|
state = managed_state(context)
|
||||||
if not supervisor_running(state):
|
if not supervisor_running(state):
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"Xray работает без proxy-client start (его процесс завершён); "
|
"Xray работает без proxy-client start (его процесс завершён); "
|
||||||
@@ -736,7 +758,7 @@ def require_supervised_xray(config):
|
|||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"Xray запущен на {}:{}, а {} указывает {}:{}; чтобы применить конфиг, "
|
"Xray запущен на {}:{}, а {} указывает {}:{}; чтобы применить конфиг, "
|
||||||
"выполните proxy-client stop, затем proxy-client start".format(
|
"выполните proxy-client stop, затем proxy-client start".format(
|
||||||
state["address"], state["port"], config_home() / "config",
|
state["address"], state["port"], context.config_dir / "config",
|
||||||
config.address, config.port,
|
config.address, config.port,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -772,9 +794,9 @@ def check_port_available(address, port):
|
|||||||
raise ProxyError("локальный порт {} занят или недоступен".format(port))
|
raise ProxyError("локальный порт {} занят или недоступен".format(port))
|
||||||
|
|
||||||
|
|
||||||
def acquire_start_lock():
|
def acquire_start_lock(context):
|
||||||
private_directory(state_home())
|
private_directory(context.state_dir)
|
||||||
lock = (state_home() / "lock").open("a")
|
lock = (context.state_dir / "lock").open("a")
|
||||||
try:
|
try:
|
||||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
except BlockingIOError:
|
except BlockingIOError:
|
||||||
@@ -848,8 +870,8 @@ def interrupt(_signum, _frame):
|
|||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
def command_status(config):
|
def command_status(config, context):
|
||||||
check_proxy(config)
|
check_proxy(config, context)
|
||||||
if config.proxy_type == "external":
|
if config.proxy_type == "external":
|
||||||
print("{} (external) доступен по адресу {}:{}".format(config.name, config.address, config.port))
|
print("{} (external) доступен по адресу {}:{}".format(config.name, config.address, config.port))
|
||||||
else:
|
else:
|
||||||
@@ -857,21 +879,21 @@ def command_status(config):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_env(config):
|
def command_env(config, context):
|
||||||
check_proxy(config)
|
check_proxy(config, context)
|
||||||
lines = ["{}={}".format(key, value) for key, value in environment_values(config)]
|
lines = ["{}={}".format(key, value) for key, value in environment_values(config)]
|
||||||
sys.stdout.write("\n".join(lines) + "\n")
|
sys.stdout.write("\n".join(lines) + "\n")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_run(config, remainder):
|
def command_run(config, remainder, context):
|
||||||
if not remainder or remainder[0] != "--" or len(remainder) == 1:
|
if not remainder or remainder[0] != "--" or len(remainder) == 1:
|
||||||
raise ProxyError("использование: proxy-client run -- COMMAND ARG...")
|
raise ProxyError("использование: proxy-client run -- COMMAND ARG...")
|
||||||
command = remainder[1:]
|
command = remainder[1:]
|
||||||
if shutil.which(command[0]) is None:
|
if shutil.which(command[0]) is None:
|
||||||
sys.stderr.write("Ошибка: команда не найдена в PATH: {}\n".format(command[0]))
|
sys.stderr.write("Ошибка: команда не найдена в PATH: {}\n".format(command[0]))
|
||||||
return 127
|
return 127
|
||||||
check_proxy(config)
|
check_proxy(config, context)
|
||||||
child_environment = os.environ.copy()
|
child_environment = os.environ.copy()
|
||||||
child_environment.update(dict(environment_values(config)))
|
child_environment.update(dict(environment_values(config)))
|
||||||
try:
|
try:
|
||||||
@@ -882,10 +904,10 @@ def command_run(config, remainder):
|
|||||||
return exit_code(completed.returncode)
|
return exit_code(completed.returncode)
|
||||||
|
|
||||||
|
|
||||||
def command_start(config, profile_file):
|
def command_start(config, profile_file, context):
|
||||||
if config.proxy_type != "xray":
|
if config.proxy_type != "xray":
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"start доступен только для type=xray; измените {}".format(config_home() / "config")
|
"start доступен только для type=xray; измените {}".format(context.config_dir / "config")
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
loopback = ipaddress.ip_address(config.address).is_loopback
|
loopback = ipaddress.ip_address(config.address).is_loopback
|
||||||
@@ -893,43 +915,43 @@ def command_start(config, profile_file):
|
|||||||
loopback = False
|
loopback = False
|
||||||
if not loopback:
|
if not loopback:
|
||||||
raise ProxyError("для type=xray поле address должно быть loopback-адресом, например 127.0.0.1")
|
raise ProxyError("для type=xray поле address должно быть loopback-адресом, например 127.0.0.1")
|
||||||
lock = acquire_start_lock()
|
lock = acquire_start_lock(context)
|
||||||
try:
|
try:
|
||||||
return run_managed_xray(config, profile_file)
|
return run_managed_xray(config, profile_file, context)
|
||||||
finally:
|
finally:
|
||||||
lock.close()
|
lock.close()
|
||||||
|
|
||||||
|
|
||||||
def run_managed_xray(config, profile_file):
|
def run_managed_xray(config, profile_file, context):
|
||||||
if state_path().exists():
|
if context.state_path.exists():
|
||||||
if xray_running(read_state()):
|
if xray_running(read_state(context)):
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
"Xray остался от прошлого proxy-client start; сначала выполните proxy-client stop"
|
"Xray остался от прошлого proxy-client start; сначала выполните proxy-client stop"
|
||||||
)
|
)
|
||||||
remove_state_files()
|
remove_state_files(context)
|
||||||
check_port_available(ipaddress.ip_address(config.address), config.port)
|
check_port_available(ipaddress.ip_address(config.address), config.port)
|
||||||
profile_path = Path(profile_file) if profile_file else config_home() / "default.url"
|
profile_path = Path(profile_file) if profile_file else context.config_dir / "default.url"
|
||||||
profile = load_profile(profile_path)
|
profile = load_profile(profile_path, context)
|
||||||
executable = resolve_xray()
|
executable = resolve_xray(context)
|
||||||
check_xray_version(executable)
|
check_xray_version(executable)
|
||||||
write_private_json(
|
write_private_json(
|
||||||
generated_config_path(), build_xray_config(profile, config.address, config.port)
|
context.generated_config_path, build_xray_config(profile, config.address, config.port)
|
||||||
)
|
)
|
||||||
child = None
|
child = None
|
||||||
try:
|
try:
|
||||||
run_xray_test(executable, generated_config_path())
|
run_xray_test(executable, context.generated_config_path)
|
||||||
print("Xray: {} ({})".format(executable, XRAY_VERSION))
|
print("Xray: {} ({})".format(executable, XRAY_VERSION))
|
||||||
print_summary(profile, config.port)
|
print_summary(profile, config.port)
|
||||||
# SIGTERM (от proxy-client stop) и SIGHUP обрабатываем как Ctrl+C.
|
# SIGTERM (от proxy-client stop) и SIGHUP обрабатываем как Ctrl+C.
|
||||||
signal.signal(signal.SIGTERM, interrupt)
|
signal.signal(signal.SIGTERM, interrupt)
|
||||||
signal.signal(signal.SIGHUP, interrupt)
|
signal.signal(signal.SIGHUP, interrupt)
|
||||||
child = subprocess.Popen(
|
child = subprocess.Popen(
|
||||||
[executable, "run", "-c", str(generated_config_path())],
|
[executable, "run", "-c", str(context.generated_config_path)],
|
||||||
stdout=subprocess.DEVNULL if SYSTEM_MODE else None,
|
stdout=context.xray_output,
|
||||||
stderr=subprocess.DEVNULL if SYSTEM_MODE else None,
|
stderr=context.xray_output,
|
||||||
)
|
)
|
||||||
write_private_json(
|
write_private_json(
|
||||||
state_path(),
|
context.state_path,
|
||||||
{
|
{
|
||||||
"supervisor_pid": os.getpid(),
|
"supervisor_pid": os.getpid(),
|
||||||
"xray_pid": child.pid,
|
"xray_pid": child.pid,
|
||||||
@@ -943,15 +965,9 @@ def run_managed_xray(config, profile_file):
|
|||||||
):
|
):
|
||||||
raise ProxyError("Xray не открыл локальный порт {}".format(config.port))
|
raise ProxyError("Xray не открыл локальный порт {}".format(config.port))
|
||||||
diagnose_ip(config)
|
diagnose_ip(config)
|
||||||
if SYSTEM_MODE:
|
|
||||||
stop_hint = "sudo systemctl stop proxy-client"
|
|
||||||
elif SELECTED_PROFILE == "default":
|
|
||||||
stop_hint = "Ctrl+C или proxy-client stop"
|
|
||||||
else:
|
|
||||||
stop_hint = "Ctrl+C или proxy-client --profile {} stop".format(SELECTED_PROFILE)
|
|
||||||
print(
|
print(
|
||||||
"Управляемый Xray работает на {}:{}; остановка: {}".format(
|
"Управляемый Xray работает на {}:{}; остановка: {}".format(
|
||||||
config.address, config.port, stop_hint
|
config.address, config.port, context.stop_hint
|
||||||
),
|
),
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
@@ -966,11 +982,11 @@ def run_managed_xray(config, profile_file):
|
|||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
child.kill()
|
child.kill()
|
||||||
child.wait()
|
child.wait()
|
||||||
remove_state_files()
|
remove_state_files(context)
|
||||||
|
|
||||||
|
|
||||||
def command_stop():
|
def command_stop(context):
|
||||||
state = managed_state()
|
state = managed_state(context)
|
||||||
# Живой супервизор сам остановит Xray; иначе Xray гасим напрямую.
|
# Живой супервизор сам остановит Xray; иначе Xray гасим напрямую.
|
||||||
running_supervisor = supervisor_running(state)
|
running_supervisor = supervisor_running(state)
|
||||||
target = state["supervisor_pid"] if running_supervisor else state["xray_pid"]
|
target = state["supervisor_pid"] if running_supervisor else state["xray_pid"]
|
||||||
@@ -984,7 +1000,7 @@ def command_stop():
|
|||||||
os.kill(state["xray_pid"], signal.SIGKILL)
|
os.kill(state["xray_pid"], signal.SIGKILL)
|
||||||
if not wait_until(stopped, STOP_TIMEOUT):
|
if not wait_until(stopped, STOP_TIMEOUT):
|
||||||
raise ProxyError("управляемый Xray не остановился за {} с".format(2 * STOP_TIMEOUT + 1))
|
raise ProxyError("управляемый Xray не остановился за {} с".format(2 * STOP_TIMEOUT + 1))
|
||||||
remove_state_files()
|
remove_state_files(context)
|
||||||
print("Управляемый Xray остановлен")
|
print("Управляемый Xray остановлен")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -1087,7 +1103,7 @@ def service_install(args):
|
|||||||
if config.proxy_type != "xray" or not ipaddress.ip_address(config.address).is_loopback:
|
if config.proxy_type != "xray" or not ipaddress.ip_address(config.address).is_loopback:
|
||||||
raise ProxyError("службе нужен type=xray и loopback-адрес")
|
raise ProxyError("службе нужен type=xray и loopback-адрес")
|
||||||
if not (settings / "default.url").exists():
|
if not (settings / "default.url").exists():
|
||||||
load_profile(source / "default.url")
|
load_profile(source / "default.url", user_context())
|
||||||
check_xray_version(str(xray))
|
check_xray_version(str(xray))
|
||||||
files = {
|
files = {
|
||||||
"usr/local/lib/proxy-client/proxy-client": (Path(__file__).read_bytes(), 0o755),
|
"usr/local/lib/proxy-client/proxy-client": (Path(__file__).read_bytes(), 0o755),
|
||||||
@@ -1189,7 +1205,6 @@ def build_parser():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global SELECTED_PROFILE, SYSTEM_MODE, SYSTEM_CONFIG, SYSTEM_RUNTIME, SYSTEM_XRAY
|
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
arguments = parser.parse_args()
|
arguments = parser.parse_args()
|
||||||
if arguments.subcommand is None:
|
if arguments.subcommand is None:
|
||||||
@@ -1203,33 +1218,22 @@ def main():
|
|||||||
raise ProxyError("service не использует пользовательские профили")
|
raise ProxyError("service не использует пользовательские профили")
|
||||||
if arguments.service_action == "install":
|
if arguments.service_action == "install":
|
||||||
return service_install(arguments)
|
return service_install(arguments)
|
||||||
SYSTEM_MODE = True
|
context = service_context(arguments)
|
||||||
SYSTEM_CONFIG = Path(arguments.config_dir)
|
return command_start(load_config(context), None, context)
|
||||||
SYSTEM_RUNTIME = Path(arguments.runtime_dir)
|
context = user_context(arguments.profile)
|
||||||
SYSTEM_XRAY = Path(arguments.xray)
|
|
||||||
if not all(p.is_absolute() for p in (SYSTEM_CONFIG, SYSTEM_RUNTIME, SYSTEM_XRAY)):
|
|
||||||
raise ProxyError("системные пути должны быть абсолютными")
|
|
||||||
if not (SYSTEM_CONFIG / "config").is_file():
|
|
||||||
raise ProxyError("нет системного config")
|
|
||||||
return command_start(load_config(), None)
|
|
||||||
SELECTED_PROFILE = arguments.profile
|
|
||||||
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,63}", SELECTED_PROFILE):
|
|
||||||
raise ProxyError("недопустимое имя профиля")
|
|
||||||
if SELECTED_PROFILE != "default" and not (config_home() / "config").is_file():
|
|
||||||
raise ProxyError("неизвестный профиль: {}".format(SELECTED_PROFILE))
|
|
||||||
if arguments.subcommand == "install":
|
if arguments.subcommand == "install":
|
||||||
return command_install(arguments.replace)
|
return command_install(arguments.replace, context=context)
|
||||||
if arguments.subcommand == "stop":
|
if arguments.subcommand == "stop":
|
||||||
return command_stop()
|
return command_stop(context)
|
||||||
config = load_config()
|
config = load_config(context)
|
||||||
if arguments.subcommand == "status":
|
if arguments.subcommand == "status":
|
||||||
return command_status(config)
|
return command_status(config, context)
|
||||||
if arguments.subcommand == "env":
|
if arguments.subcommand == "env":
|
||||||
return command_env(config)
|
return command_env(config, context)
|
||||||
if arguments.subcommand == "run":
|
if arguments.subcommand == "run":
|
||||||
return command_run(config, arguments.arguments)
|
return command_run(config, arguments.arguments, context)
|
||||||
if arguments.subcommand == "start":
|
if arguments.subcommand == "start":
|
||||||
return command_start(config, arguments.profile_file)
|
return command_start(config, arguments.profile_file, context)
|
||||||
except (ProxyError, OSError, ValueError, subprocess.CalledProcessError) as error:
|
except (ProxyError, OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||||
sys.stderr.write("Ошибка: {}\n".format(error))
|
sys.stderr.write("Ошибка: {}\n".format(error))
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
Reference in new issue
Block a user