Get started
The installer downloads release artifacts, generates local secrets and a TLS certificate, prepares Compose, then starts Valkyris and MediaMTX.
curl -fsSL https://valkyris.vercel.app/install.sh | shRequirements
- ✓Linux on amd64 or arm64
- ✓Docker Engine with Docker Compose v2
- ✓Android 8.0 (SDK 26) or later
- ✓ONVIF Profile S camera with RTSP
- ✓Server, phone and camera reachable over LAN or VPN
Keep ONVIF and RTSP ports reachable only on the private network. For remote access, connect the phone through a VPN; never publish the camera to the internet.
Architecture
The backend is the security boundary. The app never receives camera passwords or local adapter secrets. Cameras pass through MediaMTX; lighting uses a vendor-neutral driver boundary. Credentials use AES-256-GCM encryption; tokens are stored only as hashes.
Prepare a camera
- 1Connect to Wi-Fi
For a TC40, use Tapo once to complete initial provisioning.
- 2Create the Camera Account
Use dedicated credentials in advanced settings. This is not your TP-Link account password.
- 3Check the local network
The server must reach the camera ONVIF and RTSP ports on the private network.
- 4Add it in the app
Enter name, icon, IP, username and password. The primary Tapo RTSP URL is built automatically; use the advanced field only to override it.
The camera is persisted immediately. Its screen follows queued, probing, stream, ready or failed in real time, and the last sanitized error remains stored for diagnosis.
Smart lighting
The common power, brightness, white-temperature and RGB model remains available in the app and dashboard, but the core includes no proprietary manufacturer-account integration. Standard local adapters can be added behind the driver boundary without redesigning the interfaces.
Existing records are not deleted automatically during this transition. An administrator can still remove them from the app.
Android
Install the APK from the latest release. On first access, enter the HTTPS URL through which the phone reaches the server and create the personal username and password. The first device becomes administrator.
Other phones
In Settings, the administrator creates a temporary, single-use invitation. The QR is assembled inside the app using the known server URL; the backend has no public pairing page.
Native notifications
The APK uses Firebase Cloud Messaging directly and requires no companion app. After login, this phone token is registered automatically; Android receives a minimal encrypted payload and Valkyris creates the native notification or alarm.
- 1Configure the APK
Add com.ferforastieri.valkyris to the Firebase project, download google-services.json and store its base64 in the FIREBASE_ANDROID_CONFIG_BASE64 GitHub secret.
- 2Configure the server
Generate a JSON key under Project settings → Service accounts. In the app alert sheet, upload this JSON: it is validated and encrypted in your server SQLite database.
- 3Grant permission
Open the APK from the new release, sign in and allow notifications. FCM registration happens automatically.
The API accepts the same setup for administrative integrations at PUT /api/v1/settings/push with serviceAccountBase64. The account is never returned by the API.
Web dashboard on your server
Open /app/ at your installation HTTPS URL and use the personal username and password. The dashboard displays live cameras, controls registered bulbs, and shows events, family, routes and server information. Device registration and setup remain on Android.
Sessions use a Secure, HttpOnly, SameSite=Strict cookie, valid for 30 days and renewed during use. Logout or password changes revoke sessions. The dashboard ships in the Docker image and requires neither Node nor Vercel deployment. Data refreshes every 15 seconds while the tab is visible.
WebRTC · Cloudflare Tunnel
Android and the dashboard use the same WHEP endpoints. The tunnel carries HTTPS and signaling, but audio and video must reach MediaMTX through ICE. Allow 8189 UDP/TCP on the private network and set VALKYRIS_WEBRTC_HOSTS to reachable LAN/VPN addresses. The tunnel hostname is not a media address. STUN can assist direct connections; restrictive NAT requires VPN, a public media route or configured TURN.
The player does not replace WebRTC with a snapshot or HLS. An eight-second ICE wait does not abort Android negotiation when usable candidates already exist. HTTP 201 during signaling does not prove playback: verify received frames.
Location, events and motion
Android combines location sources through Fused Location Provider. Entering or leaving requires three readings spanning at least two minutes, beyond the accuracy margin. An uncertain reading or return cancels confirmation. The server requests samples while confirmation is pending without filling history with stationary points.
Alerts name the person and area and are not sent to the moving user. History shows numbered points, time and accuracy; maps zoom in on nearby people. Accuracy is estimated, and missing signal or connectivity may delay alerts. Install the current APK on every phone.
Camera rules support persistent motion in an image region, duration, sensitivity and days/time windows, including 22:00–06:00. Keep the camera fixed and redraw the region after PTZ movement. Analysis detects regional motion, not baby identity, posture, breathing or medical danger; it does not replace supervision.
Events are grouped into location, audio and camera categories. Details show person/area and position when available; clips report processing, ready or unavailable states.
Configuration
VALKYRIS_WEBRTC_HOSTSIP LAN/VPNAdvertised media addresses, comma separated.VALKYRIS_WEB_DIR/opt/valkyris/webStatic dashboard build served by the backend.VALKYRIS_LISTEN:8443Internal HTTPS listen address.VALKYRIS_DATA_DIR/dataPersistent data volume.VALKYRIS_DATABASE/data/valkyris.dbSQLite.VALKYRIS_TLS_CERT/data/tls/server.crtTLS certificate.VALKYRIS_TLS_KEY/data/tls/server.keyTLS key.VALKYRIS_MASTER_KEY_FILE/data/secrets/master.keyEncryption master key.VALKYRIS_MEDIA_APIhttp://mediamtx:9997Internal MediaMTX API.VALKYRIS_MEDIA_RTSPrtsp://mediamtx:8554Internal stream for monitoring and detectors.VALKYRIS_MEDIA_WEBRTChttp://mediamtx:8889Internal WHEP origin; WebRTC media goes directly to the phone after ICE.VALKYRIS_MEDIA_PLAYBACKhttp://mediamtx:9996Internal API used to generate recent clips.VALKYRIS_RELEASE_APIapi.github.com/…/releases/latestStable release lookup.VALKYRIS_FIREBASE_CREDENTIALS_FILE/data/secrets/firebase-service-account.jsonLegacy alternative: FCM service-account file. The app uses encrypted SQLite storage.mediamtx.yml must exist as a file before Compose starts.
HTTP API
Android and integrations use bearer tokens; the dashboard uses an HttpOnly cookie and X-Valkyris-Viewer: 1. Public routes require no session. The base path is /api/v1. JSON responses use the success, message and data envelope; errors use success, message and error.
curl -k https://SEU_SERVIDOR:8443/api/v1/cameras \
-H 'Authorization: Bearer SEU_TOKEN'Endpoint reference
OpenAPI YAML/healthPublicServer health.
/openapi.yamlPublicOpenAPI 3.1 contract served by the backend.
/api/v1/auth/statusPublicReports whether the first administrator exists.
/api/v1/admin/bootstrapPublicCreates the personal username and password and first administrator once.
/api/v1/loginPublicExchanges the personal username and password for a device token.
/api/v1/pairPublicConsumes a single-use invitation.
/api/v1/pairing-sessionsAdminCreates a temporary invitation for another phone.
/api/v1/camerasAuthenticatedLists cameras or starts asynchronous setup.
/api/v1/cameras/{id}AuthenticatedEdits or deletes a camera; connection changes restart its setup.
/api/v1/camera-operations/{id}AuthenticatedTracks ONVIF discovery and media setup.
/api/v1/cameras/{id}/ptzAuthenticatedMoves, stops or zooms when supported.
/api/v1/lightsAuthenticatedLists lighting exposed by the installed local adapter.
/api/v1/lights/{id}Authenticated / Admin to removeReads or removes a light.
/api/v1/lights/{id}/stateAuthenticatedTurns a bulb on or off and changes brightness, white temperature or color.
/api/v1/cameras/{id}/snapshotAuthenticatedReturns a JPEG frame from the current local stream.
/api/v1/cameras/{id}/recordingAuthenticatedDownloads the latest 10 seconds from the rolling buffer as MP4.
/api/v1/cameras/{id}/live/webrtc/whepAuthenticatedNegotiates and closes the authenticated WebRTC stream.
/api/v1/detectorsAuthenticatedLists normalized detector types.
/api/v1/rulesAuthenticatedLists or creates automation rules.
/api/v1/rules/{id}AuthenticatedEdits or deletes a rule.
/api/v1/usersAuthenticatedLists family profiles linked to paired devices.
/api/v1/users/{id}/historyAuthenticatedReturns a user location history.
/api/v1/me/locationAuthenticatedRecords the location of the user linked to the current device.
/api/v1/placesAuthenticated / Admin to changeLists areas or creates an alert area.
/api/v1/places/{id}AdminEdits or removes an area.
/api/v1/meAuthenticatedReads or updates this device owner profile.
/api/v1/me/passwordUserChanges the personal password and revokes other sessions.
/api/v1/viewer-sessionBrowser sessionRestores or revokes the dashboard session.
/api/v1/eventsAuthenticatedLists recent events.
/api/v1/events/{id}AuthenticatedReturns event metadata.
/api/v1/events/{id}/acknowledgeAuthenticatedAcknowledges the event and stops its alarm.
/api/v1/events/acknowledge-allAuthenticatedAcknowledges all pending events.
/api/v1/events/{id}/snapshotAuthenticatedReturns the event JPEG.
/api/v1/events/{id}/clipAuthenticatedStreams the event MP4 clip.
/api/v1/devices/pushAuthenticatedRegisters the FCM token and encrypted payload secret.
/api/v1/settings/pushAdmin to changeReads FCM setup or stores a Base64 service account encrypted in SQLite.
/api/v1/settings/retentionAdmin to changeReads or changes retention limits.
/api/v1/detectionsAuthenticatedAccepts normalized detections from local integrations.
/api/v1/system/updateAuthenticatedChecks release metadata and the APK link without executing updates.
/api/v1/realtimeAuthenticatedWebSocket for camera, lighting and event changes.
Updates
When a newer stable release exists, mobile and web show a toast without updating automatically. In app settings, the user can start downloading the signed APK from GitHub; Android requests installation confirmation.
SQLite, clips and snapshots remain in the valkyris-data volume. The API only checks releases; it cannot execute server updates.
In the 2.x series the API remains /api/v1 and migration preserves data. Rerun the installer on the host to update the backend, web, Compose and MediaMTX. Install the APK on each phone with Android confirmation. Release checks are cached for up to 15 minutes.
Security and backup
Per-minute API limits: 3000 global and 1200 per address; authentication has 30 global and 10 per address; snapshots/recordings have 60 per session/address, and WHEP creation has 12. A 429 response includes Retry-After. The backend does not trust client X-Forwarded-For or CF-Connecting-IP headers; proxy users share its budget. Counters are process-local.
For backups, use the SQLite backup API or stop the stack before copying the volume. Preserve the database, WAL when applicable, master key, certificates, media, .env, Compose and mediamtx.yml. Copying only a running .db can produce an incomplete backup.
- Do not forward ports 554, 2020, 8888 or 9997 on the router.
- Use trusted TLS through a reverse proxy or install the private CA on the phone.
- Use Tailscale, WireGuard or another VPN away from home.
- Back up the valkyris-data volume and .env together with restricted access.
Diagnostics
docker compose ps
docker compose logs --tail=200 valkyris
docker compose logs --tail=200 mediamtx
curl -k https://localhost:8443/healthOpen the camera in the app: persisted setup state and error show whether ONVIF or stream setup failed.
First verify MediaMTX health and that mediamtx.yml is mounted as a file.
Check snapshot, selected ONVIF profile and RTSP connectivity between host and camera.