research options for ios notification support for builds, MRs, assignments, etc.
ios notifications #89
Discussion
Researched. Recommendation: gitbayd speaks APNs directly.
Scope
gitbay.org only. Push works for accounts on this instance; a self-hoster gets nothing until they ship their own build under their own bundle ID. That decision removes the relay from consideration — an APNs key belongs to a bundle ID, and holding one on behalf of other instances would mean their notification text transits this server for no benefit anyone asked for.
What already exists
notify() in internal/control/notifications.go takes a notice,
resolves recipients (participants widened by watchers, minus mutes,
minus the actor), files an inbox row, and optionally queues mail. There
are 16 call sites covering issues, MRs, diff comments, review requests
and failed builds. Push is a third branch in that loop, not new
plumbing.
The delivery discipline is written twice already — internal/notify for
mail, internal/webhook for HTTP POSTs: a queue table, a drainer
goroutine, exponential backoff, dead-lettering. internal/push is a
third instance of the same shape.
Approach
No new Go dependencies. APNs requires HTTP/2 and stdlib net/http
negotiates h2 over ALPN. Token auth is an ES256 JWT — crypto/ecdsa
plus encoding/json, about 30 lines. The JWT is cached ~50 minutes;
APNs mandates refresh between 20 and 60, and minting per request returns
TooManyProviderTokenUpdates.
Config gets a [push] section alongside [mail]: enabled,
key_file, key_id, team_id, topic = "org.gitbay.gitbay",
environment. The .p8 lives at /etc/gitbay/ mode 0600 and is
referenced by path, as host_keys and the TLS key are.
Migration 0059 adds push_devices (user_id, token, label, created_at,
last_seen_at, unique on token) and a push queue mirroring the mail
queue's columns. Note the mail queue table is named notifications, so
the push queue needs a different name.
Commands, since the capability lands in the registry before any surface
renders it: notifications device add|list|remove and notifications settings push on|off, the latter matching the existing settings mail
and settings watch pair.
APNs 410 Unregistered and BadDeviceToken delete the device row.
That is the whole token lifecycle; no expiry job.
Alternatives rejected
A relay this instance operates: pure cost at gitbay.org-only scope.
Background refresh instead of push: BGAppRefreshTask is opportunistic,
routinely 15 minutes to hours, and cannot badge reliably. "Your build
failed" is exactly what it fails to deliver. Silent content-available
pushes for badging need the same infrastructure, so there is no cheaper
half-step.
Payload
Notification text is sent in full regardless of repository visibility. A private repository's name and issue number therefore reach Apple's infrastructure and a lock screen. The alternative — a generic "new activity" push with the app fetching detail — costs a Notification Service Extension holding the bearer token in a shared keychain group, which is not worth it here. Revisit if the instance stops having one human user.
Cost
Server is the small half: roughly 400-500 lines across internal/push,
the store, config and two control commands, plus an e2e against a fake
APNs endpoint.
The app is the larger half and is unwritten. krz/gitbay-ios has no
push scaffolding at all today — no app delegate adaptor, no
UNUserNotificationCenter, no background modes. It needs the permission
prompt, registration posting the token on sign-in, deregistration on
sign-out, and deep-linking a tap into existing routing. The inbox row's
path field is already the right shape for that link. Then the Push
Notifications capability on org.gitbay.gitbay, a privacy nutrition
label update, and a resubmission.
Verified
Outbound HTTP/2 from bay1 to api.push.apple.com:443 works — a GET to
/3/device/test returns 405 over h2, so TLS and ALPN reached Apple
and only the method was wrong. No network blocker.
Gaps found on the way
issue assign files no notice. This issue names assignments, but
assignment is not among the 16 notify() call sites — the dashboard
surfaces assigned work and nothing announces it. Being fixed as part of
this work.
DESIGN.org in krz/gitbay-ios records push as "not planned; propose
if the app makes the case". That row and the Parity wiki page both need
updating.
Spec and plan to follow.
referenced in commit 5063e69c97 by cmc: web: confirm a device removal on its id
2026-09-20 11:33 UTCreferenced in commit 452ae0d87e by cmc: CHANGELOG: push gating and the queue row
2026-09-20 11:33 UTCreferenced in commit d8dfc35992 by cmc: web: mask a short device token on the settings page
2026-09-20 11:33 UTCreferenced in commit dbed1606f9 by cmc: push: downgrade the scheme only for a loopback host
2026-09-20 11:33 UTCreferenced in commit 9e99af2a11 by cmc: control: issue assign notifies only new assignees
2026-09-20 11:33 UTCreferenced in commit 1dea539c7c by cmc: store: a token changing hands drops the old queue
2026-09-20 11:33 UTCreferenced in commit 16c2392828 by cmc: store: report the push queue on the dashboard
2026-09-20 11:33 UTCreferenced in commit f08b7b5e1a by cmc: control: gate push on [push] enabled
2026-09-20 11:33 UTCclosed by cmc in commit b81a25efa5: docs: push notifications
2026-09-20 11:33 UTCreferenced in commit b58fd61f44 by cmc: e2e: check the exit code in the device-reap poll
2026-09-20 11:33 UTCreferenced in commit adeefc6b75 by cmc: push: log the GITBAY_APNS_HOST scheme downgrade
2026-09-20 11:33 UTCreferenced in commit 49d601e534 by cmc: e2e: push delivery and device reaping
2026-09-20 11:33 UTCreferenced in commit 1155b57e8a by cmc: push: match scheme to the GITBAY_APNS_HOST override
2026-09-20 11:33 UTCreferenced in commit fc527a7e38 by cmc: web: require confirmation to remove a push device
2026-09-20 11:33 UTCreferenced in commit 3a58c644d0 by cmc: web: push toggle and device list on notification settings
2026-09-20 11:33 UTCreferenced in commit 90af4498e8 by cmc: control: issue assign files a notice
2026-09-20 11:33 UTCreferenced in commit c89859bef9 by cmc: control: push as the third route in notify
2026-09-20 11:33 UTCreferenced in commit 4b8e4d61f0 by cmc: control: mask short device tokens instead of echoing them whole
2026-09-20 11:33 UTCreferenced in commit 74936a4b62 by cmc: control: notifications device and settings push
2026-09-20 11:33 UTCreferenced in commit 6a187ab6f5 by cmc: push: drain the queue, started by gitbayd
2026-09-20 11:33 UTCreferenced in commit 0030cf8082 by cmc: push: truncate alert bodies on a rune boundary
2026-09-20 11:33 UTCreferenced in commit 3beb0f5b68 by cmc: push: the APNs client
2026-09-20 11:33 UTCreferenced in commit a7d44dc543 by cmc: push: APNs provider tokens
2026-09-20 11:33 UTCreferenced in commit 10c1e6efa5 by cmc: config: the [push] section
2026-09-20 11:33 UTCreferenced in commit 29548d6557 by cmc: store: the push queue, swept like the mail queue
2026-09-20 11:33 UTCreferenced in commit c9cf3e5403 by cmc: store: fix AddPushDevice id on token-reuse path
2026-09-20 11:33 UTCreferenced in commit 2979e93075 by cmc: store: push device registrations
2026-09-20 11:33 UTCreferenced in commit 94878ec5b8 by cmc: docs: preflight corrections to the push plan
2026-09-20 11:33 UTCreferenced in commit 57f3ffe0ab by cmc: docs: implementation plan for the push server half
2026-09-20 11:33 UTCreferenced in commit e4483e3d3c by cmc: docs: spec for iOS push notifications
2026-09-20 11:33 UTCreferenced in commit f327db6192 by cmc: CHANGELOG: v1.32.0
2026-09-20 20:00 UTCreferenced in commit 66304b6cd8 by cmc: push: name the account an alert is for
2026-09-20 20:58 UTCreferenced in commit 72a0560c4d by cmc: push: return the device id and badge the alert
2026-09-20 22:46 UTCreferenced in commit 9c84807c66 by cmc: wiki: push device commands reach the iOS app
2026-09-21 00:09 UTCreferenced in commit 20e869ceb2 by cmc: docs: push notifications
2026-09-21 00:22 UTCFirst live push test of the iOS client.
Commenting from a second account on purpose: notify() drops the acting user, so cmc acting on cmc's own repository notifies nobody.
Second live push test, against the TestFlight build.
Third live push test.
Badge test, with the badge actually deployed this time.
referenced in commit a453498589 by cmc: web: render the push queue on /admin
2026-09-20 11:33 UTC