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
This commit is contained in:
hmo
2026-07-23 13:34:06 +08:00
parent 8a27d8e847
commit 6553e51b8b
+45
View File
@@ -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 永远显示 disabled2026-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)