ntfy 开源的消息推送平台

ntfy 开源的消息推送平台

参考文档

GitHub - binwiederhier/ntfy: Send push notifications to your phone or desktop using PUT/POST

官方文档

本地部署

Installation - ntfy

其实你可以直接使用 ntfy.sh 这个官方服务,但是我始终觉得还是自己的安全点儿。

ntfy 提供了三种安装方式,

虽然直接 docker 安装很方便,但是为了不出现性能损耗,我们这里选择直接通过 rpm 包安装

参考文档

首先检查服务器版本

$ arch
x86_64

然后在 Releases · binwiederhier/ntfy · GitHub 中选择 x86_64 对应的版本,即后缀为 amd64 的包,然后直接安装,建议以 root 用户执行此代码

rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.rpm
# 配置开机自启
systemctl enable ntfy 
# 启动
systemctl start ntfy

这里可能会遇到几个问题:

成功启动之后,访问 80 端口,即 http://<ip>:80 即可访问 ntfy 的 web 服务。

但是这样是不够的

编辑 /etc/ntfy/server.yml,将 http 接口和 https 接口的端口都进行更换,同时配置 https 的证书位置。

关于如何这个证书怎么来的以及为什么在这个位置,请看《轻量应用服务器运维》的 为 Nginx 配置证书 小节

注意,如果是阿里云,还得去控制台开 90109011 这两个端口

listen-http: ":9010"
listen-https: ":9011"
key-file: "/etc/host_ssl/xiashuo.xyz.key"
cert-file: "/etc/host_ssl/xiashuo.xyz.pem"

我们正常情况下推送数据,应该是通过 https 进行推送,而不是 http

然后重启 ntfy

systemctl restart ntfy

然后即可通过 https://<ip>:9011 访问 ntfy 的 web 服务。

然后你就可以开始发布消息和订阅消息了。

配置

ntfy server.yml 配置项,默认在 /etc/ntfy/server.yml

官方文档给出了几个 [配置模板](Configuration - ntfy),其中最有用的就是这个:HTTP+HTTPS, 磁盘缓存 + 附件(attachments)磁盘缓存

base-url: "http://ntfy.example.com"
listen-http: ":80"
listen-https: ":443"
key-file: "/etc/letsencrypt/live/ntfy.example.com.key"
cert-file: "/etc/letsencrypt/live/ntfy.example.com.crt"
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"

这里要注意,如果你的 listen-https 配置的不是 :443,那么在 base-url 中就应该把这个端口给带上,比如 base-url: "https://ntfy.example.com:445"

默认情况下,ntfy 服务器对所有人开放,这意味着每个人都可以读写任何主题(这就是 ntfy.sh 的配置方式)。此时,如果你的 ntfy 是部署在公网上,那么为了防止恶意的推送/订阅数据,你可以选择配置身份验证和授权。参考:私有化配置

对于放到互联网上的服务,一定要做限制。比如必须登录,如果不用登陆也需要对请求的频率和速率进行限制,防止恶意用户用脚本疯狂使用你的服务,造成内存和磁盘甚至流量的浪费,产生不必要的费用

ntfy 的授权是用一个简单的基于 sqlite 的后端实现的。它使用访问控制列表 (ACL) 实现两个角色 (useradmin) 和每个主题的 readwrite 权限。访问控制项既可以应用于用户,也可以应用于每个用户 (*),* 代表匿名 API 访问。

用户列表中默认有一个 * 用户,表示在推送消息到主题中时没有指定用户名的情况,即,开启了身份验证之后,在推送消息的时候,不指定用户依然是可以推送消息的,不过此时,对应的是匿名用户 *

匿名用户 * 的权限可以通过 auth-default-access 控制

/etc/ntfy/server.yml 中配置两个选项即可:

修改配置文件 /etc/ntfy/server.yml,进行 私有化配置

auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"

然后重启

systemctl restart ntfy

配置好之后,可以使用 ntfy user 命令 添加或修改用户,使用 ntfy access 命令可以 修改特定用户对特定主题的访问控制列表。这两个命令都直接编辑 auth 数据库 (auth-file 中定义的那个),因此它们只在服务器上工作,并且只有在访问它们的用户具有正确权限的情况下才能工作。

