自建消息推送服务 Gotify 体验

2次阅读
没有评论

共计 5825 个字符,预计需要花费 15 分钟才能阅读完成。

自建消息推送服务 Gotify 体验

最近几年我在 homelab 上挂了不少跑批的脚本和监控任务,包括备份成功失败提醒、网站可用性检查、家里宽带掉线告警等等。这些通知我长期用两种方案在扛——一种是 [[Telegram]] Bot,另一种是 SMTP 邮件。两种都能用,但用到后面,两种各自的问题都浮上来了。Telegram Bot 这边,最大的麻烦是网络依赖,bot 接口必须从能访问 Telegram 的网络发起调用,墙内的脚本要么挂代理要么走中转,链路一旦多一跳,稳定性就开始打折,偶尔会出现脚本明明执行成功、消息却卡了几分钟才到的情况。SMTP 那边问题不一样,邮件协议本身就不是为「即时通知」设计的,从 sendmail 发出去到手机邮件 app 弹出推送,中间经过 MTA 队列、收件方反垃圾系统、邮件客户端的轮询间隔,端到端延迟动辄半分钟起步,重要告警等不起,琐碎通知又会被埋在一堆订阅邮件里看不见。

把两种方案都用过一轮之后我开始想,推送通知这件事并不复杂,为什么我每次都要依赖一个我并不掌控的中间方。Telegram 是别人的服务器,SMTP 链路上至少有发件 MTA、收件 MTA、客户端三段我都管不了。能不能有一个东西,发送侧就是简单的 HTTP,接收侧直接送到我手机,中间没有任何第三方?

[[Gotify]] 就是为这个场景准备的。它把推送服务回归到本来该有的样子——一个跑在自己服务器上的小程序,一头收 HTTP 请求,一头通过 WebSocket 把消息送到我手机里。这篇文章想把这个使用体验完整记录下来,包括它能做什么、不能做什么、值不值得替换掉你现在用的方案。

一个简单到几乎被遗忘的需求

推送通知本质上是一个非常古典的工程问题。一个事件在某台机器上发生,需要让另一台设备(通常是手机)尽快知道。早些年这件事甚至连服务都不需要,邮件、短信、各家厂商的私有协议都能解决一部分。后来随着移动互联网的演化,事情反而变复杂了。安卓和 iOS 厂商各自把推送通道控制了起来,FCM 在国内被墙,国内厂商的 [[小米推送]]、华为推送各自有自己的接入规则,开发者要打通这套通道,需要做大量适配工作。这套架构对一个大公司来说没什么问题,对一个只想知道「我家路由器掉线了」的普通用户来说,重得离谱。

于是这几年陆陆续续有人开始做轻量化的解决方案。常见的几个选择是 [[ntfy]]、[[Bark]]、[[Pushover]]、[[message-pusher]] 以及今天要讲的 Gotify。它们的共同点是都把使用门槛降到了一个 curl 命令,不同点在于客户端覆盖范围、是否需要自托管、是否有商业化背景。我之前在 open-source-message.md 里把这些方案做过一次扫描,留下了 Gotify 的链接,但一直没真正用起来。这次终于把它部署在自己的 VPS 上,跑了快一个月。

Gotify 是什么

Gotify 是一个用 [[Go]] 写的服务端程序,仓库地址是 github.com/gotify/server,截至 2026 年 6 月,仓库已经收获 15100 颗星,最新版本是 2026 年 2 月发布的 v2.9.1。许可证是 MIT,意味着你可以放心 fork、改、商用。整个项目的设计目标极其克制:服务端只做消息收发,客户端只做推送展示,再无其他。

它的概念模型可以用三个角色概括。第一个是 application,可以理解为「消息发送方」,比如你的备份脚本、监控系统、Webhook 接收器,每一个 application 会得到一个独立的 token。第二个是 client,对应「消息接收方」,比如你手机上跑的 Gotify Android app,每次连接服务器需要一个 client token。第三个是 user,是这两类资源的所有者,账号体系建立在用户之上。这套模型简单到只需要看一遍就能记住,但它已经覆盖了推送场景的大部分需求。

通信协议上,Gotify 用了一对常见的搭配。发送侧用 REST API,任何能发 HTTP 请求的东西都可以接入,包括 shell 脚本、Python、Go、Node.js、甚至浏览器扩展。接收侧用 WebSocket 长连接,客户端连接到服务器后保持在线,新消息一旦到达立刻推送。这种架构的优势是不依赖任何第三方推送通道,FCM 用不了的时候它依然能用;劣势同样明显——保持 WebSocket 长连接对手机的电量和后台权限有要求,这一点后面会单独讲。

它还有一套 plugin 系统。插件用 Go 写,编译成动态库放进 plugin 目录,服务器启动时会加载。常见用途是把推送内容做一次中间处理,比如把 RSS 订阅抓下来变成消息推送,或者从外部 API 拉取告警转发到 Gotify。这一块对于不喜欢写胶水代码的人来说挺友好。

