#!/bin/bash # 让 watchdog 更聪明:区分「隧道还在建」和「隧道建好了又垮」 # # 现在的逻辑(宽限期): # 重启后 150 秒内绝不重启 —— 防住了"还没建齐就被打断"的死循环。 # 但这带来一个新问题: # 如果链路是**持续性的差**,150 秒里看门狗什么都不做, # 用户就一直打不开。 # # 更聪明的判断: # 重启后记录"这次有没有建齐过"(连接数 >= 3)。 # * 没建齐过 -> 它还在努力,继续等(宽限期有意义) # * 建齐过又垮 -> 已经证明链路能撑起来,现在垮了 = 真故障, # 不用再等宽限期,立刻重启 # # 这样两种场景都对: # 链路慢(建得久) -> 耐心等,不打断 # 链路抖(建好又断)-> 果断重启,不干等 set -u WD=/root/lanremote-relay/watchdog.sh cp -a "$WD" "$WD.bak.$(date +%s)" echo "════════ 改之前的参数 ════════" grep -E '^MIN_RESTART_GAP|^GRACE|^THRESHOLD' "$WD" | sed 's/^/ /' python3 - <<'PY' from pathlib import Path path = Path("/root/lanremote-relay/watchdog.sh") text = path.read_text(encoding="utf-8") # ---- 1. 状态文件多存一个 "这次重启后建齐过吗" ---- old_write = '''write_state() { printf 'zero=%s\\nfail=%s\\n' "$1" "$2" > "$STATE_FILE"; }''' new_write = '''# 状态文件现在存三样: # zero —— 连续降级次数 # fail —— 连续失败次数 # built —— 本次重启后有没有建齐过(1=建齐过) write_state() { printf 'zero=%s\\nfail=%s\\nbuilt=%s\\n' "$1" "$2" "${3:-0}" > "$STATE_FILE" }''' if old_write in text: text = text.replace(old_write, new_write, 1) print(" 已扩展状态文件") else: print(" [警告] 找不到 write_state") # ---- 2. 重启时把 built 清零 ---- text = text.replace( " echo \"$now\" > \"$LAST_RESTART_FILE\"\n" " systemctl restart lanremote-tunnel\n" " write_state 0 0\n", " echo \"$now\" > \"$LAST_RESTART_FILE\"\n" " systemctl restart lanremote-tunnel\n" " write_state 0 0 0\n", 1) # ---- 3. 「先不重启」那两处也带上 built ---- text = text.replace( " log \"$why —— 但距上次重启才 ${gap} 秒(下限 ${MIN_RESTART_GAP} 秒),先不重启\"\n" " write_state 0 0\n", " log \"$why —— 但距上次重启才 ${gap} 秒(下限 ${MIN_RESTART_GAP} 秒),先不重启\"\n" " write_state 0 0 \"${BUILT:-0}\"\n", 1) # ---- 4. 主判断:加「建齐过就立刻重启」 ---- old_block = '''if [ "$DEGRADED" = "1" ]; then # 宽限期内不重启 —— 让上一次重启有机会把连接建齐。 # (不加这条会死循环:隧道还没建齐就被再次打断。) LAST_R=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0) SINCE=$(( $(date +%s) - LAST_R )) if [ "$SINCE" -lt "$GRACE" ]; then log "公网不通且只剩 ${CONNS} 条连接,但距上次重启才 ${SINCE} 秒(宽限 ${GRACE} 秒)—— 先让它建完" else restart_tunnel "公网不通且只剩 ${CONNS} 条隧道连接(确认故障,立即重启)" fi fi''' new_block = '''if [ "$DEGRADED" = "1" ]; then # 宽限期内不重启 —— 让上一次重启有机会把连接建齐。 # (不加这条会死循环:隧道还没建齐就被再次打断。) LAST_R=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0) SINCE=$(( $(date +%s) - LAST_R )) # 但要看「这次重启后建齐过没有」: # 建齐过(>=3 条)又垮下来 = 链路本来能撑住,现在是真故障 # -> 不用再等宽限期,立刻重启 # 从没建齐过 = 它还在努力建 # -> 继续等,别打断它 if [ "${BUILT:-0}" = "1" ]; then restart_tunnel "公网不通,且本次重启后曾建齐过连接(确认故障,立即重启)" elif [ "$SINCE" -lt "$GRACE" ]; then log "公网不通且只剩 ${CONNS} 条连接,但本次重启还没建齐过(${SINCE}/${GRACE} 秒)—— 继续等它建" else restart_tunnel "公网不通且 ${SINCE} 秒都没建齐(链路或边缘有问题,重启试试)" fi fi''' if old_block in text: text = text.replace(old_block, new_block, 1) print(" 已加「建齐过就立刻重启」判断") else: print(" [警告] 找不到立即重启那段,没改") # ---- 5. 读状态 + 记录建齐 ---- old_read = '''ZERO=$(read_state zero 0) FAIL=$(read_state fail 0)''' new_read = '''ZERO=$(read_state zero 0) FAIL=$(read_state fail 0) BUILT=$(read_state built 0)''' if old_read in text: text = text.replace(old_read, new_read, 1) print(" 已读入 built 状态") else: print(" [警告] 找不到读状态那段") # 在算出 CONNS 之后,判断有没有建齐,并持久化 old_conns = '''[ -z "$CONNS" ] && CONNS=""''' new_conns = '''[ -z "$CONNS" ] && CONNS="" # 连接数达到 3 条以上就算"建齐过"。 # 记在状态文件里,跨多次检查保留 —— 这样即使后面掉到 1 条, # 我们也知道"它本来是能建齐的"。 if [ -n "$CONNS" ] && [ "$CONNS" -ge 3 ] 2>/dev/null; then if [ "$BUILT" != "1" ]; then log "本次已建齐 ${CONNS} 条连接" fi BUILT=1 fi''' if old_conns in text: text = text.replace(old_conns, new_conns, 1) print(" 已加建齐检测") else: print(" [警告] 找不到 CONNS 那段") # ---- 6. 收尾写状态时带上 built ---- text = text.replace('write_state "$ZERO" "$FAIL"', 'write_state "$ZERO" "$FAIL" "$BUILT"') text = text.replace(' write_state 0 0\n exit 0', ' write_state 0 0 "$BUILT"\n exit 0') path.write_text(text, encoding="utf-8", newline="\n") print(" 已写回") PY echo echo "════════ 改之后 ════════" bash -n "$WD" && echo " 语法 OK" || echo " **语法有错**" grep -E '^MIN_RESTART_GAP|^GRACE|^THRESHOLD' "$WD" | sed 's/^/ /' grep -c 'BUILT' "$WD" | sed 's/^/ BUILT 出现次数: /' echo echo "════════ 立即跑一次看输出 ════════" bash "$WD" 2>&1 | tail -4 | sed 's/^/ /' echo echo " 状态文件:" cat /root/lanremote-relay/watchdog.state 2>/dev/null | sed 's/^/ /'