服务器健康哨兵

SKILL运维和基础设施
运维和基础设施 未标注权限 许可证不明确
安装这个技能

给线上服务器补「能自己报警」的能力——零依赖健康巡检 + 企微告警 + 跨机互检 + 服务自愈 + 可视化仪表盘。当用户提及「加监控」「没监控」「服务挂了没人知道」「健康检查」「健康巡检」「告警」「报警」「推送到手机」「企微机器人」「robot webhook」「监控告警」「哨兵」「自动重启」「服务不会自己起来」「定时检查服务器」「磁盘满了」「证书快过期」「想随时看到服务器状态」「仪表盘」「看板」,或刚做完服务器迁移/上线、需要补齐可观测性时,自动加载本技能。核心场景:服务正常运行但没人盯着——挂了、磁盘满、证书到期、数据损坏,全靠用户反馈才发现。提供可直接复用的 636 行 bash 哨兵脚本与完整踩坑规避,含 8 个极隐蔽的坑(exec 吞 stderr、ssh 吞 stdin、告警签名含时间戳、并行编辑覆盖、命令组退出码致 mv 不执行、对端告警回声放大、heredoc 吞反斜杠、未定义变量静默死亡),以及资源快照采集与告警解耦、百分比指标固定纵轴绘制要点、Windows 一键刷新脚本的纯 ASCII 铁律。已沉淀自真实双机生产实战。

一句话选型结论

按资料字段整理

服务器健康哨兵是运维和基础设施类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类运维和基础设施 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_2185f23a/server-health-sentinel
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

运维和基础设施
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_2185f23a仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量31.5 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
查服务与日志

按现象定位服务状态与日志线索。

写部署与巡检脚本

把重复的部署、巡检写成脚本。

故障排查

按步骤排查网络、依赖与配置。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在运维和基础设施场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
服务器健康哨兵当前 未标注权限 未标注 未标注 未标注 有
Azure 部署 调用外部服务 GitHub 仓库 664.2 万 Codex、Claude、Cursor 有
Azure diagnostics 问题管理 调用外部服务 GitHub 仓库 657.6 万 Codex、Claude、Cursor 有
Azure aigateway 部署 调用外部服务 GitHub 仓库 655.3 万 Codex、Claude、Cursor 有
Azure AI 搜索 调用外部服务 GitHub 仓库 651.4 万 Codex、Claude、Cursor 有
Azure Cloud migrate 部署 调用外部服务 GitHub 仓库 650.7 万 Codex、Claude、Cursor 有

SKILL.md 全文

31.5 KB
模型实际读到的说明书原文,来自技能仓库

服务器健康哨兵 SOP

> 适用前提(30 秒判断):Linux + systemd + root;只需 bash/curl/openssl(零外部依赖)。
> 单机能用:配置里去掉 PEER_SSH 即单机模式;双机互检需两台机器 SSH 互通(部署脚本自动建互信)。
> 适用:云主机/物理机上的 Web、API、Node/PM2 类服务。不适用:Windows 主机、K8s 集群(应选专门方案)。
> 显式调用:关键词没触发时,直接说「用服务器健康哨兵巡检 <主机/域名>」即可。

快速导航(按需跳读)

你想干什么 看哪
赶紧部署跑起来 二、快速部署
只有一台机器 二 + FAQ-Q1(去掉 PEER_SSH 即可)
换服务器 / 换环境 二·换环境清单 + references/example-configs.md(三套完整示例)
报错了要排查 十一、FAQ + 六、故障注入验证
给用户交付/汇报 十、交付清单模板

> 核心命题:服务跑起来了 ≠ 有人在看着它。宕机、磁盘满、证书过期、数据损坏,
> 在"没有任何监控"的机器上永远是用户先发现——而用户发现问题时,损失已经发生。
>
> 本技能的目标是用零外部依赖的方式补齐这块,且不增加运维负担(正常时完全安静)。


〇、能力披露与部署前确认(安全必读)

本技能会在目标机器上实际执行以下三类操作,部署前请逐项确认:

能力 具体动作 影响范围
SSH 远程执行 跨机互检读取对端 state.txt;deploy_sentinel.sh 经 SSH 安装脚本/配置 仅你配置的 PEER_SSH 与部署清单内的主机
cron 安装 写入 /etc/cron.d/sentinel(每 5 分钟以 root 执行一次巡检) 仅此一条 cron;撤销方式 rm /etc/cron.d/sentinel
外联 Webhook 异常告警 POST 到你配置的企微群机器人地址 仅异常时外联;不配置 Webhook 则完全不出网

不做的事:无遥测、无数据外传、无第三方上报——告警只发到你自己填写的群机器人;本地日志只留异常内容。

