Appearance
Client ntfy and push deployer checklist
Last updated: 13 September 2026
Restricted deployer / support packet for #119 (P4-05). Audience is sourcectl deployers and client-ops who stand up the notify host and SafeCall ntfy keys. Ordinary SafeCall administrators stay on friendly categories and a QR (Keryx application). Ordinary administrators use the English administrator provisioning handbook (#120 closed 13 September 2026). Czech handbook stays on #108. Phone-side how-to: app-onboarding-guide.md (#121; do not close).
#119 is closed (13 September 2026, OA-119-deployer-docs). Human rows: operator-actions.md. Do not close #117 or #124 from this page. Do not set auth-default-access: deny-all while official ntfy app clients are still in use (OA-119-ntfy-cutover).
Do not put passwords, tk_… tokens, user.db, Firebase Admin JSON, or APNs .p8 in this file or in git. Record only where they live (secret-store.md).
How-tos this page does not replace:
- Dual-run users, tier, exemptions, SafeCall keys: ntfy-auth-and-play-internal.md
- Firebase / FCM / APNs host and first-device swipe-away: ntfy-fcm-apns.md
- Production ACL target: provisioning-protocol.md
Secret-free copy-paste files: templates/.
Purpose and non-goals
Purpose
- Name supported ownership and topologies for the client notify host.
- Document versions, DNS/TLS, auth, credentials, APNs/FCM, cache, firewall, time, monitoring, backups, rotation, and starting capacity knobs.
- Give production and test templates with placeholders only.
- Define acceptance tests, evidence, owner, handoff, change control, and the support boundary per client.
- Cross-link install and support material without teaching ntfy to ordinary administrators.
Non-goals
- Applying
deny-allon hetest2 or any live dual-run host. - Inventing delivery SLOs (the numbers below are starting knobs).
- The OEM/Doze/Focus soak (#117).
- Store listings, Closed testing, or production Play (#125 / #124).
- A second Play or App Store app record.
Ownership and topologies
| Topology | Who runs ntfy | When to use |
|---|---|---|
| sourcectl-operated shared notify | sourcectl (current: https://notify.wantok.cz on hetest2) | Default for sourcectl-hosted SafeCall installs |
| Client-operated dedicated ntfy | The client's ops team, behind their TLS proxy | Client owns DNS, backup, and ACL; sourcectl still owns Keryx Firebase client files and the store app |
Forbidden as the Keryx FCM/APNs path: upstream-base-url: https://ntfy.sh. That wakes the official ntfy iOS app, not com.sourcectl.keryxapp.
Go/no-go and change-control owner: Thomas Minitsios (P0-05). Per-client ops contact stays in controlled records, not git.
Versions
| Component | Proven floor | Notes |
|---|---|---|
| ntfy | 2.16.0 | POST /v1/account/token must exist. A 404 makes SafeCall mint tk_local_…, which does not leave the anonymous visitor bucket |
/v1/config base_url | Always empty on 2.16 | Not a load proof. Proof of listen is listen-http (hetest2: 127.0.0.1:2586) |
| SafeCall | Keys in config/defaults.ts | server_ntfy_server_url, server_ntfy_publish_token, server_ntfy_token_manager_user, server_ntfy_token_manager_password. Empty server_keryx_status_url derives {ntfy origin}/keryx-status. server_keryx_status_read_secret is optional HMAC for phone GET; store it with ntfy tokens, never in git. |
| Keryx | Store/TestFlight build with real keryx-for-safecall client files | Installed Internal 1.0.0+3 cannot receive FCM wake |
Upgrade ntfy before inventing a mint workaround.
DNS, TLS, proxy
- HTTPS only. No private-network or loopback HTTP exception (provisioning-protocol.md).
- Public DNS name on the certificate; terminate TLS on nginx/Caddy.
- ntfy listens on localhost. Template: templates/nginx-ntfy.conf. The same file proxies
/keryx-status/to127.0.0.1:8792and leaves/as ntfy. One-time systemd is OA-keryx-status-sidecar. How-to: keryx-status. behind-proxy: true(orNTFY_BEHIND_PROXY=true) so visitor buckets use real client IPs, not the proxy.- Hostnames in
visitor-request-limit-exempt-hostsresolve once at ntfy start. Restart after edits. - NTP on the notify host and the SafeCall box. Clock skew breaks token expiry and cache windows.
Auth
| Phase | auth-default-access | Anonymous official ntfy app | Keryx / SafeCall |
|---|---|---|---|
| Dual-run (current hetest2) | read-write | Still works | Authenticated tk_… for publish and subscribe |
| Cutover (later) | deny-all | Breaks | Required for the P1-02 production target |
Users (names may change; ACL must not):
| ntfy user | Role | Topic ACL | Used by |
|---|---|---|---|
safecall-publisher | user (not admin) | * read-write | SafeCall server_ntfy_publish_token |
safecall-token-manager | user (not admin) | * read-only | SafeCall Basic auth for POST /v1/account/token |
Access tokens inherit the whole user ACL. Do not make the token-manager an admin. How-to: ntfy-auth-and-play-internal.md Parts A–D.
Credentials and rotation
| Secret | Store | In git / OA log? |
|---|---|---|
Publisher password + publish tk_… | Apple Passwords | No |
| Token-manager password | Apple Passwords | No |
| Firebase Admin JSON | NAS secrets/keryx/firebase/ | No |
APNs .p8 | NAS secrets/keryx/iOS/ | No (Key ID may live in Apple Passwords / Firebase) |
| Host + usernames only | NAS secrets/keryx/ntfy/ | Yes |
Rotate publish and token-manager secrets on the ntfy host, then util.sh config set on each SafeCall install (service restarts). Revoke Keryx subscribe tokens from the SafeCall Keryx card, not by sharing a QR into chat.
APNs / FCM
One Firebase project addresses Android com.sourcectl.keryx and iOS com.sourcectl.keryxapp. On the notify host:
firebase-key-filepointing at the Admin SDK JSONupstream-base-urlemptyserver.ymland the JSON fileroot:ntfy640so the unit user can read them
How-to: ntfy-fcm-apns.md. First-device swipe-away on 1.2.1 (8) is done (OA-119-closed-push-proof). Full OEM matrix is #117.
Cache, retention, capacity
Starting knobs for a shared company host (tune after 429s; not product SLOs):
| Knob | Starting value | Why |
|---|---|---|
Tier safecall message-limit | 100000 | Authenticated SafeCall / Keryx headroom |
| Tier message expiry | 24h | Cache window for poll-on-resume / new QR snapshot |
visitor-request-limit-exempt-hosts | Each SafeCall public egress IPv4 and IPv6 | Recheck / SOS bursts must not share the anonymous 429 bucket |
visitor-subscription-limit | Leave default 30 first | Keryx multiplexes one SSE per instance (comma-separated topics) |
| Anonymous burst | Do not raise first | Fix behind-proxy, tier, and exempt hosts first |
Messages that expire before Keryx polls are missed. That is an honest limit for safety-claims.md, not a defect by itself.
Firewall, time, monitoring, backups
- Allow 443 to the notify origin from SafeCall egress, phones, and (during dual-run) official ntfy app clients. ntfy itself binds localhost.
- Time: NTP as above.
- Monitoring:
journalctl -u ntfyplus SafeCall dashboard Recheck. No extra crash/analytics SDK (P0-05). If Recheck returns 429, stop clicking it (it burns the bucket) and re-check proxy, tier, and exempt IPs. - Backups: include
/var/lib/ntfy/user.db(or the configuredauth-file) in the host backup. Never commituser.db. Restore requires the sameauth-filepath and file mode the unit can read.
Evidence, handoff, change control, support boundary
| Item | Rule |
|---|---|
| Owner | Thomas Minitsios (go/no-go). Per-client ops contact in controlled records |
| Change control | Notify-host server.yml / unit / proxy edits are deployer work. Ordinary SafeCall admins do not edit ntfy |
Evidence on NAS evidence/keryx/ | Dates, ntfy version, host name, checksum notes. No tokens |
| Support boundary | Existing SafeCall channel only (legal/support.md). Transport setup is not ordinary onboarding |
| Per client | Record topology (shared vs dedicated), notify origin, SafeCall server_id, and whether dual-run is still on |
Acceptance tests
Documented here. Do not treat this table as the #117 soak.
| Test | How | Status (13 September 2026) |
|---|---|---|
| Publish | Dashboard Recheck, Watch/Panic Test Send, or authenticated POST with the publish token | Proven on TST000 after egress exemption (no 429) |
| Live subscribe | Keryx on a real tk_… QR and official ntfy app on the same topic | Dual-run + operator re-scan; official app still received TST000-test |
| Terminated / swipe-away | ntfy-fcm-apns.md Parts J–K | First-device 1.2.1 (8) only. Full matrix is #117 |
| Reboot / cache | After phone reboot, poll should recover messages still in the ntfy cache; expired messages are gone | Expected behavior documented. Evidence stays #117 / #118 |
| Unauthorized | Dual-run: anonymous publish still 200 (accepted interim). After deny-all: anonymous must fail | Dual-run proven. Deny-all is OA-119-ntfy-cutover |
| Failure alerts | SafeCall ntfy health / Recheck; stop on 429 | Documented. No extra monitoring product |
Templates
| File | Use |
|---|---|
| templates/ntfy-server.dual-run.yml | Current interim (read-write) |
| templates/ntfy-server.deny-all.yml | Later cutover only. Do not apply on hetest2 now |
| templates/nginx-ntfy.conf | TLS terminate + forwarded client IP |
| templates/safecall-ntfy-config.md | SafeCall util.sh config set |
Replace placeholders. Never commit filled secrets.