diff --git a/journal/2026-07-10.md b/journal/2026-07-10.md index a8004f2..7bfd542 100644 --- a/journal/2026-07-10.md +++ b/journal/2026-07-10.md @@ -142,3 +142,10 @@ tags: [scope/global, type/log] **Reason:** other **Vault notes touched:** (none) + +## Session — 2026-07-10T14:06:45Z + +**Project:** /home/jared/dev/cc-os +**Reason:** other +**Vault notes touched:** +(none) diff --git a/reference/apprise-api-selfhosted-gotchas.md b/reference/apprise-api-selfhosted-gotchas.md new file mode 100644 index 0000000..e8ecb4e --- /dev/null +++ b/reference/apprise-api-selfhosted-gotchas.md @@ -0,0 +1,33 @@ +--- +summary: Deployment gotchas for the self-hosted apprise-api Docker container — config-dir ownership (uid 1000 workers), stateful config format, and the tailnet-bind pattern used on ovh-vps. +tags: + - type/reference + - tool/apprise + - domain/notifications +scope: global +date: 2026-07-10 +last_updated: 2026-07-10 +--- + +# apprise-api self-hosted deployment gotchas + +Learned deploying `caronc/apprise` (apprise-api) via docker compose on ovh-vps (cc-os ADR-024, 2026-07-10). + +## Config-dir ownership trap + +- `docker exec apprise id` reports **root**, but the gunicorn workers actually run as **uid 1000** — the container chowns the mounted `/config` volume to `1000:1000` on first start. +- A stateful config file owned by another uid with mode `600` produces a misleading 500: `django: NOTIFY - I/O error accessing configuration using KEY: `. Fix: `chown 1000:1000` the `{key}.cfg` file (keep `600`). +- Corollary: after the container starts, the host user may lose write access to `./config` (it was chowned to 1000) — `sudo chown` back or write files as the right uid. + +## Stateful config essentials + +- `APPRISE_STATEFUL_MODE=simple` → config is a plain text file `/config/{KEY}.cfg`, one target URL per line, optional `tag=` prefix (`pushover=pover://user@token`). Untagged lines match notify calls with no tag. +- `APPRISE_CONFIG_LOCK=yes` disables config changes via the API — config is file-managed only; notify still works. +- A long random KEY (`openssl rand -hex 16`) acts as the only auth token; treat the `.cfg` filename/key as a secret. +- Notify: `POST /notify/{key}` form-encoded `body` + `title` (+ optional `tag`, `type`) → 200 on delivery. + +## Network pattern (no auth built in) + +apprise-api has **no authentication** — don't expose it publicly bare. Pattern used: bind the container port to the tailscale IP + localhost only (`100.100.43.95:8901:8000`, `127.0.0.1:8901:8000`), so every tailnet machine can notify with no public exposure, no DNS, no reverse proxy. Server env (base URL + key) lives in `~/services/apprise/apprise.env` (600) on ovh-vps. + +Related: [[vault-backlog-pilot-plan]] (ADR-024 context); Ruby client gem `apprise-client` at `~/dev/ruby-gems/apprise`.