注意,用户只能在服务器上添加,在客户端(比如 Android 客户端、web 客户端,都不能添加),在客户端添加的用户,是订阅主题,推送消息的时候所使用的已经存在的用户

你可以创建两种角色的用户

一般情况下添加一个 admin 用户即可,平时自己就用这个 admin 用户,有别的节点需要用到 ntfy,再额外创建用户配置权限

ntfy user add --role=admin <user_name>

查看所有用户

ntfy user list

后续我们跟 ntfy 进行任何交互,都需要带上身份信息,比如通过 curl 发布消息的时候,带上 -u <user_name>:<user_password>,否则会提示:身份认证错误

{"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"}

总的来说,ntfy 支持对以下功能的配置:

我们有需要再对其进行研究

安卓客户端

Google Play 上 下载安装即可,安装好之后,需要在设置 -> 管理用户中配置我们创建好的用户,比如管理员用户,这样,通过此客户端进行的消息推送和数据订阅,都是以配置的用户的权限进行的。

ntfy 的 APP 本身只能设置订阅的主题,而发送消息到主题的功能则放到了 Android 的分享功能中,只要是能分享的东西,比如文字、图片、链接等,都可以作为一个消息发送到 ntfy 主题,这个功能真的好用,可以很方便地在电脑和手机之间传递一些文字链接,图片啥的。

注意,如果是小米手机,需要给 ntfy 这个 APP 的通知权限开启悬浮通知权限,才可以从屏幕上方弹出通知,否则就只能在下拉通知栏中看通知了。

Web 端

直接访问 ntfy 服务的 web 页面。然后在 Settings -> Manage users 中配置我们创建好的用户,比如管理员用户,这样,通过此客户端进行的消息推送和数据订阅,都是以配置的用户的权限进行的。

Web 端既可以发送消息,也可以订阅消息。

只有官方提供的 ntfy web 有客户端,自己部署的 ntfy 服务端没有。

访问 ntfy web,点击地址栏的下载按钮可以下载下来一个 ntfy 桌面 APP,

生产消息

发布消息可以通过发起一个 HTTP PUT/POST 请求或通过 ntfy CLI 完成。发送消息的时候需要指定主题和内容,内容就相当于是 POST 请求的请求体,而主题是包含在请求路径中的,其他的信息比如消息标题,消息优先级,都是通过定制请求消息头来实现。

用户身份认证,是将 <user_name>:<user_password> 通过 Base64 编码,然后以 Authorization: Basic <base64_code> 的格式放到消息头

主题是第一次订阅这个主题或第一次发布内容到这个主题的时候动态创建的,因此不会出现主题不存在的的情况。而且因为没有用户的注册,所以主题本质上是一个密码,只要知道一个主题,就可以往主题中推送数据或者消费主题中的数据,因此为了保证数据不被别人污染或者窃取,应该尽量选择一些不容易猜到的东西作为主题的名字,一般选择随机字符串。

我遇到了主题自动删除的 bug,可能是因为长时间没有往这个主题中发送消息,系统自动删除了这个主题。

这个问题告诉我们,消息中间件只能作为消息传递的通道,不要将消息存储在消息通道中,或者说不要依赖消息通道的存储功能

发布消息的方式很简单,只要是能发起一个 HTTP PUT/POST 请求就可以发布消息,各种编程语言都有 HTTP 客户端,通过这些客户端发起一个请求也很简单,这里就不多重复了,ntfy cli 这种方式只能在 ntfy 服务端使用,通用性不强,因此我们主要看通过 curl 命令生产消息的方式

关于 curl 命令,我们在《curl》中学习过

简单实践如下:

<ip> 换成 ntfy 所在的主机名或者 ip,将 <port> 换成 ntfy 服务监听的端口,将 <topic> 换成 ntfy 服务的主题

如果 ntfy 服务进行了 私有化配置,还需要配置用户信息,如果 ntfy 服务器没有进行私有化配置,比如 ntfy.sh,则不需要指定用户名密码

$ curl -u <user_name>:<user_password> -d "you should be a full stack developer" https://<ip>:<port>/<topic>
{"id":"6qx6X0mlhnZ3","time":1695641162,"expires":1695684362,"event":"message","topic":"<topic>","message":"you should be a full stack developer"}

除此之外,我们还可以通过定制请求的消息头,来定制发送到主题的消息,例如指定消息标题、标签还有优先级:

$ curl \
>   -u <user_name>:<user_password> \
>   -H "Title: Unauthorized access detected" \
>   -H "Priority: urgent" \
>   -H "Tags: warning,skull" \
>   -d "Remote access to phils-laptop detected. Act right away." \
>   https://<ip>:<port>/<topic>
{"id":"r5X6iJOfxGzB","time":1695715276,"expires":1695758476,"event":"message","topic":"<topic>","title":"Unauthorized access detected","message":"Remote access to phils-laptop detected. Act right away.","priority":5,"tags":["warning","skull"]}

总共有 5 个优先级可以指定

我们还可以带上图片附件,通过指定 Attach 消息头指定附件,不过附件地址必须可以通过 URL 来访问,这样就无法将本地文件作为附件

$ curl \
> -u <user_name>:<user_password> \
> -H "Title: message with title" \
> -H "Priority: high" \
> -H "Attach: https://cdn.jsdelivr.net/gh/liangkang1436/image-hosting@main/picgo-images/202309261656725.png" \
> -d "something wrong with app,this is the log" \
> https://<ip>:<port>/<topic>
{"id":"FEhxvlLjtZg6","time":1695718600,"expires":1695761800,"event":"message","topic":"<topic>","title":"message with title","message":"something wrong with app,this is the log","priority":4,"attachment":{"name":"202309261656725.png","url":"https://cdn.jsdelivr.net/gh/liangkang1436/image-hosting@main/picgo-images/202309261656725.png"}}

如果要将本地文件作为附件,则需要通过将其作为 PUT 请求的消息体,同时指定消息头中的 Filename 属性:

$ curl \
> -u <user_name>:<user_password> \
> -H "Title: message with log file" \
> -H "Priority: high" \
> -T /opt/info.log \
> -H "Filename: app.log" \
> https://<ip>:<port>/<topic>
{"id":"LwaNM7ZNQmN4","time":1695718985,"expires":1695762185,"event":"message","topic":"<topic>","title":"message with log file","message":"You received a file: app.log","priority":4,"attachment":{"name":"app.log","type":"text/plain; charset=utf-8","size":1263745,"expires":1695729785,"url":"https://<ip>:<port>/file/LwaNM7ZNQmN4.txt"}}

这样的话,我们就可以在系统报错的时候,把错误日志作为附件发出来。

发送消息的时候,总共可以支持以下这么多特性:

具体请看 发布消息时可以传递的所有参数的列表

订阅消息

除了我们前面看到的,可以在 Android 端订阅消息,可以在 web 端订阅消息,除此之外,还可以在 桌面端 订阅消息。还可以在 命令行 中订阅消息,我们还可以 编写代码通过 API 的方式 订阅消息,也就是说,我们可以把 ntfy 当作一个消息中间件来用。

一个简单的场景就是,比如我们想跟阮一峰一样,想要做科技周刊,那么我们可以在平时把我们临时看到的有意思的东西或者新闻很,推送到指定主题,然后自己写一个 Python 脚本跑在服务器上,一直在订阅这个主题,并把信息保存在本地(不在 ntfy 中缓存)然后等一个星期到了,这个 Python 脚本根据你推送的内容,自动生成周刊,这真的是太棒了。

使用的例子

其实主要就是把 ntfy 当作是一种通知手段,尤其可以简单的通过命令来生产消息之后,我们可以将命令执行的结果传递。发送到手机或者桌面端,这极大地增加了 ntfy 的适用范围。

我们甚至还可以在系统内部内置 ntfy 相关的 API,与专门的 ntfy 服务器通信。

此外,ntfy 也可以用作是手机和电脑之间的通信工具,传一些文字或者图片上面的,还是很方便的

ntfy 和消息中间件相比,没有那么底层,也没有那么重,使用起来也没有那么复杂,作为一个消息推送平台,可以看做是轻量的消息中间件

有了 ntfy,我个人开发的应用的所有的消息推送都可以走 ntfy。

和其他工具的简单对比

算来算去,还是 ntfy 最好用,开源,免费。

附录