部署前必须确认(deploy_sentinel.sh 会交互式要求输入 YES;自动化场景需显式 AUTO_CONFIRM=1):

  1. 目标主机是你自己的资产(IP / SSH 账户核对无误)
  2. SSH 使用哨兵专用密钥(默认 ~/.ssh/sentinel_ed25519、各节点 /root/.ssh/sentinel_ed25519),不复用个人密钥
  3. 告警接收范围:Webhook 发到哪个群、群里有谁、是否适合出现这些运维信息

SSH 安全默认值(相对"裸免密"的加固,也是安全扫描的整改口径):

  • 不硬编码个人密钥:密钥路径一律走配置(SSH_KEY),默认指向哨兵专用密钥
  • 主机密钥校验不关闭:StrictHostKeyChecking=accept-new(首连记录指纹、之后变更即拒)+ 专用 known_hosts 文件;高安全环境可预置 known_hosts 后改 yes
  • 互信条目带来源限制:from=&lt;两节点IP&gt;,no-pty,no-agent-forwarding,no-port-forwarding
  • 无口令密钥的说明:cron 无人值守巡检需要免密钥,这是自动化前提。缓解手段:专用密钥 + 专用账户 + from= 来源限制 + IdentitiesOnly=yes,退役时删除密钥即可;高安全环境可用 ssh-agent 带口令托管
  • 建议专用低权限账户:为哨兵建独立账户(如 sentinel),自愈命令经 sudoers 白名单放开——sentinel ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl restart myapp,并把 SSH_USER/PEER_SSH 改为该账户;自愈命令越多,白名单越要逐条收紧

一、四条设计原则(先定原则,再写代码)

原则 含义 反面教训
零依赖 只用 bash + curl + coreutils + openssl,不装 Prometheus/Zabbix 小机器(2G 内存)装监控套件本身就是负担
异常才出声 正常时完全静默,不写日志不推送 每 5 分钟一条"一切正常",一周后没人再看
自愈优先 服务掉线先尝试自动拉起,拉不起来才告警 半夜宕机,等早上人来了再修,白丢 8 小时
交叉互检 每台机器检查对端;一台彻底宕机时由另一台代报 单机自检的根本缺陷:自己挂了就无法给自己报警

关于"自愈"的一个关键认知

systemd 的 Restart=always 只管"进程退出",管不了"进程活着但不响应"(僵死/hang)。
哨兵必须额外做 HTTP 探测,把僵死也纳入重启触发条件——这是 systemd 覆盖不到的一层。


二、快速部署(3 步)

# 1) 装脚本 + 配置
install -m 755 sentinel.sh /opt/sentinel/sentinel.sh
cp sentinel.conf.example.txt /etc/sentinel.conf

# 2) 挂 cron(每 5 分钟;正常时静默,所以日志不会涨)
cat &gt; /etc/cron.d/sentinel &lt;&lt;&#x27;EOF&#x27;
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
*/5 * * * * root /opt/sentinel/sentinel.sh &gt;&gt; /opt/sentinel/sentinel.log 2&gt;&amp;1
EOF

# 3) 跑一次并看结果
/opt/sentinel/sentinel.sh
cat /opt/sentinel/state.txt

> 现成脚本在本技能 scripts/ 目录下:sentinel.sh、scripts/sentinel.conf.example.txt(配置模板,按节点改 IP/域名/服务名)、deploy_sentinel.sh(一键部署含双向互信、部署前确认目标与告警范围)、
> pull_states.sh + build_report.py + refresh_report.sh(本地仪表盘)。
> Windows 一键刷新启动器源码在 references/refresh-dashboard-bat.md(复制另存为 .bat 双击即用)。
> 部署到新环境时只需改配置里的 IP / 域名 / 证书路径 / 服务名;SSH 一律用哨兵专用密钥(SSH_KEY),勿硬编码个人密钥。

配置模板要点

NODE_NAME=&quot;node-a-web&quot;
SERVICES=&quot;nginx myapp&quot;          # systemd 管的服务
CHECK_PORTS=&quot;3001&quot;                  # ★ 端口探测:覆盖 PM2/裸进程管理的服务
CHECK_URL_LOCAL=&quot;https://127.0.0.1/&quot;      # 走本地,绕开 DNS 与外网
CHECK_URL_LOCAL_HOST=&quot;app-a.example.com&quot;       # ★ 必须带 Host 头,否则命中默认站点
CHECK_URL_PUBLIC=&quot;https://app-a.example.com/&quot;  # 真实用户视角
CERT_FILE=&quot;/etc/nginx/cert/app-a.example.com.pem&quot;
JSON_CHECK_FILES=&quot;/opt/myapp/data.json&quot;   # JSON 损坏会让服务起不来,必须提前发现
LOG_DIRS=&quot;/opt/myapp/logs /var/log/nginx&quot;
PEER_SSH=&quot;root@192.0.2.12&quot;      # 交叉互检的目标
SSH_KEY=&quot;/root/.ssh/sentinel_ed25519&quot;   # 哨兵专用密钥(勿用个人密钥)
SSH_STRICT=&quot;accept-new&quot;                 # 主机密钥校验:首连记录指纹、之后变更即拒
SSH_KNOWN_HOSTS=&quot;/root/.ssh/sentinel_known_hosts&quot;
WECOM_WEBHOOK=&quot;&quot;                    # 群机器人地址,留空则只写本地日志

