A spoke could be silently failing to sync (expired token → 401, or hub unreachable) with the only signal buried in the daemon log. Now: - hephd tracks SyncHealth (last attempt/success time, last error, auth-failure flag) from the background sync loop and sync.now, classifying a 401 as an auth failure. sync.status returns it plus the pending merge-conflict count. - heph-tui shows a live status-line indicator (spoke only): '⟳ <age>' since the last good sync, red '⚠ auth' when re-login is needed, '⚠ offline' when the hub is unreachable, and '⚠ N conflicts' when conflicts are pending. The event loop polls on a 2s tick so the age advances and failures appear while idle. - docs: recommended Authentik access/refresh token validity to stop frequent re-logins (with the iOS PWA localStorage-eviction caveat). Closes the 'Add hub connection status to heph-tui' and 'Spoke sync health: surface unhealthy state instead of silent 401 spam' backlog items. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.5 KiB
| title | modified | tags | |
|---|---|---|---|
| Host heph-pwa from the hub | 2026-06-04 |
|
Host heph-pwa from the hub
How to serve the heph-pwa mobile app from the canonical hub (indri) in
the hub-and-spoke deployment, with OIDC auth — the production counterpart of the
unauthenticated single-machine demo. Assumes the heph-pwa work is merged and
released, so the installed hephd already has --web-root and CORS.
Read set-up-sync-hub first — this builds directly on the hub it stands up (server mode, Authentik OIDC, Tailscale transport).
What the app needs from the hub
The PWA is a thin, online-only client: it loads its static shell over HTTP and
makes JSON-RPC calls to the hub's /rpc. So the hub must (1) serve the shell
files and (2) accept the app's authenticated RPC calls. Both are already in
hephd --mode server:
--web-root <dir>serves the shell for any non-API path (with anindex.htmlSPA fallback). The shell is unauthenticated — it is only HTML/JS; all data still flows through the OIDC-gated/rpc.- Every response carries permissive CORS headers and answers the
OPTIONSpreflight, so the shell may instead be hosted anywhere and still call the hub cross-origin.
1. Put the shell on the hub
The release does not yet bundle the app, so fetch the heph-pwa/ directory at
the same version tag the hub runs (keeping shell and hub in lockstep matters
— see Upgrades below), and copy it to a stable path:
# on indri, matching the running hephd version (e.g. v1.4.0)
git clone --depth 1 --branch v1.4.0 \
https://forge.ops.eblu.me/eblume/hephaestus.git /tmp/heph-src
sudo mkdir -p /var/lib/heph/web
sudo cp -r /tmp/heph-src/heph-pwa/. /var/lib/heph/web/
Future improvement: have the release workflow package a
heph-pwa-<version>.tar.gzasset (as it already does for docs), so this step becomes "download + extract" and the lockstep is automatic. Until then, pin the clone to the hub's tag.
2. Add --web-root to the hub service
Extend the hub invocation from set-up-sync-hub with --web-root (everything
else — issuer, audience, db — unchanged):
hephd --mode server \
--http-addr 0.0.0.0:8787 \
--db /var/lib/heph/heph.db \
--web-root /var/lib/heph/web \
--oidc-issuer https://authentik.ops.eblu.me/application/o/heph/ \
--oidc-audience <heph-client-id>
In the systemd unit (or launchd plist), add the two --web-root arguments and
systemctl restart hephd. Self-update is compatible now that the release ships
the flag — just refresh the web-root on each upgrade (next section).
3. Terminate TLS (recommended)
Serve the app over HTTPS so it is a secure context: only then do the service worker (offline launch), proper PWA install, and the Web Speech mic work. (On iOS, "Add to Home Screen" and keyboard dictation work over plain HTTP too, so HTTPS is a polish step, not a blocker.) Two good options:
-
Tailscale serve — tailnet-only, automatic MagicDNS cert, no public exposure:
tailscale serve --bg --https=443 http://127.0.0.1:8787 # app is then at https://indri.<tailnet>.ts.net/Bind
hephdto127.0.0.1:8787in this case and let Tailscale be the only thing exposing it. -
Reverse proxy (Caddy / nginx) terminating a real cert, if the hub should be reachable beyond the tailnet. Proxy all paths (
/,/rpc,/sync/*) tohephd.
Either way the app is same-origin with the hub, so no CORS is involved and the app defaults its hub URL to its own origin.
4. Connect a phone
-
Ensure the phone is on the tailnet (or can reach the proxy).
-
Open the hub URL (
https://indri.<tailnet>.ts.net/) and Add to Home Screen. -
The app defaults its Hub URL to the origin it loaded from — no typing.
-
Sign in: open Settings → Login with Authentik. The app reads the hub's
GET /configfor the issuer + client id (zero-config) and runs an Authorization-Code + PKCE redirect to Authentik; after you approve it lands back on the app, signed in, and silently refreshes the token from then on. (A manual Bearer token field remains as a fallback for hubs without OIDC, or for pasting a one-off token.)Re-prompted for login too often? The fix is the Authentik provider's refresh token validity, not the app — see the token-lifetime note in set-up-sync-hub. (On iOS, Safari may also purge an un-installed PWA's storage after ~7 idle days; Add to Home Screen mitigates it.)
Prerequisite — register the PWA redirect URI. Browser PKCE needs the app's
origin registered on the Authentik heph provider's Redirect URIs (Authentik
also keys token-endpoint CORS off those origins). Add the PWA origin(s) with a
trailing slash, e.g. https://heph.ops.eblu.me/ (and http://localhost:8787/
for local dev). In blumeops this is the redirect_uris list on the heph
provider blueprint.
Upgrades
On each hub upgrade, refresh the shell so it matches the running hephd:
git -C /tmp/heph-src fetch --depth 1 origin v1.5.0 && git -C /tmp/heph-src checkout v1.5.0
sudo cp -r /tmp/heph-src/heph-pwa/. /var/lib/heph/web/
The service worker is versioned (CACHE = "heph-pwa-vN"), so an updated shell
evicts the old cache on next load. Hard-refresh once if a phone seems stuck on a
stale version.
Related
- heph-pwa — the app itself (features, quick-add, voice, triage)
- set-up-sync-hub — stand up the hub + Authentik OIDC this doc extends
- run-the-daemon — run
hephdas a managed service - v1-prototype-tech-spec — RPC API and auth model