Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ const FEATURES = [
},
]

const DOING = ["服务器基础信息", "网络延迟", "流量统计"]
const DOING = ["服务器基础信息", "网络延迟", "流量统计", "掉线、流量与到期通知"]

const NOT_DOING = [
"web terminal",
"远程 SSH",
"通知与告警",
"负载告警",
"插件系统",
"ICMP / HTTP 探测",
"agent 自动更新",
Expand Down
240 changes: 240 additions & 0 deletions src/content/config/notify.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
hub 可以把节点掉线、流量快用完、快到期这几件事推给你。渠道有两个:Telegram 机器人,以及一个可以
自定义请求体的 Webhook。两个都配上就两边都发。

全部在 hub 上完成,agent 不用升级。

## 通知哪些事

| 事件 | 什么时候发 | 在哪开关 |
|---|---|---|
| 🔴 离线 | 节点断开超过宽限期(默认 3 分钟) | **每个节点单独开**,默认关 |
| 🟢 恢复在线 | 发过离线通知的节点重新连上 | 跟着离线走 |
| ⚠️ 流量提醒 | 本期用量达到阈值(默认 80%)和 100% 时各一次 | 节点填了每月流量额度就生效 |
| ⏳ 即将到期 | 每天 9 点汇总一条,列出 7 天内到期的节点 | 节点填了到期日就生效 |
| 🔁 已自动续期 | 在线节点过了到期日,hub 把日期往后顺延时 | 同上 |
| 🔑 面板登录 | 应急密码或 GitHub 登录成功 | 全局开关,默认开 |

几条规则:

- **宽限期内断开又连上的,什么都不发。** 恢复通知只跟在离线通知后面,网络抖一下不会刷屏。
- **反复掉线的节点会自动安静下来。** 节点在最近 1 小时内有过一次超过宽限期的掉线,下一次掉线要持续
**30 分钟**才报;稳定在线满 1 小时后恢复正常宽限期。真掉线仍然会报,只是最多晚 30 分钟。模拟一台
「在线 1 分钟、掉 4 分钟」来回反复的机器,一小时从 23 条降到 2 条。几十秒的断开(比如重启 agent)不算。
- **同时发生的事合并发。** hub 每 30 秒检查一次,同一轮里新离线的节点合成一条、恢复的合成一条、
流量超标的合成一条。hub 自己断网时,40 台节点在模拟里是 2 条「N 台节点离线」,不是 40 条。
- **重启 hub 不会重发。** 已经报过离线的节点记在数据库里,hub 重启后不会再报一遍,节点回来时照样发恢复。
hub 停机期间掉线的节点,hub 起来后过了宽限期也会补报。
- **流量按节点的计费方式算**(上下行相加、取较大值、仅上行、仅下行),周期按重置日算,和面板显示的
是同一个数。流量换了周期会重新计。
- **只报登录成功,不报失败。** 失败尝试谁都能发起,报失败等于让别人往你的 Telegram 里刷屏。失败有
登录限流管着,见[登录与安全](/config/auth)。
- **一条消息最多列 20 台节点**,其余写成「……另外 N 台」。Discord 一条消息上限 2000 字符、企业微信
2048 字节,超了整条会被拒收。
- **每条通知最多送一次。** 渠道暂时不通时会重试,重试完仍失败就丢弃,不会等渠道恢复后补发。
- **不做 CPU、内存、磁盘这类负载告警。**

<Note warn>
**hub 自己停机或断网时,什么通知都发不出来。** 探针没法报告自己的死亡。要盯住 hub 本身,用一个外部
拨测服务(UptimeRobot、Uptime Kuma 之类)定时访问 hub 的地址。
</Note>

<Note>
「每天 9 点」和消息里的时间都按 hub 所在机器的时区算。Docker 部署记得设 `TZ`,否则是 UTC 的 9 点,
见 [Docker 部署](/install/docker)。
</Note>

## 配置

后台左侧的**通知**页。填好渠道并保存,再点右上角**发送测试**(测试用的是已保存的配置),每个渠道的报错
会原样显示出来。企业微信、钉钉、飞书是例外,见下面「[常见服务的写法](#常见服务的写法)」。

然后在同一页的**离线通知**卡片里打开要盯的节点,可以全部打开;单台节点的编辑弹窗里也有这个开关。
流量和到期提醒不用单独开。

事件卡片里三个数字:

| 设置 | 默认 | 范围 |
|---|---|---|
| 离线宽限期 | 3 分钟 | 1–1440 |
| 流量提醒 | 80% | 0–100,填 0 关闭 |
| 到期提醒 | 7 天 | 0–365,填 0 同时关闭到期和续期通知 |

<Note>
宽限期从 hub **发现连接断开**时算起,hub 每 30 秒检查一次,所以宽限期 3 分钟时,通知在断开后
3 到 3.5 分钟之间到达,和 agent 的上报间隔无关。agent 进程退出、机器正常重启时连接会立刻关闭;
机器直接断电、断网时连接不会正常关闭,hub 最长要 150 秒才发现,通知相应更晚。
</Note>

## Telegram

1. 找 [@BotFather](https://t.me/BotFather) 发 `/newbot`,按提示起名,拿到形如 `123456:ABC-DEF…`
的 token。
2. 拿 Chat ID:
- **发给自己**:先给你的机器人随便发一句话,再打开
`https://api.telegram.org/bot<token>/getUpdates`,找 `"chat":{"id":…}` 里的数字。
- **发到群组**:把机器人拉进群,在群里发一句话,同样看 `getUpdates`。群组的 ID 是负数,通常以 `-100` 开头。
- **发到公开频道**:把机器人设为频道管理员,Chat ID 填 `@频道名`。
3. 两项填进面板,保存,发送测试。

消息是纯文本,默认第一行是标题,下面是正文:

```text
🔴 香港 · 甲商家 离线
最后上报 09-15 20:13 +08:00
```

想改格式就改**消息模板**,占位符见下面「[自定义内容](#自定义内容)」。

hub 要能访问 `api.telegram.org`。访问不了时给 hub 进程设 `HTTPS_PROXY` 环境变量,它会让 hub 的**所有**
出站请求都走这个代理(包括转发 agent 二进制、GitHub 登录、查国家码):

```bash
systemctl edit monitor-hub # 加两行后保存:[Service] 和 Environment=HTTPS_PROXY=http://127.0.0.1:7890
systemctl restart monitor-hub
```

Docker 部署在 `docker run` 里加 `-e HTTPS_PROXY=http://…`。

## 自定义内容

Telegram 有一个**消息模板**,Webhook 有一个**请求体模板**,两个渠道各一个,所有事件共用。每类事件的
标题和正文 hub 已经写好,模板只决定怎么排、加什么前后缀。填写时输入框下面会用一条离线通知实时预览。

| 占位符 | 内容 |
|---|---|
| `{{title}}` | 标题,如 `🔴 香港 · 甲商家 离线`、`⚠️ 香港 · 甲商家 流量提醒` |
| `{{message}}` | 正文,如 `最后上报 09-15 20:13 +08:00`;多台节点时一行一台 |
| `{{node}}` | 涉及的节点名,多台用 `, ` 隔开;登录和测试为空 |
| `{{event}}` | `offline` `online` `traffic` `expiry` `renew` `login` `test` 之一 |
| `{{site}}` | 设置页里的站点名称,默认 `Monitor` |
| `{{time}}` | 通知产生的时间,如 `09-15 20:16 +08:00`;离线通知比断开晚一个宽限期 |

不认识的 `{{…}}` 原样保留。模板清空后保存,恢复默认。

**有多个 hub** 时,把站点名称设成能区分的名字,模板里加上 `{{site}}`:

```text
[{{site}}] {{title}}
{{message}}
```

**钉钉、飞书要求消息包含关键词**时,把关键词固定写在模板开头,每条消息才都带着它,
写法见下面「[常见服务的写法](#常见服务的写法)」。

## Webhook

hub 以 `POST` 发送,`Content-Type: application/json`,请求体按模板生成。请求头里写了 `Content-Type`
的话会替换掉默认值。

**URL 要填最终地址。** hub 不跟随跳转:`http://` 地址被跳到 `https://`、或者少了末尾的 `/`,发送测试会
直接报 `301 Moved Permanently`,把 URL 改成它跳去的地址即可。跟随跳转的话,POST 会被改成不带内容的
GET,对方照样回 200,面板显示发送成功而消息根本没到;请求头里的凭证也会被带到跳转后的主机。

在请求体模板里,占位符的值会按 JSON 字符串转义(引号、反斜杠、换行都处理了),所以**必须写在引号
里面**。写错了预览会直接提示;保存时 hub 也会用一组带引号和换行的样本代入一遍,不是合法 JSON 就
拒绝保存。

默认请求体:

```json
{"event":"{{event}}","node":"{{node}}","title":"{{title}}","message":"{{message}}"}
```

请求头可选,一行一个,写成 `Name: value`,用来带鉴权。

### 常见服务的写法

**Discord**:URL 填频道的 Webhook 地址。

```json
{"content":"{{title}}\n{{message}}"}
```

**Slack**:URL 填 Incoming Webhook 地址。

```json
{"text":"{{title}}\n{{message}}"}
```

**企业微信群机器人**:URL 填 `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=…`。

```json
{"msgtype":"text","text":{"content":"{{title}}\n{{message}}"}}
```

**钉钉群机器人**:URL 填 `https://oapi.dingtalk.com/robot/send?access_token=…`。安全设置选**自定义关键词**,
只填一个固定的词(比如 `探针`),再把它写在模板开头。别挑只出现在部分消息里的词:
「通知」只在测试消息里有,「节点」只在多台汇总里有,测试能收到,单台节点的离线、恢复却会被拒收。
**加签**要按时间戳实时算签名,这里做不到。

```json
{"msgtype":"text","text":{"content":"【探针】{{title}}\n{{message}}"}}
```

**飞书群机器人**:同样不能开签名校验,安全设置用**自定义关键词**,做法和钉钉相同。

```json
{"msg_type":"text","content":{"text":"【探针】{{title}}\n{{message}}"}}
```

<Note warn>
企业微信、钉钉、飞书拒收消息时(key 或 token 填错、关键词不匹配)仍然返回 HTTP 200,错误码写在响应内容里,
hub 分辨不出来,**发送测试**照样显示已发送。配这三家时,以群里真的收到测试消息为准。
</Note>

**Bark**:URL 填 `https://api.day.app/push`。

```json
{"device_key":"你的 key","title":"{{title}}","body":"{{message}}"}
```

**ntfy**:URL 填 `https://ntfy.sh`(或自建地址),需要鉴权时请求头加 `Authorization: Bearer …`。

```json
{"topic":"你的 topic","title":"{{title}}","message":"{{message}}"}
```

**Gotify**:URL 填 `https://gotify.example.com/message`,请求头加 `X-Gotify-Key: 应用 token`。

```json
{"title":"{{title}}","message":"{{message}}"}
```

## 凭证不回读

Bot Token、Webhook URL、Webhook 请求头这三项保存后面板读不回来,只显示「已设置」。输入框留空保存
表示不改;要停用一个渠道,点那张卡片上的**清除**;只想删掉请求头,点**清除请求头**。

Webhook URL 也算凭证:Discord、Slack、企业微信、钉钉、Bark 的地址本身就是密钥,拿到就能往你的频道
里发消息。

<Note warn>
数据库备份里包含这些凭证。导出的备份文件按密钥保管。
</Note>

## 收不到通知

先点**发送测试**:各渠道的错误会直接显示在面板上(企业微信、钉钉、飞书除外,看群里有没有收到)。常见的:

| 报错 | 原因 |
|---|---|
| `telegram: 401 Unauthorized` | token 填错,或者机器人被删了 |
| `telegram: 400 Bad Request` … `chat not found` | Chat ID 不对,或者你还没给机器人发过消息、机器人不在群里 |
| `telegram: 403 Forbidden` … `bot was blocked by the user` | 你屏蔽了机器人 |
| `webhook: 301 Moved Permanently …` | URL 会跳转,改成它跳去的地址,见上面 Webhook 一节 |
| `webhook: 4xx …` | 服务端拒收,后面跟着服务端自己的错误信息 |
| `error sending request` | hub 连不上那个地址 |

测试能收到、真实事件收不到:

- 离线通知要在**离线通知**卡片里打开对应节点,默认是关的。
- 离线通知来得晚:节点最近 1 小时内有过一次超过宽限期的掉线,这一次要掉满 30 分钟才报,见上面「反复掉线的节点」。
- 流量提醒要节点填了**每月流量额度**,到期提醒要节点填了**到期日**。
- 钉钉、飞书只收到一部分:关键词没有出现在每条消息里,见上面「钉钉群机器人」。
- 看 hub 日志。连不上、对方 5xx 或限流(429)时隔 10 秒再试,最多 3 次;token 错、地址跳转这类重试
也没用的错误不重试。最终没送出去的每条记一行:

```bash
journalctl -u monitor-hub | grep 'not delivered' # 二进制部署
docker logs monitor 2>&1 | grep 'not delivered' # 容器部署
```
3 changes: 2 additions & 1 deletion src/content/dev/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ POST /api/nodes/{id}/token PUT /api/nodes/{id}/traffic
GET /api/ping-tasks POST /api/ping-tasks
DELETE /api/ping-tasks/{id}
GET /api/settings PUT /api/settings
POST /api/notify/test
GET /api/themes POST /api/themes
DELETE /api/themes/{short} GET /api/themes/{short}/preview
POST /api/themes/{short}/update
Expand All @@ -126,7 +127,7 @@ POST /api/db/restore POST /api/db/vacuum
| 表 | 作用 |
|---|---|
| `setting` | key/value 配置,替代配置文件 |
| `node` | 节点配置 + agent 上报的静态信息 |
| `node` | 节点配置 + agent 上报的静态信息,含离线通知的开关与状态 |
| `traffic` | **单调递增的流量累计**。1:1 于 node,但每次上报都写 |
| `metric` | 历史明细,每节点每分钟一行,按保留天数删 |
| `ping_task` / `ping_node` | 探测任务及其节点分配 |
Expand Down
2 changes: 1 addition & 1 deletion src/content/guide/philosophy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ hub 只有四个命令行参数:`--listen`、`--db`、`--themes`、`--site`。

| 不做 | 原因 |
|---|---|
| 通知与告警 | 探针的输出是数据,告警是另一个系统的事 |
| 负载告警 | 阈值、持续时长、节点范围是一整套规则引擎。通知只做掉线、流量、到期、登录这几件判定明确的事 |
| 远程 SSH / web terminal | 只读的观察者被攻破,和能执行命令的工具被攻破,代价差一个数量级 |
| 插件系统 | 等于把第三方代码请进 hub 的进程 |
| ICMP / HTTP 探测 | TCP 一种够用,三种要三套超时语义、三套图例 |
Expand Down
1 change: 1 addition & 0 deletions src/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const nav: Section[] = [
title: "配置",
items: [
{ path: "/config/auth", label: "登录与安全", desc: "应急密码与 GitHub 单点登录的配置,以及登录不通时的排查路径。", keywords: "github oauth sso 登录 密码 应急 白名单 callback" },
{ path: "/config/notify", label: "通知", desc: "Telegram 与 Webhook 推送掉线、流量、到期和登录,以及常见服务的请求体写法。", keywords: "通知 告警 telegram tg bot webhook discord slack 钉钉 企业微信 飞书 bark ntfy gotify 离线 掉线 到期" },
{ path: "/config/traffic", label: "流量统计", desc: "三个流量数字的算法、周期与配额口径,以及和商家对不上的原因。", keywords: "流量 traffic 重置日 月流量 计费 sum max 上行 下行 配额" },
],
},
Expand Down