为什么 CHECK_PORTS 不可省:systemctl 查不到 PM2 / 裸进程管理的服务。
常见的 web(3000) 和 api(8080) 若由 PM2 管理,只查 systemd 会漏检。

换环境改哪里(就 2 处,有清单不怕漏)

# 改什么 在哪改 说明
1 两台机的 IP 命令行参数:bash deploy_sentinel.sh deploy &lt;节点A-IP&gt; &lt;节点B-IP&gt;;拉数据 bash pull_states.sh &lt;ip1:tag1&gt; &lt;ip2:tag2&gt; 脚本里的 192.0.2.x 只是占位示例,不必进脚本里改
2 业务配置 /etc/sentinel.conf 一个文件:服务名/端口/探测 URL/证书路径/PEER_SSH/Webhook 换机必改项全在这

可选:SSH_USER(专用低权限账户)、NODE_NAME、各类阈值。
三套可直接照抄的完整配置(节点 A / 节点 B / 单机)见 references/example-configs.md。
部署前先跑 bash deploy_sentinel.sh check &lt;A-IP&gt; &lt;B-IP&gt; 做环境自检——每一项都给"过/不过 + 原因"。


三、架构:交叉哨兵与回声放大防护

   ┌─────────────────┐                    ┌─────────────────┐
   │  节点 A 192.0.2.11  │                    │  节点 B 192.0.2.12  │
   │  自检 10 类      │─── SSG 拉取 ─────▶ │  自检 10 类      │
   │  写 state.txt    │◀── 对端 state ──── │  写 state.txt    │
   │  推送企微         │                    │  推送企微         │
   └─────────────────┘                    └─────────────────┘
        发现对方不通 / 停止 / 有告警 → 代为报警

对端检查做三件事:
1. SSH 不通 → 「疑似整机故障」
2. 对端 ts 超过 PEER_STALE_MIN(20 分钟)未更新 → 「哨兵已停摆」(能抓到"机器活着但哨兵死了")
3. 对端有告警 → 本机代为转发

★★★ 回声放大:交叉哨兵的头号陷阱

错误做法:把对端告警也写进自己的 state。结果:

轮1  A 报「B 不可达」
轮2  B 读到 A 的告警 → 报「A 报告 B 不可达」
轮3  A 读到 B 的告警 → 报「B 报告 A 报告 B 不可达」
轮4  ……字符串逐轮变长,永远无法回到正常

正确做法:对端告警放进独立变量 PEER_ALERTS,绝不写入自己的 state;
state 里只保留本机真实状态(另记一个 peer_alerts=&lt;计数&gt; 供仪表盘参考,
但读取方只解析 alert_N,忽略 peer_alerts)。

ALERTS=&quot;&quot;        # 本机异常 → 写入 state
PEER_ALERTS=&quot;&quot;   # 对端转来的异常 → 只用于推送,永不写入 state
add_peer() { ... }   # 与 add_alert 分离

验证收敛的方法:清空两台 state,连跑 4 轮。
- ❌ 错误实现:每轮字符串变长,永远 ALERT
- ✅ 正确实现:第 1~2 轮可能有一次性提示,第 3 轮起稳定静默、verdict=OK


四、检查项清单(10 类)

# 检查项 抓什么 关键实现
1 服务存活 进程掉了 systemctl is-active + 失败自动 restart
2 端口监听 PM2/裸进程服务掉了 ss -lntH 查内核监听表(不产生 TCP 连接)
3 本机 HTTP 进程活着但不响应(僵死) curl 走 127.0.0.1 + Host 头
4 公网 HTTPS 域名/证书/带宽/防火墙问题 curl 走真实域名 + TTFB 阈值告警
5 磁盘 撑满 df -P 取值比 /
6 内存 / Swap OOM 前兆 /proc/meminfo 的 MemAvailable;Swap 使用率
7 证书到期 到期后全站报错 openssl x509 -enddate 算剩余天数
8 数据完整性 JSON 损坏致服务起不来 node -e &quot;JSON.parse(...)&quot;
9 日志体积 日志吃盘 du -sm + 无轮转时顺手补清理任务
10 对端互检 另一台整机故障 见第三节

> ⚠️ 检查项 4 的 TTFB 不要只做"是否超时",记录成趋势(ttfb.log),
> 性能退化是渐进过程,只有趋势能提前发现。