和同类方案放在一起看

Gotify 并不是这个赛道上唯一的玩家,理性的做法是搞清楚它和别人到底差在哪。

[[ntfy]] 是当下名气最大的对手。它的优势是同时提供官方的 ntfy.sh 公共实例和自托管选项,HTTP 协议设计更现代,支持纯文本 POST、支持 Markdown、支持 emoji 标签,并且有 iOS 客户端。劣势是 iOS 推送依赖作者维护的 APNs 中转服务,自托管版本要 iOS 收到推送需要做额外配置,并不是完全去中心化。

[[Bark]] 是 iOS 用户的首选。它的设计哲学是「极简 + iOS only」,安卓端基本没有官方支持。如果你完全在苹果生态,Bark 是更顺手的方案。

[[message-pusher]] 是国人 songquanpeng 写的中转网关,本身不解决「最后一公里」推送,而是把多种推送通道(微信、邮件、Bark、Telegram 等)抽象成统一接口。它和 Gotify 不是替代关系,反而经常被组合使用。

放在这几个里面看,Gotify 的定位非常清晰:一个纯粹的、自托管的、安卓优先的推送服务器。它不试图什么都做,它的边界感很强。如果你恰好是「有一台 Linux 服务器 + 用安卓 + 不想依赖第三方」这三条都满足的用户,Gotify 几乎是完美匹配。如果你有 iOS 设备,Gotify 不能解决你的问题,你需要看 ntfy 或 Bark。

部署一台属于自己的推送服务器

Gotify 的部署体验是我见过的开源项目里相当干净的一类。官方提供了 Docker 镜像,几乎不需要任何配置就能跑起来。

最小化的 docker compose 配置长这样:

version: "3"
services:
  gotify:
    image: gotify/server
    container_name: gotify
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      - GOTIFY_DEFAULTUSER_PASS=changeme
    volumes:
      - ./gotify_data:/app/data

跑起来之后访问 8080 端口,登录默认账号 admin 和你设置的密码,进入 Web UI 就可以创建 application 和 client。创建 application 时会得到一个 token,这个 token 就是后面所有发送脚本里要用的认证凭证。Web UI 设计得朴素但功能完整,能看到历史消息、按 application 过滤、调整优先级、删除消息,没有任何花哨的功能堆叠。

发送消息的命令简单到令人意外。官方文档给出的最小例子就是一行 curl:

curl "https://push.example.com/message?token=APP_TOKEN" -F "message=hello world"

带上标题和优先级的完整版本也只是多两行:

curl "https://push.example.com/message?token=APP_TOKEN" \
  -F "title=备份完成" \
  -F "message=数据库已备份至 R2,大小 348 MB" \
  -F "priority=5"

priority 从 0 到 10,Android 客户端会根据这个数字决定是否震动、是否声音、是否显示在锁屏。我自己的习惯是日常通知用 4,重要告警用 7 以上。

生产环境还需要套一层 HTTPS。Gotify 文档里专门列出了 Caddy、nginx、Traefik 几种反向代理的示例配置,照抄就行,关键点是要把 WebSocket 的升级头透传,否则 Android 客户端连不上。我用的是 Caddy,配置只有四行,自动签 Let’s Encrypt 证书,省事到几乎不像生产部署。

Android 客户端与一些必要的妥协

手机端的体验是 Gotify 最关键也最容易踩坑的部分。

官方 Android 客户端可以从 Google Play 或 F-Droid 安装,启动后填入服务器地址、用户名密码、自动创建 client,然后开始接收消息。整个流程加起来不到一分钟。消息到达的速度在网络通畅时几乎是即时的,我用 ping 命令计时,从 curl 发出到手机震动通常在两秒以内,比微信公众号模板消息还快。

问题出在长连接的维持上。Gotify 不走 FCM,意味着客户端必须自己保持一条到服务器的 WebSocket 连接。安卓的省电策略对常驻后台进程非常不友好,尤其是 MIUI、ColorOS、EMUI 这一类国产 ROM,默认会把不在白名单的 app 杀掉。结果就是你以为推送服务在跑,实际上手机锁屏几分钟之后客户端已经被系统冻结,下一条消息要等到你点开手机才送达。

我的应对办法是三层。第一层,在系统设置里把 Gotify 加入「电池优化白名单」「自启动」「后台运行」全部允许。第二层,在 Gotify 客户端设置里启用「Foreground service」选项,让它在通知栏长驻一个小图标,换取系统不杀掉它的待遇。第三层,如果厂商 ROM 实在过于激进,可以在路由器或者 VPS 上跑一个简单的心跳脚本,每隔几分钟向服务器发一条优先级为 0 的测试消息,触发客户端的接收逻辑保活。三层叠加之后,我的 Pixel 8 已经连续运行了将近一个月没有出现消息延迟。

