Skip to content

feat(autoheal): add system self-healing daemon (PR-3 of #59) - #92

Open
suantea wants to merge 1 commit into
PIKACHUIM:mainfrom
suantea:feat/system-selfheal
Open

suantea wants to merge 1 commit into
PIKACHUIM:mainfrom
suantea:feat/system-selfheal

Conversation

@suantea

@suantea suantea commented Sep 5, 2026 •

Copy link
Copy Markdown
Collaborator

由 AI 助手整理(issue #59 PR-3)。

背景

当前 NetPanel 的 Manager.StartAll() 只在启动时拉起服务,运行时崩溃需手动恢复。

改动

  • backend/service/autoheal/heal.go(新包):
    • ProbeResult + Config + Manager 骨架
    • 周期性探活(30s 间隔):frp_client/cftunnel/portforward
    • 自动重启:RestartFn 回调,失败写审计日志
    • 证书预警:扫描 DomainCert,≤7 天剩余发送 callback 告警
    • 与 syslog/callback 集成(审计条目 + 告警推送)
  • backend/main.go:创建 autohealMgr 并接入 Start/Stop 生命周期 + registerStopHandlers

验证:go build ./... 通过。

@suantea
suantea force-pushed the feat/system-selfheal branch 2 times, most recently from 1d6ada8 to 1ad001a Compare September 5, 2026 07:23

@PIKACHUIM PIKACHUIM left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢贡献 @suantea 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。
⚠️ 重要提醒:最终是否合并由项目维护者人工判定。

🎯 结论

✅ Approve — 自愈守护骨架清晰,仅 2 处 P2 可优化

📖 概要

新增 autoheal 包,周期性探活(30s)frp_client/cftunnel/portforward,未运行则触发 RestartFn 自动重启,证书 ≤7 天剩余发送 callback 告警。

🧭 整体方案

探活回调(ProbeFunc)+ 重启回调(RestartFn)依赖注入设计,解耦具体服务实现;周期 ticker + stopCh 优雅停止;证书预警独立于进程探活。方案清晰可扩展。

📊 变更统计

4 文件(+305 / -34)| 功能 ⭐⭐⭐⭐ | 最小改动 ⭐⭐⭐⭐ | 前向兼容 ⭐⭐⭐⭐⭐ | 方案设计 ⭐⭐⭐⭐

🚨 关键问题

无重大问题。

⚠️ 次要问题(P2,不阻塞)

  1. RestartMaxRetries 定义未使用:Config 声明了 RestartMaxRetries,但 probeOne 中没有任何重试上限逻辑,重启失败仅写 HEAL_FAIL 审计。若服务持续崩溃会每 30s 无限重试。建议实现「失败 N 次后暂停告警」或明确移除该字段。
  2. RestartFn 为 nil 时误报 HEAL_OK:probeOne 中即使 RestartFn 为 nil(未注入),仍会走到 m.logAudit(..., "HEAL_OK", "已自动重启"),实际未执行任何重启。建议在 RestartFn == nil 时直接返回,不写 HEAL_OK。

⚠️ 边缘提醒(P2)

  • Stop() 中 close(m.stopCh),若 Start() → Stop() → Start() 重复调用,第二次 Start 复用已关闭的 stopCh 可能导致 panic。当前调用方(main.go)通常只启停一次,风险低,但建议防御性重建 channel。

✅ 亮点

  • 依赖注入清晰,探活逻辑与重启逻辑解耦。
  • 证书过期/临期分级告警(expired / expiring_in_N_days)。
  • 失败/成功均写审计日志,可追踪。

🎯 结论:✅ Approve — 建议合并(最终由维护者判定)。

@pikachuren pikachuren left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @suantea 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。
⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出是否合并的决策。

🎯 结论

🔄 Request Changes — 自愈守护的整体骨架合理,但存在「重启次数上限未生效」「证书告警每 30 秒重复轰炸」「Start/Stop 生命周期缺陷」3 个 P1,建议修后再合并

📖 概要

feat(autoheal): 系统自愈守护(PR-3 of #59) · 关联 #59 · 周期性探测子进程存活并自动重启,附带证书到期预警
核心改动:新增 service/autoheal(Manager + BuildProbeFunc),每 30s 探测 frp_client / cftunnel / portforward 的启用项,未运行则调用 RestartFn 重启;同时扫描证书有效期并在 7 天内告警,写 syslog 并触发 callback。main.go 完成接线与优雅关闭。

🧭 整体方案

用 ticker + 探针函数 + 注入式重启回调的方式实现通用自愈守护,ProbeFunc/RestartFn 通过依赖注入解耦具体服务,设计上是清晰的。两个需要收敛的点:一是「告警去重」与「重启退避/上限」这两个自愈场景最关键的保护都缺失或未生效;二是本 PR 夹带了两个与自愈无关的提交(CI pr-checks 重构、selector 取消确定性修复),且与 #91 完全重复。

📊 变更统计

4 个文件(+305 / −34 行) | 功能 ⭐⭐⭐⭐ | 最小改动 ⭐⭐⭐⭐ | 前向兼容 ⭐⭐⭐⭐ | 方案设计 ⭐⭐⭐

🚨 关键问题

P0(阻塞合并):无

P1(建议修复):

  • ⚠️ backend/service/autoheal/heal.go:31,74,141-146 证书告警重复轰炸:Config.CertWarnDays 配置了 7 天,checkCertWarnings() 每 30 秒遍历全部 DomainCert,对每个临期证书无条件调用 alertCert()。一张 7 天后到期的证书,在 7 天内会写约 2 万条 syslog 并触发约 2 万次 callback。建议加去重:按 certID + reason 记录上次告警时间(例如每天最多一次),或在 reason 里去掉具体天数后按天粒度去重。
  • ⚠️ backend/service/autoheal/heal.go:31,141-146 RestartMaxRetries 定义了但从未使用:probeOne() 直接调用 m.cfg.RestartFn,既没有重试计数也没有退避。若某个服务持续崩溃(例如配置错误),会每 30 秒无条件重启一次,日志与 callback 同样会被刷爆。建议接入 RestartMaxRetries(达到上限后停手并升级为一次告警),并加上退避。
  • ⚠️ backend/service/autoheal/heal.go:39,64-72 Start() / Stop() 生命周期不完整:Stop() 里 close(m.stopCh) 后 stopCh 永久处于已关闭状态;若之后再次 Start(),run() 会立刻从 <-m.stopCh 返回,守护静默失效(ticker 已新建但无人消费)。当前 startServer() 只走一次路径所以暂时安全,但 service 模式下重复调用就会踩到。建议在 Start() 中重建 stopCh(或用 context.Context + sync.Once)。
  • ⚠️ gofmt 不合规(已用 gofmt -l 核对):backend/service/autoheal/heal.go、backend/main.go。#109 正在给 CI 加 gofmt 门禁,届时会被拦下,建议先跑 gofmt -w。

P2(可选):

  • 💡 backend/service/autoheal/heal.go:175-206 BuildProbeFunc 里 3 处 db.Where("enable = ?", true).Find(&xxx) 忽略 Find 的 error。DB 出错时会返回空集合,表现为「没有任何服务需要探活」,自愈静默失效。建议至少记录 error。
  • 💡 backend/service/autoheal/heal.go:167-174 alertCert 触发 callback 时只传了 Type: "cert_" + reason,没有带证书名/域名/到期时间等上下文;且 reason 里含天数(expiring_in_3_days)意味着每天都会产生一个新的时间类型,回调规则很难配置(需要每天新增一条规则)。建议 reason 收敛为固定枚举(expiring / expired),把天数放进 payload。
  • 💡 backend/service/autoheal/heal.go:176-206 BuildProbeFunc 每次 tick 都全表查询三张配置表。若配置项不多,可接受;建议在 PR 描述里说明预期规模,或后续改为只查启用项 + 加索引说明。
  • 💡 backend/service/autoheal/heal.go:186-196 判断 cftunnel 运行状态依赖 GetStatus() 返回 map 里的 "running" 布尔键,属于弱契约(键名拼写错了会静默判为「未运行」并触发无意义重启)。建议改为返回明确的结构体或枚举。
  • 💡 本 PR 夹带的 ci: fix PR checks ... 与 fix(selector): ProbeLines 取消确定性 两个提交与 #91 完全重复,建议提取为独立 PR。

📂 逐文件分析

backend/service/autoheal/heal.go

改动意图:自愈守护主体 —— 周期探活 + 重启 + 证书预警
问题分析:⚠️ 告警无去重(P1)、RestartMaxRetries 未使用(P1)、Start/Stop 生命周期缺陷(P1)、忽略 DB error(P2)、callback 事件类型发散(P2)
详细建议:

// 告警去重(示例:同证书同原因每天最多一次)
type alertKey struct{ id uint; reason string }
if last, ok := m.lastAlert[key]; !ok || time.Since(last) > 24*time.Hour {
    m.alertCert(reason, c.Name, c.Domains)
    m.lastAlert[key] = time.Now()
}

Start() 中改为 m.stopCh = make(chan struct{});probeOne 里接入重试计数与退避。

backend/main.go

改动意图:接线 autohealMgr,加入启动序列与 registerStopHandlers
问题分析:⚠️ 格式化(gofmt)不合规;接线本身正确,autohealMgr.Stop() 已加入关闭链。
详细建议:gofmt -w。

其余文件:backend/service/selector/selector.go 的 ProbeLines ctx 取消前置检查实现正确(消除了 select 在两条通道均就绪时随机选择导致跳过取消分支的竞态);.github/workflows/pr-checks.yml 的 artifact 传递修复方向正确,但两者都与自愈主题无关,建议拆分。

✅ 待处理清单

  • [P1] 证书告警去重(避免 7 天 2 万条)
  • [P1] 接入 RestartMaxRetries + 退避
  • [P1] Start() 重建 stopCh,修复重复启停缺陷
  • [P1] heal.go / main.go 执行 gofmt -w
  • [P2] BuildProbeFunc 检查并记录 Find 的 error
  • [P2] callback 事件类型收敛为固定枚举,天数放 payload
  • [P2] 拆分夹带的 CI / selector 提交

🎯 结论:🔄 Request Changes — 骨架与依赖注入设计合理;但「告警去重」和「重启上限」是自愈功能最关键的两个保护且当前都缺失/未生效,建议补齐后再合并

新增 backend/service/autoheal 模块实现系统级自愈守护:

- 周期性探活(frp_client/cftunnel/portforward):探测进程存活状态
- 自动重启:单进程失败后触发 RestartFn,失败写入审计日志
- 证书预警:扫描 DomainCert 表,≤7 天剩余发送 callback 告警
- 与 syslog/callback 集成:自愈动作记录审计条目并通过回调通道推送
- main.go 接入:初始化 autohealMgr 并在 Start/Stop 生命周期注册

验证:go build ./... 通过。
@suantea
suantea force-pushed the feat/system-selfheal branch from 582238c to a28262c Compare September 23, 2026 09:01

@pikachuren pikachuren left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @suantea 提交!
🤖 AI 自动审核声明。

🎯 结论

🔄 Request Changes — P1 问题需修复(重启无退避+健康检查失效)

📖 概要

系统自愈守护进程 · PR #59-3
核心改动:AutoHeal 引擎、health 端点检查、自动重启

🚨 关键问题

P1:

  • 💡 autoheal/engine.go attemptRestart 只有 5s 固定延迟,无退避策略,服务必现崩溃时会以最快速度死循环重启耗尽 CPU。
  • 💡 健康检查用 GET /system/health,该端点未在 PR #117 里实现,会 404 导致误判。

P2:重启动作无鉴权(engine 内部调用 mgr.Start,未经中间件校验)。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants