运维手册

最后更新 2026-10-30 · 适用 longgame(13 条数据线)与 mcpreflight · 当前状态:线上实跑通过(health.html 显示 ok)

先说结论:这套东西现在能自己跑,但"自己能不能跑"这件事还差一个外部见证

2026-10-30 对线上记录的核查结果:

事实证据
GitHub 的 schedule 事件被静默丢弃longgame 最后两次 schedule 成功在 10-22、10-23;此后到 10-30 共 158 小时没有任何 schedule 运行。10-26(周一)whole weekly-collect 整次没跑。
失败没有任何声音17 个 workflow,无 failure 通知、无 Issue、无邮件。10-30 手动触发的 weekly-collect 失败后,也没人知道。
看门狗和被监控对象在同一根线上原 if-stale.mjs 挂在 daily-collect 内部。触发器死了,看门狗一起死。
单线失败能拖死整轮run-all 原逻辑"任一 line 失败即 exit 1",导致 gate/publish 被跳过——数据采到了却不发布。

代码层面能补的已经补了(下面三层)。补不了的是:仓库完全不知道自己"没被叫醒"。 这件事必须由仓库外的某个东西来问一句"你还在吗"。

三层保底(已上线)

层位置做什么防的是什么
页级scripts/line.mjs三档容错:tolerate(已知抓不到,只记事件)/throttled(202/403/417/429/空挑战页,重试一次后放行)/其余(首次告警放行,连续两轮才判失败)单页反爬拖垮一条线
线级scripts/run-all.mjs单线失败只记录(写进 data/collect-runs.json),只有本轮尝试的线全部失败才判整轮失败一条线故障让其它 12 条线的数据无法发布
心跳级scripts/heartbeat.mjs一日三次自检 → 超期线当场补采 → critical 开 Issue → 落健康页与心跳文件整周断档无人知

新鲜度按最后一次成功采集算,不按最后一次运行算。差别很关键:一条每周都跑、每次都全挂的线, 用"最后运行时间"算会永远显示今天刚采过——这就是绿灯下的死亡。

上线当天实跑暴露的故障(已修)

这套机制不是"写完就完事"。第一次真跑在 GitHub 上,立刻暴露出四个只在真实环境里才出现的问题:

故障表现根因修法
门禁信号永远是假警健康页每轮都报 gate-missing,而真正的 gate FAIL 反而看不见assurance/.gate/result.json 落在 runner 的工作目录里,job 结束即销毁;心跳在另一个 workflow 里永远读不到它心跳改成问 Actions API 要最近一次 assurance-gate 的结论(conclusion),本地跑时回退读文件。已实测读到 passed=true 并写进健康页
没有变更被当成失败self-upgrade 首跑即红,还开出一条假告警 Issuegit add data/self-tuning.json:该文件只在真有变更时才生成,git add 打到不存在的路径会以 128 退出改为"存在才 add",另外 7 个 workflow 的同类隐患一并加上 || true
陈旧阈值和周期打架self-output-stale 连报 216 小时docs/status.json 一周才产一次,阈值设 48 小时等于每周有 5 天在报假警阈值对齐到 192 小时(一个周期 + 1 天宽限)
推对了代码,推错了分支workflow 手动触发能跑,定时器毫无动静默认分支是 master,改动推到了 main;schedule 只在默认分支触发快进 master 后推送,schedule 与 pages 部署同时恢复
这四个里没有一个能被代码审查发现——它们全都是"在真实环境跑一次"才现形的。 所以这套系统的验收标准不是"代码写完了",而是"线上真跑过一轮并且绿灯"。 2026-10-30 13:28 UTC 的那轮心跳 overall=ok,是它第一次真正通过验收。

现在的告警通道

Issue 标题前缀什么时候开什么时候自动关
workflow-failed: <workflow 名>任一 workflow 失败(每个 workflow 最后一步 notify-failure.mjs)人工关
longgame-critical心跳判定 critical:某线超期 1.5 倍周期 / 连续两轮全挂 / 门禁 FAIL下次心跳判定恢复时自动关
longgame-schedule-silence上次心跳距今超 30 小时心跳恢复后自动关

健康状态随时可看:health.html(每轮自动重写),原始数据 docs/health/latest.json,模型可读摘要 docs/health/latest.json 与 data/heartbeat.json(心跳时间戳在这里,外部监控盯这个)。

唯一补不上的那块:外部 dead-man switch

必须由人在仓库外做一次配置,代码做不到。 理由很直白:cron 被丢弃时,仓库里的任何脚本都不会运行,包括看门狗和心跳。 "我没被叫醒"这句话,只能由外面的人替它说。

