nikovpn.py: фиксы выбора протокола и сквозной проверки

- 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 при перезаливке бинарников
This commit is contained in:
Niko Marmeladkov 2026-08-26 11:51:54 +03:00
parent d4d00aa0e1
commit c887b77784
Signed by: Niko
GPG key ID: E3B955F9442D44E3

View file

@ -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("пустой список кандидатов")