内容
# 通知中心
[](./LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org)
[](./docker-compose.yml)
[](./clients/mcp/install.md)
[](#贡献)
自托管、免费、多通道通知网关。发送一条消息并附上令牌,它会异步地通过持久队列分发到您启用的每个通道:ntfy、Telegram、Email、Slack、Discord、WhatsApp 或通用 webhook。该项目旨在为 [Claude Code](#claude-code-hook) hook 提供通知服务,以便在无需阻塞 Claude 的情况下,将“任务启动”/“任务完成”/“Claude 需要您”通知推送到各个项目中。
## 为什么
Claude Code(或任何长时间运行的脚本)会让您在终端前等待数分钟,却无法得知任务何时完成或需要输入。通知中心是一个小型、100% 免费、自我托管的服务,您只需运行一次(`docker compose up`),即可将“发送推送通知”转换为一行 HTTP 调用,并通过 Redis 支持的队列进行解耦,队列具有重试和每个通道的死信功能。
## 架构
```
客户端(curl / hook 脚本)
-> POST /notify(Bearer 令牌) [Fastify API]
-> 入队调度作业 [Redis / BullMQ]
-> 调度工作程序解析通道集
-> 每个通道的投递作业 [Redis / BullMQ]
-> 投递工作程序通过通道适配器发送
(ntfy / telegram / email / slack / discord / whatsapp / webhook)
```
- **API** 仅验证 + 入队;它永远不会直接发送,因此缓慢/损坏的通道不会使 `/notify` 接口阻塞。
- **工作程序** 执行实际的分发和投递;每个通道都有自己的作业,因此重试/失败是按通道隔离的(一个通道的故障不会影响其他通道)。
- **通道是可插拔的**:每个通道都实现相同的接口(`send(notification)`);启用一个通道只需配置开关 + 凭证,添加一个全新通道只需编写一个小型适配器文件(参见通用 `webhook` 适配器作为参考示例)。
## 快速开始
```bash
git clone https://github.com/richardfcampos/notify-hub.git && cd notify-hub
./scripts/setup-env.sh # 引导式设置:提示每个通道的凭证(隐藏输入),生成网关令牌,写入 .env 文件并赋予 600 权限。或者手动执行:
# cp .env.example .env && $EDITOR .env
docker compose up -d --build
curl http://localhost:8080/health
# => {"status":"ok","redis":true}
```
发送通知:
```bash
curl -X POST http://localhost:8080/notify \
-H "Authorization: Bearer <your-token-from-TOKENS>" \
-H "Content-Type: application/json" \
-d '{"title":"notify-hub","message":"hello from notify-hub"}'
# => 202 {"jobId":"..."}
```
`POST /notify` 接口接受:
| 字段 | 必需 | 备注 |
| ----- | -------- | ----- |
| `message` | 是 | 非空字符串 |
| `title` | 否 | 默认值为 `"Notification"` |
| `priority` | 否 | 可选值为 `low`、`default`、`high`、`urgent` |
| `tags` | 否 | 字符串数组,传递给支持它的通道(例如 ntfy) |
| `channels` | 否 | 要针对的通道子集;省略则使用令牌的默认通道 |
| `metadata` | 否 | 自由格式对象,传递给通道适配器(例如 `webhook` 通道) |
响应:`202 {jobId}`(已入队)· `400`(无效请求体 / 未知通道名称)· `401`(缺少/未知令牌)· `503`(队列不可达,因此调用者不会阻塞)。
## 配置
所有配置均通过环境变量实现(参见 [.env.example](./.env.example) 获取所有键值)。主要配置项:
- `PORT` -- API 监听端口(compose 映射 `${PORT:-8080}` 到主机)。
- `TOKENS` -- `;`-分隔的 `name:token:defaultChannel1,defaultChannel2` 条目。例如:`phone:supersecrettoken:ntfy,telegram;desktop:othertoken:discord`。
- `CHANNELS_ENABLED` -- 以逗号分隔的通道列表,启用例如 `ntfy,telegram,discord`。**未列出的通道永远不会尝试**,即使请求要求它。**未配置的通道缺少必需凭证,服务会拒绝启动**,并指定通道和缺少的键值 -- 因此错误配置立即被捕获,而不是以后静默丢弃。
## 通道
每个通道在 `CHANNELS_ENABLED` 中启用时需要的环境变量键值。
| 通道 | 环境变量键值 | 设置备注 |
| ------- | -------- | ----------- |
| `ntfy` | `NTFY_URL`, `NTFY_TOPIC` | 使用 `https://ntfy.sh`(公共)或自托管的 ntfy 服务器;在 ntfy 应用中订阅主题 |
| `telegram` | `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` | 通过 [@BotFather](https://t.me/BotFather) 创建机器人;通过发送消息然后点击 `getUpdates` 获取聊天 ID |
| `email` | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_TO` | 任意 SMTP 提供商(Gmail 应用密码、SendGrid 等) |
| `slack` | `SLACK_WEBHOOK_URL` | Slack 应用 -> 传入 Webhooks -> 添加到通道 |
| `discord` | `DISCORD_WEBHOOK_URL` | 服务器通道设置 -> 集成 -> Webhooks |
| `whatsapp` | `WHATSAPP_PHONE`, `WHATSAPP_APIKEY` | 通过 [CallMeBot](https://www.callmebot.com/blog/free-api-whatsapp-messages/) 获取免费个人 API -- 消息机器人激活,限速 |
| `webhook` | `WEBHOOK_URL` | 引用可扩展性适配器:POST 完整的通知 JSON 到您控制的任意 URL(Gotify、自定义监听器等) |
添加全新通道:实现 `NotificationChannel` 接口(一个 `send()` 方法)在 `src/channels/adapters/` 中,导出 `ChannelRegistryEntry`(工厂 + 必需环境变量键值),并在 `src/channels/channel-registry.ts` 中添加一行。不需要其他核心更改。
## Claude Code hook
零依赖的 hook 脚本将“启动”/“结束”/“需要输入”事件推送到通知中心全局,跨越每个 Claude Code 项目。参见 [`clients/claude-code/install.md`](./clients/claude-code/install.md) 获取完整设置(settings.json 片段 + 环境变量)。
## MCP 服务器
通知中心还作为 MCP(模型上下文协议)服务器通过 stdio 提供,暴露三个工具 -- `send_notification`、`list_channels`、`check_gateway_health` -- 因此代理(Claude Code、Claude Desktop、任意 MCP 客户端)可以将通知推送并检查网关作为工具调用而非手工 HTTP。 它是已经运行的网关的薄客户端(无直接 Redis 访问)。参见 [`clients/mcp/install.md`](./clients/mcp/install.md) 获取完整设置(`claude mcp add` 命令 + 通用 `mcpServers` JSON 配置)。
## 管理面板
用于管理上述所有内容的本地、暗黑主题的仪表板,无需手动编辑 `.env`:
- 查看每个通道(ntfy、Telegram、Email、Slack、Discord、WhatsApp、webhook),切换启用/禁用,编辑其凭证 -- 默认掩码,点击一次显示。
- 管理令牌配置文件(`TOKENS`):添加/删除,编辑令牌,选择每个配置文件的默认通道。
- **保存并应用** 验证,备份 `.env`(带时间戳),写入新文件,并运行 `docker compose up -d --no-build api worker` -- 一键操作,无需终端。
- **发送测试** 每个通道发送真实通知并显示实际投递结果(✅ 已发送,或失败原因 ❌),而非仅“已入队”。
- 网关状态(健康检查,Redis,活动通道)和最近工作程序投递的尾部。
随堆栈自动启动,无需额外步骤:
```bash
docker compose up -d
# => http://127.0.0.1:8081
```
`npm run admin` 仍然作为主机侧开发替代方案(在自身迭代时无需重建 Docker):
```bash
npm run admin
# => 管理面板:http://127.0.0.1:8081
```
**可达性:** 在 compose 中,主机侧绑定默认为 `0.0.0.0`(像其他服务在典型 homelab 主机上一样),因此面板可从其他设备访问 -- 例如通过 Tailscale tailnet 作为 `http://<machine>:8081`。**面板无身份验证,显示机密**,因此任何可以访问端口的人都可以读写配置:请将此范围限制在信任的网络中(WireGuard/Tailscale tailnet 是一个合适的选择;不受信任的 LAN 不适合)。在 `.env` 中设置 `ADMIN_BIND=127.0.0.1` 以使其仅限本地访问。显式绑定模板由 `src/admin/compose-invariants.test.ts` 断言。主机侧开发模式(`npm run admin`)默认绑定到 `127.0.0.1`。
**Docker-socket 权衡:** `admin` 服务挂载 `/var/run/docker.sock` 以便 Save & Apply 可以在容器内运行 `docker compose up -d --no-build api worker` 针对真实堆栈(它从不重建 `admin` 服务 -- 那会杀死容器 mid-请求)。这赋予管理容器对主机 Docker 守护进程的控制权,与 Portainer 等工具使用的模式相同。出于上述可达性说明,接受该面板是个人工具,在信任的网络上 -- 结合可达性说明,请将“谁可以打开面板”视为“谁可以管理此 Docker 主机”。
## 开发
```bash
npm install
npm run build # tsc -> dist/bin/{api,worker,admin}.js, 复制
# src/admin/ui -> dist/admin/ui(静态 UI 资产)
npm run test # 完整套件 -- 需要 Docker(启动 redis:7-alpine
# via testcontainers 进行 BullMQ 重试/死信
# 集成测试);设置 REDIS_TEST_URL 以重用
# 运行的 Redis 而不是容器
npm run test:unit # 无 Docker 的快速子集(仅 src 单元测试)
npm run test:integration # 仅 Redis 支持的队列集成测试
npm run dev:api # tsx,无构建步骤
npm run dev:worker
```
## 验证
`docker compose up -d --build` 已在真实 ntfy.sh 主题上运行端到端:
- `docker compose ps` -- 所有 3 个服务(`redis`、`api`、`worker`)启动,`api` 健康检查 `healthy`。
- `curl http://localhost:8080/health` -> `200 {"status":"ok","redis":true}`。
- `curl -X POST http://localhost:8080/notify -H "Authorization: Bearer <token>" -d '{"title":"notify-hub","message":"smoke test"}'`
-> `202 {"jobId":"1"}`。
- 工作程序日志:`{"channel":"ntfy","msg":"sending notification"}` 后跟
`{"channel":"ntfy","msg":"notification sent"}`。
- 在 ntfy 端确认:`curl "https://ntfy.sh/<topic>/json?poll=1"`
返回确切的消息:`{"title":"notify-hub","message":"smoke test", ...}`。
如果您的环境无法访问公共互联网(ntfy.sh),请将 `NTFY_URL` 指向自托管的 ntfy 实例并重复相同的测试步骤。
## 贡献
欢迎贡献 -- 最有用的贡献是新的通道适配器。每个通道都是一个小型自包含文件,实现一个接口:
1. 添加 `src/channels/adapters/<name>-channel.ts` 实现
`NotificationChannel`(一个 `send(notification)` 方法)及其
`ChannelRegistryEntry`(工厂 + 必需环境变量键值)。
2. 在 `src/channels/channel-registry.ts` 中注册它。
3. 添加单元测试(使用 `test/helpers/fakes.ts` 中的伪造对象 -- 测试中无真实网络)。
4. `npm run test:unit` 必须通过;打开 PR。
想法:Gotify、Matrix、Pushover、Signal、Mattermost、Rocket.Chat、SMS 网关。错误报告和文档修复同样受到赞赏 --
[打开问题](https://github.com/richardfcampos/notify-hub/issues)。
## 许可证
[MIT](./LICENSE) © Richard Campos