Files
Notizarr/README.md
T
2026-09-07 09:54:30 +00:00

239 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Notizarr
Self-hosted notification hub — replaces ntfy for a small homelab, with a
polished web UI/PWA, Web Push, per-topic push control, and scoped API tokens
for LAN publishers. Full design rationale lives in
[docs/phase1/](docs/phase1/00-README.md).
Status: **Phases 2–9 complete**, Phase 10 (hardening/test-gap-filling) in
progress. Auth, topics, notification ingestion (canonical + ntfy-compat +
webhook), rate limiting, dedup, SSE live updates, retention cleanup, the
full user-facing UI, Web Push, and the admin UI are all built and verified
against a live dev stack. See
[docs/phase1/08-implementation-plan.md](docs/phase1/08-implementation-plan.md)
for the phase breakdown and [MVP acceptance status](#mvp-acceptance-status)
below for where things stand against the original 22 criteria.
> **Note:** this was written in an environment without Node.js or Docker
> installed, so none of it is executed here — it's been verified
> incrementally against a real dev stack running on the target Unraid box
> instead. `AUTH_MODE=oidc` has since been tested end-to-end against a real
> Pocket ID instance over real HTTPS (via Pangolin), and Web Push has been
> confirmed delivering to an installed iPhone PWA — see "Auth modes" and
> [docs/phase1/05-pwa-web-push-design.md](docs/phase1/05-pwa-web-push-design.md).
## Auth modes
Set via `AUTH_MODE` (`.env` / `docker-compose.yml` environment): `disabled`,
`proxy`, or `oidc` — see [docs/phase1/02-threat-model.md](docs/phase1/02-threat-model.md).
`disabled` refuses to start when `NODE_ENV=production` (it grants every
request implicit admin access — dev/test only). `oidc` is implemented
against the `openid-client` v5 API but hasn't been run against a real IdP
yet; test it once Pocket ID (or similar) is configured.
All mutating requests on the web listener (`POST`/`PUT`/`PATCH`/`DELETE`
under `/api/`) require a `X-Requested-With: notizarr` header — this is the
CSRF defense described in the threat model, not optional. The ingestion
listener (`:8081`) instead requires `Authorization: Bearer nzr_...` — create
a token via Settings → Admin → API Tokens.
## Repo layout
```
apps/server/ Node.js + TypeScript backend (Fastify), two listeners
apps/web/ React + TypeScript + Vite frontend, installable PWA
packages/shared/ Reserved for cross-package types (unused so far)
docker/ docker-compose.yml (dev) and docker-compose.prod.yml (Unraid template)
scripts/ backup.sh / restore.sh for the Postgres database
docs/phase1/ Architecture, threat model, DB/API/PWA design, implementation plan
docs/ Migration guides (ntfy, Home Assistant/Seerr/Uptime Kuma)
```
Full breakdown: [docs/phase1/07-folder-structure.md](docs/phase1/07-folder-structure.md).
## Local development
Requires Node.js 22+ and pnpm (`corepack enable` will fetch the pinned
version automatically).
```bash
pnpm install
pnpm dev
```
This runs the web app in `vite build --watch` and the server via `tsx
watch` concurrently. The server serves the built web assets itself — open
http://localhost:8080. `PORT_INGEST` (8081) is the LAN ingestion listener.
Needs a local Postgres — either run one yourself and set `DATABASE_URL`, or
use the dev Docker Compose stack below, which includes one.
## Local development via Docker
```bash
docker compose -f docker/docker-compose.yml up -d
```
Brings up `notizarr-postgres-dev` and `notizarr-dev` (hot-reload: the repo
is bind-mounted in). Default host ports (deliberately not 8080/8081/5432,
since those are commonly already taken on an Unraid box):
- Web UI: http://localhost:18080
- Ingestion listener: http://localhost:18081
- Postgres: `127.0.0.1:15432` (`psql -h localhost -p 15432 -U notizarr
notizarr`, password `notizarr_dev_password`)
Whenever a `package.json` dependency changes, rebuild **and** renew the
anonymous `node_modules` volumes in one go, or the container keeps running
against the old, pre-change `node_modules`:
```bash
docker compose -f docker/docker-compose.yml up -d --build --force-recreate -V
```
Override host ports if they also collide, either inline or via a
`docker/.env` file:
```bash
HOST_PORT_WEB=28080 HOST_PORT_INGEST=28081 HOST_PORT_PG=25432 \
docker compose -f docker/docker-compose.yml up -d
```
Check health:
```bash
curl http://localhost:18080/health
curl http://localhost:18080/ready # 503 until Postgres is reachable
curl http://localhost:18081/health
```
Generate the PWA icon set once (needs `sharp`, already a devDependency —
this writes real PNGs into `apps/web/public/icons/`, committed to the repo
rather than regenerated on every build):
```bash
docker compose -f docker/docker-compose.yml exec notizarr pnpm --filter @notizarr/web generate-icons
```
## Unraid deployment (production)
Deployment root: `/mnt/tank/appdata/notizarr`. Full design:
[docs/phase1/06-docker-unraid-design.md](docs/phase1/06-docker-unraid-design.md).
1. Build and tag the image (from a repo checkout, e.g. on the Unraid box or
any machine with Docker, then push/copy it to where Unraid can pull it —
or build directly on Unraid if the repo is checked out there):
```bash
docker build -f apps/server/Dockerfile --target runtime -t notizarr:0.1.0 .
```
2. On the Unraid host, create the deployment directory structure:
```bash
mkdir -p /mnt/tank/appdata/notizarr/{postgres,app,backups,logs}
```
3. Copy `docker/docker-compose.prod.yml` to
`/mnt/tank/appdata/notizarr/docker-compose.yml`, and `.env.example` to
`/mnt/tank/appdata/notizarr/.env` — fill in real values (DB password,
OIDC settings, `PUBLIC_BASE_URL`, and especially `BIND_ADDR_INGEST`, which
defaults to `127.0.0.1` until you explicitly set it to your LAN IP).
4. From `/mnt/tank/appdata/notizarr`:
```bash
docker compose up -d
```
Point Pangolin at `notizarr:8080` (same Docker network, preferred) or the
host's `PUBLIC_BASE_URL`. Never point it at 8081.
## Backup and restore
Everything that matters — notification history, users, topics, API tokens,
push subscriptions, the VAPID keypair, the session secret — lives in
Postgres (see [docs/phase1/06-docker-unraid-design.md](docs/phase1/06-docker-unraid-design.md#backup--restore)),
so backing up the database is a complete backup.
```bash
cd /mnt/tank/appdata/notizarr
./scripts/backup.sh
```
Writes a timestamped, gzipped `pg_dump` plus a copy of the compose file
(env vars, including the DB password and OIDC client secret, are inline
there rather than in a separate `.env` — keep `backups/` off shared or
public storage) into `backups/`, keeping the last 14 of each. Restore
with:
```bash
./scripts/restore.sh backups/notizarr-db-<timestamp>.sql.gz
```
This is destructive (replaces all current data) and asks for confirmation.
Consider scheduling `backup.sh` via an Unraid User Scripts cron entry for
regular backups.
## Migrating from ntfy / integrating other services
- [docs/migration-ntfy.md](docs/migration-ntfy.md) — endpoint mapping,
before/after examples, what's intentionally not replicated
- [docs/home-assistant.md](docs/home-assistant.md) — `rest_command` example
replacing `ntfy.publish`, plus Seerr, Uptime Kuma, and generic webhook
configuration
## Troubleshooting
- **A `docker compose up -d --build` didn't pick up a dependency change.**
Anonymous `node_modules` volumes are reused by default — add `-V
--force-recreate` (see [Local development via
Docker](#local-development-via-docker)).
- **A mutating API call returns `csrf_check_failed`.** The web app must
send `X-Requested-With: notizarr` on every `POST`/`PATCH`/`DELETE`
(`apps/web/src/lib/api.ts` does this automatically for the built-in UI —
this only bites hand-rolled `curl` testing). If the error is specifically
`"origin mismatch"`, the browser's `Origin` header didn't match the
request's own `Host` header — that's a real cross-origin request being
correctly blocked, not a false positive.
- **Push notifications don't work / diagnostics show "Service worker: Not
registered".** Service workers and the Push API require a secure context
— HTTPS, or `localhost`. A plain `http://<lan-ip>:port` origin will never
register a service worker; this isn't a bug, it's the browser enforcing
the spec. Test push once Pangolin is serving Notizarr over real HTTPS.
- **iPhone won't show push notifications even over HTTPS.** Confirm iOS
16.4+, and that Notizarr was installed via Share → Add to Home Screen —
Safari does not support Web Push for ordinary browser tabs, only
installed PWAs. Check Settings → Push diagnostics in the app.
- **`AUTH_MODE=disabled` refuses to start.** This is intentional when
`NODE_ENV=production` — that mode grants every request implicit admin
access and is dev/test-only (see `apps/server/src/index.ts`). Use `oidc`
or `proxy` for anything reachable off localhost.
- **First-ever login should be an admin but isn't.** Bootstrap-admin only
promotes the very first user *ever created* in the `users` table. If a
non-admin user was created first (e.g. during testing), promote them
manually via Settings → Admin → Users using an existing admin account, or
directly in Postgres if none exists yet.
## MVP acceptance status
All 22 hard acceptance criteria are built and verified against a live dev
stack reachable over real HTTPS (Docker Compose, Postgres healthy,
auth/topics/tokens/publish/push-toggle/PWA-install all confirmed working,
history and settings survive container restarts, OIDC login against a real
Pocket ID instance, and push confirmed arriving on an installed iPhone
PWA — 17–18 were the last two blocked on real HTTPS and are now closed
out too).
## Commands
| Command | Does |
|---|---|
| `pnpm dev` | Run web + server in watch mode |
| `pnpm build` | Production build of both |
| `pnpm typecheck` | TypeScript, no emit, across all workspaces |
| `pnpm lint` | ESLint across the repo |
| `pnpm test` | Tests across all workspaces |
## Documentation
- [docs/phase1/](docs/phase1/00-README.md) — architecture, threat model,
database model, API design, PWA/Web Push design, Docker/Unraid design,
implementation plan
- [docs/migration-ntfy.md](docs/migration-ntfy.md),
[docs/home-assistant.md](docs/home-assistant.md) — migration guides