remote.DocumentationHome

Web console and Web Push

The browser console is served at https://remote.ivjn.us/console/. It supports registration with email verification, account/password or operator-token login, workspace-scoped devices, streaming commands, an xterm PTY, Android screenshots/taps/navigation/text input, metrics, audit records, device creation/deletion, and opt-in notifications. Readonly accounts cannot execute commands or view Android screens. Offline devices remain visible.

Devices advertising ubus expose an ubus tab with object, method and optional JSON-object parameters (blank sends null). Calls use sealed sessions and require an online, enabled device with operation access. Cancel stops waiting; it cannot undo a method already executed on the device. Device switches and permission loss clear pending calls and results.

Registration creates a separate workspace. The email must be verified using its eight-digit code before login; the console supports resending codes and returning to verification after a reload. Exchange must have SMTP configured (SMTP_HOST, SMTP_PORT, SMTP_FROM, and authentication/security settings in .env.example). Without an available mail service, registration reports an error instead of bypassing verification.

Credentials stay in page memory. Refreshing requires login again. Logging out closes sessions, attempts to remove this browser's push subscription, and revokes password-login sessions. Primary operator tokens retain their existing lifetime. Notification clicks focus an open console without reloading its credentials.

Build and serve

cd website/console
npm ci
npm test
npm run build

Copy dist/ to the website's console/ directory. Keep the existing website/assets/ available at /assets/ for shared fonts and the mark. Serve index.html, sw.js, and the manifest with revalidation/no-cache. Hashed assets may be cached immutably. Do not cache /api/* or /ws/*.

The console uses same-origin /api/* and /ws/browser. Reverse proxy these to Exchange, preserving Host and allowing WebSocket upgrades. Do not strip the /api or /ws prefix. The Vite development server proxies API/WS to 127.0.0.1:18082. Rust Exchange permits same-origin browser access by default. Configure exact origins with REMOTE_CORS_ORIGINS; only an explicit * permits all origins. Worker origins use CORS_ORIGINS and its own defaults. Bearer authentication and one-use WebSocket tickets remain required; cross-origin cookies are not enabled.

Browser authentication uses POST /api/ws-ticket with Bearer authorization, followed by GET /ws/browser and a first JSON frame {"ticket":"..."}. Tickets expire in 30 seconds, are single-use, and recheck the operator's enabled state and credential validity when consumed. Long-lived credentials are never put in WebSocket URLs. Native clients keep using /ws/operator.

All device operations use native sealed WebSocket sessions. HTTP provides authentication, device metadata, key and grant directories, and relay session administration; see HTTP API for Agent operations.

Enable Web Push

Set both variables in the Exchange environment (missing both disables push):

WEB_PUSH_VAPID_PRIVATE_KEY=<base64url-unpadded-32-byte-P256-private-key>
WEB_PUSH_VAPID_SUBJECT=https://remote.ivjn.us

Generate a private key with python scripts/generate-vapid-key.py. Store it outside Git in a restricted environment file, keep it stable across restarts, and back it up securely. The public key is derived automatically. Changing the key requires browsers to subscribe again.

WEB_PUSH_ALLOWED_ORIGINS defaults to the exact trusted origins https://fcm.googleapis.com,https://updates.push.services.mozilla.com,https://web.push.apple.com. Add another browser vendor only after verifying its HTTPS push origin. Client-supplied endpoints cannot expand this allowlist. Redirects are never followed.

Authenticated routes:

Method and path Purpose
GET /api/webpush/config Enabled state and public application-server key
GET/POST /api/webpush/subscriptions List owned metadata or subscribe/upsert
DELETE /api/webpush/subscriptions/{id} Remove an owned subscription
POST /api/webpush/test Queue a visible test to the caller's subscriptions
POST /api/webpush/messages Send to yourself, or as admin to another account in the same workspace
POST /api/webpush/devices/{id}/messages Notify subscribed administrators who manage the authenticated device

Subscriptions use the browser's PushSubscription.toJSON(), application_server_key, and triggers. triggers holds at most one online object and one offline object. Each object has kind, tick_count (default 3, range 1–100), and optional device_ids. offline notifies after tick_count consecutive misses; online notifies after tick_count consecutive successes. device_ids is one device id (a string), a list of device ids, or omitted/null for every device in the workspace. An empty triggers array subscribes the browser without automatic presence notifications. missed_count and success_count are not accepted. Messages use optional operator_id and notification with title, body, optional same-origin relative url and tag. /webpush/ is a minimal reference client; the production console uses its own worker scoped to /console/.

Migration 0005_web_push.sql adds subscriptions and the durable outbox. 0008_online_offline_triggers.sql moves device notifications onto triggers and drops missed_count / success_count. 202 means queued, not delivered or displayed. Jobs expire after 15 minutes. Network errors, 408, 429 and 5xx retry up to five attempts; 404/410 remove the matching subscription revision. A 90-second lease allows pending work to recover after worker failure. Delivery is at least once and browser display depends on vendor delivery, permission and operating-system policy. Device events before durable enqueue are best-effort under overload; events already queued survive restarts. Limits: 20 subscriptions/account, 32 queued messages/subscription and 30 mutations/minute/account.

iOS/iPadOS may require adding the console to the home screen before enabling notifications. Browser Push service reachability is separate from Exchange connectivity.

Validation

Run cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, and the console's npm tests/build. For the PostgreSQL/HTTPS integration test, install psycopg[binary], set WEB_PUSH_TEST_ADMIN_URL to a test PostgreSQL connection with CREATE DATABASE rights, then run python scripts/test-webpush.py. It creates and removes a disposable database; never point at a database role with unintended privileges.

Send from every client

Web Console has a message form in Browser notifications. Mobile Console Settings has Web Push send/test controls and an Open Web Console link. Desktop Agent local settings, Android Agent Settings and the browser extension configuration page can send using device credentials. Headless/OpenWrt agents use the command below. Receive notifications by enabling them in Web Console/PWA; this does not add native FCM/APNs receivers.

remote-cli webpush status
remote-cli webpush subscriptions
remote-cli webpush test
remote-cli webpush send --title "Backup ready" --body "Completed"
remote-cli webpush send --operator-id ACCOUNT_UUID --title "Notice" --body "Hello"
remote-agent --config /path/to/config.json webpush --title "Device ready" --body "Completed"
{"notification":{"title":"Backup ready","body":"Completed","url":"/console/","device_id":"edge-1"}}

POST /api/webpush/messages uses operator Bearer authentication. Omit operator_id to send to yourself; another recipient requires admin permission and must be in the same workspace. The JSON body above also works with remote-cli webpush send --json-file message.json. Empty presence triggers disable automatic online/offline alerts while retaining generic message delivery.

POST /api/webpush/devices/{id}/messages uses that device’s Bearer token and accepts a notification object directly (title, body, optional tag). Only active subscriptions of enabled administrators who manage that device receive it. The server sets device_id, event=device.message, occurred_at and /console/; callers cannot choose an account or impersonate another device. Device sends share a 30 requests/minute/workspace budget and a maximum of 256 subscriptions per send. Account subscription and queue limits still apply. No active recipient returns 202 {"queued":0}; queued counts subscriptions, not deliveries.