4.1 资源快照:采集与告警必须解耦 ★ 2026-09-17 补

踩过的坑:第一版只做阈值告警 —— 磁盘/内存不超阈值就什么都不写。
结果仪表盘上完全看不到机器有多少核、内存多大、负载多少。
用户问"怎么不显示 CPU 配置",才发现"配置运行情况"整体缺失。

正确做法:独立一段无条件采集,写进 state 的 metrics 对象;阈值判断仍归检查项 5/6。
两者职责分离,仪表盘永远有数据,告警逻辑也不被污染。

采集项(全部零依赖,纯 /proc + df + ps):

项 来源 备注
CPU 使用率 /proc/stat 两次采样取差 最准;别用 top -bn1(首帧是开机均值,假的)
CPU 型号 / 核数 /proc/cpuinfo model name、nproc
负载 1/5/15 /proc/loadavg 必须按核数归一化:2 核 load1=2 就是满载
内存 / Swap /proc/meminfo 总量 + 已用 + 可用 + 百分比
磁盘 df -P 注意 -P 保证单行输出(长设备名会折行)
网络速率 /proc/net/dev 汇总除 lo 外所有网卡;rx=入 tx=出
TCP 连接数 /proc/net/tcp[6] 状态 01 反映真实并发,比"在线人数"诚实
进程数 / 运行时长 /proc 计数、/proc/uptime
Top N 进程 ps -eo rss,pmem,pcpu,comm --sort=-rss 回答"谁在吃资源"

★ 采样窗口必须复用:CPU 和网卡都要"两次采样取差",共用一个 sleep,别各等一次。

CS1=$(cpu_snap); NS1=$(net_snap); sleep 1; CS2=$(cpu_snap); NS2=$(net_snap)
# 网卡汇总(除 lo):split($2,f,&quot; &quot;) 中单空格是 awk 的&quot;按空白切分&quot;特例
net_snap() {
  awk -F: &#x27;/:/ { iface=$1; gsub(/[ \t]/,&quot;&quot;,iface); if (iface==&quot;lo&quot;) next;
                n=split($2, f, &quot; &quot;); rx+=f[1]; tx+=f[9] }
      END { printf &quot;%d %d&quot;, rx+0, tx+0 }&#x27; /proc/net/dev
}

4.2 资源趋势(res.log)

和 TTFB 同样思路:每次巡检追加一行,保留最近约 2016 行(7 天)。

ts cpu% mem% disk% load1 rx_mbps tx_mbps tcp_est

拿不到小数第二位的取样窗口会导致带宽趋势失真,公式 (Δbytes×8)/秒/1e6。

> ★★ 百分比类指标必须用固定纵轴(0–100)画图。
> 自适应纵轴会把"全程 3% 的平稳线"画得和"90% 的告警线"一样高,完全误导。
> 计数类(TCP)和速率类(带宽)才用自适应。给 sparkline 加 lo/hi 参数即可。

4.3 仪表盘上怎么呈现

  1. 资源条(三级配色 <70% 绿 / 70–85% 橙 / >85% 红):CPU、内存、磁盘、Swap(有才显示)
  2. 规格行:CPU 型号×核数 · 负载 · 运行时长 · 进程数 · 出网带宽 · TCP 连接 · Swap
    - 无 Swap 时标橙色「未设置」 —— 轻量机常默认无 Swap,是隐患,要显眼
  3. Top 3 进程表:名称 / 内存 / 占比 / CPU
  4. 资源趋势:CPU / 内存 / 出网带宽 / TCP 连接数 四张小图,2 列栅格,窄屏单列

验证要验到像素:用 Playwright 读 getBoundingClientRect().width,
确认"条长 ÷ 轨道长 ≈ 百分比"。这是唯一能证明"图表没骗人"的方法。
> 实测:CPU 2% → 21/1034px、内存 24% → 248/1034px、磁盘 32% → 331/1034px,全部吻合。


五、八个致命坑(都极隐蔽,实测踩过)

坑 1|exec 9&gt;file 2&gt;/dev/null 把脚本 stderr 永久丢进黑洞 ★最坑

exec 不带命令时,其重定向永久作用于当前 shell。所以 2&gt;/dev/null 会让
脚本后续所有 stderr 消失。叠加 set -u 后,任何未定义变量都变成"静默死亡":
无任何输出、退出码 1,日志里什么都查不到。

症状:脚本跑到一半就没了;state.txt 只生成 .txt.tmp。

# ❌ 错误
exec 9&gt;&quot;${STATE_DIR}/.lock&quot; 2&gt;/dev/null
# ✅ 正确:只重定向需要的 fd
exec 9&gt;&quot;${STATE_DIR}/.lock&quot;

配套防御:所有计数变量提前初始化(N_ALERT=0; N_PEER=0; ...),
让 set -u 不会因为将来漏赋值而炸掉整轮。

