KW Knowledge base

Vistralio — Architecture

## Process model

Mitch Wigham
Updated 24 June 2026 · 11 views

Vistralio — Architecture

Process model

Vistralio runs as a split set of systemd services behind nginx:

Service Unit Port Purpose
Connect vistralio-connect.service 8000 Main REST API, auth, admin/business logic, settings, tenants, licensing runtime, API Explorer
Media vistralio-media.service 8200 /api/streams/*, edge WebSocket, bridge tunnel lifecycle, snapshot/live proxy
Core vistralio-core.service 8300 Built SPA shell and static frontend assets
Worker vistralio-worker.service Per-camera detection loops, recording child processes, event creation, retention sweeps, frame sharing
License Server vistralio-ls.service 8100 Standalone license server UI/API
MQTT vistralio-mqtt.service 1883/8883 Embedded MQTT broker for notifications and Home Assistant

nginx routes per virtual host:

saas.vistralio.co.uk (full UI + API):

  • /api/streams/* and /api/edge/wsvistralio-media (:8200)
  • /api/*vistralio-connect (:8000)
  • everything else → vistralio-core (:8300)

connect.vistralio.co.uk (primary external API entry point):

  • root / → redirects to /api/
  • /api/streams/*vistralio-media (:8200)
  • /api/*vistralio-connect (:8000)
  • everything else → 404

updates.vistralio.co.uk — update endpoints only (/api/updates/*)

license-server.vistralio.co.ukvistralio-ls (:8100)

Core data model

Single MariaDB database (sentinel), tenant-aware. Tenant-scoped rows include:

  • cameras / devices, events, schedules
  • areas (sites), edge routers
  • known faces / plates, event types
  • activity log entries, groups

Admins can override tenant context. Standard users operate inside their current tenant only.

Devices, streams, and bridge routing

The product surface is Devices; the API namespace remains /api/cameras for compatibility. Each device has:

  • a device_type: camera, camera_speaker, or doorbell
  • a connection_method: direct or bridge
  • up to four path slots: detect, live, record, talk

Direct mode — the server dials the device RTSP URL directly via FFmpeg. FFmpeg decodes H264/MJPEG, scales frames to max 1280 px wide, and outputs MJPEG frames to a pipe. The media service parses JPEG markers (FFD8/FFD9) and pushes frames to WebSocket clients.

Bridge mode — three components work together:

  1. vistralio-worker opens two RTSP connections via the bridge tunnel: one for the recording child process, one for the detection child.
  2. After each detection frame the worker writes the latest JPEG to /dev/shm/vistralio/cam_{id}.jpg (atomic: write .tmp then rename).
  3. vistralio-media reads from that shared file for live stream WebSocket clients — zero extra RTSP connections, so the 2-connection hardware limit is never breached.

Detection pipeline

RTSP/HTTP frame ──▶ detector (YOLO / ALPR / face)
                        │
                        ├──▶ Event (DB) + snapshot + clip metadata
                        ├──▶ Notifier (MQTT / SMTP)
                        └──▶ /dev/shm JPEG for live view (bridge cameras)

Detection supports:

  • full-frame scanning when a label has no zones
  • named zones for event routing and wording
  • separate masks for ignored areas
  • per-object confidence overrides
  • per-zone threshold and dwell time

Worker dynamic camera loading

The worker loads cameras at startup, then runs camera_watcher() which polls the database every 30 seconds. Any newly-enabled camera is started (recording child + detection child) without restarting the service. New cameras are live within ~30 seconds of being saved in the UI.

Recordings and event media

The worker records segmented MP4 files (default 60 seconds per segment). It also writes event snapshots, object crops, and plate crops alongside recordings.

vistralio-media serves live stream WebSocket frames and snapshots. recordings.py serves segment playback, exports, and event media.

Authentication and authorization

  • Main API: short-lived JWTs (HS256, secret in /etc/vistralio/vistralio.yaml)
  • Media URLs: separate short-lived media tokens (5-minute TTL)
  • Optional 2FA (TOTP)
  • Tenant switching for multi-tenant accounts

RBAC is string-based. settings.admin is a broad override.

DNS model

Two public hostnames are tracked in the settings table:

Setting key Default Purpose
dns.current (inferred from request Host) Web UI hostname
dns.api_hostname connect.vistralio.co.uk API / Connect hostname

The API hostname controls edge router enrollment commands, the API Explorer link, and external integration docs. Changing it does not require an nginx reload — it is a pure settings-table update.

The web hostname uses a staged dry-run flow (stage → probe → promote) because changing it requires an nginx server_name reload and TLS certificate re-issuance. scripts/apply-dns.sh performs that reload.

Connection pool

SQLAlchemy: pool_size=20, max_overflow=40, pool_pre_ping=True, pool_recycle=3600. Handles ~60 concurrent DB-holding requests without queueing. The events list endpoint also uses a 2-second server-side TTL cache (per tenant + query params) to reduce DB load under concurrent users.

Licensing

Licensing is enforced at runtime in the API and worker layers, gating: camera count, user count, tenant count, exports, branding, SMTP, face and plate recognition. Federated licensing (primary → downstream → instance) is supported.

Security posture

  • Fernet-encrypted credential storage (camera passwords, SMTP, license keys)
  • JWT secret in /etc/vistralio/vistralio.yaml (never in DB)
  • Short-lived media tokens instead of reusing main JWT
  • Tenant isolation at the SQLAlchemy query layer
  • Bridge device fingerprint pinning after first enrollment
  • Secret redaction in the activity log

Extension points

  • Detectors in backend/app/detectors/
  • Notifier integrations in backend/app/services/notifier.py
  • Edge bridge protocol in backend/app/api/edge.py
  • License entitlements in backend/app/services/licensing.py
  • Admin UI sections in frontend/src/pages/Admin.jsx

Still need help?

Log a support ticket and the team will pick it up from this page.