



























Gotify 是一个开源支持自建的通知推送服务,可以被集成在许多使用场景里,用来推送各种重要通知。本篇文章将在我的云服务器上自建 Gotify 服务,并使用它来提醒一些重要服务的状态。
Gotify 支持使用 docker 搭建,搭建与使用也非常简单,下面是我使用的 Compose 文件:
---
services:
gotify:
image: gotify/server:latest
container_name: gotify
volumes:
- ./data:/app/data
- gotify-log:/var/log/gotify
restart: unless-stopped
networks:
- frontend
- backend
environment:
- TZ=Asia/Shanghai
- GOTIFY_DEFAULTUSER_PASS=${GOTIFY_DEFAULTUSER_PASS}
- GOTIFY_SERVER_TRUSTEDPROXIES=[172.25.0..0/24]
labels:
- "traefik.enable=true"
- "traefik.http.routers.gotify.entrypoints=https"
# 需要改成自己的域名
- "traefik.http.routers.gotify.rule=Host(`go.example.com`)"
- "traefik.http.routers.gotify.tls=true"
- "traefik.http.routers.gotify.tls.certresolver=cloudflare"
- "traefik.http.services.gotify.loadbalancer.server.port=80"
- "traefik.docker.network=frontend"
networks:
frontend:
external: true
backend:
external: true
volumes:
gotify-log:
external: true我使用了 Traefik 作为反向代理,只需在上面的 Compose 文件里填好 lable 就可以让 Traefik 自动为容器开启代理了,具体 Traefik 的部署,可以看我之前的文章。
环境变量 GOTIFY_DEFAULTUSER_PASS 是 Gotify 默认用户 admin 的密码,我没有直接把密码写在 Compose 文件里,更推荐的方法是在同目录新建一个 .env 的文件,再把一些涉及敏感信息的变量写入 .env 文件里,比如这里就可以在 .env 文件里设定密码:GOTIFY_DEFAULTUSER_PASS=SuperSecretPass,也可以不设置这个变量,那么默认的用户密码就是 adminadmin,要记得在第一次登陆到网页面板后及时更改密码;另一个环境变量 GOTIFY_SERVER_TRUSTEDPROXIES 是可信的反向代理地址,需要设为反向代理服务器的 ip 或 ip 段,不然就无法在日志中显示访问 Gotify 的客户端真实 ip。
网络部分我习惯创建两个网络,所有需要开放前端服务的放在 frontend 网络里,不需要开放前端服务的放在 backend 网络里,不过在后面的应用实例中我需要让 Gotify 和两个网络里的容器都能够联通,所以将 Gotify 同时容器放在了两个网络里。记得要提前创建好两个网络:
# Traefik 运行在 frontend 网络里,所以 frontend 网络的网段应该与 GOTIFY_SERVER_TRUSTEDPROXIES 变量保持一致
docker network create frontend --subnet 172.25.0.0/24
docker network create backenddocker 卷部分 gotify-log 是用来存放 Gotify 的日志以供 Crowdsec 读取的,这个卷也要提前创建好:
docker volume create gotify-log一切设置好,启动容器后,在浏览器中输入自己配置好的域名,就可以打开 Gotify 的网页面板了,默认用户名是 admin,密码是之前自己设置的密码。在右上角的 USERS 里面,可以创建新用户,也可以修改当前用户的密码。

之后便可以尝试推送第一条消息了,在右上角的 APPS 里面,创建一个新的应用,名字和描述可以随便设置,优先级代表的是在 Android 系统上的消息优先级,最高是 10。创建以后会获得一个唯一的 token,将其复制下来。

也可以根据自己的需要创建多个应用,用来为不同的消息推送设置不同的推送频道。
想要推送消息至 Gotify,无需额外的软件,直接用 curl 就可以手动推送一条消息:
# 把域名改成自建的域名,<apptoken> 改成自己刚刚复制的 token
curl "https://go.example.com/message?token=<apptoken>" -F "title=这是标题" -F "message=这是正文" -F "priority=5"
也可以用 Gotify 自己的 cli 软件来推送消息,可以支持更复杂的格式
gotify push --url https://go.example.com --token <apptoken> --priority 10 --title "这是标题" "Gotify cli 支持换行 \n这是第二行"
为了能够在 Android 手机上接收到 Gotify 的消息推送,可以安装 Gotify 的 Android 应用,第一次打开需要输入自建的 Gotify url,点击下方的 Check URL 确认连接无误后,输入用户名和密码就可以登陆了。