方案 A:外部定时器触发 workflow(推荐,能自愈)

任意一台常开的机器(VPS / NAS / 家里的旧电脑 / 树莓派)加一条定时任务, 每 6 小时叫一次心跳。它跑起来就会自己补采超期线:

# crontab -e
0 */6 * * * curl -sS -X POST \
  -H "Authorization: Bearer $GH_PAT" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/xka0085-byte/longgame/actions/workflows/heartbeat.yml/dispatches \
  -d '{"ref":"master"}'

分支必须写 master。这个仓库的默认分支是 master, 而 GitHub 的 schedule 事件只在默认分支上触发——代码推到一个非默认分支上, workflow 文件在,长得也对,但定时器永远不会叫它。2026-10-30 就踩过一次: 改动推到了 main,workflow 手动 dispatch 能跑、schedule 全哑。

需要一个 fine-grained PAT,权限只要 Actions: write(不需要 repo 全权限)。 把 token 放在那台机器的环境变量里,不要写进仓库。

方案 B:外部监控盯心跳时间戳(最省事,只能告警不能自愈)

用任何支持"HTTP 内容变化"或"JSON 字段"检查的服务(Healthchecks.io / UptimeRobot / 自建), 盯 https://xka0085-byte.github.io/longgame/health/latest.json 里的 generated_at,超过 30 小时未更新就发邮件。五分钟能配完,不需要 token, 但它只能告诉你"断了",不能替你补。

两个都做最好:A 负责把系统叫醒,B 负责在 A 也失效时叫醒你。 只做 A 的风险是 A 自己挂了没人知道;只做 B 的风险是每次断档都要等你自己动手。

自我升级的边界

每周一(外加周四兜底)self-upgrade.yml 会按真实采集证据自动调整策略参数, 并且只允许动两处:

允许自动改条件为什么安全
portfolio.json 某页 tolerate该页连续 ≥3 轮不可用、近 20 轮零成功、失败状态全属反爬/限流集合只是让已知抓不到的源不再阻断整轮;不写入任何数值,保留上次数据
data/self-tuning.json 周期容忍天数该线真实采集间隔长期宽于名义周期只影响"多久算超期"的判定,每次 +1 天,weekly 上限 +3、monthly 上限 +7

不允许自动改(脚本内白名单,没有外部入口):解析器、evidence.json、门禁阈值、 assurance_level、verified_by_third_party、任何 workflow 文件。

每次变更都先跑 verify.mjs 与 validate-contracts.mjs,不过就原样回滚, 并在 data/self-upgrade-log.jsonl 留下 before/after、证据与回滚命令。 诊断结论写 data/diagnostics.json——"看起来像 bug"的判断只记录,不动代码。

为什么不自动改解析器:自动改解析逻辑一旦出错,会把一条降级的线变成一条 看起来正常但是错的线。这套项目对外只承诺"可复核",宁可红着也不能假装绿着。

看到告警时的处理顺序

workflow-failed

  1. 点开 Issue 里的运行链接,看失败在哪一步。
  2. 采集失败 → 看 data/lines/<id>.json 最后一条 run 的 pages/dirs/queries 字段,状态码就是原因。
  3. 门禁失败 → 看 assurance/.gate/result.json 里哪一项没过。门禁失败不许绕过,修数据或修实现。

longgame-critical

  1. 看 health.html 哪条线红了。
  2. 若是超期:心跳已尝试补采,补采也失败的线会在 Issue 里列出 heal.failed。
  3. 若是某页永久 404/改版:到 portfolio.json 改 URL 或把这条线 status 降级; 不要为了让红灯变绿而加 tolerate——那等于把"没数据"说成"没问题"。

longgame-schedule-silence

这条最特殊:它只有在外部触发器把人叫醒、补跑了一次心跳之后才会出现。 出现就意味着 GitHub 的 cron 又丢了事件。处理方式是确认外部触发器还在工作(方案 A/B)。

人每月要做的三件事

  1. 读一次月报,对"是否继续跟这条线"点个头。
  2. 看一眼 Issues 里有没有挂着没关的 workflow-failed。
  3. 确认外部触发器(方案 A 或 B)还活着。
自检的边界:这是项目自己的内部检查,不是第三方验证。心跳只能证明"这一次跑了", 不能证明"该跑的那次跑了";健康页全绿只说明已知检查项都通过,不说明没有未知缺陷。

返回 首页 · 健康页 · 方法说明