Add ntfy push and Listmonk newsletter with lightweight Docker deploy tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
eric
2026-06-08 02:24:55 -05:00
parent 47b1ae8514
commit 22a960799c
24 changed files with 846 additions and 3 deletions

View File

@@ -0,0 +1,47 @@
# Listmonk 邮件通讯(轻量 / Go
NomadCNA 仅自托管 **Listmonk**Go 编写,~128MB用于城市/活动邮件订阅。
| 服务 | 域名 | 内网端口 |
| --- | --- | --- |
| Listmonk | https://listmonk.nomadro.cn | 9030 |
**不自托管**(内存占用高,已移除):
- Cal.comNode预约改由站内消息协调
- Owncast / PeerTube直播/点播沿用 **MiroTalk** + **MinIO**
## 国内镜像
`/etc/docker/daemon.json` 参考 `daemon.json.cn`,拉取用 `pull-images-cn.sh``docker.1ms.run` 前缀)。
## 部署
```bash
python scripts/deploy-listmonk.py
```
## API
管理后台创建 Token 后:
```
LISTMONK_API_KEY=...
LISTMONK_DEFAULT_LIST_IDS=1
```
## ntfy 手机推送Go已有容器 `nomadro-ntfy`
| 域名 | 端口 |
| --- | --- |
| https://ntfy.nomadro.cn | 127.0.0.1:9180 |
- HTTPS`docker/integrations/nginx/ntfy.nomadro.cn.conf` + Certbot
- 认证:`deny-all` 默认;匿名用户可读 `nomadcna-*` 主题App 订阅)
- 后端发布:服务器 `.env.local` 配置 `NTFY_AUTH_TOKEN=tk_...`
- 业务接入:`create_notification``targetUserId` 推送;设置页开启「手机推送」
```bash
python scripts/ssh-fix-ntfy.py # 修复 HTTPS
python scripts/ssh-setup-ntfy-acl3.py # ACL + 后端 token如需重建
```

View File

@@ -0,0 +1,9 @@
{
"max-concurrent-downloads": 3,
"max-concurrent-uploads": 1,
"registry-mirrors": [
"https://docker.1ms.run",
"https://dockerproxy.net",
"https://mirror.ccs.tencentyun.com"
]
}

View File

@@ -0,0 +1,43 @@
# 轻量集成:仅 ListmonkGo+ PostgreSQL
# 视频/预约沿用既有 MiroTalk + The Lounge不自托管 Cal/Owncast/PeerTube
services:
postgres-int:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-nomadro}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
ports:
- "127.0.0.1:5433:5432"
volumes:
- pg-int:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-nomadro}"]
interval: 10s
timeout: 5s
retries: 5
mem_limit: 128m
listmonk:
image: listmonk/listmonk:v5.0.0
restart: unless-stopped
depends_on:
postgres-int:
condition: service_healthy
ports:
- "127.0.0.1:9030:9000"
environment:
LISTMONK_app__address: 0.0.0.0:9000
LISTMONK_app__admin_username: ${LISTMONK_ADMIN_USER:-admin}
LISTMONK_app__admin_password: ${LISTMONK_ADMIN_PASSWORD:?set LISTMONK_ADMIN_PASSWORD}
LISTMONK_db__host: postgres-int
LISTMONK_db__port: 5432
LISTMONK_db__user: ${POSTGRES_USER:-nomadro}
LISTMONK_db__password: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
LISTMONK_db__database: listmonk
LISTMONK_db__ssl_mode: disable
command: [sh, -c, "./listmonk --install --idempotent --yes && ./listmonk"]
mem_limit: 128m
volumes:
pg-int:

View File

@@ -0,0 +1 @@
-- 已由 compose POSTGRES_DB=listmonk 初始化,此文件保留兼容旧卷

View File

@@ -0,0 +1,50 @@
upstream nomadro_listmonk {
server 127.0.0.1:9030;
keepalive 8;
}
server {
listen 80;
server_name listmonk.nomadro.cn;
root /www/wwwroot/www.nomadro.cn;
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/wwwroot/www.nomadro.cn;
}
location / {
return 301 https://$host$request_uri;
}
access_log /www/wwwlogs/listmonk.nomadro.cn.log;
error_log /www/wwwlogs/listmonk.nomadro.cn.error.log;
}
server {
listen 443 ssl http2;
server_name listmonk.nomadro.cn;
root /www/wwwroot/www.nomadro.cn;
ssl_certificate /etc/letsencrypt/live/listmonk.nomadro.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/listmonk.nomadro.cn/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
client_max_body_size 25m;
location / {
proxy_pass http://nomadro_listmonk;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
access_log /www/wwwlogs/listmonk.nomadro.cn.log;
error_log /www/wwwlogs/listmonk.nomadro.cn.error.log;
}

View File

@@ -0,0 +1,54 @@
upstream nomadro_ntfy {
server 127.0.0.1:9180;
keepalive 8;
}
server {
listen 80;
server_name ntfy.nomadro.cn;
root /www/wwwroot/www.nomadro.cn;
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/wwwroot/www.nomadro.cn;
}
location / {
return 301 https://$host$request_uri;
}
access_log /www/wwwlogs/ntfy.nomadro.cn.log;
error_log /www/wwwlogs/ntfy.nomadro.cn.error.log;
}
server {
listen 443 ssl http2;
server_name ntfy.nomadro.cn;
root /www/wwwroot/www.nomadro.cn;
ssl_certificate /etc/letsencrypt/live/ntfy.nomadro.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ntfy.nomadro.cn/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
client_max_body_size 25m;
location / {
proxy_pass http://nomadro_ntfy;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
access_log /www/wwwlogs/ntfy.nomadro.cn.log;
error_log /www/wwwlogs/ntfy.nomadro.cn.error.log;
}

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# 国内 VPS仅拉取 Listmonk 栈Go + Postgres Alpine
set -eu
MIRRORS=(docker.1ms.run dockerproxy.net)
pull_one() {
local src="$1"
local dst="$2"
for m in "${MIRRORS[@]}"; do
echo "==> pull ${m}/${src}"
if docker pull "${m}/${src}"; then
docker tag "${m}/${src}" "${dst}"
return 0
fi
done
docker pull "${dst}"
}
pull_one "library/postgres:16-alpine" "postgres:16-alpine"
pull_one "listmonk/listmonk:v5.0.0" "listmonk/listmonk:v5.0.0"
docker images | grep -E 'postgres|listmonk' || true