Skip to content

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:

Secret-free copy-paste files: templates/.

Purpose and non-goals ​

Purpose

  1. Name supported ownership and topologies for the client notify host.
  2. Document versions, DNS/TLS, auth, credentials, APNs/FCM, cache, firewall, time, monitoring, backups, rotation, and starting capacity knobs.
  3. Give production and test templates with placeholders only.
  4. Define acceptance tests, evidence, owner, handoff, change control, and the support boundary per client.
  5. Cross-link install and support material without teaching ntfy to ordinary administrators.

Non-goals

  • Applying deny-all on 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 ​

TopologyWho runs ntfyWhen to use
sourcectl-operated shared notifysourcectl (current: https://notify.wantok.cz on hetest2)Default for sourcectl-hosted SafeCall installs
Client-operated dedicated ntfyThe client's ops team, behind their TLS proxyClient 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 ​

ComponentProven floorNotes
ntfy2.16.0POST /v1/account/token must exist. A 404 makes SafeCall mint tk_local_…, which does not leave the anonymous visitor bucket
/v1/config base_urlAlways empty on 2.16Not a load proof. Proof of listen is listen-http (hetest2: 127.0.0.1:2586)
SafeCallKeys in config/defaults.tsserver_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.
KeryxStore/TestFlight build with real keryx-for-safecall client filesInstalled 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/ to 127.0.0.1:8792 and leaves / as ntfy. One-time systemd is OA-keryx-status-sidecar. How-to: keryx-status.
  • behind-proxy: true (or NTFY_BEHIND_PROXY=true) so visitor buckets use real client IPs, not the proxy.
  • Hostnames in visitor-request-limit-exempt-hosts resolve 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 ​

Phaseauth-default-accessAnonymous official ntfy appKeryx / SafeCall
Dual-run (current hetest2)read-writeStill worksAuthenticated tk_… for publish and subscribe
Cutover (later)deny-allBreaksRequired for the P1-02 production target

Users (names may change; ACL must not):

ntfy userRoleTopic ACLUsed by
safecall-publisheruser (not admin)* read-writeSafeCall server_ntfy_publish_token
safecall-token-manageruser (not admin)* read-onlySafeCall 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 ​

SecretStoreIn git / OA log?
Publisher password + publish tk_…Apple PasswordsNo
Token-manager passwordApple PasswordsNo
Firebase Admin JSONNAS secrets/keryx/firebase/No
APNs .p8NAS secrets/keryx/iOS/No (Key ID may live in Apple Passwords / Firebase)
Host + usernames onlyNAS 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-file pointing at the Admin SDK JSON
  • upstream-base-url empty
  • server.yml and the JSON file root:ntfy 640 so 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):

KnobStarting valueWhy
Tier safecall message-limit100000Authenticated SafeCall / Keryx headroom
Tier message expiry24hCache window for poll-on-resume / new QR snapshot
visitor-request-limit-exempt-hostsEach SafeCall public egress IPv4 and IPv6Recheck / SOS bursts must not share the anonymous 429 bucket
visitor-subscription-limitLeave default 30 firstKeryx multiplexes one SSE per instance (comma-separated topics)
Anonymous burstDo not raise firstFix 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 ntfy plus 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 configured auth-file) in the host backup. Never commit user.db. Restore requires the same auth-file path and file mode the unit can read.

Evidence, handoff, change control, support boundary ​

ItemRule
OwnerThomas Minitsios (go/no-go). Per-client ops contact in controlled records
Change controlNotify-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 boundaryExisting SafeCall channel only (legal/support.md). Transport setup is not ordinary onboarding
Per clientRecord 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.

TestHowStatus (13 September 2026)
PublishDashboard Recheck, Watch/Panic Test Send, or authenticated POST with the publish tokenProven on TST000 after egress exemption (no 429)
Live subscribeKeryx on a real tk_… QR and official ntfy app on the same topicDual-run + operator re-scan; official app still received TST000-test
Terminated / swipe-awayntfy-fcm-apns.md Parts J–KFirst-device 1.2.1 (8) only. Full matrix is #117
Reboot / cacheAfter phone reboot, poll should recover messages still in the ntfy cache; expired messages are goneExpected behavior documented. Evidence stays #117 / #118
UnauthorizedDual-run: anonymous publish still 200 (accepted interim). After deny-all: anonymous must failDual-run proven. Deny-all is OA-119-ntfy-cutover
Failure alertsSafeCall ntfy health / Recheck; stop on 429Documented. No extra monitoring product

Templates ​

FileUse
templates/ntfy-server.dual-run.ymlCurrent interim (read-write)
templates/ntfy-server.deny-all.ymlLater cutover only. Do not apply on hetest2 now
templates/nginx-ntfy.confTLS terminate + forwarded client IP
templates/safecall-ntfy-config.mdSafeCall util.sh config set

Replace placeholders. Never commit filled secrets.