Phone notifications
The fleetd phone app shows full notifications: a Navi’s reply, a lead’s message, an approval waiting for you, an order that landed or stopped. They are end-to-end encrypted. The hub seals each one to your phone’s own key, and only the phone can open it.
Apple and Google deliver a push only for the app’s publisher, so a hub can’t reach your phone directly. It goes
through a push relay that holds the publisher’s credentials. For the published app, that relay is
https://push.fleetd.sh, and every hub uses it out of the box: pair the phone, allow notifications, and they arrive.
There is nothing to set up and no key.
Act from a notification
Section titled “Act from a notification”Press and hold a notification, or swipe it left on the lock screen and tap Options, to act without opening the app:
| Notification | Buttons |
|---|---|
| An approval of a command, shown whole with where it runs | Approve, Deny |
| Money, an irreversible command, a command that uses a secret, a file edit, an email or anything else a Navi sends or shares | Review, which opens its full card in the app |
| A Navi’s or a lead’s message | Reply, which sends your text into that chat |
| A secret, an order, an alert | none: tap it to open the app |
- Approve, Deny and Reply ask you to unlock the phone first: they act with the phone’s own key.
- Approve shows only when the notification holds everything the card would: the whole command and where it runs. Anything longer, anything that uses a secret, edits a file, spends money, can’t be undone or reaches someone, opens the card in the app instead. So does a notification whose command, title or reason has characters that could hide or reorder its text, such as invisible or right-to-left marks.
- The outcome replaces the notification, without a sound: “Approved: …”, or why it wasn’t, such as “Already answered on the desktop”. A sent reply just disappears.
- The app icon’s badge counts what needs you: each open approval or secret, plus each chat waiting for your reply. Every notification brings the new count, and the app sets it again after you act.
- A chat’s notifications stack together, as in Messages.
The buttons, the badge and the text are sealed with the rest of the notification: the relay sees nothing more. A phone app from before this shows the same notifications without buttons until it updates.
What the relay sees
Section titled “What the relay sees”- It sees: your phone’s device token, the size of each sealed notification, when it was sent, and the IP address of the hub that sent it.
- It can’t see: any content. Not the title, the text, the project, the person or the hub’s URL. These are sealed to the phone’s key before they leave the hub.
It keeps nothing past the request and logs no token or ciphertext. A stranger who learns a device token can post to the relay, but can’t seal anything the phone opens: iOS shows at most the generic “fleetd: New activity”, Android shows nothing. The relay takes at most 60 pushes a minute to one device and 600 a minute from one address.
Turn notifications off
Section titled “Turn notifications off”On the hub machine:
fleetd push-relay set off # the hub sends no phone notificationsfleetd push-relay set https://push.fleetd.sh # back to the defaultoff is kept across restarts. From another machine, add --hub <url> --key-file <path> with a key that has admin
on every machine. The phone app registers each paired phone with the hub once its person allows notifications: see
Phone app › Notifications.
Run your own relay
Section titled “Run your own relay”Only if you build and sign the app yourself, under your own bundle ID: push.fleetd.sh can’t deliver for it. Run
fleetd push-relay with your own Apple and Firebase credentials and point your hubs at it.
What you need
Section titled “What you need”- iOS: an APNs auth key (a
.p8file) from your Apple developer account (Certificates, IDs & Profiles › Keys, with Apple Push Notifications service), its key ID, your team ID, and the bundle ID your app build uses. - Android: a Firebase project with the app registered, and a service account JSON key from it (Project settings › Service accounts › Generate new private key).
Either one is enough to start the relay. A platform without credentials is refused.
Start it
Section titled “Start it”On any machine your hubs can reach, such as the hub machine itself:
export FLEETD_APNS_KEY_FILE=~/keys/AuthKey_ABC123DEFG.p8export FLEETD_APNS_KEY_ID=ABC123DEFGexport FLEETD_APNS_TEAM_ID=TEAM123456export FLEETD_APNS_TOPIC=com.example.fleetd # the app's bundle ID# export FLEETD_APNS_SANDBOX=1 # for development builds from Xcodeexport FLEETD_FCM_SERVICE_ACCOUNT=~/keys/firebase-relay.json
fleetd push-relay --listen 127.0.0.1:4795It prints which platforms are on. Keep the key files out of any repo, readable only by the user that runs the relay.
curl http://127.0.0.1:4795/ answers {"ok":true,"apns":true,"fcm":true}.
FLEETD_APNS_SANDBOX must match the builds your phones run: 1 for development builds from Xcode, unset for
TestFlight and release builds. Until a push has gone through, the relay treats an APNs or Firebase answer that could
mean its own setup is wrong (another environment, bundle ID or Firebase project) as its failure, not the phone’s, and
the hub keeps the phone and logs what to fix.
Relay key
Section titled “Relay key”Give your relay a key whenever it listens on more than 127.0.0.1, so it takes only your hubs: a file holding a long
random string, outside any repo.
openssl rand -hex 32 > ~/keys/push-relay.keychmod 600 ~/keys/push-relay.keyexport FLEETD_PUSH_RELAY_KEY_FILE=~/keys/push-relay.keyIt then refuses a push without Authorization: Bearer <key> with 401. Give the same file to each hub that uses it.
To keep it running, use a systemd user unit with the same variables in Environment= lines or an
EnvironmentFile=. If hubs on other machines use it, listen on the tailnet address (--listen 100.x.y.z:4795) or
put it behind HTTPS. Behind a reverse proxy on the same machine, the relay counts each push against the address in
the proxy’s X-Forwarded-For.
Point a hub at it
Section titled “Point a hub at it”On the hub machine:
fleetd push-relay set http://127.0.0.1:4795fleetd push-relay set http://100.x.y.z:4795 --relay-key-file ~/keys/push-relay.key--relay-key-file seals the relay’s key to the hub on your machine. The hub keeps it sealed and sends it with every
push. Setting the URL again without it drops the key. The hub never sends a key to push.fleetd.sh, and reaches it
over HTTPS only.
What is sent, and to whom
Section titled “What is sent, and to whom”- A Navi’s messages and approvals go to its owner only.
- A project lead’s messages go to the person who last wrote in that project’s chat, else the hub’s owner.
- Approvals, failed fetches, machines down under running work and usage limits: what the hub already marks as needing you. Money and irreversible commands are urgent.
- Orders that landed or stopped.
- During the hub’s quiet hours a notification arrives without a sound, unless it is urgent.
A phone signed out of the hub, or whose person was removed, gets nothing more.
Limits
Section titled “Limits”The relay takes a ciphertext of at most 3500 characters, 60 pushes a minute to one device and 600 a minute from one address. It keeps nothing and logs no token or ciphertext. When Apple or Google say a token is no longer valid (the app was removed), the relay tells the hub, and the hub forgets that phone. If one notification would drop more than half of a person’s phones, or their only one, the hub keeps them and logs that the relay’s setup is the likelier cause. The hub logs each relay failure once an hour at most.
The wire format and the encryption are in docs/design/push.md.