# -*- coding: utf-8 -*- r'''写监测单元的两个 systemd 单元文件。 用户要求「在后台设立检测单元」。 为什么用 timer 而不是常驻服务: * 这台机器只有 460MB 内存,常驻 bash 循环白占一份内存 * 常驻脚本万一自己挂了就永远不检查了;timer 到点拉起来一次, 崩了下一分钟自己就回来 * 而且每次都是全新进程,不会有"跑久了状态变脏"的问题 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent OUT = ROOT / "relay" SERVICE = '''[Unit] # ykxmz 网站监测单元 —— 每 60 秒看一次公网,挂了就叫紧急预案。 # # 由 lanremote-monitor.timer 拉起(不是常驻进程,见 timer 里的说明)。 Description=ykxmz 网站监测单元(每 60 秒检查公网,异常时启动紧急预案) After=network-online.target Wants=network-online.target [Service] Type=oneshot # 检查本身最长 12 秒,预案最长 5 分钟,给 6 分钟余量 TimeoutStartSec=360 # ⚠ 用 bash 显式跑,别靠 shebang + 可执行位 —— # 部署时文件权限被改掉是最常见的"脚本不执行"原因。 ExecStart=/bin/bash /root/lanremote-relay/monitor.sh # 环境变量 # FAIL_THRESHOLD 连续失败几次才动手(默认 3 次 ≈ 3 分钟) # COOLDOWN 救完之后多少秒内不再救(默认 600 秒) # 不设的话:预案刚重启完隧道还没连上,下一次检查 # 又判失败又救一遍,来回重启反而永远连不上 # RESCUE_REBOOT 预案里是否允许"重启整机"这张底牌。**默认 0(不开)**。 # 想开就把下面这行的 0 改成 1 —— 但先想清楚: # 如果问题出在供电或上游线路,重启只会让恢复更慢, # 而且半夜自动重启一台没人管的机器风险很大。 Environment=RESCUE_REBOOT=0 Environment=FAIL_THRESHOLD=3 Environment=COOLDOWN=600 StandardOutput=append:/root/lanremote-relay/monitor.log StandardError=append:/root/lanremote-relay/monitor.log [Install] WantedBy=multi-user.target ''' TIMER = '''[Unit] # 每 60 秒检查一次公网。 # # 为什么是 60 秒不是 5 秒: # 实测这条 4G 线路本来就有 2~4% 的偶发失败,检查太勤会一直看到 # "失败"(其实一两秒后自己就好了)。配合 monitor.sh 里 # "连续 3 次才动手"的防抖,实际最快 3 分钟响应,够用了。 # # OnBootSec=90s:开机后等 90 秒再开始查。 # 隧道一般几十秒才连上,太早开始会一开机就误判、白抢救一轮。 Description=每 60 秒检查一次网站是否可用 [Timer] OnBootSec=90s OnUnitActiveSec=60s AccuracySec=5s Unit=lanremote-monitor.service [Install] WantedBy=timers.target ''' def main() -> int: (OUT / "lanremote-monitor.service").write_text( SERVICE, encoding="utf-8", newline="\n") (OUT / "lanremote-monitor.timer").write_text( TIMER, encoding="utf-8", newline="\n") for n in ("lanremote-monitor.service", "lanremote-monitor.timer"): p = OUT / n print(" ✓ %s(%d 字节)" % (n, p.stat().st_size)) return 0 if __name__ == "__main__": sys.exit(main())