跳转到内容

通知扩展:Telegram 等第三方通知 ​

OMV 的告警默认只会发邮件,但邮件常常被淹没在收件箱里。读完这一篇,你能把同样的告警同时转发到 Telegram,并且明白这套机制是怎么工作的,以后换成别的渠道也只改最后一行。

开始之前 ​

  • 先在 系统 → 通知 → 设置(System → Notification → Settings)里把邮件通知配置好并发出一封测试邮件,见 配置邮件通知。这套机制建立在邮件通知之上,邮件通知没有启用,脚本就不会被触发。
  • 你需要一个 Telegram 账号。
  • 命令在 OMV 的 shell(root 身份)里执行,不写 sudo。
  • OMV 本身依赖 wget 和 jq,下面的脚本只用它们,不需要额外安装软件。

脚本写错会让通知投递失败

影响范围:脚本在邮件投递过程中运行。如果它以非零状态退出,Postfix 会把这封内部邮件当作投递失败,可能重试或退信;脚本输出的内容也可能出现在退信里。

前置条件:脚本不管发送是否成功,都要以 exit 0 结束,并且不要向标准输出写东西,需要记录就用 logger。Token 不要写进脚本,也不要写进会被分享的任何地方。

怎么回退:删除脚本文件即可,OMV 自己的邮件通知不受影响。

通知是怎么触发脚本的 ​

这套机制来自 OMV 的源码,/usr/share/openmediavault/notification/sink.d/README 里有说明:

  1. 启用邮件通知后,OMV 把 Postfix 配置成:所有从通知发件人发出的邮件,都密送一份给内部地址 [email protected]。
  2. 这个内部地址由一个专用的 Postfix 投递通道(omvnotificationfilter)处理,它调用 /usr/share/openmediavault/notification/postfix-runner.sh,并且以系统用户 openmediavault-notify 身份运行。
  3. postfix-runner.sh 读取邮件,拆出收件人、主题、日期和正文,然后用 run-parts 依次运行 sink.d 目录里的所有可执行文件。

所以 OMV 发出的每一封邮件,包括 S.M.A.R.T.、RAID、监测、更新、认证相关的告警,以及你自己的计划任务邮件,都会触发这个目录里的脚本一次。脚本拿到这些输入:

输入内容
第 1 个参数 $1正文文件的路径,已去掉邮件头
第 2 个参数 $2发件人地址
第 3 个参数 $3收件人地址
环境变量 OMV_NOTIFICATION_FROM、OMV_NOTIFICATION_RECIPIENT发件人、收件人
环境变量 OMV_NOTIFICATION_SUBJECT、OMV_NOTIFICATION_DATE邮件主题、日期
环境变量 OMV_NOTIFICATION_MESSAGE_FILE正文文件的路径,和 $1 相同

出于安全考虑,脚本以专用用户 openmediavault-notify 运行,这个用户的登录 shell 是 nologin,只能读它有权限读的文件。后面存放 Token 的配置文件就要给它读权限。

两个细节:

  • 文件名请只用字母、数字、下划线和连字符,不要带 .sh 这样的扩展名。运行脚本用的是 run-parts --lsbsysinit,名字不符合它的规则就会被悄悄忽略,没有任何提示。
  • 这套机制按「每封邮件一次」触发,不区分告警类型。想只转发一部分,在脚本里按主题过滤,见下文。

创建 Telegram 机器人 ​

  1. 在 Telegram 里找到官方的 @BotFather,发送 /newbot,按提示起名。它会给你一串 Token,格式像 123456:ABC-DEF...。
  2. 打开你新建的机器人,点 Start,随便发一条消息。
  3. 在 OMV 的 shell 里查你的聊天 ID。先用 read 把 Token 读进变量,这样它不会出现在命令历史里:
sh
read -rs TOKEN          # 粘贴 Token 后按回车,屏幕上不会回显
wget -qO- "https://api.telegram.org/bot${TOKEN}/getUpdates" | jq '.result[].message.chat.id'
unset TOKEN

输出的数字就是聊天 ID。如果结果是空数组,说明机器人还没收到消息,回到第 2 步再发一条。这个接口的详细说明见 Telegram Bot API。

保存 Token ​

把 Token 和聊天 ID 放在单独的配置文件里,脚本从它读取:

sh
install -m 640 -o root -g openmediavault-notify /dev/null /etc/omv-telegram.conf
nano /etc/omv-telegram.conf

在文件里写入两行,把尖括号里的内容换成你自己的值:

sh
TG_TOKEN='<机器人 Token>'
TG_CHAT_ID='<聊天 ID>'

文件的权限是 640,属主 root,属组 openmediavault-notify:root 能改,通知用户能读,其他用户读不到。

创建脚本 ​

脚本放在 sink.d 目录里,文件名 50telegram:

sh
nano /usr/share/openmediavault/notification/sink.d/50telegram

内容如下:

sh
#!/bin/sh
# 由 postfix-runner.sh 以 openmediavault-notify 用户调用
# $1 正文文件  $2 发件人  $3 收件人;主题等信息在 OMV_NOTIFICATION_* 环境变量里

CONF=/etc/omv-telegram.conf
[ -r "$CONF" ] || exit 0
. "$CONF"
[ -n "$TG_TOKEN" ] && [ -n "$TG_CHAT_ID" ] || exit 0

# 可选:只转发你关心的告警,其余忽略。去掉下面几行的注释,并按需修改关键词
# case "$OMV_NOTIFICATION_SUBJECT" in
#   *SMART*|*"event on /dev/md"*|*"I/O error"*|"Monitoring "*|*"Errors were detected"*) ;;
#   *) exit 0 ;;
# esac

