ntfy 开源的消息推送平台

注意
本文最后更新于 2023-11-20,文中内容可能已过时。

ntfy 开源的消息推送平台

参考文档

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

官方文档

本地部署

Installation - ntfy

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

ntfy 提供了三种安装方式,

  • 直接安装二进制 tar 包或者 rpm 包或者 deb 包,你可以根据不同的操作系统和 CPU 架构来选择不同的压缩包,同时支持 Windows 安装

  • Docker 安装

  • Kubernetes/Kustomize

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

参考文档

首先检查服务器版本

1
2
$ arch
x86_64

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

1
2
3
4
5
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

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

  • ntfy_2.7.0_linux_amd64.rpm下不下来,我们可以在本机上将这个 rpm 包下载下来(大概 20 多 M),然后上传到目标服务器上

  • 执行rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.rpm的时候,需要创建用户和用户组,此时可能报错:useradd: cannot open /etc/passwd,这个问题,我们在《SSH 密钥登录》中也遇到过,解决方法很简单,先执行

    1
    2
    
    chattr -ia /etc/shadow
    chattr -ia /etc/passwd

    然后再进行rpm -ivh安装,安装完成之后,再执行

    1
    2
    
    chattr +ia /etc/shadow
    chattr +ia /etc/passwd
  • 执行systemctl start ntfy的时候发现无法启动成功,但是直接执行ntfy serve确实可以启动成功的,感觉是因为安装过程中创建的ntfy用户权限不够。直接更改/usr/lib/systemd/system/ntfy.service文件,此文件由systemctl enable ntfy命令创建

    1
    2
    3
    
    # User 和 Group 默认是 ntfy
    User=root
    Group=root

    然后重新加载

    1
    
    systemctl daemon-reload

    然后即可通过systemctl启动

    1
    
    systemctl restart ntfy

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

但是这样是不够的

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

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

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

1
2
3
4
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

1
systemctl restart ntfy

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

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

配置

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

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

1
2
3
4
5
6
7
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中配置两个选项即可:

  • auth-file配置的是用户访问记录数据库;如果不存在,它将自动创建;建议的位置/var/lib/ntfy/user.db

  • auth-default-access定义的是,在没有找到访问控制项时的默认的访问权限;可以设置为read-write(默认)、read-onlywrite-only or deny-all.

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

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

然后重启

1
systemctl restart ntfy

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

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

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

  • user:具有此角色的用户没有特殊权限。需要额外配置针对某一个主题的读写权限

  • admin:具有此角色的用户默认可以对所有主题进行读/写。没有必要进行更细粒度访问控制。

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

1
ntfy user add --role=admin <user_name>

查看所有用户

1
ntfy user list

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

1
{"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 端既可以发送消息,也可以订阅消息。

  • Subscribe to topic:订阅主题

  • Publish notification:向指定的主题发送消息

  • All notifications:订阅的所有主题的所有消息

只有官方提供的 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》中学习过

  • -u参数设置连接到 http 服务的时候指定用户和密码

  • -d参数指定 POST 请求的请求体,例如-d "i am a post body"

  • -H参数指定请求头中特定属性的值,例如-H "Name: xiashuo"

  • -T,以 PUT 请求上传文件,例如-T /opt/info.log

简单实践如下:

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

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

1
2
$ 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"}

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

1
2
3
4
5
6
7
8
$ 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 个优先级可以指定

  • max/urgent:最大优先级,需要弹框,震动加声音提示

  • high:高优先级

  • default:默认优先级

  • low:低优先级

  • min:最小优先级

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

1
2
3
4
5
6
7
8
$ 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属性:

1
2
3
4
5
6
7
8
$ 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 最好用,开源,免费。

附录

0%