Merge branch 004-ntfy-stack-behind-caddy

This commit is contained in:
Waldson Patrício 2026-07-23 14:49:57 -03:00
commit 04df6b9c09
3 changed files with 157 additions and 1 deletions

View file

@ -13,3 +13,7 @@ sync.waldson.com.br {
git.waldson.com.br {
reverse_proxy forgejo-forgejo-1:3000
}
push.waldson.com.br {
reverse_proxy ntfy-ntfy-1:8080
}

View file

@ -22,6 +22,8 @@ syncthing/ the Syncthing stack (own compose file); GUI behind Caddy,
sync protocol published on 22000
forgejo/ the Forgejo stack (own compose file); web UI behind Caddy,
git-over-SSH published on 2222
ntfy/ the ntfy stack (own compose file); push server behind Caddy,
nothing published (pure HTTP through the proxy)
```
## Prerequisites
@ -221,6 +223,95 @@ A major bump (editing the tag to the next number) runs one-way database
migrations. Do it one major at a time, back up `forgejo_data` first, and read the
Forgejo release notes before changing the tag.
## ntfy stack
A self-hosted [ntfy](https://ntfy.sh) push-notification server (the owner's
alerts endpoint for scripts, cron, and the Android app) at
`push.waldson.com.br`. It lives in `ntfy/` and deploys to
`/home/waldson/central-proxy/ntfy` on the droplet.
Nothing is published on the host: ntfy is pure HTTP and reached only through
Caddy at `push.waldson.com.br`, so it rides Caddy's existing 80/443 and opens no
new firewall port. Caddy transparently upgrades the long-lived WebSocket
connections the Android app and web UI use for instant delivery. The central
`Caddyfile` carries the `push.waldson.com.br` block.
Access is **deny-all**: `NTFY_AUTH_DEFAULT_ACCESS=deny-all` means no topic can be
published or subscribed without an authenticated user or token, so there is no
open surface for strangers even though the URL is public. The whole instance is
configured from `NTFY_*` environment variables in `compose.yaml` (no
`server.yml`), so the posture is reproduced on every recreate. The container runs
as UID 1000, which cannot bind port 80, so ntfy listens on `:8080` inside the
container and Caddy reverse-proxies to `ntfy-ntfy-1:8080` - the same high-port
shape as Syncthing (`:8384`) and Forgejo (`:3000`).
### Where data lives
The auth database (`user.db`), the message cache (`cache.db`), and uploaded
attachments all live under the host bind mount `ntfy/data` (mounted at
`/var/lib/ntfy` in the container), so state survives container recreation and is
inspectable over plain SSH. The directory is git-ignored via the repo's `data/`
rule. The container runs as `1000:1000`; if the droplet's deploy user has a
different id, adjust `user:` in `compose.yaml` so the db files stay owned by that
user.
### Deploy
From `ntfy/` on the droplet:
```sh
docker compose up -d
```
The container comes up as `ntfy-ntfy-1` on `proxy_net`, which is the name Caddy
reverse-proxies to. A restart preserves users, tokens, and cached messages:
```sh
docker compose restart
```
### First-run setup (CLI)
There is no web installer and no sign-up path. After the container is first up,
create the admin user and (for scripts) a publish token via the CLI:
```sh
# Admin can read/write every topic (prompts for a password).
docker exec -it ntfy-ntfy-1 ntfy user add --role=admin <admin>
# A token for unattended publishing from scripts/cron.
docker exec -it ntfy-ntfy-1 ntfy token add <admin>
```
For a non-admin user, grant per-topic access explicitly (deny-all denies
everything else):
```sh
docker exec -it ntfy-ntfy-1 ntfy user add <name>
docker exec -it ntfy-ntfy-1 ntfy access <name> <topic> rw
```
Publish with the token from anywhere:
```sh
curl -H "Authorization: Bearer <token>" -d "backup finished" \
https://push.waldson.com.br/<topic>
```
In the Android app, add the server `https://push.waldson.com.br`, sign in with
the user (or set the token), and subscribe to the topic.
### Upgrade
The image is pinned to the current ntfy major (`binwiederhier/ntfy:v2`). Patch
and minor updates within that major are safe:
```sh
docker compose pull && docker compose up -d
```
A major bump is a deliberate tag edit; read the ntfy release notes first.
## Droplet groundwork
Baseline host configuration for the droplet (`wloud`, 174.138.36.232,
@ -262,7 +353,8 @@ firewall blocks them. Ports in use:
| 2222 | tcp | Forgejo git-over-SSH |
| 22000 | tcp+udp | Syncthing sync protocol |
If ufw rules are ever tightened, keep the four rows above allowed. To
ntfy adds no row here: it is reached only through Caddy on 80/443. If ufw
rules are ever tightened, keep the four rows above allowed. To
re-verify reachability from any machine outside the droplet:
```sh
@ -285,6 +377,7 @@ and point at the droplet:
| www.waldson.com.br | A | 174.138.36.232 | redirect to apex |
| git.waldson.com.br | A | 174.138.36.232 | Forgejo |
| sync.waldson.com.br| A | 174.138.36.232 | Syncthing GUI |
| push.waldson.com.br| A | 174.138.36.232 | ntfy push server |
personaledson.com.br and nazareonline.com.br also resolve here for the
existing stacks. Check resolution with:

59
ntfy/compose.yaml Normal file
View file

@ -0,0 +1,59 @@
name: ntfy
# Self-hosted ntfy push-notification server behind the central Caddy proxy at
# push.waldson.com.br. The HTTP API/web UI is reached only through Caddy on
# proxy_net (nothing is published to the host); Caddy transparently upgrades the
# long-lived WebSocket connections the Android app and web UI use for instant
# delivery. Access is deny-all by default: no topic works without a user or
# token, both created out of band via the CLI (see README.md for the deploy,
# user-creation, and upgrade runbook).
services:
ntfy:
# Pinned to the current major so `pull` brings patch/minor fixes while a
# major bump stays a deliberate tag edit.
image: binwiederhier/ntfy:v2
# The image's entrypoint is `ntfy`; the server needs the `serve` subcommand.
command: serve
restart: unless-stopped
# Run as the droplet's deploy user so the auth/cache SQLite files in the
# bind mount stay owned by 1000 and are manageable over plain SSH, matching
# the Syncthing and Forgejo stacks. A non-root process cannot bind :80, so
# ntfy listens on :8080 below and Caddy reverse-proxies to that high port -
# the same shape as Syncthing (:8384) and Forgejo (:3000).
user: "1000:1000"
environment:
# Public-facing identity. Drives the links and topic URLs the web UI and
# clients show; without it ntfy would advertise its container hostname.
NTFY_BASE_URL: https://push.waldson.com.br
# Unprivileged listen port so the container can run as UID 1000. Caddy
# reaches this by container name on proxy_net.
NTFY_LISTEN_HTTP: ":8080"
# Trust Caddy's X-Forwarded-For so rate limiting and logs see the real
# client IP instead of the proxy's container address.
NTFY_BEHIND_PROXY: "true"
# Deny-all: every publish and subscribe requires an authenticated user or
# token. There is no open topic and no sign-up path; the admin user and
# any publish tokens are created via the CLI (see the runbook).
NTFY_AUTH_FILE: /var/lib/ntfy/user.db
NTFY_AUTH_DEFAULT_ACCESS: deny-all
# Message cache backs the poll/since catch-up feature and survives
# restarts. Duration left at ntfy's 12h default.
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
# File attachments enabled at ntfy's default size limits, stored under the
# data dir so they persist with everything else.
NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
volumes:
# Auth db, message cache, and attachments all live here on a host bind
# mount, so the whole instance survives recreation and is inspectable over
# SSH. Git-ignored via the repo's data/ rule.
- ./data:/var/lib/ntfy
# No ports published: the server is pure HTTP and reached only through Caddy
# on proxy_net. Nothing new to open on the host firewall.
mem_limit: 256m
networks:
- proxy_net
networks:
proxy_net:
external: true