Reviewed-on: #4
Webka
A private camera and screen streaming app for your local network. Nuxt 3 / Vue 3 / TypeScript, Pinia, English/Russian/Spanish, and an Axum/Rust service with SQLite and browser-to-browser WebRTC.
One-command start
Install Node 22.12+ (npm included) and stable Rust/Cargo once. Then, from the repository root:
npm run dev:all
This starts http://127.0.0.1:3000. To listen on all network interfaces at port 3002:
npm run dev:all -- --host 0.0.0.0 --port 3002
The -- after dev:all passes the options to the launcher. Use ordinary double hyphens. npm start -- --host 0.0.0.0 --port 3002 is an alias. --help shows usage.
The launcher installs frontend dependencies automatically on the first run, builds Nuxt, builds Rust, and runs one server serving both the frontend and API on the chosen port. First startup needs internet and can take several minutes. Subsequent runs reuse unchanged dependencies/frontend output; Cargo builds incrementally. Restart the command after code changes to rebuild. This convenience command does not provide hot reload.
The terminal prints the local URL, LAN URLs when available, and the setup-token file location. Open the local URL on the server computer for camera capture; use a printed LAN URL on other devices to watch. The QR code uses the detected LAN address when listening on all interfaces. Local and detected private IPv4 origins are explicitly allowed; arbitrary origins remain blocked. If the selected port is busy, startup stops with an actionable message.
Press Ctrl+C to stop the app and any build subprocesses. Accounts remain in data/. The first login page creates the administrator account using the token from data/setup-token. The administrator cannot be deleted; resetting its password ends its sessions.
For an existing HTTPS proxy, use the homepage setting below. Browser capture on another LAN device still requires a trusted HTTPS address.
For frontend hot reload, the separate development workflow remains available: run cargo run --manifest-path backend/Cargo.toml and npm --prefix frontend run dev in separate terminals, then open http://localhost:3000.
Existing Nginx Proxy Manager on another machine
Set just the address you want to open in a browser (the address served by NPM):
npm run dev:all -- --homepage https://camera.example.com --port 3002
Replace https://camera.example.com with your actual HTTPS domain or IP, including a public port if needed. A bare hostname/IP is treated as HTTPS. A trailing slash is accepted; hosting under a subpath is not supported.
This mode automatically:
- Listens on
0.0.0.0so the other machine can reach Webka (unless you explicitly set--host). - Serves the frontend and API over HTTP on the chosen internal port; your existing NPM handles HTTPS.
- Uses the homepage for QR/share links, secure cookies, and allowed request origins.
- Disables mDNS, even if
WEBKA_MDNS=1was previously set. No Caddy,.localhostname, or certificates on the Webka computer are needed.
In Nginx Proxy Manager → Proxy Hosts, configure:
| Setting | Value |
|---|---|
| Domain Names | Your homepage hostname, e.g. camera.example.com |
| Scheme | http |
| Forward Hostname / IP | The LAN IP of the Webka computer, e.g. 192.168.1.50 |
| Forward Port | 3002 (or your --port) |
| Websockets Support | Enabled |
| SSL | Select your existing trusted certificate; enable Force SSL |
Open the homepage HTTPS address on both the host and viewer. Its hostname must resolve to the NPM machine, and NPM must be able to reach the Webka machine's LAN IP/port. The homepage is the browser-facing address, not the internal upstream address. For an HTTPS IP address, the certificate must cover that IP and NPM must route it to Webka. See the official NPM guide and WebSocket proxy support.
To save the address, create webka.config.json in the repository root (or copy webka.config.example.json):
{
"homepage": "https://camera.example.com",
"port": 3002
}
Then start with only npm run dev:all. homepage alone is sufficient if the internal port is the default 3000. Optional host and port may also be saved; command-line options override the file. Restart to apply changes. This machine-specific file is ignored by Git. Homepage mode takes priority over legacy WEBKA_ORIGIN, WEBKA_ALLOWED_ORIGINS, and WEBKA_MDNS values.
Legacy manual proxy setup via WEBKA_ORIGIN and WEBKA_ALLOWED_ORIGINS remains supported when no homepage is configured. The launcher does not configure NPM or provision its certificates. The proxy carries page/API/signaling traffic; WebRTC media remains peer-to-peer on the LAN.
Use on your LAN
A webpage cannot launch a Rust server on the device running its browser. Start the Rust service once on a computer on your LAN. A phone, tablet, or computer can then sign in as the administrator and act as the capturing host. This repository does not include a native iOS/Android service wrapper. Each service instance owns its account database.
Browser camera/screen APIs require a secure context: localhost on the service machine, or trusted HTTPS on a LAN device. LAN HTTP IP addresses cannot capture camera/screen. If you already have an HTTPS proxy, use the homepage mode above. If you do not, the included Caddyfile.example is an optional alternative with a trusted local certificate:
-
Point
webka.localat the service computer, through mDNS (WEBKA_MDNS=1), your router, or a hosts entry..localresolution and multicast support vary by network/device; a local DNS name or IP with a matching trusted certificate also works. -
Configure Caddy with the example below, and install/trust Caddy’s local CA on every client device. Keep this CA private.
-
Start Webka with its exact public origin:
WEBKA_ORIGIN=https://webka.local WEBKA_MDNS=1 \ WEBKA_PUBLIC_PORT=443 WEBKA_BIND=127.0.0.1:3001 \ cargo run --release --manifest-path backend/Cargo.toml -
Open
https://webka.local, create the admin account, add viewer accounts in Accounts, enable preview, and choose Go live. Share the QR code/local address. Each viewer signs in with an account created on this service.
Caddy terminates HTTPS and proxies to the loopback Rust server. Cookies gain Secure automatically when WEBKA_ORIGIN starts with https://. WebSocket handshakes and all mutations validate an explicitly configured Origin. No CORS wildcard or trusted forwarded-IP headers are used. The login throttle sees the reverse proxy as one IP, so its ten-attempts-per-minute limit is shared behind that proxy.
Use a private LAN and firewall; do not expose the service through public port forwarding. The app uses no cloud accounts, public STUN/TURN, telemetry, CDNs, or external fonts. WebRTC connects peers directly with local ICE candidates. Client isolation, VLANs, and firewalls that block peer UDP can prevent streams; no cloud relay fallback is provided. The one active host sends a separate peer stream to each viewer (32 signaling connections total, including the host). Capacity depends on the host device and Wi-Fi.
Configuration
| Variable | Default | Purpose |
|---|---|---|
WEBKA_BIND |
127.0.0.1:3001 |
Rust listener |
WEBKA_ORIGIN |
http://localhost:3000 |
Exact public origin; used for CSRF checks, QR and secure cookies |
WEBKA_ALLOWED_ORIGINS |
none | Additional trusted exact origins; launcher configures local/LAN aliases |
WEBKA_DATA |
data |
Local database and persistent secrets |
WEBKA_ASSETS |
frontend/.output/public |
Generated frontend; paths are relative to the current directory |
WEBKA_MDNS |
off | Set 1 to advertise _webka._tcp.local. and webka.local |
WEBKA_PUBLIC_PORT |
443 for HTTPS, otherwise 3000 |
Advertised public port |
Back up WEBKA_DATA securely. On Unix the data directory is owner-only and secret files are created with mode 0600. On Windows configure equivalent directory ACLs. Session tokens are random, HMAC-signed, stored only as SHA-256 digests in SQLite, and sent in HttpOnly/SameSite=Strict cookies. Argon2id stores salted passwords. Sessions expire after 30 minutes without activity and always after 12 hours. Visible authenticated pages send a heartbeat; hidden pages do not. Revocation closes signaling and tells the host to close the affected peer. A recipient may still retain media already received.
Features and implementation
/login: protected first-run setup and local sign-in./host: camera preview, front/back/screen selection, start/stop, uptime, bitrate presets, QR link and viewer disconnection./watch: live video, connection/resolution indicator, fullscreen, logout and automatic reconnect with backoff./settings/accounts: viewer creation/deletion, password reset, last login and active session revocation./settings/general: light/dark glass, accent, locale and security information.- Shared
GlassCard,GlassButton,GlassNavBar,GlassModalandLanguageSwitcherwith layered rim highlights, pointer-responsive specular light, rounded geometry, reduced-motion support, native keyboard focus and dialog focus containment. - Content panels stay opaque. Glass is used for navigation, source/share controls and dialogs. CSS edge layers approximate refraction; they do not implement physically accurate optical lensing.
Quality presets control the host’s per-peer send bitrate; the viewer shows actual received resolution. Capture is video-only. Closing/navigating away from the host tab stops capture; a browser may also suspend hosting when the device locks or the tab is backgrounded. Screen share support varies, particularly on mobile. Back-camera discovery becomes reliable after granting camera permission. Physical-device tests are documented in docs/MANUAL_TEST_PLAN.md.
Nuxt 3 is retained to match the requested stack. Its upstream documentation now marks it end-of-life; plan a Nuxt 4 upgrade before broader distribution. Reference: https://nuxt.com/docs/3.x/getting-started/installation.
Validation
npm run test:install
npm run test:launcher
cargo fmt --manifest-path backend/Cargo.toml --check
cargo test --manifest-path backend/Cargo.toml
cargo clippy --manifest-path backend/Cargo.toml --all-targets -- -D warnings
npm --prefix frontend run typecheck
npm --prefix frontend run build
node frontend/scripts/check-locales.mjs
cargo build --manifest-path backend/Cargo.toml
python3 scripts/smoke.py
# Optional POSIX integration test: reserves port 3002, tests startup/restart/stop.
python3 scripts/test-launcher.py
# Optional POSIX HTTPS proxy integration test (requires OpenSSL).
python3 scripts/test-proxy.py
test:install checks the frontend lockfile from a temporary folder without node_modules. When updating dependencies, run it with both npm 10 (bundled with Node 22) and npm 11; root npm install only installs the launcher package and does not repair the frontend lockfile.
See docs/API.md for REST/signaling contracts and docs/MANUAL_TEST_PLAN.md for real-device checks. Design references: Apple’s introduction and Meet Liquid Glass. No Apple assets are included. Nothing is published by the build commands.