Files
ykxmz/relay/monitor-lightsail.sh
T
xmz7891 47e08af151 监测单元和预案重写成"分层"版(来源改为本地整份文件)
== 为什么重写 ==

certbot 签证书时加了 http -> https 的 301,而监测单元原来判断
"curl 拿到 200 才算健康"、且 curl **默认不跟跳转** -> 拿到 301
-> 判成"网站挂了" -> 又要启动预案 -> 重启中转 -> 再查还是 301。
这是**第二次**踩同一类坑了(上一次是改监听地址忘了改探测地址)。

共同教训:**改了入口的行为,必须回头看所有探测入口的地方。**

== 新设计:分两层探测 ==

    PRIMARY   https://yk.xxmxxs.top/v1/health     用户实际走的路
    FALLBACK  http://127.0.0.1:13245/v1/health    中转本身

两个都查才能分清哪一层坏了,进而选对抢救动作:

    PRIMARY 挂 + FALLBACK 好  -> edge 层(CF / DNS / nginx)
   两个都挂                  -> core 层(中转服务本身)

预案据此走不同的阶梯:

    edge: 重载 nginx -> 重启 nginx -> 查 443 与证书
    core: 重启中转 -> 强杀重拉 -> 中转+nginx 都重启

不区分层次的话只能瞎重启,而 edge 层的问题重启中转一万次也没用。

== 做法上的改进:不再远端打补丁 ==

前几轮一直是"SSH 过去用嵌套 heredoc 现场改 monitor.sh",
连续踩三次:PowerShell 在本机求值 \、引号被吃、
脚本里的 %{http_code} 和 Python 的 %% 格式化打架。

现在改成:relay/monitor-lightsail.sh 和 relay/rescue-lightsail.sh
是**唯一事实来源**,本地编辑 -> 整份上传 -> 重装,
改了什么一目了然,也能进 git。

== 顺带 ==

因为 Lightsail 防火墙还没放行 443,PRIMARY 一直是 000,
监测会把 fails 一直加上去、白生成抢救报告。
所以暂时把 monitor.timer 停掉,等 443 开了再启用
(脚本 _build/pause_monitor.py,恢复是一条 systemctl start)。
2026-10-08 22:53:44 +08:00

