The admin bearer must not ship in a distributed build, so hosts no
longer use it at all. A Mac self-enrolls with the relay on first use
(POST /v1/host/enroll) and receives its own scoped credential (only
the SHA-256 is stored server-side; the credential lives in the login
Keychain). Register and notify are now authorized per host: a host
can wake only devices it registered itself (admin remains an
operator-only override); room-registered records are owner-tagged
with the DO id and refused out-of-band. Each device record's APNS
environment now wins over the global APNS_ENV secret, so mixed
sandbox/production fleets work.
Settings ▸ Remote's section is now just "Relay" with a single
"Push notifications" toggle that takes effect immediately; the relay
URL and APNS-environment overrides appear only in local dev builds.
PushRelayConfig reduces to enabled + baseURL (built-in production
default) + apnsEnv (production default). PUSH_SETUP.md §3/§4 updated;
stale dotted-bundle-id comments in env.ts/wrangler.jsonc fixed.
Co-Authored-By: Claude Fable 5 <[email protected]>
An app can't hold a secret the machine's owner can't extract (root +
debugger defeats bundling, Keychain, and obfuscation; the Secure
Enclave can't import external keys), and a leaked .p8 can push to
every user of the topic. So the push path is relay-only again: the
key lives exclusively in the relay's Worker secrets and the Mac only
asks the relay to send. Settings and PUSH_SETUP.md revert to the
relay-only form, with the rationale recorded in §4.
Co-Authored-By: Claude Fable 5 <[email protected]>
Answering "can push be local when on LAN": a backgrounded iOS app can
only be woken through Apple's push service, so a LAN-only wake path
doesn't exist — but the *sender* can be this Mac. DirectAPNSSender
holds the APNS .p8 (path + Key ID + Team ID, runtime-configured like
the relay: env vars first, then the Settings-written defaults keys)
and posts the same content-free approval.pending tickle straight to
api.push.apple.com (ES256 provider JWT via CryptoKit, 40-min cache,
per-device throttle) — no Cloudflare dependency for push.
ApprovalPushing unifies the two senders; SyncHost wakes non-connected
paired devices through whichever is configured (direct wins). The
Settings ▸ Remote push section gains a sender picker with the direct
fields (.p8 chooser, Key ID, Team ID). PUSH_SETUP.md §4 rewritten as
Option A (this Mac) / Option B (relay). JWT signing verified against
the public key in tests; payload asserted byte-compatible with the
worker's apns.ts tickle.
Co-Authored-By: Claude Fable 5 <[email protected]>
The relay URL / admin secret / APNS environment are runtime host
settings (PushRelayConfig.resolve reads env then UserDefaults when
the sync server starts), not build settings — and a Finder-launched
app never sees shell env. Add an "Approval push (relay)" section to
Settings ▸ Remote writing the same defaults keys, with an Apply
button that restarts remote access so changes take effect. Update
PUSH_SETUP.md §4 to point at the UI and keep the env-var form for
scripted runs.
Co-Authored-By: Claude Fable 5 <[email protected]>
docs/PUSH_SETUP.md lists the manual steps left to light up push /
Live Activities end-to-end: APNS key, the one-click Xcode Push
capability, Cloudflare provisioning + secrets (with the bundle-id
topic gotcha: xyz.blakeslee.nucleic-remote, not .remote), host relay
config, and the on-device verification checklist. Also adds
CODE_SIGN_ENTITLEMENTS so the existing entitlements file actually
signs into the app.
Co-Authored-By: Claude Fable 5 <[email protected]>