Generic raw capture inputs now have an honest, non-streamable Raw video row rather than a false Cam Link identity. Both consumers now pin published bindings 2026.9.11; see publication and rollout.
Web-based control interface for live video streaming with cellular bonding. Built with Svelte 5 (frontend) and Bun (backend).
bun install
bun run devOpens at http://localhost:6173. Backend runs on port 3002.
CeraUI/
├── apps/
│ ├── frontend/ # Svelte 5 PWA
│ └── backend/ # Bun/TypeScript server
├── packages/
│ ├── rpc/ # Shared RPC schemas + validation constants
│ └── i18n/ # Internationalization (10 languages)
└── docs/ # Documentation
The app is organized into three primary destinations:
- Live — a unified device-first source list leads the destination: every capture device, built-in pipeline, test pattern, and LAN network-ingest (RTMP/SRT) slot renders as one picker, with a single "Codec & delay" affordance owning all audio configuration. Below it, a "Stream setup" card shows three always-visible readiness rows (Encoder, Destination, Network — no collapse, no ready bar) each fusing a state dot with its config summary and a one-tap edit/fix affordance, plus the Start control. Pick a source, adjust encoder/server settings, and go live. While streaming, the view switches to a live cockpit: telemetry strip, bitrate hot-adjust, per-link ingest stats, and Stop. A persistent HUD bar shows four at-a-glance facts (live/idle/offline state, health verdict, bitrate, SoC temperature) across all destinations, with per-link signal detail and full telemetry available in an expanded sheet.
- Network — connectivity overview. Bonded link status, WiFi networks (connect/disconnect/forget), cellular modems (APN, roaming, network type), Ethernet interfaces, hotspot configuration, and provider-aware Bluetooth controls. PipeWire images never try to start the retired BlueALSA unit, and a connected Bluetooth microphone is offered only when the installed provider agrees with the selected audio backend. Newly attached cellular hardware remains visible while modem services probe it. After two authoritative misses, only strong cellular evidence retains a non-actionable “Not controllable” row; descriptor-only guesses disappear and stay retired while attached, including across monitor restarts. Bluetooth descriptors never qualify by shape alone: wireless admission requires the full RNDIS triplet
e00103, andID_MM_DEVICE_IGNORE=1always excludes the device. Successful SIM PIN, PUK, and PIN2 unlocks update the affected modem row immediately over the existing push channel, without a page reload. Calm info/warning bands surface interface-topology issues without ever blocking a connection: a same-subnet notice when two bonded links deliberately share a subnet (normal for policy-routed bonding), and a policy-route warning if a bonded WiFi/modem link is missing its expected routing table. - Settings — system and device configuration. All actions open focused dialogs: cloud remote, LAN password, SSH, logs, software updates, power, version info, and per-protocol network-ingest (RTMP/SRT) enable/disable. The software-update dialog now answers rather than going quiet: a check that could not reach the repositories, or that landed on a captive portal, says so instead of reporting "up to date"; the result names which address family worked when only one did; a package that ships with the next OS image or that apt kept back is listed as such rather than offered for install; and an update refused before it starts — most often for insufficient free space — reports its own reason and clears the progress overlay instead of leaving "Applying…" on screen.
A dev-only DevTools destination is available in development builds.
-
Live session source switching: the cockpit can offer engine-reported synthetic fallback legs alongside capture legs without adding devices to discovery. It retains the legacy two-capture gate when the engine lacks the contract. Explicit empty rosters offer no switches; legacy unknown rosters have a distinct notice. The U6 hardware drill remains 0/10.
-
Verified dongle credentials: login submission verifies before saving to the existing permission-protected store. Failed attempts preserve a previous saved login; failed drafts remain only until the dialog closes. Portal reachability and rejected authentication have distinct messages in all ten locales. Router diagnostics remain reachable even without writable settings. See
CONFIG_PERSISTENCE.mdfor protocol limitations. -
Progressive Web App: offline capabilities and native app-like performance
-
Internationalization: 10 languages with full RTL support
-
Touch/kiosk mode:
?mode=touchURL flag scales touch targets to 44px minimum -
Responsive: desktop and mobile layouts with a persistent bottom HUD dock on mobile
-
Acknowledged stream stops: the backend reports a stop only after cerastream confirms it is idle; an unrelated in-flight engine request cannot swallow Stop while the media session continues in the background. The request and cleanup budgets leave an explicit margin inside the existing 12-second bound, and a late, undispatched connection is closed without sending a stale stop. Failed stop IPC also completes local cleanup without claiming engine idle; restoration reconciles that state before admitting its one restart. Live composition disable now uses staged transactional reconfiguration rather than a save-only acknowledgement. The companion engine correction is released in cerastream 2026.9.5; CeraUI 2026.9.3 prepares the consumer release. Released-build product-path qualification remains separate from the candidate evidence; see composition lifecycle evidence.
-
Capability-gated AP+STA WiFi: proven radios can keep their station link while hosting a hotspot; unsupported or unreadable drivers retain the honest exclusive switch. The deterministic virtual interface is type-checked before reuse, and adapters with temporarily unresolved hardware identity remain visible as degraded rows rather than disappearing. Physical-radio validation is still pending; see
docs/AP-STA-CONCURRENT-MODE.md. -
Device Health telemetry: memory, per-cluster CPU frequency, DDR bus load, GPU load, and (on the vendor kernel) per-core decoder load, alongside the existing SoC temperature and load-average traces. Every signal is omitted rather than fabricated when its kernel interface is absent. A newly signed-in browser receives the latest completed device-stats reading immediately rather than waiting for the next periodic sample.
-
Media-load detail: the compact per-core hint uses the driver's reported encode/decode/JPEG/RGA inventory. MPP load and utilization remain separate, including values above 100%; Media details opens the full bound-session and provenance view. Older devices keep their percentage/busy/idle/unavailable display. Both-board visual validation remains outstanding; see
docs/ENCODER-LOAD.md. -
Hardware preview encoding: on capable boards (RK3588), an operator can toggle the local preview between software and hardware encode, with an honest fallback message when the board's encoder plugin is missing or rejects a setting. See
docs/DEVICE-STATS-VALIDATION.mdfor the outstanding board-validation checklist (open, unrun as of writing). -
Engine-owned codec offering: the Encoder dialog reads the live per-codec encoder ladder published by cerastream, including explicit 4K60 qualification state. Older snapshots retain a golden-pinned board fallback rather than losing their encode ceiling.
-
Per-uplink health: bounded device-specific checks feed default-route election. Host election prefers device-bound repository HTTPS, retaining an ordinary-connectivity fallback when no NIC passes. Apt waits for route repair and a fresh unbound family check before dispatch; see host uplink policy and limitations. Generic gateway checks race the first IPv4 and IPv6 targets with a 250 ms stagger instead of walking every DNS answer serially, while active SRTLA links use passive RTT/NAK telemetry instead of competing probes. Captive portals remain visible as degraded links.
-
Flow-sticky client sharing: the backend assigns new hotspot/shared-LAN flows across healthy uplinks while preserving established-flow affinity and keeping locally-originated SRTLA traffic outside its NAT path. The image carrier is the remaining deployment dependency; see
docs/UPLINK_STEERING.md. -
Streaming-first client shaping: while live, locally-originated SRT/SRTLA traffic occupies an uncapped priority band and only steering-marked client flows receive an adaptive CAKE/HTB ceiling. See
docs/UPLINK_SHAPING.md.
| Command | Description |
|---|---|
bun run dev |
Start frontend + backend with mprocs TUI |
bun run build |
Build for production |
bun run --filter frontend dev |
Frontend only |
bun run build:frontend |
Build frontend only |
Development mode includes hardware mocking. All mock state is Zod-validated at startup
and can be reset between tests via resetMockState().
Mock modems identify their own USB-network interfaces with usb_modem_net, so
Network lists each under Cellular once its roster row claims the interface.
The interface name and address remain in that row's Details disclosure; ordinary
Ethernet ports and isolated-dongle controls remain separate.
bun run dev # Default: 3 modems + WiFi (multi-modem-wifi)
bun run dev:single-modem # 1 modem, no WiFi
bun run dev:streaming # Active streaming simulation
bun run dev:modem-pin-locked # 2 modems, modem 0 SIM PIN-locked (PIN 0000)
bun run dev:bt-mic-paired # Bluetooth on, HFP mic already paired
MOCK_SCENARIO=streaming-active bun run dev # Override inline| Scenario | Modems | WiFi | Streaming |
|---|---|---|---|
multi-modem-wifi |
3 (5G/4G/3G) | Yes | Idle — Bluetooth on with nothing paired yet |
single-modem |
1 | No | Idle |
streaming-active |
3 | Yes | Active (with live telemetry) |
modem-pin-locked |
2 | No | Idle — modem 0 SIM PIN-locked (fixture PIN 0000); exercises the SIM unlock/PUK flow |
bt-mic-paired |
1 | Yes | Idle — Bluetooth on with an HFP mic already paired, trusted and connected (battery 80%) |
caps-full |
2 | Yes | Idle — full engine caps: H265 + hw accel, audio-capable source, live audio switch, SRT transport |
engine-starting |
1 | No | Idle — engine still booting, minimal safe floor + engineStarting flag |
engine-unavailable |
1 | No | Idle — engine unreachable, cached/minimal snapshot + engineUnavailable flag |
The mock subsystem also simulates add-on state, kiosk state, SIM PIN/PUK lock states,
Bluetooth (adapter, discoverable roster, pair/trust, a timed scan window),
cerastream engine errors, and device-detection overrides. See apps/backend/src/mocks/
for the fixture factory and schema definitions.
In production the WebSocket RPC URL is derived purely from the page origin
(window.location): the single backend binary serves the static frontend and the
WebSocket on the same host and port, so no socket host/port/protocol is configured
and the VITE_SOCKET_* variables are ignored. The variables below apply to
development only, where Vite (:6173) and the backend (:3002) are separate
origins; all are optional.
These dev-only values live in the tracked .env.development file, which Vite
loads only in mode === "development" and the bun run dev scripts load via
dotenv -e .env.development. A production build never reads .env.development, so
none of these can reach the shipped bundle. The gitignored root .env is loaded in
every Vite mode, so it must stay absent — a CI guard fails the build if a stray
.env bakes a ws://localhost literal into dist/public. NODE_ENV is not read
from any env file (Vite sets the frontend mode; the backend dev scripts export it
inline). See .env.example for the full layout.
| Variable | Scope | Home | Dev Default | Description |
|---|---|---|---|---|
VITE_SOCKET_ENDPOINT |
dev only | .env.development |
ws://<page hostname> |
WebSocket endpoint (scheme + host, no port) |
VITE_SOCKET_PORT |
dev only | .env.development |
3002 |
Backend dev WebSocket port |
MOCK_SCENARIO |
dev only | .env.development |
multi-modem-wifi |
Hardware mock scenario |
LOG_LEVEL |
dev + prod | shell / systemd env | (per-transport default) | Override Winston log level for all transports. Dev console default: info; prod console: warn; file: debug. Set to debug to enable per-RPC call trace lines. |
bun run test:release-package-contracts
BUILD_ARCH=arm64 ./scripts/build/build-debian-package.sh
BUILD_ARCH=amd64 ./scripts/build/build-debian-package.shRelease assets are published through publish-release.yml; see
docs/BUILD_PIPELINE.md for the stable APT handoff.
CeraUI pins the published @ceralive/cerastream@2026.9.11 in both
the backend and shared RPC package, carrying schema 0.21.0's capture status,
failover-rate policy, raw-input classifications, and source-mode session-switch fields.
The producer's exported
ChangeConfigParams and installed schema are regression-tested directly.
The bindings-skew gate checks the schema version and preservation of the three
new HDMI capture causes. The released 2026.9.1 build retains the early-import
SIGUSR1 window. The current source closes the ordered add-on poke's startup race
with a systemd readiness barrier; see Boot readiness.
This is not a claim of full board qualification.
ARM64: Orange Pi 5/5+, Radxa Rock 5B AMD64: Intel N100/N200 Mini PCs, standard x86 computers
See BUILD_PIPELINE.md for full build documentation.
| Document | Description |
|---|---|
| LIVE_DEVELOPMENT.md | Local dev, deploy to device, image build, debugging |
| ARCHITECTURE.md | System overview and data flow diagrams |
| BUILD_PIPELINE.md | Build system and CI/CD |
| APT_VERSION_CONTROL.md | Debian package versioning |
| BRANDING.md | Branding guidelines |
| TOUCHSCREEN.md | Touch/kiosk layout mode |
| UPLINK_STEERING.md | Shared-client flow steering, route/NAT ownership, and validation status |
| UPLINK_SHAPING.md | SRT-priority qdisc hierarchy, adaptive client caps, ownership, and validation status |
| TECHNICAL_DEBT.md | Machine-checkable tech-debt register (source-experience overhaul) |
| CONVENTIONS.md | CeraUI-local conventions including tech-debt register contract |
| apps/frontend/docs/DEVTOOLS.md | Development tools |
bun add [package] --cwd apps/frontend # Add dependency to frontend
bun add [package] # Add shared dependency (root)
bun run clean # Clean all build artifacts- Frontend: Svelte 5, TailwindCSS v4, shadcn-svelte (bits-ui v2), Vite
- Backend: Bun, TypeScript, WebSocket RPC (oRPC)
- Build: Bun workspaces, mprocs
If you find CeraUI useful, consider supporting CeraLive development:
Software updates classify gstreamer1.0-libuvcsrc as an application package.
The portable libuvc source supports H.264/H.265; it is separate from RK3588's
MPP/RGA acceleration. Existing engine source IDs and factory aliases stay intact.