坑 2|在被 ssh host &#x27;bash -s&#x27; &lt;&lt;EOF 喂入的脚本里再调 ssh,必须加 -n

子 ssh 默认吃掉父进程的 stdin(即 heredoc 剩余的行),导致外层会话提前 EOF。
症状同样是"跑到一半就没了、退出码异常",极易误判为脚本 bug。

# ✅ 内层 ssh 一律加 -n;密钥走配置(哨兵专用),主机密钥校验不关闭
POUT=$(ssh -n -i &quot;$SSH_KEY&quot; -o IdentitiesOnly=yes -o BatchMode=yes \
          -o StrictHostKeyChecking=&quot;$SSH_STRICT&quot; -o UserKnownHostsFile=&quot;$SSH_KNOWN_HOSTS&quot; \
          &quot;$PEER_SSH&quot; &quot;cat $STATE_TXT&quot; 2&gt;/dev/null)

> 推论:测试方式本身会触发真 bug。"换个方式跑就好了"不等于没问题,
> 反而说明脚本对执行环境有隐含依赖,必须修掉。

坑 3|告警去重签名绝不能包含时间戳

消息体里带 HH:MM → 跨分钟后签名就变 → 去重永远失效、每轮都推,等于没做节流。

# ❌ SIG=$(printf &#x27;%s&#x27; &quot;$MSG&quot; | md5sum ...)     # MSG 含时间
# ✅ 签名只取&quot;问题内容&quot;
SIG_SRC=&quot;v=$VERDICT a=$N_ALERT w=$N_WARN h=$N_HEAL p=$N_PEER
$ALERTS
$WARNS
$PEER_ALERTS&quot;
SIG=$(printf &#x27;%s&#x27; &quot;$SIG_SRC&quot; | md5sum | cut -d&#x27; &#x27; -f1)

节流策略:新问题 → 立即出声;同一问题 → 每 REALERT_EVERY 轮(默认 6 = 30 分钟)重提一次;
恢复正常 → 发一条"已恢复"并清空签名。

坑 4|同一文件的多处编辑不可并行发出

并行编辑基于同一份快照,后写的会覆盖先写的。后果是部分改动静默丢失,
而且 bash -n 语法检查照样通过,表面完全看不出来。

症状:明明改了、语法也对,运行时却报 unbound variable 或行为不符预期。

规则:
1. 同一文件多处修改严格串行
2. 改完必须做要素点检——grep 关键标识符并对比期望次数:

chk() { local k=&quot;$1&quot; exp=&quot;$2&quot;; local got=$(grep -c -- &quot;$k&quot; &quot;$F&quot;);
        [ &quot;$got&quot; -eq &quot;$exp&quot; ] &amp;&amp; printf &quot;✅ %s %s\n&quot; &quot;$k&quot; &quot;$got&quot; \
        || printf &quot;❌ %s %s (期望 %s)\n&quot; &quot;$k&quot; &quot;$got&quot; &quot;$exp&quot;; }
chk &quot;port_listening&quot; 2
chk &quot;add_peer&quot; 4
chk &quot;N_PEER&quot; 6

坑 5|{ ...; [ cond ] &amp;&amp; cmd; } &gt; tmp &amp;&amp; mv tmp final 的退出码陷阱

命令组末尾若是 [ cond ] &amp;&amp; cmd 且 cond 为假,整组退出码为 1 → &amp;&amp; mv 永不执行,
状态文件静默写不出来。

# ❌ 末尾条件为假时,mv 不执行
{ ...; [ -n &quot;$WARNS&quot; ] &amp;&amp; printf ...; } &gt; &quot;$TMP&quot; &amp;&amp; mv &quot;$TMP&quot; &quot;$FINAL&quot;
# ✅ 分两行,不依赖退出码
{ ...; true; } &gt; &quot;$TMP&quot; 2&gt;/dev/null
mv -f &quot;$TMP&quot; &quot;$FINAL&quot; 2&gt;/dev/null

坑 6|--dry-run 不是所有工具的通用参数(证书/巡检场景常见)

acme.sh 没有 --dry-run(那是 certbot 的)。
验证要选"零副作用且真能证伪"的方式,例如证书续期就手工实测 HTTP-01 挑战通道:
往 webroot 放随机 token → 从公网 curl 取回 → 比对一致即通道可用。

坑 7|heredoc 里写 JS/JSON 会吞掉反斜杠(2026-09-17)

cat &gt; x.js &lt;&lt;&#x27;EOF&#x27; ... EOF 即使加了引号,经工具层传递后 \\ 仍会变成 \,
path.replace(/\\/g, &#x27;/&#x27;) 直接变成 /\/g → SyntaxError。
→ 写 Node/JS/Python 脚本一律用文件写入工具,不要用 heredoc 拼。

坑 8|声明了变量却在几百行后才用 —— 用要素点检兜住

本次新增资源快照时,趋势行里引用了一个最终没定义的名字(LOAD_CPU_NORM)。
bash -n 语法检查完全查不出来(它是合法变量名),
靠"grep 关键标识符计数是否等于 0"抓到。
→ 每轮改动后跑一次要素点检(见第五节坑 4 的检查函数),它比语法检查有效得多。


六、验证方法:故障注入(不可省略)

只验"正常时安静"是不够的——必须验"异常时真报得出来"。

# 1) 备份配置,把阈值改成必然触发
cp /etc/sentinel.conf /tmp/sc.bak
sed -i &quot;s/^DISK_THRESHOLD=.*/DISK_THRESHOLD=1/&quot; /etc/sentinel.conf
rm -f /opt/sentinel/last_sig /opt/sentinel/same_count

# 2) 第一次:应出声
/opt/sentinel/sentinel.sh        # 期望:报「磁盘 / 使用率 xx%(阈值 1%)」

# 3) 第二次:应被去重节流(静默)
/opt/sentinel/sentinel.sh        # 期望:无输出;same_count=2

# 4) 恢复:应静默并清空签名
cp /tmp/sc.bak /etc/sentinel.conf
/opt/sentinel/sentinel.sh
[ -s /opt/sentinel/last_sig ] &amp;&amp; echo &quot;❌ 签名未清理&quot; || echo &quot;✅ 已清理&quot;

逐项都要注入验证,不能只测一项就以为整条链路可信:

注入手法 期望告警
DISK_THRESHOLD=1 磁盘使用率超阈值
CHECK_PORTS 加不存在的 9999 端口未在监听
JSON_CHECK_FILES 指向损坏文件 数据文件 JSON 损坏
CERT_FILE 指向不存在的路径 证书文件不存在
临时 systemctl stop nginx 服务停止 + 已自动重启(验证自愈)
断开对端 SSH(改错 PEER_SSH) 对端不可达(验证交叉互检)

七、企微告警推送

方式 A:群机器人 Webhook(最简单,推荐)

企微群 → 右上角 → 群机器人 → 添加 → 复制 Webhook 地址,填入配置:

WECOM_WEBHOOK=&quot;https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxx&quot;
push_wecom() {
  [ -z &quot;$WECOM_WEBHOOK&quot; ] &amp;&amp; return 0
  local body
  body=$(printf &#x27;%s&#x27; &quot;$1&quot; | sed &#x27;:a;N;$!ba;s/\n/\\n/g&#x27; | sed &#x27;s/&quot;/\\&quot;/g&#x27;)
  curl -s --max-time 10 -H &#x27;Content-Type: application/json&#x27; \
    -d &quot;{\&quot;msgtype\&quot;:\&quot;text\&quot;,\&quot;text\&quot;:{\&quot;content\&quot;:\&quot;$body\&quot;}}&quot; \
    &quot;$WECOM_WEBHOOK&quot; &gt;/dev/null 2&gt;&amp;1
}

> JSON 里换行必须转成 \n、双引号要转义,否则推送 400。

方式 B:自建应用消息(复用已有 corpid/secret,需应用有发消息权限)

用已有 corpid/secret 换 access_token 后调发消息接口,touser 填接收人 userid。

方式 C:留空 = 只写本地日志

留空不影响功能(巡检照常、state 照常写),只是不推送。适合先跑起来、后配推送。


八、可视化仪表盘

pull_states.sh 拉两台 state.json + ttfb.log 到本地 data/,
build_report.py 生成自包含 HTML(无 CDN 依赖):总览横幅 + 每机卡片
(verdict 徽章 / 告警清单 / 响应时间 sparkline)+ 自动深浅色适配。

bash refresh_report.sh           # 刷新,不打开
bash refresh_report.sh --open    # 刷新并自动打开浏览器

> 值得单独做成仪表盘的原因:状态文件是给机器读的,人能一眼看懂的只有图。
> 巡检数据全在 state.json 里,不聚合展示就白采集了。

★ 交付时必须讲清"这是快照,不会自动更新"

这是最容易让用户误解的一点:仪表盘是个静态 HTML,双击打开后数据永远停在生成那一刻,
看着像实时监控,其实不是。不主动讲清,用户会以为"在看着实时状态",出事后才发现看到的是半小时前的。

两条改进(都要做):
1. 页面顶部加"数据时效"横幅:显示"数据截至 MM-DD HH:MM(N 分钟前)";
超过 15 分钟自动变黄底并提示刷新。让用户一眼知道快照有多旧。
2. 给一个双击就能用的入口(见下),别要求用户记命令。

一键刷新入口(给非技术用户的正确交付方式)

真正的实时告警靠服务器上的 cron + 企微推送;本地仪表盘只是"想看时才看"的视图。
所以本地入口必须做到双击即可:

① refresh_report.sh --open —— 用 explorer.exe &lt;Windows路径&gt; 打开,
中文文件名安全(explorer 直接收参数,不经 shell 解析)。

② refresh_dashboard.bat —— 双击即用,三步:拉取 → 生成 → 打开浏览器。
源码随附于 references/refresh-dashboard-bat.md(复制全文另存为 .bat,内容纯 ASCII)。

> ### ★★★ Windows .bat 的唯一铁律:内容必须纯 ASCII
> 中文版 Windows 的 cmd 默认代码页是 GBK(936),而 .bat 若以 UTF-8 写入,
> 里面的中文路径会变成乱码,表现为"双击没反应"或"找不到文件"。
> 对策:bat 里一个中文字都不写 —— 中文文件名通过 bash 内部处理(bash 是 UTF-8 环境),
> bat 只负责"找到 bash 并调用脚本"。开头加 chcp 65001 &gt;nul 让 bash 的中文输出正常显示。
> bat &gt; @echo off &gt; chcp 65001 &gt;nul &gt; set &quot;BASH=C:\Program Files\Git\bin\bash.exe&quot; &gt; set &quot;SCRIPT=/c/Users/&lt;user&gt;/.../refresh_report.sh&quot; &gt; &quot;%BASH%&quot; -lc &quot;bash %SCRIPT% --open&quot; &gt; if errorlevel 1 pause &gt;
> 校验方法:LC_ALL=C grep -cP &#x27;[^\x00-\x7F]&#x27; file.bat 必须为 0。
> 另:bat 里要多路径探测(用户自装 Git → 内置 PortableGit),避免单点失效;
> 失败时 pause 让用户看得到错误,成功时不必停留。

③ 桌面入口 —— 把 .bat 复制到桌面即可(脚本内已用绝对路径,放哪都能跑)。
> ⚠️ 不要用 PowerShell 的 WScript.Shell COM 去建 .lnk —— 会被安全策略拦下
> (COM 对象实例化可执行任意代码)。直接 cp 一个 .bat 更简单也更可靠。

Git Bash 调原生 Python 的路径坑

/c/Users/... 形式的路径原生 Python 不认,需转 Windows 形式:

DIR_WIN=&quot;$(cd &quot;$(dirname &quot;$0&quot;)&quot; &amp;&amp; pwd -W 2&gt;/dev/null || echo &quot;$DIR&quot;)&quot;
DIR_WIN=&quot;${DIR_WIN//\//\\}&quot;   # 统一反斜杠:显示规范,explorer 也更稳
&quot;$PY&quot; &quot;$DIR_WIN/build_report.py&quot;

九、顺手要补的运维缺口(有监控也要治理)

发现机器"没有任何自动维护"时,除加哨兵外还应补:

# 1) 应用日志无轮转 → 定期清理(按应用写入格式调整)
cat &gt; /etc/cron.d/app-log-clean &lt;&lt;&#x27;EOF&#x27;
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 3 * * * root find /opt/&lt;app&gt;/logs -maxdepth 1 -name &quot;access-*.ndjson&quot; -mtime +30 -delete
EOF

# 2) journald 无上限 → 设限额(老机器上实测能涨到 190M+)
mkdir -p /etc/systemd/journald.conf.d
cat &gt; /etc/systemd/journald.conf.d/limits.conf &lt;&lt;&#x27;EOF&#x27;
[Journal]
SystemMaxUse=300M
MaxRetentionSec=30day
EOF
systemctl restart systemd-journald

> 注意 /opt/&lt;app&gt;/logs/access-YYYY-MM-DD.ndjson 这类按天命名的日志,
> 不适合用 logrotate(会被重命名成 .1 反而乱),用 find -mtime 定期删更干净。


十、交付清单模板

类别 内容
交付物 巡检脚本、两份配置、部署脚本、仪表盘生成器、HTML 报告、一键刷新启动器(.bat 源码见 references/,另存即用,可放桌面)、真浏览器校验脚本
部署位置 各机 /opt/sentinel/sentinel.sh + /etc/sentinel.conf + /etc/cron.d/sentinel
运行时产物 各机 /opt/sentinel/:state.txt state.json(含 metrics)ttfb.log res.log
验证记录 ①故障注入逐项结果(正常静默 / 异常出声 / 去重节流 / 恢复清理 / 交叉互检收敛)②仪表盘像素级校验(条长÷轨道长 ≈ 百分比)
治理补建 日志清理 cron、journald 限额
待用户动作 提供群机器人 Webhook 即可推送到手机

汇报时要说清三件事:①现在出问题会怎么被发现(链路);
②仪表盘是快照、怎么刷新(双击哪里);③还需要他做什么才能收到手机推送(Webhook)。
若用户提"加上 CPU/配置情况",说明第 4.1 节的资源快照没做——采集与告警必须解耦。


十一、常见问题(FAQ)

Q1|只有一台机器,能用吗?
能。/etc/sentinel.conf 里 PEER_SSH=&quot;&quot; 留空即单机模式,其余功能(巡检/告警/自愈/仪表盘)全部照常,交叉互检自动跳过。

Q2|换服务器 / 换环境要改哪些地方?
就 2 处:①命令行传新 IP(deploy / pull_states 都支持传参);②/etc/sentinel.conf 里的业务配置。照抄版配置见 references/example-configs.md。先跑 check 自检再部署。

Q3|收不到手机推送,怎么排查?
按顺序三查:①/etc/sentinel.conf 的 WECOM_WEBHOOK 填了没有;②群机器人还在不在群里(被移除则地址失效);③本地是否产生过告警——正常时本来就完全静默,先手动造一个异常(如临时停个服务)验证链路。

Q4|cron 没跑怎么查?

grep sentinel /etc/cron.d/sentinel     # 条目在不在
systemctl status cron 2&gt;/dev/null || systemctl status crond   # 服务起没起
/opt/sentinel/sentinel.sh &amp;&amp; cat /opt/sentinel/state.txt      # 手动跑一遍看结果

Q5|跨机互检报"不可达",是什么原因?
看告警括号里的原因:rc=255 是 SSH 连接层(网络不通 / 认证被拒 / 密钥不对 / 主机指纹变更);可达但状态为空 是对端哨兵没装或没跑。bash deploy_sentinel.sh check 能逐项定位。

Q6|Webhook 外联会泄露数据吗?
不会多发一个字节:只在异常时把告警文本 POST 到你自己填写的群机器人地址,无遥测、无第三方上报;WECOM_WEBHOOK 留空则完全不出网。

Q7|关键词没触发技能怎么办?
显式调用:直接说「用服务器健康哨兵巡检 <主机/域名>」。

Q8|怎么停用 / 卸载?

rm /etc/cron.d/sentinel                      # ① 立即停用(巡检不再跑)
rm -rf /opt/sentinel /etc/sentinel.conf      # ② 卸载文件
rm /root/.ssh/sentinel_ed25519*              # ③ 删互检密钥,并到对端 authorized_keys 删对应行

Q9|误报太多 / 太吵怎么调?
调 /etc/sentinel.conf 阈值:DISK_THRESHOLD、TTFB_ALERT_MS、MEM_AVAIL_MIN_MB、CERT_WARN_DAYS;REALERT_EVERY 控制同一问题重推间隔(6 = 30 分钟)。

Q10|仪表盘数据为什么不更新?
它是生成时刻的快照,不会自动刷新。Windows 双击 refresh_dashboard.bat(源码见 references/refresh-dashboard-bat.md)或跑 scripts/refresh_report.sh 重新生成。


[2026-10-06] ★★ 应用日志目录过阈值(WARN「日志目录已达 xxxMB」)的标准处置

判别:生产机哨兵 WARN 日志目录 /opt/myapp/logs 已达 520MB(阈值 500MB)。
磁盘通常还很空(本例 49G 用 22%)⟹ 是"该轮转了",不是告急,不用慌。

已落地的轮转设施(生产机 192.0.2.11):
- 脚本:/root/log_rotate.sh [days=2] —— N 天前的 access-*.ndjson 逐个
gzip -c &gt; archive/xx.gz → gzip -t 校验通过才删原文件(失败自动保留原文件);归档保留 90 天。
- cron:30 3 * * *(保留今天+昨天,稳态 ≈ 50~80MB)。
- journald 已限 200M(/etc/systemd/journald.conf.d/size.conf)。
- 归档在 /opt/myapp/logs/archive/,没删数据,zcat/gunzip 随时可查。

其他机器遇到同样 WARN:把 /root/log_rotate.sh 抄过去改 LOGDIR 即可(脚本幂等:.gz 已存在即跳过)。

★ 踩坑:paramiko 长任务超时 ≠ 任务失败

exec_command(cmd, timeout=300) 压 124 个文件(476MB)超了通道超时——
SSH 通道断开,但远端 bash 继续跑完了。处理原则:
1. 超时后先回去查现场(残留 .tmp 数、已完成 .gz 数、pgrep 进程),再决定是否重跑;
2. 重跑也安全(脚本幂等),但别盲目重发覆盖;
3. 更长任务用 nohup cmd &gt; log 2&gt;&amp;1 &amp; 挂后台,再轮询结果。

常见问题

5 问
答案由本站依据条目字段整理
服务器健康哨兵怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_2185f23a/server-health-sentinel。安装方式为未标注。装完刷新技能列表或重开客户端。

服务器健康哨兵支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

服务器健康哨兵权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

服务器健康哨兵收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

服务器健康哨兵用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。