From 269017c50a5c6b1bc9eaa8d9962a2c683374f41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Tue, 28 Apr 2026 20:25:25 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BE=D1=87?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=BA=D0=B0=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81?= =?UTF-8?q?=D1=88=D0=B5=D0=B3=D0=BE=20=D1=82=D1=83=D0=BD=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BD=D0=B0=20VPS=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При повторном нажатии «⇄ Тоннель» — если порт уже занят старым sshd-соединением — теперь автоматически убиваем его через fuser/ss+kill на VPS до того как роутер запустит autossh. --- server/main.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/server/main.py b/server/main.py index d8ac629..e55121e 100644 --- a/server/main.py +++ b/server/main.py @@ -302,6 +302,26 @@ async def delete_router(name: str, x_admin_password: str = Header("")): # ── Tunnel helpers ──────────────────────────────────────────────────────────── +def _kill_tunnel_port(port: int) -> None: + """Убить процессы, удерживающие reverse-tunnel порт на VPS (127.0.0.1:PORT).""" + try: + # fuser -k работает на большинстве Linux + subprocess.run(["fuser", "-k", f"{port}/tcp"], + capture_output=True, timeout=5) + except (FileNotFoundError, subprocess.TimeoutExpired): + pass + try: + # Fallback: ss + kill + r = subprocess.run( + ["bash", "-c", + f"ss -tlnp 'sport = :{port}' | grep -oP 'pid=\\K[0-9]+' | xargs -r kill"], + capture_output=True, timeout=5, + ) + _ = r # noqa + except Exception: + pass + + def _gen_ed25519_keypair(name: str) -> tuple[str, str]: """Генерировать ed25519 keypair на VPS через ssh-keygen. Возвращает (private_pem, public_openssh).""" with tempfile.TemporaryDirectory() as tmpdir: @@ -352,6 +372,8 @@ async def tunnel_cmd(name: str, x_admin_password: str = Header("")): # Порт if rcfg.get("tunnel_port"): port = int(rcfg["tunnel_port"]) + # Убить старые зависшие соединения на этом порту (VPS-сторона) + await asyncio.to_thread(_kill_tunnel_port, port) else: used = {int(v.get("tunnel_port")) for v in R.values() if v.get("tunnel_port")} port = config.TUNNEL_PORT_START