Appearance
Keryx Firebase / FCM / APNs closed-app wake
Last updated: 13 September 2026 (#119 preflight: real client files, 1.2.0+7)
Operator how-to for killed-state / swiped-away Keryx alerts on Android and iOS. Client code already polls on FCM/APNs wake. This page stands up the Keryx Firebase project and the sourcectl notify host firebase-key-file.
Owed rows live in operator-actions.md. Do not close #119 or #117 from this page. First-device smoke is OA-119-closed-push-proof. The full OEM/Doze/Focus matrix stays #117.
Do not put the Firebase Admin / ntfy service-account JSON, APNs .p8, Key ID, or tokens in this file or in git. Record only where they live.
Official ntfy knobs: Firebase (FCM) and iOS instant notifications.
Purpose and non-goals
Purpose
- Create one Firebase project that can address Android
com.sourcectl.keryxand iOScom.sourcectl.keryxapp. - Ship a new TestFlight IPA and Play Internal AAB that include the committed
keryx-for-safecallclient files (installed Internal1.0.0+3cannot receive this wake). - Point the sourcectl notify host at that project with
firebase-key-file. - Upload the team APNs Auth Key to Firebase so iOS wakes work.
- Prove one swipe-away banner on one Android and one iPhone.
Non-goals
- Setting
upstream-base-url: https://ntfy.sh. That wakes the official ntfy iOS app, not Keryx. - Inventing a
/v1/account/deviceAPI. Keryx subscribes to FCM topics (~pollplus each QR topic) the same way official ntfy does. - ntfy auth dual-run Parts A–D (ntfy-auth-and-play-internal.md). Anonymous
read-writeis enough for a first FCM proof. auth-default-access: deny-all(OA-119-ntfy-cutover). After that cutover ntfy sends iOSpoll_requestwith a generic “New message” APNs alert; a Notification Service Extension is the follow-on, not this page.- Waking on quiet SafeCall snapshots that publish
Firebase: no. Those refresh cards on resume/poll only. - Full #117 soak, store listings, or Play production.
Already true
| Item | Value |
|---|---|
| Android package | com.sourcectl.keryx |
| iOS bundle | com.sourcectl.keryxapp |
| APNs entitlement | Store/TestFlight IPAs prove aps-environment=production (#110) |
| Client code | Android FCM service + iOS Firebase topic subscribe; no perpetual dataSync FGS |
| Firebase project | keryx-for-safecall (client files committed; Admin SDK stays on NAS) |
| Android client config | keryx/android/app/google-services.json (com.sourcectl.keryx) |
| iOS client config | keryx/ios/Runner/GoogleService-Info.plist (com.sourcectl.keryxapp) |
| Repo / next candidate | 1.2.0+7 in keryx/pubspec.yaml |
| Local GLaDOS artifacts (13 September 2026) | AAB SHA-256 f3a93e6e806a46096503b4160c0411693c26487c42dc69a0aece656ea8b90c88; IPA SHA-256 3e5b93a9ef1d16d6372336a046fb49730491d8335c764f039d5b09db068a9faa (1.2.0 (7), aps-environment=production) |
| Installed Internal binaries | 1.0.0+3 (10 September 2026) — superseded for FCM proof |
| Typical notify host | https://notify.wantok.cz (hetest2 /etc/ntfy/) |
| NAS APNs key | secrets/keryx/iOS/apns-keryx.p8 (OA-110-apns-p8 done) |
| NAS Admin SDK | secrets/keryx/firebase/ntfy-firebase-adminsdk.json (OA-119-firebase done) |
| Firebase APNs | Production + Development keys uploaded for com.sourcectl.keryxapp (OA-119-ntfy-apns done) |
| Notify host file | hetest2 /etc/ntfy/server.yml + /etc/ntfy/firebase.json (root:ntfy 640); ntfy 2.16 listens on 127.0.0.1:2586. Part I1 done 13 September 2026 (200 + Publishing to Firebase). /v1/config base_url is always empty on 2.16 — ignore it |
| NAS | Unlock first if empty: nas-unlock.md |
Do not create a second Play or App Store app record.
Order of work
- OA-110-apns-p8 — team APNs Auth Key in NAS
secrets/keryx/iOS/. - OA-119-firebase — confirm Console apps for
com.sourcectl.keryxandcom.sourcectl.keryxapp(client files already in repo); store the Admin SDK JSON on NAS. - OA-119-ntfy-apns — upload the
.p8to Firebase Cloud Messaging. - OA-119-ntfy-fcm —
base-url+firebase-key-fileon the notify host. - Rebuild and upload a new AAB/IPA (
bun keryxorbun run release:keryx). Install from Play Internal and TestFlight. Installed Internal1.0.0+3cannot receive this wake; next candidate is1.2.0+7. - OA-119-closed-push-proof — swipe-away smoke on one phone per platform.
Auth dual-run (OA-119-ntfy-auth through OA-119-safecall-config) can stay parallel. During anonymous dual-run, ntfy sends FCM event=message with the real title/body. After deny-all it sends event=poll_request.
Part F — APNs Auth Key
Row: OA-110-apns-p8.
Not required to build an IPA. Required before Firebase can send APNs to com.sourcectl.keryxapp.
- In Apple Developer → Keys, create an APNs Auth Key (or reuse the existing team key). Team
8P59575P2K. - Download the
.p8once. Store it only in NASsecrets/keryx/iOS/(secret-store.md). - Put the Key ID and Team ID in Apple Passwords, not git. NAS notes may record the filename and Key ID only.
Do not enable Broadcast Push. Do not check the .p8 into the repo.
Part G — Firebase project
Row: OA-119-firebase.
- Open Firebase Console as a sourcectl operator.
- Confirm the existing keryx-for-safecall project (do not create a second one). Client files are already committed.
- Confirm an Android app: package
com.sourcectl.keryx. Re-downloadgoogle-services.jsononly if the Console file drifted fromkeryx/android/app/google-services.json. - Confirm an iOS app: bundle
com.sourcectl.keryxapp. Re-downloadGoogleService-Info.plistonly if the Console file drifted fromkeryx/ios/Runner/GoogleService-Info.plist. - In Project settings → Service accounts, generate a Firebase Admin SDK JSON if it is not already on NAS. Store it as
secrets/keryx/firebase/ntfy-firebase-adminsdk.json(filename may vary). Never commit it. This isfirebase-key-fileon the notify host.
Client google-services.json and GoogleService-Info.plist are not the Admin SDK. They may be committed so Debug and store builds compile.
Part H — APNs key in Firebase
Row: OA-119-ntfy-apns.
Needs OA-110-apns-p8.
- Firebase Console → Project settings → Cloud Messaging.
- Under Apple app configuration for
com.sourcectl.keryxapp, upload the APNs Authentication Key (.p8), Key ID, and Team ID. - Use the production APNs environment for TestFlight / store IPAs.
Do not set ntfy upstream-base-url: https://ntfy.sh for Keryx. Later customer-hosted ntfy hosts may set upstream-base-url to this sourcectl notify host (the Firebase-connected one), never to ntfy.sh.
Part I — Notify host Firebase
Row: OA-119-ntfy-fcm.
Work on the ntfy server, not on a SafeCall box. Paths may differ on Docker.
Copy the Admin SDK JSON onto the host (for example /etc/ntfy/firebase.json). The Debian ntfy unit runs as user ntfy. root:root mode 640 is unreadable by that user and the process starts with defaults (empty public base_url, no Firebase). Use root:ntfy mode 640 on server.yml and firebase.json. Do not leave the Admin SDK on Samba [public].
In /etc/ntfy/server.yml:
yaml
base-url: "https://NOTIFY_HOST"
firebase-key-file: "/etc/ntfy/firebase.json"
# message-expiry-duration: "24h"base-url must be the public HTTPS origin phones already use (same value as SafeCall server_ntfy_server_url, without inventing a second host).
Leave upstream-base-url unset on this sourcectl host. Setting it to https://ntfy.sh sends wakes to the official ntfy app.
If the cache is still the ntfy default (~12h), set a longer message-expiry-duration (24h is the starting knob already used for the SafeCall tier in the dual-run how-to). Missed wakes can only recover what is still cached.
bash
sudo chown root:ntfy /etc/ntfy/server.yml /etc/ntfy/firebase.json
sudo chmod 640 /etc/ntfy/server.yml /etc/ntfy/firebase.json
sudo systemctl restart ntfy
sudo systemctl status ntfy --no-pagerntfy 2.16 always returns "base_url":"" from GET /v1/config (it forces the web UI to window.location.origin). That is not proof the YAML is unloaded. Proof the file loaded: the process listens on the listen-http from server.yml (here 127.0.0.1:2586). If firebase-key-file is set and unreadable or invalid, ntfy serve fails to start.
Confirm Firebase with Part I1 logs, not /v1/config.
SafeCall alert publishes must not send the Firebase: no header. Quiet state snapshots (intent=state) may keep it; those are not this proof.
I1. Prove ntfy can talk to Firebase
From a laptop, publish to a throwaway topic the operator controls:
bash
curl -sS -o /dev/null -w "%{http_code}\n" \
-H "Title: Keryx FCM probe" \
-H "Priority: high" \
-d "closed-app probe" \
"https://NOTIFY_HOST/PROBE_TOPIC"Expect 200. Raise log level if needed (log-level: debug or ntfy serve --log-level=debug) and look for Publishing to Firebase or Unable to publish to Firebase. INFO startup will not mention Firebase. This does not yet prove a phone received the wake.
Proven 13 September 2026 on hetest2: GLaDOS 200 to /keryx-fcm-probe-1789299971; DEBUG message_firebase=true and Publishing to Firebase with no Unable to publish to Firebase. After the probe, set log-level back to info so production logs stay readable.
Part J — New binaries
Installed Internal 1.0.0+3 cannot address real FCM devices. Repo HEAD already has keryx-for-safecall client files. GLaDOS built 1.2.0+7 on 13 September 2026 (bun run release:keryx -- --contracts). After Parts G–I:
- Do not commit the Admin SDK JSON.
- Prefer the existing GLaDOS artifacts (rebuild only if the tree changes):
keryx/build/app/outputs/bundle/release/app-release.aabandkeryx/build/ios/ipa/keryx.ipa. - Upload the AAB to Play Internal (ntfy-auth-and-play-internal.md Part E) and the IPA to Internal TestFlight (internal-testflight.md).
- Install those builds. Re-scan the QR if the phone still has an old subscribe session from before this cut.
Part K — First-device swipe-away proof
Row: OA-119-closed-push-proof.
Does not close #117. Record the date and build versions on #119 (and a short note on #117).
On one Play-certified Android 12+ phone and one TestFlight iPhone, each already scanned into Keryx with notification permission granted:
- Open Keryx. Dashboard Recheck (or a test-category send) still updates the test card while the app is foregrounded.
- Swipe Keryx away from Recents. Do not force-stop on Android and do not force-quit from iOS Settings. Those remain expected misses.
- Publish a high-priority alert the phone is subscribed to. Dashboard Recheck and Mobile Access Send test notification stay
priority=min(weak FCM wake). Watch/Panic Test Send on an asset isurgentand is the software stand-in when no hardware SOS/watch is available. - Expect a lock-screen banner without opening Keryx.
- Open Keryx and confirm the matching card/history updated.
- Optionally force-stop / force-quit once and confirm silence until reopen.
If Android is silent but iOS is not (or the reverse), check: real client config in the installed binary, FCM topic subscribe after QR scan, notify host firebase-key-file, and (iOS) APNs key uploaded for com.sourcectl.keryxapp.