From c887b777840da5f0c739cd8a5c036d2293e16847 Mon Sep 17 00:00:00 2001 From: Niko Marmeladkov Date: Wed, 26 Aug 2026 11:51:54 +0300 Subject: [PATCH] =?UTF-8?q?nikovpn.py:=20=D1=84=D0=B8=D0=BA=D1=81=D1=8B=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=BF=D1=80=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=B8=20=D1=81=D0=BA=D0=B2?= =?UTF-8?q?=D0=BE=D0=B7=D0=BD=D0=BE=D0=B9=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - choose_candidates: кандидату назначается его собственный протокол из scheme, а не общий выбранный — иначе TUIC/Naive-ссылки собирались как hysteria2 и не работали - verify_tunnel: SSH-форвард строится через rt.ssh_args() — теперь передаются --identity/sshpass (раньше проверка падала с нестандартным именем ключа) - BatchMode=yes только без пароля (с sshpass он ломал авторизацию) - stop_service: добивает sing-box/ipt2socks вне procd — иначе Text file busy при перезаливке бинарников --- nikovpn.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/nikovpn.py b/nikovpn.py index cf48b17..7266eb2 100644 --- a/nikovpn.py +++ b/nikovpn.py @@ -65,7 +65,7 @@ class Router: def __init__(self, host, user="root", port=22, password=None, key=None): self.host, self.user, self.port = host, user, port opts = ["-o", "StrictHostKeyChecking=accept-new", - "-o", "ConnectTimeout=10", "-o", "BatchMode=yes"] + "-o", "ConnectTimeout=10"] pre = [] if password is not None: if not shutil_which("sshpass"): @@ -73,10 +73,16 @@ class Router: pre = ["sshpass", "-p", password] opts += ["-o", "PreferredAuthentications=password", "-o", "PubkeyAuthentication=no"] + else: + opts += ["-o", "BatchMode=yes"] if key: opts += ["-i", str(key)] + self.pre, self.opts = pre, opts self.base = pre + ["ssh", "-p", str(port)] + opts + [f"{user}@{host}"] + def ssh_args(self, *extra): + return self.pre + ["ssh", "-p", str(self.port)] + self.opts + list(extra) + def run(self, cmd, timeout=180, input_bytes=None, check=False): r = subprocess.run(self.base + [cmd], capture_output=True, timeout=timeout, @@ -287,7 +293,7 @@ def choose_candidates(rt: Router, links_text: bytes, naive_ok: bool) -> list: rest = [l for l in group if not host_alive(l)] picked += alive + rest for l in picked: - l["protocol"] = proto + l["protocol"] = l["scheme"].split("+")[0] l["sni"] = qp(l, "sni") or l["host"] return picked @@ -721,6 +727,16 @@ def human(n): def stop_service(rt: Router): if rt.exists("/etc/init.d/nikovpn"): rt.run("/etc/init.d/nikovpn stop >/dev/null 2>&1") + # процессы, запущенные в обход procd, держат бинарники (Text file busy) + rt.run("kill $(pgrep sing-box) 2>/dev/null; " + "kill $(pgrep ipt2socks) 2>/dev/null") + for _ in range(5): + _, out, _ = rt.run("pgrep sing-box; pgrep ipt2socks") + if not out.strip(): + break + rt.run("kill -9 $(pgrep sing-box) 2>/dev/null; " + "kill -9 $(pgrep ipt2socks) 2>/dev/null") + time.sleep(1) def verify_tunnel(rt: Router, local_port: int) -> str | None: @@ -733,9 +749,9 @@ def verify_tunnel(rt: Router, local_port: int) -> str | None: t.bind(("127.0.0.1", 0)) local_port = t.getsockname()[1] fwd = subprocess.Popen( - ["ssh", "-p", str(rt.port), "-o", "StrictHostKeyChecking=accept-new", - "-o", "BatchMode=yes", "-f", "-N", "-L", - f"{local_port}:127.0.0.1:{SOCKS_PORT}", f"{rt.user}@{rt.host}"], + rt.ssh_args("-f", "-N", "-L", + f"{local_port}:127.0.0.1:{SOCKS_PORT}", + f"{rt.user}@{rt.host}"), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(1.5) r = subprocess.run( @@ -832,7 +848,7 @@ def do_install(rt: Router, args, update=False): if code != 200 or not links.strip(): die(f"не удалось получить список серверов (код {code})") - cands = choose_candidates(rt, links, naive_ok) + cands = choose_candidates(rt, links, bool(naive_ok)) if not cands: die("пустой список кандидатов")