154 lines
5.5 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================================
# 网站监测单元(Lightsail 版)
# ============================================================================
#
# 每 60 秒查一次网站,连续 3 次不通就叫紧急预案。
#
# ── 为什么查两层 ──────────────────────────────────────────────────────
#
# PRIMARY https://yk.xxmxxs.top/v1/health
# 用户实际走的路:Cloudflare -> nginx -> 中转,全程 https
#
# FALLBACK http://127.0.0.1:13245/v1/health
# 中转本身,绕开 Cloudflare 和 nginx
#
# 两个都查才能**分清是哪一层坏了**,从而选对抢救动作:
#
# PRIMARY 挂 + FALLBACK 好 -> 问题在 CF / DNS / nginx 层(edge)
# 两个都挂 -> 中转服务本身有问题(core)
#
# 只查一个的话就没法区分 —— 不管是哪层坏了都只能"重启中转",
# 而 edge 层的问题重启中转根本没用。
#
# ── 为什么用 timer 而不是常驻进程 ──────────────────────────────────────
#
# 崩了下一分钟自己就回来,不会有"跑久了状态变脏"的问题。
#
# ── 踩过的两个坑(都写在这里免得再犯)────────────────────────────────
#
# 1. 改监听地址之后忘了改探测地址
# 把中转改成只绑 127.0.0.1 之后,探测还在查公网 :13245,
# 于是连续报"网站挂了" -> 启动预案 -> 重启中转 -> 再查还是失败,
# 差一点就是无限重启循环。
#
# 2. 忘了 curl 默认不跟跳转
# certbot 加了 http -> https 的 301 之后,探测拿到 301 而不是 200,
# 又被判成"挂了"。所以下面用 -L。
#
# 共同教训:**改了入口的行为,必须回头看所有探测入口的地方。**
# ============================================================================
set -u
RELAY_DIR="${RELAY_DIR:-/opt/ykxmz-relay}"
PRIMARY_URL="${PRIMARY_URL:-https://yk.xxmxxs.top}"
FALLBACK_URL="${FALLBACK_URL:-http://127.0.0.1:13245}"
FAIL_THRESHOLD="${FAIL_THRESHOLD:-3}"
COOLDOWN="${COOLDOWN:-600}"
CHECK_TIMEOUT="${CHECK_TIMEOUT:-10}"
STATE="$RELAY_DIR/monitor_state.txt"
LOG="$RELAY_DIR/monitor.log"
LOCKFILE="/tmp/ykxmz-monitor.lock"
if ! mkdir "$LOCKFILE" 2>/dev/null; then
exit 0
fi
trap 'rmdir "$LOCKFILE" 2>/dev/null' EXIT
log() { echo "$(date '+%F %T') $*" >> "$LOG"; }
# ---- 读上次状态 ----
FAILS=0
LAST_RESCUE=0
if [ -f "$STATE" ]; then
FAILS=$(grep -m1 '^fails=' "$STATE" 2>/dev/null | cut -d= -f2)
LAST_RESCUE=$(grep -m1 '^last_rescue=' "$STATE" 2>/dev/null | cut -d= -f2)
FAILS=${FAILS:-0}
LAST_RESCUE=${LAST_RESCUE:-0}
fi
NOW=$(date +%s)
# ---- 探两层 ----
# -L 跟跳转:nginx 那边 http 会 301 到 https。
# 不跟跳转的话拿到 301 会被误判成故障(这个坑踩过一次)。
PUB_CODE=$(curl -sL -o /dev/null -w '%{http_code}' \
--max-time "$CHECK_TIMEOUT" "$PRIMARY_URL/v1/health" 2>/dev/null)
PUB_DUR=$(curl -sL -o /dev/null -w '%{time_total}' \
--max-time "$CHECK_TIMEOUT" "$PRIMARY_URL/v1/health" 2>/dev/null)
LOC_CODE=$(curl -s -o /dev/null -w '%{http_code}' \
--max-time "$CHECK_TIMEOUT" "$FALLBACK_URL/v1/health" 2>/dev/null)
PUB_CODE=${PUB_CODE:-000}
LOC_CODE=${LOC_CODE:-000}
PUB_DUR=${PUB_DUR:-0}
# 健康 = 用户那层通。这层的通才算"网站是好的"。
CODE="$PUB_CODE"
# 哪一层坏了?给预案用的提示
LAYER=""
if [ "$PUB_CODE" != "200" ] && [ "$LOC_CODE" = "200" ]; then
LAYER="edge"
elif [ "$PUB_CODE" != "200" ] && [ "$LOC_CODE" != "200" ]; then
LAYER="core"
fi
write_state() {
{
echo "fails=$1"
echo "last_rescue=$2"
echo "last_code=$PUB_CODE"
echo "local_code=$LOC_CODE"
echo "layer=$LAYER"
echo "last_check=$NOW"
echo "last_dur=$PUB_DUR"
} > "$STATE"
}
if [ "$CODE" = "200" ]; then
PREV=$(grep -m1 '^last_code=' "$STATE" 2>/dev/null | cut -d= -f2)
if [ "$PREV" != "200" ] && [ -n "$PREV" ]; then
log "网站恢复(HTTP 200,${PUB_DUR}s)—— 之前连续失败 $FAILS 次"
fi
write_state 0 "$LAST_RESCUE"
exit 0
fi
FAILS=$((FAILS + 1))
log "网站异常 第 $FAILS 次(用户层 HTTP $PUB_CODE,中转层 HTTP $LOC_CODE${LAYER:+,层次=$LAYER},${PUB_DUR}s)"
write_state "$FAILS" "$LAST_RESCUE"
if [ "$FAILS" -lt "$FAIL_THRESHOLD" ]; then
exit 0
fi
SINCE=$((NOW - LAST_RESCUE))
if [ "$LAST_RESCUE" != "0" ] && [ "$SINCE" -lt "$COOLDOWN" ]; then
log " 已连续失败 $FAILS 次,但距上次抢救只过了 ${SINCE}s(冷却 ${COOLDOWN}s)"
exit 0
fi
# 知识库提示:历史上哪一级最常奏效
HINT=""
if [ -f "$RELAY_DIR/rescue_knowledge.md" ]; then
HINT=$(grep -oE '第 [0-9] 级奏效' "$RELAY_DIR/rescue_knowledge.md" 2>/dev/null \
| sort | uniq -c | sort -rn | head -1 | grep -oE '[0-9]')
fi
log "连续失败 $FAILS 次,启动紧急预案(层次:${LAYER:-未知})${HINT:+,知识库提示第 $HINT 级常奏效}"
write_state "$FAILS" "$NOW"
timeout 300 bash "$RELAY_DIR/rescue.sh" \
"监测单元:网站连续 $FAILS 次不通(层次 ${LAYER:-未知})" >> "$LOG" 2>&1
RC=$?
if [ "$RC" = "0" ]; then
log " 预案完成:已恢复"
else
log " 预案完成:仍未恢复(退出码 $RC),报告在 rescue_reports/"
fi
exit 0