修 devssh:定义了 HOST_FALLBACKS 却从来没用过

随身 WiFi 跟着教室网络走,地址会在 192.168.2.156 和
192.168.10.105 之间变。devssh.py 里明明定义了 HOST_FALLBACKS,
但 run / upload / download 三个函数**全都直接用 HOST** ——
于是设备一换网段就全部连不上,而且报的是连接超时,
看起来像设备挂了。

现在改成按候选列表顺序试:
    环境变量 YKXMZ_DEV_HOST 优先
    -> 192.168.2.156(当前)
    -> 192.168.10.105(换网前用过)
并记住上次通的那个,后续调用不用每次从头试。
连上时打一行 `[devssh] 连上 x.x.x.x`,免得以后又搞不清连的是哪台。

还加了 `--probe` 子命令,一句话看现在哪个地址能用:

    python _build/devssh.py --probe

实测:192.168.2.156 通、192.168.10.105 不通,自动选中前者。
This commit is contained in:
xmz7891
2026-10-07 20:29:37 +08:00
parent 75c15f793c
commit 41bf110e22
4 changed files with 169 additions and 27 deletions
+89 -27
View File
@@ -4,38 +4,100 @@
用法:
python _build/devssh.py "命令" # 跑一条命令
python _build/devssh.py --script 文件 # 跑一段脚本
== 地址会自动试 ==
这台设备跟着教室网络走,地址会变:
192.168.2.156 <-> 192.168.10.105
所以这里**按顺序试多个地址**,谁先连通就用谁,不用改代码。
想强制指定就用环境变量:
$env:YKXMZ_DEV_HOST = "192.168.10.105"
⚠ 以前的坑:定义了 HOST_FALLBACKS 但**三个函数都直接用 HOST**,
那个常量根本没被用过。于是设备一换网段就全部连不上,
而且报的是「Connection refused / timed out」,看起来像设备挂了。
"""
from __future__ import annotations
import os
import sys
import time
from pathlib import Path
import paramiko
# 随身 WiFi(Debian)的地址。
#
# 这台设备跟着教室网络走,地址会变:
# 192.168.2.156 -> 192.168.10.105(换网络了)
# 所以优先读环境变量 YKXMZ_DEV_HOST,没有才用下面的默认值。
# 换网络时不用改代码:
# $env:YKXMZ_DEV_HOST = "192.168.10.105"
import os
HOST = os.environ.get("YKXMZ_DEV_HOST", "192.168.10.105")
USER = "root"
PASSWORD = os.environ.get("YKXMZ_DEV_PASS", "1313144")
# 旧地址留一份,方便回退或同时探测
HOST_FALLBACKS = ("192.168.10.105", "192.168.2.156")
# 候选地址,按顺序试。
# 环境变量优先(想锁定时用),然后是最近见过的两个地址。
_CANDIDATES: list[str] = []
def _build_candidates() -> list[str]:
out = []
env_host = (os.environ.get("YKXMZ_DEV_HOST") or "").strip()
if env_host:
out.append(env_host)
for host in ("192.168.2.156", # 当前地址
"192.168.10.105"): # 换网络前用过
if host not in out:
out.append(host)
return out
CANDIDATES = _build_candidates()
HOST = CANDIDATES[0] # 兼容老代码:默认取第一个
HOST_FALLBACKS = tuple(CANDIDATES[1:])
# 记住这次连通的是哪个,后续调用不用每次从头试
_CONNECTED: str | None = None
def _connect(timeout: int = 15):
"""按候选顺序连,返回 (client, host)。全都不通才抛异常。"""
global _CONNECTED
order = list(CANDIDATES)
if _CONNECTED and _CONNECTED in order:
# 上次通的那个排最前,省一次失败尝试
order.remove(_CONNECTED)
order.insert(0, _CONNECTED)
errors = []
for host in order:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(host, username=USER, password=PASSWORD,
timeout=timeout, banner_timeout=20,
auth_timeout=20, look_for_keys=False,
allow_agent=False)
if _CONNECTED != host:
print(f" [devssh] 连上 {host}")
_CONNECTED = host
return client, host
except Exception as exc: # noqa: BLE001
errors.append(f"{host}: {type(exc).__name__} {exc}")
try:
client.close()
except Exception: # noqa: BLE001
pass
raise RuntimeError("所有候选地址都连不上:\n " + "\n ".join(errors))
def probe() -> str | None:
"""探一下现在哪个地址能用(不抛异常)。"""
try:
client, host = _connect(timeout=8)
client.close()
return host
except Exception: # noqa: BLE001
return None
def run(command: str, timeout: int = 600, quiet: bool = False) -> tuple:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASSWORD, timeout=15,
banner_timeout=20, auth_timeout=20,
look_for_keys=False, allow_agent=False)
client, _host = _connect()
try:
_stdin, stdout, stderr = client.exec_command(command, timeout=timeout)
out = stdout.read().decode("utf-8", "replace")
@@ -52,10 +114,7 @@ def run(command: str, timeout: int = 600, quiet: bool = False) -> tuple:
def upload(local: str, remote: str, mode: int = 0o644) -> None:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASSWORD, timeout=15,
look_for_keys=False, allow_agent=False)
client, _host = _connect()
try:
sftp = client.open_sftp()
sftp.put(local, remote)
@@ -67,10 +126,7 @@ def upload(local: str, remote: str, mode: int = 0o644) -> None:
def download(remote: str, local: str) -> None:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASSWORD, timeout=15,
look_for_keys=False, allow_agent=False)
client, _host = _connect()
try:
sftp = client.open_sftp()
sftp.get(remote, local)
@@ -83,13 +139,19 @@ def download(remote: str, local: str) -> None:
if __name__ == "__main__":
if len(sys.argv) < 2:
print(__doc__)
print(" 候选地址: %s" % ", ".join(CANDIDATES))
print(" 现在能连的: %s" % (probe() or "(一个都不通)"))
sys.exit(1)
if sys.argv[1] == "--script":
run(Path(sys.argv[2]).read_text(encoding="utf-8"), timeout=600)
elif sys.argv[1] == "--probe":
got = probe()
print(" 能连的地址: %s" % (got or "(一个都不通)"))
sys.exit(0 if got else 1)
else:
args = sys.argv[1:]
timeout = 600
if args and args[0] == '--timeout':
if args and args[0] == "--timeout":
timeout = int(args[1])
args = args[2:]
run(" ".join(args), timeout=timeout)
sys.exit(run(" ".join(args), timeout=timeout)[0])