diff --git a/Caddyfile b/Caddyfile index 72c6a37..726bf9e 100644 --- a/Caddyfile +++ b/Caddyfile @@ -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 +} diff --git a/README.md b/README.md index a37e13a..7e61355 100644 --- a/README.md +++ b/README.md @@ -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 + +# A token for unattended publishing from scripts/cron. +docker exec -it ntfy-ntfy-1 ntfy token add +``` + +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 +docker exec -it ntfy-ntfy-1 ntfy access rw +``` + +Publish with the token from anywhere: + +```sh +curl -H "Authorization: Bearer " -d "backup finished" \ + https://push.waldson.com.br/ +``` + +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: diff --git a/ntfy/compose.yaml b/ntfy/compose.yaml new file mode 100644 index 0000000..03aa8de --- /dev/null +++ b/ntfy/compose.yaml @@ -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