text="[$(hostname)] ${OMV_NOTIFICATION_SUBJECT}

$(head -c 3500 "$1")"

payload=$(jq -n --arg chat "$TG_CHAT_ID" --arg text "$text" \
  '{chat_id: $chat, text: $text}') || exit 0

wget -q -O /dev/null --timeout=15 --tries=1 \
  --header='Content-Type: application/json' \
  --post-data="$payload" \
  "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
  || logger -t omv-telegram "sending to Telegram failed"

exit 0

保存后设置权限:

sh
chown root:root /usr/share/openmediavault/notification/sink.d/50telegram
chmod 755 /usr/share/openmediavault/notification/sink.d/50telegram

几点说明:

  • head -c 3500 把正文截到 3500 字节,因为 Telegram 单条消息的文本上限是 4096 个字符。
  • 没有设置 parse_mode,消息按纯文本发送,不用处理特殊字符的转义。
  • exit 0 在每条路径上都保留,原因见前面的警示块。
  • Token 在 wget 的命令行里,同一台机器上的其他用户在发送的那几秒钟内,可以通过进程列表看到它。NAS 上通常没有不受信任的本地用户,但你要知道这一点。

测试 ​

先手动运行脚本,用通知用户的身份,不依赖邮件:

sh
printf '这是一条测试消息\n' > /tmp/omv-test-body
runuser -u openmediavault-notify -- env OMV_NOTIFICATION_SUBJECT='手动测试' \
  /usr/share/openmediavault/notification/sink.d/50telegram /tmp/omv-test-body sender recipient
rm /tmp/omv-test-body

Telegram 里收到消息,说明脚本、Token 和网络都没问题。

再走完整流程:到 系统 → 通知 → 设置,点页面下方的测试(Test)按钮,它会发一封测试邮件,同时应该触发 Telegram 消息。

检查结果 ​

  • Telegram 里收到了来自你机器人的消息,开头是 [主机名] 邮件主题。
  • ls -l /usr/share/openmediavault/notification/sink.d/50telegram 显示 -rwxr-xr-x。
  • ls -l /etc/omv-telegram.conf 显示 -rw-r-----,属主 root,属组 openmediavault-notify。
  • 没收到消息时,先看日志:
sh
journalctl -t omv-telegram --since "1 hour ago"
journalctl --since "10 minutes ago" | grep -i omvnotificationfilter

第一条是脚本自己记录的发送失败,第二条是 Postfix 对内部投递通道的处理记录。

常见问题 ​

脚本没有任何反应。 依次检查:邮件通知是否已启用(系统 → 通知 → 设置 里的已启动(Enabled)是否勾选);文件名是不是带了 .sh;文件是不是可执行;用上面的方法手动运行。

手动运行成功,但走邮件流程没反应。 最常见的原因是配置文件对 openmediavault-notify 不可读,检查它的属组和权限。

NAS 需要代理才能访问 Telegram。 网络 → 代理(Network → Proxy)里的设置通常不会传给 Postfix 调用的脚本。在脚本开头加一行 export https_proxy='<代理地址>',wget 会读取它。

消息太多,被日常邮件淹没。 打开脚本里的过滤那几行,只转发主题里含有关键词的告警。几类常见告警的主题格式如下,写过滤条件时参考,最终以你实际收到的邮件为准:

告警主题出处
系统监测Monitoring <动作> -- <事件> <服务>OMV 的 monit 配置模板
块设备 I/O 错误(8.5.10 起)I/O error detected on device '<设备>'OMV 的 rsyslog 配置模板
Btrfs 设备错误Errors were detected on the file system mounted at <挂载点> [UUID=...]OMV 的每日 cron 脚本
需要重启Reboot requiredOMV 的每日 cron 脚本
有待应用的配置更改Pending configuration changesOMV 的每日 cron 脚本
有用户被锁定Locked users overviewOMV 的每日 cron 脚本
证书即将到期A SSL certificate expires in <N> daysOMV 的每日 cron 脚本
S.M.A.R.T.主题里含 SMARTsmartd 生成,格式由 smartmontools 决定
软 RAID<事件> event on /dev/mdX:<主机名>mdadm 的监控程序生成

想换成别的渠道。 只需替换脚本最后的发送请求,输入输出的部分不变。ntfy、Gotify、Discord Webhook 这类服务都提供简单的 HTTP 接口,具体格式以各自的官方文档为准。官方插件之外,omv-extras 的 openmediavault-snapraid-aio 自带了 Apprise 通知,可以直接发到 Telegram、Discord、Slack、Gotify 等很多服务,首次运行时它会用 pipx 自动安装 Apprise,需要联网。界面里的字段是 Apprise URLs、Attach report on warnings 等,没有官方中文译名,以界面为准。

想用自己的目录,而不是 OMV 的 sink.d。 在 /etc/default/openmediavault 里设置环境变量 OMV_NOTIFICATION_SINK_DIR(用 omv-env set -- OMV_NOTIFICATION_SINK_DIR <目录>),postfix-runner.sh 会改为运行这个目录里的脚本。注意这样会让 sink.d 里自带的脚本不再运行,它只在调试开关打开时才有作用,影响很小。目录和脚本同样要让 openmediavault-notify 能读、能执行。

参考资料 ​

OpenMediaVault 非官方中文使用指南,与 openmediavault 项目及其开发者无隶属关系。