值得一提的是,Gotify 没有官方 iOS 客户端,也没有 desktop 客户端。第三方有一些社区项目,但维护活跃度参差不齐。如果你打算多端使用,最现实的做法是把 Web UI 当作 desktop 客户端用——它本身就是一个 PWA,可以「添加到桌面」,关键告警来的时候浏览器也能弹出系统通知。

实际场景的接入示例

把 Gotify 接进去几个我每天会用的场景,可以看到它能省下多少胶水代码。

家里 OpenWrt 路由器掉线监测。我在 VPS 上跑一个 cron,每两分钟 ping 一次家里宽带的公网 IP,连续三次失败就发一条 priority 8 的消息。脚本只有 20 行 bash,最关键的发送部分就是上面那行 curl,token 写在脚本顶部一个变量里。

Cloudflare R2 备份成功通知。我的 [[mailcow]] 邮件服务器每晚做一次本地快照,然后 rclone 推到 R2。备份脚本结尾追加 curl 调用,把备份大小、耗时、远端文件 SHA256 一并塞进 message 字段。priority 用 3,因为这是日常成功消息,不需要打扰我。

GitHub Actions 工作流失败告警。在 workflow 末尾加一个 if: failure() 的步骤,用 curl 把仓库名、分支、commit message 推到 Gotify。priority 用 6,介于「需要关注」和「立刻处理」之间。

整体来说,越是高频、低优先级、纯文本的通知场景,Gotify 越合适。它不擅长的是带图片附件、带交互按钮的富消息——那些需求超出了它的设计边界,硬塞会很别扭。

一些不那么好的地方

聊完优点,把短板也老实摆出来。Gotify 不是没有缺陷,只是它选择不去解决那些它认为不属于自己边界内的问题。你能不能接受这些取舍,需要看你的实际场景。

第一,没有官方 iOS 支持。这一点前面说过两次,因为它真的会直接劝退一部分用户。社区里有人尝试做过 iOS 客户端,但 Apple 推送通道的限制让自托管的 WebSocket 模式几乎不可能在 iPhone 上长期保活,要做出可用的 iOS 体验必须借助 APNs 中转,这本身就违背了 Gotify「完全自托管」的核心承诺。结果就是这条路在可见的未来基本被堵死。

第二,多用户隔离能力弱。Gotify 有 user 概念,但权限模型设计得相当简单,admin 和普通用户的差别主要在能否管理别人的资源,并没有更细粒度的 application 共享、消息可见性控制、组织/团队这种概念。这让它不太适合「一个团队多个角色共享同一台 Gotify」的场景,本质上还是一个「一个人或一个小团队自用」的工具。如果你需要服务于多人的告警分发,可能要在 Gotify 上面再套一层网关。

第三,消息历史的搜索能力一般。Web UI 支持按 application 过滤,按时间倒序浏览,但没有真正意义上的全文搜索。长期堆积的消息想回头找一条特定告警,体验比较粗糙,你得靠肉眼翻页或者手动 SQL 查 SQLite 数据库。对于把 Gotify 当作长期事件归档的人来说,这是一个真实的痛点。

第四,移动客户端的 UI 设计偏工程师味。功能不缺——通知、优先级、震动、声音、客户端管理这些一个都不少,但是视觉层面的打磨明显不如主流 IM。图标、字号、留白、动画都带着一股「能用就行」的开源味。功能党不会在意,颜值党可能会觉得每次点开都有点别扭。

这些短板叠加在一起,决定了 Gotify 不是一个适合所有人的方案,但对于它真正瞄准的那群用户来说,这些都是可以接受的代价。

最后

用了快一个月之后,Gotify 给我最深的感受不是某个具体功能,而是它的克制感。它不试图做 ntfy 那种「公共云 + 自托管」双形态,不试图做 Bark 那种「极简纯 iOS」差异化,也不试图做 message-pusher 那种「多通道聚合网关」。它只做一件事——在你的服务器上跑一个进程,给你的安卓手机推消息,做完了。

这种克制在如今的开源世界里其实有点稀缺。很多自托管项目走着走着就开始堆功能,最后变成一个臃肿的服务,部署起来要一堆依赖、配起来要看半天文档。Gotify 反其道而行,把所有想加的东西都挡在 plugin 系统外面,核心服务保持极薄。这种设计哲学换来的好处是,你可以信任它会一直简单可靠,不会哪天升级之后突然多出五个新功能、配置文件格式还变了。

我的结论是,如果你符合「有 VPS + 用安卓 + 想要可控的推送」这三个条件,Gotify 值得花一个晚上部署起来。它不会成为你 homelab 里最闪亮的项目,但很可能成为最让你忘记它存在的那一个——而对于一个推送服务来说,「让你忘记它的存在」就是最高的褒奖。

正文完
 0
评论(没有评论)