# ARM 应用健康监控上线 ## Goal 把已通过本地契约的公网 `/ready` 健康监控安全部署到现有 ARM 异机,启用 5 分钟 timer,并通过一次不影响 OA 用户的真实告警/恢复演练证明“持续故障能发现、重复故障会降噪、恢复会通知”。 ## Scope - 通过现有受控 SSH alias 连接 ARM;先做只读身份、sudo、systemd、既有备份 dead-man 和 webhook/helper 合同预检。 - 只向临时目录传输已提交版本的探测器、共用告警 helper 和独立安装器;校验源/目标哈希,不传输 webhook 正文。 - 复用既有 dead-man unit 中已经配置的 root-owned `0600` webhook 文件路径;路径只在 ARM 内解析和校验,不回显其内容。 - 先执行 `install` 安装 disabled units,核对精确受管目标、权限和 unit 合同,再手工运行一次真实公网健康探测。 - 健康基线通过后启用 5 分钟 timer;演练期间暂时停止 timer,使用同一已安装探测器、helper、webhook 和生产状态目录对本机不可达 HTTPS 端口连续执行三次:第一次 pending、第二次发送真实故障告警、第三次验证降噪。 - 随后通过正式 systemd service 探测真实公网 `/ready`,发送一次恢复通知,再恢复 timer 并核对 enabled/active、最近结果和下一次计划时间。 - 将输出压缩为脱敏状态、类别、计数、布尔检查和提交哈希;不记录 webhook、地址、原始响应、完整 journal 或远端绝对配置路径。 ## Confirmed Facts - 仓库提交 `bb5113c` 已实现并通过应用健康专项 22 项、自动备份兼容回归 73 项、后端 1018 项及前端构建。 - 安装器固定受管探测器、helper、状态目录和 systemd unit 名;`install` 默认 disabled,`uninstall` 保留共用 helper、webhook 和状态证据。 - ARM 已有 SSH alias,但当前 alias 不是 root;上线前必须证明非交互 sudo 可用,否则停止,不尝试口令或变更账户。 - webhook 不在仓库;既有 off-host dead-man service 是复用其路径的唯一事实源。 - 本任务已获用户确认:允许 ARM 安装、timer 启用、真实企微故障/恢复演练,并在失败时回滚应用健康组件。 ## Requirements - R1:任何写入前先完成只读预检;SSH 身份、`sudo -n`、既有 dead-man unit、root-owned `0600` webhook、Python/systemd 和公网 `/ready` 任一不满足即停止。 - R2:不得读取、打印或复制 webhook 正文;只允许把 ARM 内部已验证的 webhook 文件路径传给 root 安装器。 - R3:只安装提交版本的三个文件,并以 SHA-256 比对证明传输和受管目标一致;三个部署源相对 `HEAD` 必须无未提交改动,Trellis 任务记录可按正常流程保持未提交;不得从远端旧副本部署。 - R4:首次安装发现应用健康 unit、探测器或状态已存在时停止并报告,不覆盖未知运行状态;只有确认是本任务中刚创建的对象后才继续。 - R5:手工健康探测必须在启用 timer 前成功,且只保留稳定状态字段;不得用本地成功代替 ARM 公网链路成功。 - R6:真实故障演练不得停止 OA、Caddy、数据库、备份 receiver 或 dead-man;只使用 ARM 本机确定不可达的 HTTPS 端口,并在演练期间停止应用健康 timer 避免竞态。 - R7:第一次失败不得发送,第二次发送一条,第三次 helper 返回降噪;正式 service 恢复后发送一次恢复通知。 - R8:最终 timer 必须 enabled/active,最近一次 service 为成功,下一次计划不超过 5 分钟;备份 dead-man timer 仍保持原状态。 - R9:任一步失败时停止继续扩大变更。若尚未发送故障告警,卸载本任务组件;若已经发送故障告警,优先恢复正式探测并发送恢复通知,失败则保持 timer 停用并明确报告未闭环告警。 ## Acceptance Criteria - [x] AC1:只读预检证明受控 alias 可连接、`sudo -n` 可用、既有 dead-man/webhook/helper 合同成立,且未输出秘密或完整配置路径。 - [x] AC2:ARM 上安装的探测器和共用 helper 与提交版本哈希一致;unit/状态目录权限符合合同,install 后 timer 明确 disabled/inactive。 - [x] AC3:ARM 手工正式 `/ready` 探测成功,probe state 为健康且输出无地址、路径、payload 或原始异常。 - [x] AC4:timer 启用后为 enabled/active,调度周期为 5 分钟且最近正式执行成功。 - [x] AC5:受控演练证据显示第一次 pending、第二次 sent=true、第三次 sent=false;群内只产生一条脱敏故障告警。 - [x] AC6:正式 service 恢复成功且 sent=true,后续健康执行 sent=false;群内只产生一条恢复通知。 - [x] AC7:备份 receiver、dead-man unit/timer、归档与状态均未被卸载或覆盖;备份兼容合同在上线前仍通过。 - [x] AC8:临时传输目录已清理;最终只保留已批准受管文件、独立状态和 units,工作树与审计记录不含秘密。 - [x] AC9:用户确认实际收到一条故障告警和一条恢复通知;在此确认前只报告“自动链路证据通过,人工收件待确认”。 ## Out of Scope - 修改 OA、Caddy、数据库、DNS、证书、Compose 或公网部署。 - 停止真实服务来制造故障。 - 修改备份 receiver/dead-man 配置、归档、timer 或 webhook 内容。 - 新建机器人、变更企业微信配置、发送业务数据或原始日志。 - 增加证书到期预警、业务登录/审批合成监控或第三方 dead-man。 ## Test Plan - 上线前复跑 `bash scripts/qa/run-app-health-monitor-contract.sh` 与 `bash scripts/qa/run-automatic-backup-contract.sh`。 - ARM 只读预检、源/目标哈希、权限、unit 内容和 state schema 检查均返回有限布尔/状态 token。 - 先 disabled 安装和健康手工执行,再 enable;演练用同一正式状态链完成 pending/send/dedup/recovery。 - 结束后检查 timer、service、备份 dead-man 保持情况、临时目录清理、Trellis/task 治理和 `git diff --check`。 ## Key Decisions - 使用“不影响 OA 的本机不可达端口”制造监控输入故障,不停止任何真实服务。 - 复用正式状态目录做一次完整状态机演练,timer 在演练窗口暂停,避免健康执行插入失败序列。 - webhook 路径从既有 dead-man unit 在 ARM 内解析,正文始终不读取、不传输、不回显。 - 用户已批准操作范围,但 Trellis 仍要求本规划摘要之后再明确批准实施。 ## Execution Evidence - 2026-07-27:本地应用健康专项 22 项、自动备份兼容回归 73 项通过;三个部署源相对 `HEAD` 无未提交改动,SHA-256 已记录为脱敏技术证据。 - 2026-07-27:受控 SSH alias 可连接,但首个 `sudo -n true` 预检返回“需要密码”;流程按 R1 在任何远端写入前停止。 - 未创建 ARM 临时目录、未上传文件、未修改 systemd、未读取 webhook、未发送真实企微消息。 - 本地 SSH 配置中未发现指向同一 ARM 的 root alias。继续执行需要用户在 ARM 侧提供非交互 root 运维入口;不得在聊天中发送 sudo/root 密码。 - 2026-07-27:用户在自己的终端建立临时 sudoers 入口后,完整只读预检通过;disabled 安装、提交哈希、权限和 ARM 公网健康基线均通过,健康基线未发送消息。 - 2026-07-27:timer 已启用;隔离演练依次得到 pending、`sent=true`、`sent=false`,正式 systemd service 恢复成功并更新为 `PASS_APP_HEALTH_READY`,重复健康为 `sent=false`。 - 2026-07-27:最终 timer enabled/active、下一次运行在 5 分钟内、最近 service 成功;备份 dead-man enabled/active 未变。临时上传目录和临时 sudoers 规则均已删除。 - 自动链路已证明一条故障和一条恢复发送成功;随后由用户完成实际群内收件确认。 - 2026-07-27:用户确认企微群实际收到一条“内部 OA 运维告警”和一条“内部 OA 运维恢复通知”,真实收件闭环完成。