# -*- coding: utf-8 -*- r'''把「网站监测 + 紧急预案 + 抢救报告分区」部署到中转服务器。 部署的东西: relay.py / admin.py 后台多一个「抢救报告」分区 + /rescue/ 下载 monitor.sh / rescue.sh 监测单元 + 紧急预案 lanremote-monitor.service/.timer 每 60 秒拉一次 monitor.sh lanremote-nettune.service 顺手修掉设备上那份过期的(见下) == 顺手修的设备问题 == 设备上那份 lanremote-nettune.service 是**很早以前的版本**,有两个毛病: 1. ExecStart 里写了 `iptables ... || iptables ...` systemd 的 ExecStart **不支持 `||`** —— 它把 `||` 当成一个参数, iptables 报 `Bad argument '||'`,服务直接 exit 2 失败。 2. 里面有一句 `ip link set wlan0 mtu 1400`,而 net-tune.sh 自己 实测过"降到 1400 反而掉到 50%"并把网卡设回 1500。 **两条 ExecStart 顺序是 net-tune.sh 先、1400 后**,所以最终是 1400 —— 也就是说这份过期配置一直在主动损害稳定性。 本地 relay/deploy.sh 生成的是对的(单入口 -> deploy-tune.sh)。 这里直接按那个正确形状重写设备上的 unit。 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "_build")) from devssh import upload, run # noqa: E402 RELAY_DIR = "/root/lanremote-relay" UNIT_DIR = "/etc/systemd/system" FILES = [ "relay.py", "admin.py", "landing.py", "showcase.py", "monitor.sh", "rescue.sh", "net-tune.sh", "watchdog.sh", "deploy-tune.sh", ] NETTUNE_UNIT = '''[Unit] Description=ykxmz 网络调优(MSS 钳制 + 路径 MTU 探测) After=network-online.target Wants=network-online.target Before=lanremote-relay.service lanremote-tunnel.service lanremote-tunnel-h2.service [Service] Type=oneshot RemainAfterExit=yes # ⚠ 只跑 net-tune.sh 这一个入口 —— MSS / 接口 MTU / 路径探测的唯一出处。 # # 设备上原来那份 unit 有两条毛病: # * ExecStart 里写了 `iptables ... || iptables ...` # systemd 的 ExecStart **不支持 `||`**,它把 `||` 当参数传给 # iptables,报 `Bad argument '||'`,服务直接 exit 2 失败。 # * 还有一句 `ip link set wlan0 mtu 1400`,而 net-tune.sh 自己 # 实测过"降到 1400 反而掉到 50%"并把网卡设回 1500。 # 两条 ExecStart 是 net-tune.sh 先、1400 后,所以最终停在 1400 —— # 这份过期配置一直在主动损害稳定性。 # # (仓库里的 deploy.sh 生成的是 `ExecStart=/bin/bash deploy-tune.sh` # 那种形状,但设备上并没有 deploy-tune.sh,所以这里直接用 # net-tune.sh,和设备的实际情况对齐。 # deploy-tune.sh 也一并传上去了,以后想切回单入口随时可以。) ExecStart=/bin/bash /root/lanremote-relay/net-tune.sh [Install] WantedBy=multi-user.target ''' def main() -> int: print("=" * 62) print(" 部署 监测单元 + 紧急预案 + 抢救报告分区") print("=" * 62) print() print(" ── 1. 上传文件 ──") for name in FILES: local = ROOT / "relay" / name if not local.is_file(): print(" [跳过] %s 不存在" % name) continue upload(str(local), "%s/%s" % (RELAY_DIR, name)) print(" ✓ %s" % name) for unit in ("lanremote-monitor.service", "lanremote-monitor.timer"): local = ROOT / "relay" / unit upload(str(local), "%s/%s" % (UNIT_DIR, unit)) print(" ✓ %s -> %s" % (unit, UNIT_DIR)) print() print(" ── 2. 修掉过期的 nettune 服务(MTU 打架 + ExecStart 里的 ||)──") import tempfile tmp = Path(tempfile.gettempdir()) / "lanremote-nettune.service" tmp.write_text(NETTUNE_UNIT, encoding="utf-8", newline="\n") upload(str(tmp), "%s/lanremote-nettune.service" % UNIT_DIR) print(" ✓ 已替换") print() print(" ── 3. 语法检查 ──") out = run( "cd %s && for f in relay.py admin.py landing.py showcase.py; do " "python3 -c \"import ast,sys; ast.parse(open(sys.argv[1],encoding='utf-8').read())\" $f " "|| exit 1; done && echo PY_OK; " "bash -n monitor.sh && bash -n rescue.sh && echo SH_OK; " "systemd-analyze verify %s/lanremote-monitor.service 2>&1 | head -3 || true" % (RELAY_DIR, UNIT_DIR), timeout=120) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" ── 4. 建目录 + 知识库 + 重载并启用 ──") out = run( "mkdir -p %s/rescue_reports && chmod +x %s/monitor.sh %s/rescue.sh && " "[ -f %s/rescue_knowledge.md ] || printf '# 紧急预案知识库\\n\\n" '每次自动抢救成功,这里会记一条"第几级奏效"。\\n' '以后遇到同类症状,监测单元会优先试历史上奏效的那一级。\\n" > %s/rescue_knowledge.md; ' "systemctl daemon-reload && " "systemctl restart lanremote-nettune.service; sleep 3; " "systemctl enable --now lanremote-monitor.timer 2>&1 | tail -1; " "sleep 2; systemctl is-active lanremote-monitor.timer" % (RELAY_DIR, RELAY_DIR, RELAY_DIR, RELAY_DIR, RELAY_DIR), timeout=180) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" ── 5. 复核 ──") out = run( "echo 'nettune: '$(systemctl is-active lanremote-nettune.service); " "echo 'monitor.timer: '$(systemctl is-active lanremote-monitor.timer); " "echo 'MTU: '$(cat /sys/class/net/wlan0/mtu); " "echo 'MSS 规则: '$(iptables -t mangle -L -n 2>/dev/null | grep -ci mss)' 条'; " "echo '报告目录: '$(ls %s/rescue_reports 2>/dev/null | wc -l)' 份'; " "echo '后台分区: '$(curl -s -o /dev/null -w '%%{http_code}' http://127.0.0.1:8080/rescue/nonexistent.txt)" % RELAY_DIR, timeout=120) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" 提示:监测单元默认每 60 秒查一次公网,连续 3 次不通才动手;") print(" 抢救完 10 分钟内不再动手(防抖,避免来回重启)。") print(" 日志在 %s/monitor.log 和 %s/rescue.log。" % (RELAY_DIR, RELAY_DIR)) return 0 if __name__ == "__main__": sys.exit(main())