reference
deputypwa HTTP API
The deputyOS PWA is a small axum web app that provides a LAN-trusted dashboard, log viewer, key rotation surface, and Web Push subscription endpoint. Routes are registered in deputypwa/src/routes.rs.
The deputyOS PWA is a small axum web app that
provides a LAN-trusted dashboard, log viewer, key rotation surface,
and Web Push subscription endpoint. Routes are registered in
deputypwa/src/routes.rs.
The PWA shells out to deputyctl <subcmd> --json for everything it
displays. There is no server-side state beyond the VAPID keypair and
the in-memory single-shot flash slot.
[TOC]
Trust model
The PWA is LAN-trusted. Same trust model the wizard hands off when it finishes — operators on the LAN are assumed allowed. Production bakes can layer Tailscale or Cloudflare Tunnel for stronger auth without changing this code.
There is no token or session check on the PWA’s protected routes today. (The wizard’s auth flow is independent; see wizard HTTP.)
Public routes
| Method | Path | Response |
|---|---|---|
| GET | / | 302 Location: /app/dashboard |
| GET | /healthz | 200 ok (text/plain) |
| GET | /manifest.webmanifest | The PWA’s web app manifest (application/manifest+json). |
| GET | /sw.js | The service worker (text/javascript). |
| GET | /static/style.css | Bundled CSS. |
| GET | /static/icon.svg | Bundled icon. |
Application routes
All return HTML rendered by the bundled templates module.
| Method | Path | Renders | Underlying deputyctl calls |
|---|---|---|---|
| GET | /app/dashboard | Dashboard view | deputyctl status --json, version --json, limits --json, cost --json, doctor --json |
| GET | /app/logs?lines=<n> | Journal tail viewer | deputyctl status --json then journalctl -u <unit> -n <lines> |
| GET | /app/keys | Provider key list | deputyctl model list --json |
| POST | /app/keys/rotate | Flash → 302 to /app/keys | deputyctl model set --provider <p> --key-from-stdin --yes |
| POST | /app/push/subscribe | 201 Created (text) | – (writes to subscriptions file) |
| GET | /app/push/vapid-public | VAPID public key, base64url encoded | – |
Per-route detail
GET /app/dashboard
Calls data::fetch_dashboard on a blocking thread. The function
shells out to the five deputyctl ... --json subcommands and
deserialises each into a typed view; failures fill the corresponding
slice with defaults and set stub=true (so the UI shows “stub” badges
rather than crashing).
In dev-stub mode (DEPUTYPWA_DEV_STUB=1) or when deputyctl is not on
PATH, the function returns a synthetic dashboard so the UI is
exercisable without an image.
Response shape: HTML — the dashboard template renders status, version, limits summary, cost summary, and doctor verdict.
GET /app/logs?lines=<n>
Query: lines (optional, defaults to 100; clamped to 10..1000).
Calls data::fetch_dashboard for the active unit name, then
data::fetch_journal_tail(unit, lines) which shells out to
journalctl -u <unit> -n <lines> on a blocking thread.
Falls back to openclaw.service if the active profile is unknown.
Response: HTML — the logs template renders the journal output as preformatted text.
GET /app/keys
Calls data::fetch_providers (which shells deputyctl model list --json). Renders the provider list, each row with a “Rotate” form
that POSTs to /app/keys/rotate. The page also surfaces a single-shot
flash message stored in memory.
In dev-stub mode, returns a synthetic catalogue.
POST /app/keys/rotate
Form body:
provider=<id>&api_key=<value>
Validation: both fields non-empty.
Action: spawns deputyctl model set --provider <id> --key-from-stdin --yes on a blocking thread, pipes api_key to stdin. On success
flashes “Rotated
In dev-stub mode, no subprocess is run — flashes
(dev-stub) would rotate <id> via ….
Response: 302 Location: /app/keys so the flash renders on next
render of the keys page.
POST /app/push/subscribe
Body: application/json. The full Web Push subscription object as
emitted by PushManager.subscribe():
{
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"expirationTime": null,
"keys": {
"p256dh": "BNcRdr...base64url...",
"auth": "BTBZi...base64url..."
}
}
Action: appends one JSON line to <data_dir>/push-subscriptions.jsonl
(default /var/lib/deputyos/push-subscriptions.jsonl; tests pin via
AppState::with_subscriptions_path). Mode 0640.
Response: 201 Created with body ok on success;
500 Internal Server Error with an HTML error page on failure.
GET /app/push/vapid-public
Returns the VAPID public key as base64url-encoded text/plain. Used
by the service worker / front-end to pass to PushManager.subscribe().
If push is disabled (openssl wasn’t available at boot, or the keypair
hasn’t been generated), returns the empty string with text/plain.
The VAPID keypair is generated lazily on first PWA startup at
<data_dir>/vapid.pem (mode 0600). Generation shells out to openssl ecparam -genkey -name prime256v1. See
deputypwa/src/push.rs.
Web Push subscription flow
sequenceDiagram
participant Browser
participant SW as Service worker
participant PWA
participant Push as Push provider
Browser->>SW: register('/sw.js')
SW->>PWA: GET /app/push/vapid-public
PWA-->>SW: <base64url public key>
SW->>Push: PushManager.subscribe({applicationServerKey})
Push-->>SW: PushSubscription
SW->>PWA: POST /app/push/subscribe (JSON)
PWA-->>SW: 201 Created
Note over PWA: appends to push-subscriptions.jsonl
The PWA itself doesn’t send notifications today — cost-alert and
update-applied hooks are the future emitters. The subscription file
is the durable record they’ll consume.
Dev-stub mode
When DEPUTYPWA_DEV_STUB=1 is set (or when deputyctl is not on PATH):
/app/dashboardrenders a synthetic dashboard with a “stub” badge./app/keysrenders a synthetic provider list./app/keys/rotateflashes(dev-stub) would rotate ...instead of shelling out./app/logsmay show “(journal task failed)” ifjournalctlisn’t on PATH.
This makes make pwa exercisable on a developer laptop without a
baked deputyOS image.
Side-effect map
| Operation | Files written | Subprocesses spawned |
|---|---|---|
GET /app/dashboard | – | 5× deputyctl ... --json |
GET /app/logs | – | 1× deputyctl status --json + 1× journalctl |
GET /app/keys | – | 1× deputyctl model list --json |
POST /app/keys/rotate | /etc/deputyos/secrets.env (via deputyctl) | 1× deputyctl model set |
POST /app/push/subscribe | <data_dir>/push-subscriptions.jsonl (append) | – |
GET /app/push/vapid-public | (lazy) <data_dir>/vapid.pem (first call only) | (lazy) openssl ecparam (first call only) |
See also
- Reference / CLI / deputypwa — the
servesubcommand, env vars, dev-stub mode. - Reference / CLI / deputyctl — the
--json-emitting subcommands the PWA shells. - Reference / Schemas / Providers —
what
/app/keyslists. - Reference / System / Filesystem layout —
vapid.pem,push-subscriptions.jsonlpaths. - How-to / Rotate keys — the user
workflow that goes through
/app/keys/rotate. - Operations / Monitoring and logs —
what
/app/logsis showing.