From 6553e51b8b60b7719ed8f202014d45f028951b9c Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 23 Jul 2026 13:34:06 +0800 Subject: [PATCH] fix: RDP tunnel status adopts detached/wild tunnels instead of false-disabled - _rdp_find_tunnel_process(): find ssh.exe hosting -R 8080 forwarding via CIM - _rdp_status(): adopt actual tunnel process when pid file stale/missing - _rdp_enable(): adopt existing tunnel instead of spawning duplicate that fails on 'remote port forwarding failed' (8080 occupied) - Fixes 2026-07-23 incident: tunnel alive but dashboard showed disabled, repeated enable attempts failed because port was occupied by working tunnel --- xmpp_agent_core.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/xmpp_agent_core.py b/xmpp_agent_core.py index b00d1ee..b726267 100644 --- a/xmpp_agent_core.py +++ b/xmpp_agent_core.py @@ -317,6 +317,15 @@ def _rdp_enable(): except Exception: pass _rdp_kill_tunnel() + # 收养现存隧道:如果有 ssh 进程已在跑 -R 8080 转发(比如 bot 崩溃后留下的 + # detached ssh、或状态文件丢失后的野生隧道),直接写入 pid 文件收养, + # 不再起新进程——否则新 ssh 会因 8080 被占报 "remote port forwarding failed", + # 导致 dashboard 永远显示 disabled(2026-07-23 事故)。 + _existing = _rdp_find_tunnel_process() + if _existing: + _rdp_adopt_tunnel(_existing) + log(f'RDP: tunnel already running (PID {_existing}), adopted — no new ssh needed') + return True, f'RDP access enabled (adopted existing tunnel, PID {_existing})' # Health check: if RDP not responding, auto-restore _t.sleep(2) if not _rdp_health_check(): @@ -403,6 +412,36 @@ def _rdp_kill_tunnel(): except Exception: pass +def _rdp_find_tunnel_process(): + """Find a running ssh.exe hosting the -R 8080:localhost:3389 reverse tunnel. + Returns PID or None. Used to adopt tunnels started outside _rdp_enable + (e.g. by a crashed/restarted bot that left a detached ssh behind).""" + import subprocess as _sp + try: + # 纯单引号写法:避免 powershell -Command 下双引号转义失效 + ps_cmd = ("Get-CimInstance Win32_Process | " + "Where-Object { $_.Name -eq 'ssh.exe' -and $_.CommandLine -like '*8080:localhost:3389*' } | " + "Select-Object -ExpandProperty ProcessId") + r = _sp.run(['powershell', '-NoProfile', '-Command', ps_cmd], + capture_output=True, text=True, timeout=15) + for line in r.stdout.splitlines(): + line = line.strip() + if line.isdigit(): + return int(line) + except Exception: + pass + return None + +def _rdp_adopt_tunnel(pid): + """Write pid file for an externally-started tunnel so status tracking works.""" + try: + with open(_RDP_PID_FILE, 'w') as f: + f.write(str(pid)) + log(f'RDP: adopted existing tunnel (PID {pid})') + return True + except Exception: + return False + def _rdp_status(): import subprocess as _sp, winreg as _wr tunnel_on = False @@ -413,6 +452,12 @@ def _rdp_status(): tunnel_on = 'ssh.exe' in r.stdout except Exception: pass + # pid file missing or process dead → look for an actual tunnel process and adopt it + if not tunnel_on: + found = _rdp_find_tunnel_process() + if found: + _rdp_adopt_tunnel(found) + tunnel_on = True rdp_on = False try: k = _wr.OpenKey(_wr.HKEY_LOCAL_MACHINE, r'SYSTEM\CurrentControlSet\Control\Terminal Server', 0, _wr.KEY_READ)