- Proxy notification image_url server-side (GET /api/v1/notifications/:id/image) instead of loading it directly in the browser, so LAN-only image sources (e.g. Frigate snapshots) still load for clients off that network - Tighten CSP img-src to 'self' now that images are same-origin - Update threat model / architecture docs to match the new image-fetch design - Expand README: Home Assistant quick-start, Pocket ID/OIDC walkthrough, Pangolin note, backup automation, new troubleshooting entries - Fix docker-compose.yml: document that --env-file .env must be passed explicitly (Compose's implicit .env lookup was unreliable with -f from a different directory) - Fix image-proxy tests to build a real session cookie (AUTH_MODE=oidc applies inside the test container too, so requireAuth needs one)
106 lines
4.9 KiB
Markdown
106 lines
4.9 KiB
Markdown
# Migrating from ntfy
|
|
|
|
Notizarr's ingestion listener (`:8081`) accepts ntfy-style publish requests
|
|
at `POST /:topic`, so most existing ntfy integrations only need a URL and an
|
|
auth header changed — not a rewrite. This isn't a full ntfy protocol clone
|
|
(see [What's different](#whats-different) below); it covers the parts of
|
|
ntfy that are actually used for publishing a message.
|
|
|
|
## Quick reference
|
|
|
|
| ntfy | Notizarr |
|
|
|---|---|
|
|
| `https://ntfy.sh/mytopic` | `http://<lan-ip>:8081/mytopic` |
|
|
| No auth, or ntfy access tokens | `Authorization: Bearer nzr_...` (create one in Admin → API Tokens) |
|
|
| `Title:` header | `Title:` header — same |
|
|
| `Priority:` header | Accepted, stored, `min`/`1` skips push — see [Priority](#priority) |
|
|
| `Tags:` header | Not mapped yet — see [What's different](#whats-different) |
|
|
| `Click:` header | `Click:` header — same, becomes the notification's link |
|
|
| `Attach:` header | `Attach:` header — same, treated as an image URL |
|
|
| `Filename:` header | `Filename:` header — stored as metadata |
|
|
| plain-text body | plain-text body — same, becomes the message |
|
|
| JSON publish (`{"topic", "message", ...}`) | JSON publish — same shape, topic comes from the URL path instead |
|
|
| Query params (`?title=...`) | Query params — same |
|
|
|
|
## Before / after examples
|
|
|
|
**Plain curl, ntfy:**
|
|
```bash
|
|
curl -H "Title: Backup finished" -d "Backup completed successfully" ntfy.sh/mytopic
|
|
```
|
|
|
|
**Plain curl, Notizarr:**
|
|
```bash
|
|
curl -H "Title: Backup finished" \
|
|
-H "Authorization: Bearer nzr_your_token_here" \
|
|
-d "Backup completed successfully" \
|
|
http://<lan-ip>:8081/mytopic
|
|
```
|
|
|
|
The only changes: the host/port, and adding the `Authorization` header. If
|
|
you were using ntfy's optional access tokens, this direction is the same —
|
|
you're just switching to a Notizarr-issued token with real per-service
|
|
scoping instead.
|
|
|
|
**JSON publish, ntfy:**
|
|
```bash
|
|
curl ntfy.sh -d '{"topic":"mytopic","title":"Backup finished","message":"Backup completed successfully","click":"https://backups.example.com"}'
|
|
```
|
|
|
|
**JSON publish, Notizarr:**
|
|
```bash
|
|
curl -H "Authorization: Bearer nzr_your_token_here" \
|
|
-d '{"topic":"mytopic","title":"Backup finished","message":"Backup completed successfully","click":"https://backups.example.com"}' \
|
|
http://<lan-ip>:8081/mytopic
|
|
```
|
|
|
|
Same JSON body. `topic` in the body is redundant with the URL path but
|
|
harmless if you leave it — Notizarr always takes the topic from the path on
|
|
this endpoint.
|
|
|
|
## Getting a token
|
|
|
|
1. Log into Notizarr as an admin.
|
|
2. Settings → Admin → API Tokens → Create token.
|
|
3. Give it a name matching the service (e.g. "Gitea", "Uptime Kuma").
|
|
4. Copy the `nzr_...` value immediately — it's shown once.
|
|
5. Optionally scope it to specific topics under "Restrict topics" so a
|
|
leaked token from one integration can't publish everywhere.
|
|
|
|
## What's different
|
|
|
|
Deliberately **not** replicated, because the goal is easy migration, not a
|
|
full protocol clone (and some of these are explicit non-goals — see
|
|
[docs/phase1/00-README.md](phase1/00-README.md)):
|
|
|
|
- **Priority.** ntfy's priority levels (min/low/default/high/urgent, or
|
|
1-5) are accepted and stored on the notification. Only `min`/`1` changes
|
|
delivery: that notification is skipped during push fan-out (still stored
|
|
and shown in-app), so a single noisy sender can be silenced without a
|
|
separate topic. The other levels are accepted but don't yet change
|
|
anything further (no sound/vibration mapping, no urgent-bypasses-DND).
|
|
Whether a topic pushes at all otherwise is controlled by that topic's push
|
|
ON/OFF setting — see [docs/phase1/04-api-design.md](phase1/04-api-design.md).
|
|
- **Tags/emoji.** ntfy's `Tags:` header (e.g. `warning,skull`) isn't parsed
|
|
into emoji prefixes. If you want an emoji in the title, put it directly in
|
|
the `Title:` header or JSON `title` field, same as ntfy's own
|
|
emoji-in-title convention.
|
|
- **Attachments.** `Attach:` is treated as an image URL. The server fetches
|
|
and proxies it (`GET /api/v1/notifications/:id/image`) rather than
|
|
handing the raw URL to the browser — deliberately so a LAN-only image
|
|
source (a camera snapshot, say) still loads for a client that's off that
|
|
LAN, since the server sits on it persistently. Still no arbitrary file
|
|
upload, and the fetch is narrowly scoped (see the SSRF section of
|
|
[docs/phase1/02-threat-model.md](phase1/02-threat-model.md)) — only the
|
|
fixed URL already stored on a notification the caller can see, never a
|
|
client-supplied one.
|
|
- **Auth is required by default.** Unlike a bare ntfy instance, publishing
|
|
always needs a valid `Authorization: Bearer` token — there's no
|
|
anonymous-publish mode.
|
|
|
|
## Home Assistant, Seerr, Uptime Kuma, and other services
|
|
|
|
See [docs/home-assistant.md](home-assistant.md) for a worked Home Assistant
|
|
example (replacing `ntfy.publish`) and equivalent webhook configurations for
|
|
Seerr, Uptime Kuma, and any service with a generic webhook option.
|