ios 设备可以安装 igotify 应用,但是由于一些系统限制,可能无法在后台接收消息,需要部署 iGotify-Notification-Assistent 才可以接受后台消息,因为我并没有 ios 设备,所以无法演示具体的使用方法。
更多关于 Gotify 应用实现与使用场景,可以看 Gotify 官方的 contrib 仓库。
Crowdsec 是一个类似 Fail2ban 的服务,可以监控服务日志并封禁恶意 ip,关于 Crowdsec 的部署在我之前的文章里也有提及。Crowdsec 官网有一个社区提供的规则集用来监控 Gotify 的日志,不过监控日志的前提是有日志可供读取,问题是 Gotify 官方的 docker 镜像默认不会把日志写入到文件中,所以 Crowdsec 就无法读取日志。
要想让 Crowdsec 读取 Gotify 的日志,需要自己构建 Gotify 的镜像,其实也很简单,在任意目录新建一个名为 Dockerfile 的文件,内容如下:
FROM gotify/server
ENTRYPOINT /bin/bash -c '/app/gotify-app | tee /var/log/gotify/gotify.log'文件的内容很简单,实质上就是在 Gotify 官方镜像的基础上,修改了启动命令,将日志输出到容器内的 /var/log/gotify/gotify.log,在与 Dockerfile 相同目录下,运行:
# 将 tag 指定为 gotify/server:logger
docker build -t gotify/server:logger .便可创建新镜像,要想使用这个自定义镜像,就在前面的 Compose 文件里,把镜像的 tag 由默认的 gotify/server:latest 改成 gotify/server:logger。
我在前面的 Compose 文件里将保存日志的路径挂载到了 gotify-log 的 docker 卷里,再将这个 Docker 卷挂载到 Crowdsec 容器,便可让 Crowdsec 读取 Gotify 的日志了。
在 Crowdsec 容器这边,首先运行 docker exec crowdsec cscli collections install baudneo/gotify 来安装 Gotify 规则集,之后编辑 Crowdsec 的 Compose 文件,在最后引入之前创建的 gotify-log 卷:
volumes:
# 这里省略引入的其他 docker 卷
gotify-log:
external: true然后在前面的文件挂载部分挂载 gotify-log 卷:
volumes:
# 这里省略其他的挂载路径
- gotify-log:/var/log/gotify此外还要告诉 Crowdsec 要从哪里读取日志,编辑 Crowdsec 的 acquis.yaml 文件,在最后添加:
---
filenames:
- /var/log/gotify/gotify.log
labels:
type: gotify完整的 Crowdsec 配置,可以看我的的 Github 仓库。
之后重启 Crowdsec 容器,并访问几次 Gotify 的网页面板从而生成一些日志文件,运行 docker exec crowdsec cscli metrics,在 Acquisition Metrics 看到有读取 /var/log/gotify/gotify.log 的日志,就说明配置成功了。

配置好 Gotify 之后,便可以在其他自建服务中使用 Gotify 推送消息了,很多知名的自建服务都提供了 Gotify 推送集成,在这里我会演示我自己的三个应用场景。
当 Crowdsec 检测到恶意 ip 后,可以配置将恶意 ip 的信息推送到 Gotify,可以让我及时发现潜在的攻击并采取行动。

要为 Crowdsec 启用 Gotify 通知,首先要启用 Crowdsec 的 HTTP 插件,编辑 Crowdsec 配置文件目录里的 profiles.yaml 文件,找到下方的内容并取消注释,要注意一共有两处,分别用来处理 ip 和 ip 段:
#notifications:
# - http_default取消注释后大概是下图的样子:

之后配置 Crowdsec 的 HTTP 插件,编辑配置目录的 notifications/http.yaml 文件,删掉文件里所有的内容,然后直接照抄 Crowdsec 文档里的内容就好,只需要更改两处内容,url 设为自己的 Gotify 地址,X-Gotify-Key 设为之前设定的 APP 的 token。因为我的 Crowdsec 与 Gotify 运行在同一个 docker 网络里面,如果 url 填成公网的域名会因为一些奇奇怪怪的路由问题无法连接,所以我这里直接把 url 写成了 http://gotify/message。

配置完成后重启 Crowdsec 容器,先测试一下 Gotify 配置是否可用,运行 docker exec crowdsec cscli notifications list 可以列出目前配置好的通知服务,可以看到服务名是 http_default:

然后运行 docker exec crowdsec cscli notifications test http_default,如果成功接收到消息,就说明配置成功了。

Watchtower 可以被用来自动监控并更新 docker 镜像,还可以配合 Gotify,当发现有容器镜像更新时推送消息。下面是我使用的 Watchtower Compose 文件:
---
services:
watchtower:
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=Asia/Shanghai
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_ROLLING_RESTART=true
- WATCHTOWER_DISABLE_CONTAINERS=gotify
- WATCHTOWER_NOTIFICATIONS=gotify
- WATCHTOWER_NOTIFICATION_GOTIFY_URL=${WATCHTOWER_NOTIFICATION_GOTIFY_URL}
- WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=${WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN}
# 当 Gotify 没有使用反向代理和 TLS 加密时使用
# - WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY=true
networks:
- backend
networks:
backend:
external: trueWatchtower 主要是通过环境变量来进行配置。WATCHTOWER_DISABLE_CONTAINERS 是忽略更新的容器名,因为 Gotify 的容器镜像是手动创建的,如果不忽略的话每次检查更新时都会报错,所以要忽略检查更新 Gotify 容器;WATCHTOWER_NOTIFICATIONS 是所使用的通知服务,这里我使用的是 gotify,关于更多通知相关的配置,可以看 Watchtower 的文档;WATCHTOWER_NOTIFICATION_GOTIFY_URL 和 WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN 分别是 Gotify 的地址和 APP token,由于这两个变量涉及敏感信息,我将其单独放在了 .env 文件里;WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY 这个变量只有在 Gotify 地址没有使用 TLS 加密、且要确保安全时才使用,对于我的情况,因为容器都是运行在同一个 docker 网络里的,地址可以直接用 http://gotify,所以这个变量要设为 true,如果使用了非 TLS 加密的地址,但没有设置这个变量,就会无法推送消息。
之后再在同目录创建一个 .env 文件,填入自己真实的 Gotify 地址和 token:
WATCHTOWER_NOTIFICATION_GOTIFY_URL=http://gotify
WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=SuperSecretToken启动容器后,Watchtower 会尝试发送一条测试消息,如果能接收到消息,就说明配置成功了。

除了在已有的服务中集成 Gotify,也可以在自己的自定义脚本中使用 Gotify,在前面部署部分,提到了可以使用 curl 或 Gotify cli 来推送消息,那么我就可以解析脚本的输出结果,将需要的信息通过 Gotify 发送,以便出现问题时可以及时排查。
在我之前的文章中,我提到我会定时备份 Vaultwarden 和 FreshRSS 的数据,并将其同步到坚果云上,不过后来我买了阿里云盘的三方权益会员,所以我现在是使用阿里云盘 cli 将其备份到我的阿里云盘上,我设置了备份脚本只会保留最近几天的备份文件,阿里云盘 cli 我选择了排他性上传,云盘上多余的文件会被删除。

根据阿里云盘 cli 的输出结果,我写了个简单的脚本:
#!/bin/bash
# 自建 Gotify 的 url
GOTIFY_URL=https://gotify.example.com
# Gotify 的 token
GOTIFY_TOKEN=SuperSecretToken
# 上传文件
export ALIYUNPAN_CONFIG_DIR=/home/user/.config/aliyunpan
/usr/local/bin/aliyunpan sync start -ldir /home/user/backup -pdir /backup --drive resource --mode upload --policy exclusive --cycle onetime --up 1 --dp 1 --log true > /tmp/aliyunpan.log
# 推送消息
UPLOAD_STATUS=$(grep '已完成' /tmp/aliyunpan.log)
if [ -z "${UPLOAD_STATUS}" ]; then
FULL_LOG=$(cat /tmp/aliyunpan.log)
/usr/local/bin/gotify push --url ${GOTIFY_URL} --token ${GOTIFY_TOKEN} --priority 10 "备份同步失败 \n${FULL_LOG}"
else
DELETED=$(grep '多余文件' /tmp/aliyunpan.log)
UPLOADED=$(grep '上传文件' /tmp/aliyunpan.log)
if [ -z "${DELETED}" ]; then
DELETED=没有文件删除
fi
if [ -z "${UPLOADED}" ]; then
UPLOADED=没有文件上传
fi
/usr/local/bin/gotify push --url ${GOTIFY_URL} --token ${GOTIFY_TOKEN} --priority 4 "${DELETED} \n${UPLOADED}"
fi这个脚本所做的事,大致可以分为以下几步:
将阿里云盘 cli 的输出结果保存到 /tmp/aliyunpan.log 文件里,供后面解析;
从日志文件里查找「已完成」关键词,如果没有找到,说明文件上传失败,就会推送「备份同步失败」的字样,并附上完整的日志内容,因为同步失败多半是出了问题,可能是阿里云盘登陆过期,需要手动排查,故而将消息优先级设为了最高 10;
如果找到了「已完成」关键词,就说明同步成功了,就会从日志文件里分别找到删除和上传了哪些文件,并且把已经删除和上传的文件列表作为消息推送到 Gotify,因为这个消息不是很紧急,所以优先级设为了比较低的 4。
我设置了一个 cron 任务,每天凌晨自动运行一次,我就可以每天把备份文件自动同步,并及时获取同步状态了。

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。