Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

KeepPeek

Status: KeepPeek is in Alpha qualification and is not yet production-ready. The active Alpha gate tracks promotion evidence.

KeepPeek is a local-first network video recorder and WebRTC media gateway for IP cameras. It runs on Linux, macOS, and Windows and keeps camera media on hardware controlled by the user without requiring a vendor cloud relay.

The core service has a deliberately focused job:

  • discover and connect supported cameras;
  • ingest and record their encoded media without re-encoding it;
  • store recordings as standard MP4 files;
  • provide live and recorded media through WebRTC;
  • keep camera, stream, recording, and server health observable;
  • accept and store events from supported cameras or independent services;
  • locate, review, and export bounded evidence clips;
  • deliver rule-driven notifications and forward normalized events to MQTT.

KeepPeek does not require object detection or another AI service to record and review video. Camera-native analytics can publish events directly, while optional inference, transcoding, home automation, and commercial services connect through the open API. Recording remains useful when those services are absent or unavailable.

The server is written in Rust for predictable, memory-safe concurrent media handling. The first-party interface uses Svelte and runs in the browser, relying on the browser and operating system for compatible video decoding. KeepPeek remains codec-aware without bundling a video codec pack into the core service.

KeepPeek is developed with AI assistance through a human-directed process. AI is used as a tool for implementation, validation, documentation, and review; design decisions and accountability remain with people.

KeepPeek aims to remain a compact native service. Optional services own their detector runtimes, models, and codec dependencies, so an installation can choose the extra work it needs.

Reading the book

Feature guide and navigation

Use this chapter to find the part of KeepPeek that owns a task. It describes the current application, including controls that are visible but unavailable. A design reference or generated API type does not, by itself, mean that a feature works in the running server.

KeepPeek remains in Alpha qualification. Availability also depends on your role, camera grants, device capabilities, recording coverage, and browser codec support.

Find the right screen

The desktop shell and mobile navigation lead to the same workflows. On a phone, More opens Settings; Administrator-only navigation is hidden for Users.

ScreenRouteUse it forRead next
Dashboard, also called Peek/Watch the fleet, select a saved dashboard, arrange cameras, and adjust wall display settings.Live wall
Viewer/viewerFocus on a camera, switch feeds, listen to supported audio, and enter History.Live wall, digital zoom
Keep/keepInvestigate a recorded day, seek to an exact moment, inspect gaps and events, and create evidence exports.Recording and evidence
Events/eventsSearch stored events by time, camera, type, source, zone, confidence, image availability, and text. Review or dismiss events and open their recording context.Recording and evidence
Cameras/camerasInspect the inventory and open camera configuration. Start discovery or manual setup at /cameras/new.Camera controls
Camera details/cameraInspect and edit the selected camera and use the device controls it advertises.Camera controls
System health/system-healthDiagnose recorder, storage, camera, stream, and integration health. Open an individual camera for detailed evidence.Camera and stream health
Camera health/system-health/camera/[cameraId]Inspect lifecycle, stream progress, and recording evidence for one camera.Camera and stream health
Settings/settingsManage server configuration, dashboards, storage, backups, access, events, and integrations.Visual configuration management
Logs/settings/logsInspect live server and browser logs and collect diagnostics.Reporting bugs
Recording integrity/recordingsInspect recording coverage, gaps, and integrity before opening maintenance.Recording maintenance
Recording maintenance/recordings/maintenanceInspect recording integrity and explicitly plan and confirm maintenance.Recording maintenance
Initial setup/setupComplete the first-run setup workflow.Get started

Camera and event links carry the selected identity and, when appropriate, a timestamp. Keep those query parameters when sharing a deep link. A link does not grant its recipient permission to view the camera.

From live view to evidence

  1. Select a camera in Dashboard or Viewer and inspect whether its live stream is advancing.
  2. Use History to enter Keep, or open an event’s recording context from Events.
  3. Check the selected camera, date, and UTC time. A gap means recorded coverage is absent for that interval; a loaded player or thumbnail is not proof of continuous recording.
  4. Seek, play, and zoom to investigate. Digital zoom changes the browser view; PTZ moves a physical camera and has separate capability and permission requirements.
  5. Review the event, add the supported bookmark or evidence selection, and export the needed interval. Check the export result and retained coverage before relying on the file.

For controls, limits, time handling, and failure states, use Recording and evidence. For missing footage, start with camera health, then use maintenance or archive recovery when the evidence points to stored data.

Find a setting

Settings presents sections on desktop and a focused section index on mobile. Saving a draft, applying a change to a running camera, and restarting the server are distinct actions; inspect the reported result rather than assuming that a successful file write proves activation.

Settings areaWhat it controlsDocumentation
DashboardsSaved layouts, camera placement, audience, and wall presentation/resource choices.Live wall
Storage & retentionRecording paths, retention, pressure thresholds, policies, and maintenance.Configuration reference, recording maintenance
Backup & restoreBackup lifecycle, uploads, restore planning, activation, and rollback evidence.Backup and restore
Event sourcesRead-only catalog and origin context. The live source registry is unavailable; configure native adapters through the camera/configuration workflows.Native events, external analysis
GroupsThe declared group model and its current unavailable runtime controls.Groups: current status
NotificationsRules, delivery destinations, history, and errors.Notifications and integrations
AccessNamed credentials, Administrator/User roles, camera grants, and session revocation.Authentication
IntegrationsMQTT configuration and connection evidence, alongside reference cards whose configuration is unavailable. Home Assistant setup is documented separately.Notifications and integrations, Home Assistant
Appearance & timeDark, Light, or Match system appearance and the browser’s time and reduced-motion context.Visual configuration management
System & updatesServer version and health context and the explicit restart action. Follow the documented upgrade procedure to replace the server.Upgrades
Logs & diagnosticsLive logs and downloadable diagnostic evidence.Reporting bugs

Set appearance and inspect system context

For appearance, open Settings > Appearance & time and select Dark, Light, or Match system. The choice applies immediately and is saved in this browser when local storage is available; it does not change another browser or the server configuration. Video surfaces stay dark. Without a saved preference, the interface starts in Dark mode.

The browser’s reported time zone and reduced-motion preference are context, not server settings. Server time zone, clock format, week-start preferences, and an alternate interface language are not configurable through this panel. Keep’s UTC selection and export timestamps retain their documented meaning. System & updates shows server evidence and supports restart; an automatic update check or update-channel selector is not available. Use the upgrade procedure.

Choose an event or integration path

  • Record without analytics: No detector or notification service is required for recording and playback.
  • Use camera analytics: Enable a supported native adapter and verify real events and its connection health. Camera support differs by model and firmware.
  • Use external analysis: Run a separate service that subscribes to media and publishes events through the API. KeepPeek does not install or manage detector models in its core.
  • Send alerts or automate: Match committed events with notification rules, use Pushover where configured, or forward normalized events through MQTT. A rule match and successful delivery are different states. The notification rule’s Forwarder action is currently unavailable; the MQTT integration is configured separately.
  • Embed a camera in Home Assistant: Use the direct browser card with an appropriate named credential and browser-to-recorder connectivity.

The integration chapters explain their requirements and failure behavior. Protocol scenarios that describe future client possibilities are not an installation checklist for a shipped service.

Understand unavailable controls

Read the reason shown by an unavailable control. Do not interpret an unavailable count as zero, a saved configuration as a healthy connection, or advertised camera support as a successful operation. Browser Talk and live participant groups remain unavailable.

Camera access labels, dashboard audiences, and planned live participant groups are different concepts. Groups explains that distinction. The release-readiness chapter records remaining qualification boundaries; reporting bugs explains how to capture a reproducible failure safely.

Get started

Status: KeepPeek is in Alpha qualification and is not yet production-ready. The active Alpha gate tracks promotion evidence.

Choose the installation that fits the host. Linux has first-class Docker support; macOS and Windows use native packages and service integration.

Before installation

Choose a host with enough disk capacity for the camera bitrates and retention you need. KeepPeek stores camera media without transcoding; a browser still needs to support the selected codec. Start with a small camera set and measure it before increasing the workload.

Use the release page to select a build for the host. Record that version and retain its checksum information. By default, clients on private and local networks receive Administrator access without signing in; review the access policy before exposing the listener to other devices.

Linux with Docker

The published image supports linux/amd64 and linux/arm64. It runs KeepPeek directly as the non-root user 65532 and listens on port 8081.

KeepPeek stores configuration, owner-only secrets, recordings, the recording catalog, thumbnails, and logging settings under /config/keeppeek in the container. Mount /config from the host so that data survives container replacement:

mkdir -p keeppeek-data
sudo chown 65532:65532 keeppeek-data
docker run --rm --name keeppeek -p 8081:8081 \
	-v "$(pwd)/keeppeek-data:/config" \
	ghcr.io/xnorpx/keeppeek:latest

The ownership command is for the newly created Linux directory. Use the permissions appropriate to your existing storage if the directory already contains data. This example runs in the foreground and removes the container when it stops; the mounted host data remains. Configure your deployment’s restart policy for unattended operation. Select an explicit release tag or digest instead of the moving latest tag when you need to reproduce the installation.

Open http://localhost:8081 after startup. If the container cannot create its configuration or recordings, check the host mount and permissions for user 65532.

macOS

macOS packages target Apple Silicon (arm64). Download the release DMG and its matching .sha256 file, then verify the checksum:

shasum -a 256 -c keeppeek-<version>-macos-aarch64.dmg.sha256

Replace <version> with the downloaded version. Drag KeepPeek.app into /Applications and open it once. The application installs and starts the per-user com.keeppeek launch agent, which starts after that user logs in. Open http://localhost:8081 in a browser.

Signing and notarization depend on the release workflow’s configured credentials; check the chosen artifact rather than assuming every DMG has both. The macOS installation guide explains packaging and service logs. Use Upgrades and migrations when replacing an existing installation.

Windows

Windows releases provide signed x86-64 and ARM64 binaries and an installer. Choose the architecture that matches the host and run the installer. Select Run KeepPeek as a Windows service for background operation through the Service Control Manager. The service is named KeepPeekService. Without that component, run the installed keeppeek.exe as a standalone process.

Open http://localhost:8081 after startup. To inspect the service from PowerShell:

sc.exe query KeepPeekService
sc.exe qc KeepPeekService

The service reads configuration in its own process account’s environment, which can differ from your interactive %APPDATA%. Inspect the service identity and actual configuration location before moving data between standalone and service operation. See Upgrades and migrations and the Windows service guide for service lifecycle and logging.

Configuration and secrets

The first start creates the KeepPeek configuration directory for the selected installation. Reusable private values belong in owner-only secrets.toml beside config.toml:

InstallationDefault secrets path
macOS~/Library/Application Support/keeppeek/secrets.toml
Linux${XDG_CONFIG_HOME:-$HOME/.config}/keeppeek/secrets.toml
Windows%APPDATA%\keeppeek\secrets.toml
Docker/config/keeppeek/secrets.toml

Never add secrets.toml to source control, logs, screenshots, support bundles, or command arguments. Enter camera credentials and other private values directly on the machine running KeepPeek. See the configuration reference for references, precedence, and migration behavior.

These paths belong to the account running KeepPeek. --config <path> selects a different configuration file with secrets.toml beside it. Inspect the effective recording paths separately; omitted storage paths still use the operating system’s default KeepPeek directory.

On first startup, use a trusted-local browser to retrieve and save the Initial Administrator key if you need remote access. The browser shows it once. Create named credentials for individual remote users or integrations in Settings > Access & roles. See Authentication and access control.

Create a recovery backup

Review Settings storage and retention values before relying on the recorder. Confirm the actual recording, catalog, and thumbnail paths, available space, and retention policy. Defaults are starting values, not a sizing recommendation for every camera fleet.

Open Settings → Backup and restore as an Administrator and create a validated configuration bundle before relying on the recorder. Download the ZIP and retain it separately from the recorder. The ZIP contains exactly config.toml and plaintext secrets.toml, so handle it as sensitive data. It intentionally omits recordings.db, sessions, MP4 recordings, and thumbnail JPEG bytes.

Test recovery on an isolated installation. Export uses GET /config/export; apply uses POST /config/apply with the ZIP body. Apply validates both files, capacity, and target paths before staging. Live files change only after a controlled restart. See Backup and restore for the complete workflow and limits.

For footage recovery, also create and test a separate catalog and media archive. A successful ZIP export alone does not prove recordings can be recovered after disk loss.

Add the first camera

With no cameras configured, an Administrator can select Add camera directly from the Dashboard’s No cameras yet message. The same wizard is available from Cameras → Add camera. KeepPeek discovers common cameras where possible and keeps manual RTSP entry available. Do not trust discovery alone: authentication, the requested main or sub streams, keyframes, finalized MP4 recordings, and playback all need to validate.

Edit an existing camera from its Camera page. Use Settings only for server-wide configuration. Camera-specific credentials and stream choices belong with the camera.

Set a recording policy that matches the footage you need: the built-in default is event-boost, not continuous recording of both streams. Open live view, then review a new finalized interval in Keep. An incompatible browser codec is different from absent footage; retain a compatible substream when the camera provides one.

The Users and design choices chapter describes supported vendor paths and the pragmatic camera compatibility policy.

After the first stream is verified, continue with Camera and stream health and Recording and evidence. Review Release readiness and known limitations before relying on the installation for evidence.

Complete the first-use check

  • Verify live view, recorded playback, recording integrity, and a small downloaded evidence export.
  • Test each remote credential from an address that is not trusted-local. Check allowed and denied cameras, and the separate dashboard audience.
  • If you enable native events, external analysis, notifications, or MQTT, test a controlled event through its intended destination and inspect failures.
  • Restart once and confirm recording resumes, durable settings remain, and remote users can sign in again. Pending notification and MQTT work does not survive that restart.

Authentication and access control

KeepPeek is local-first: cameras and recordings stay on the recorder, and KeepPeek does not expose itself through a cloud relay. Access depends on two decisions:

  1. Is the client on a trusted local network?
  2. If not, which remote credential and role does the client have?

Local clients use KeepPeek as Administrator without signing in. Remote clients must sign in with a named bearer credential or an explicitly configured OIDC/identity-proxy method, assigned either the Administrator or User role. External methods are opt-in; they do not change the trusted-local policy.

Treat local network configuration as an authentication boundary. Any device in a configured local range receives Administrator access.

First run

On first start, KeepPeek creates a cryptographically random Initial Administrator credential. Open the setup page from a local client and select Retrieve initial key. The key is displayed only for that operation.

Save it immediately using Copy or Download, then choose Hide permanently. KeepPeek does not log the key and cannot display it again. If it is lost, a local Administrator can rotate the Initial Administrator credential from Settings > Access & roles.

KeepPeek does not report remote access as ready until the initial key has been retrieved. Local camera setup remains available while remote access is pending.

Local and remote clients

KeepPeek classifies the effective client address before checking a credential.

  • Local: the address is in access.local_networks. The client becomes Administrator without a sign-in screen.
  • Remote: every other address. The client must provide an active, unexpired bearer credential or use an explicitly configured external authentication method.
  • Unknown or malformed: treated as remote and denied without a valid credential.

The default local networks include IPv4 and IPv6 loopback, RFC 1918 private networks, IPv4 and IPv6 link-local networks, and IPv6 unique-local networks. IPv4-mapped IPv6 addresses are normalized before matching.

Container bridge networks commonly fall within 172.16.0.0/12, which is local by default. Narrow the list if other containers on the host must not receive Administrator access.

Configure the policy in config.toml:

[access]
local_networks = ["127.0.0.0/8", "192.168.50.0/24", "::1/128"]
trusted_proxies = []
require_secure_remote = true
failed_authentication_limit = 5
failed_authentication_window_secs = 60
session_idle_timeout_secs = 1800
session_absolute_timeout_secs = 86400
max_sessions_per_principal = 64
max_sessions_per_address = 128

Setting local_networks replaces the defaults. Include the loopback ranges explicitly when you customize it.

A VPN can use either policy:

  • Add its client range to local_networks when every VPN client should be Administrator.
  • Leave it out when each VPN client should sign in with an individual credential and role.

Restart KeepPeek after changing the network policy.

External sign-in

Administrators configure providers, exact origins, explicit role/camera mappings, identities, and browser-session revocation under Settings → Access. Browser cookies are secure, HTTP-only, host-only, and bound to one allowed origin. Provider tokens never reach the application UI. Sign-out revokes dependent connections; restart invalidates all external browser sessions.

Provider secrets remain references into secrets.toml. Changing the last usable remote Administrator path requires fresh proof against the exact proposed configuration and explicit confirmation on the original control connection. The same protection applies to configuration ZIP restore. An unavailable provider, stale proof, or failed write cannot activate the candidate.

See the field reference and deployment and recovery guide for TLS, reverse-proxy header sanitation, mixed-mode deadlines, and troubleshooting.

Roles

KeepPeek has two fixed roles.

OperationAdministratorUser
View live and recorded media and eventsYesYes
Operate PTZ and presetsYesYes
Use personal notification and review stateYesYes
Configure cameras, storage, recording, and integrationsYesNo
Export, delete, or change retentionYesNo
View logs, health diagnostics, and security auditYesNo
Manage credentials and sessionsYesNo

The server enforces this policy for HTTP and every WebRTC control operation. Hidden navigation and controls make the User interface clearer, but they are not the security boundary.

The User operations above are limited to that credential’s allowed cameras. Custom roles are not currently available.

Camera and dashboard access

In Settings > Access & roles, choose the person icon beside a User credential to set User access. The default is Everything, including current and future groups and cameras. To restrict that user, choose Selected groups and cameras. Select camera groups, individual cameras, or both: either a matching group or an explicit camera selection grants access. Selecting neither grants no cameras. Existing explicit restrictions remain unchanged.

Camera groups use configuration namespaces. For example, [outdoor.front_door] puts the camera in the outdoor group. These groups are separate from saved dashboards and live-sharing rooms.

Camera grants are enforced by the server for live and recorded media, event previews, notification inbox/history, recording coverage, and camera controls. A grant does not permit Administrator-only operations such as export or camera configuration. Saving permissions closes that User’s existing sessions; they sign in again with the same key. Stale saves return a conflict without discarding the draft.

Use the dashboard’s access controls to select its viewers separately. Sharing a dashboard does not grant its cameras. A User sees only allowed camera tiles and can select visible dashboards, but cannot modify or share the server-owned layouts. The complete layout remains intact for its Administrator even when a User’s view has fewer tiles.

Both settings live in the existing configuration: camera grants in each access_credentials.credentials[].camera_access record and dashboards in peek_layouts. See the configuration reference for field names and limits.

Trusted-local clients still receive Administrator access. To restrict someone on your LAN or VPN, ensure their address is not in access.local_networks and have them sign in with a User credential.

Remote sign-in

Open the recorder through HTTPS and enter the issued UUID in the Remote sign-in form. The browser keeps the key only in memory for the current page session. It is not placed in the URL, rendered as hidden text, logged, or written to localStorage or sessionStorage.

Reloading or closing the page discards the key and requires sign-in again. Expired or revoked sessions cannot retrieve protected resources. The browser normally returns to sign-in; if a session change during page initialization leaves an expired-session error visible, reload and sign in again. See Session expired after a permission change.

API clients send the same credential in the HTTP header:

Authorization: Bearer 550e8400-e29b-41d4-a716-446655440000

KeepPeek never accepts credentials in query parameters. Do not put a key in a copied recording link, guest link, SDP document, or command line that may be retained in shell history.

Managing credentials

Open Settings > Access & roles as an Administrator. The credential table shows each identity’s name, description, role, status, last use, expiry, and revision without revealing its key.

Create

Choose New credential, enter a descriptive name, select Administrator or User, and optionally set an expiry. The new key is shown once. Store it before hiding the result.

Use a separate named credential for each person, browser installation, service, or integration. This makes audit records useful and allows one client to be revoked without affecting the others.

Rotate

Rotate a credential when its key may have been copied or exposed. Rotation returns a replacement key once, invalidates the previous revision, and closes work authenticated with the old key.

Disable and enable

Disable a credential to suspend it without deleting its identity or history. Disabling closes its active sessions. Enabling it increments the revision; clients must establish new sessions.

Revoke

Revocation is permanent. It closes active sessions and prevents reconnecting with that credential. A revoked credential cannot be enabled or rotated; create a replacement identity instead.

Credential metadata, camera grants, and SHA-256 verifiers are stored under [access_credentials] in config.toml. Raw keys are not stored there. The compatibility Initial Administrator key remains protected in owner-only secrets.toml.

Sessions and revocation

Each WebRTC session has a random opaque ID and is bound to its principal, role, effective client address, network classification, credential revision, and expiry. A session ID does not authorize a request by itself.

By default, sessions expire after 30 minutes without control activity or after 24 hours absolute, whichever comes first. Credential expiry can shorten that lifetime. KeepPeek allows up to 64 active sessions per principal and 128 per effective address.

Administrators can list and revoke sessions from Settings > Access & roles. A client can delete only its own session. Repeating deletion for an already closed session is safe.

Credential rotation, disable, revoke, or expiry closes matching WebRTC and authenticated log streams. KeepPeek checks expiration every 100 ms. WebRTC gets up to one second for graceful close, after which KeepPeek reports a warning. Normal cleanup releases PTZ movement, stored-media cursors, searches, discovery, and queued transfers owned by the session.

Reverse proxies and HTTPS

Prefer a private VPN for remote access. If KeepPeek is Internet-reachable, terminate HTTPS at a maintained reverse proxy, firewall the KeepPeek listener so clients cannot bypass that proxy, and trust only the proxy’s exact address.

Direct remote HTTP is rejected when require_secure_remote = true. A configured trusted proxy is the declared TLS boundary: KeepPeek cannot inspect the client-side TLS connection after the proxy terminates it.

KeepPeek accepts one forwarded-client contract: exactly one X-Forwarded-For field containing a comma-separated chain of IP addresses without ports or quoted values. The proxy must remove any client-supplied forwarding headers before setting it. KeepPeek walks the chain from right to left and selects the first address that is not a trusted proxy.

Other forwarding headers make the contract ambiguous. Remove Forwarded, X-Real-IP, X-Forwarded-Host, and X-Forwarded-Proto before forwarding to KeepPeek.

Example Nginx configuration:

location / {
    proxy_pass http://127.0.0.1:8081;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Forwarded "";
    proxy_set_header X-Real-IP "";
    proxy_set_header X-Forwarded-Host "";
    proxy_set_header X-Forwarded-Proto "";
    proxy_set_header Host $host;
}

Pair it with an exact proxy address:

[access]
trusted_proxies = ["127.0.0.1/32"]
require_secure_remote = true

Do not trust a proxy unless its listener, forwarding-header sanitation, network path, and bypass firewall are all under the same administrative control.

Audit and metrics

KeepPeek keeps a bounded security audit for remote authentication, credential changes, session lifecycle, denied commands, sensitive Administrator operations, and proxy-classification failures. Each record includes the principal, role, action, target, result, client classification, and UTC timestamp. It never contains a raw key, verifier, Authorization header, cookie, SDP, log payload, or media.

Audit history, active sessions, and credential last-use activity stay in memory and reset on restart. Graceful shutdown does not persist them. Credential identities, verifiers, roles, revisions, lifecycle metadata, and camera grants persist atomically in config.toml; this does not make audit records durable.

If an installation requires durable security evidence, retain protected service logs outside the recorder and verify that the required actions are represented. Logs are not a guaranteed complete copy of the bounded audit. Collect required audit records before eviction or restart using an Administrator client and retain them in the installation’s external evidence system. Without that collection, the pre-restart audit cannot be recovered from a configuration backup.

GET /metrics exposes label-free counters and gauges for authentication successes and failures, authorization denials, session creation and revocation, active sessions, and active credentials. Logs, metrics, diagnostics, and audit access require Administrator.

Troubleshooting

A local browser shows Remote sign-in

Check the browser’s source address and access.local_networks. A forwarding header from an untrusted peer intentionally forces remote classification. If a proxy is involved, configure its exact address in trusted_proxies and send only the supported X-Forwarded-For contract.

Remote access returns HTTP 426

The request reached KeepPeek over unprotected direct HTTP while require_secure_remote is enabled. Use HTTPS through a configured trusted proxy. A VPN does not by itself change the server’s address classification or secure-transport check. A VPN client kept outside local_networks still needs the supported secure boundary for remote sign-in; adding its range to local_networks instead grants Administrator access to that range.

Remote sign-in is temporarily limited

Five failed attempts from one effective address within 60 seconds are rejected by default. Wait for the window to expire, verify the key, and confirm the credential is enabled, unexpired, and not revoked.

A credential works for viewing but not Settings

It has the User role. Issue an Administrator credential only when that client must change recorder configuration or manage security.

Session expired after a permission change

Saving User camera grants deliberately closes that credential’s existing sessions. Rotation, disable, revoke, and expiry also invalidate authenticated work. If a dashboard opened during that change remains at API session expired or was revoked, reload the page and sign in again with the current key. The current UI can retain this error during initialization even though the server continues to deny requests from the invalid session.

For a rotated credential, use the replacement key. A disabled, expired, or revoked credential will not work merely because the page was reloaded. Ask the recorder Administrator to inspect its status.

A shared dashboard is empty or has fewer cameras

Check both the dashboard audience and the User’s camera grants. Audience membership makes the dashboard visible; it does not grant cameras. Verify the intended group or individual cameras in User access, save the current revision, then sign the User in again. The Administrator’s complete layout is retained even when the User sees fewer tiles.

A supposedly restricted person still has Administrator access

Check their effective source address first. A trusted-local request receives Administrator access before a remote key is considered. Exclude that client’s network from local_networks if it must use a restricted User credential, while retaining the local recovery access you need.

A key was lost or exposed

From a local Administrator session, rotate the affected credential. Store the replacement key before hiding it, then update only the client or integration that owns that named credential.

Verify access before deployment

Create a disposable User credential and test from a genuinely remote-classified address. Check an allowed camera and a denied camera through live view, Keep, event images, and controls. Confirm that Settings, exports, and diagnostics remain unavailable to that User. Test a dashboard whose audience includes the User but whose tiles include a denied camera.

Change the grants and confirm the old session loses access. Reconnect and verify the new scope, then disable or revoke the test credential when finished. A successful local browser check does not test remote restrictions. Record the proxy or VPN topology with your release qualification evidence.

Credentials and grants are included in configuration ZIPs. Before applying a ZIP from another recorder, ensure you can use its restored access policy and retain a local recovery path. Sessions, last-use activity, and audit history are not restored by that ZIP. See what survives a restart.

Camera controls

As an Administrator, open Cameras, select a camera, and use its overview and control panels to inspect capabilities and operate supported devices. A live picture, a manufacturer name, or a capability badge alone does not prove that a camera command will succeed. KeepPeek reports command failures separately from camera and stream health.

The Cameras navigation and configuration editors are Administrator workflows. The API also allows authorized Users to read motion-detection status and issue supported PTZ commands for their granted cameras. Changing detector configuration requires Administrator access. Camera grants and advertised capabilities still apply to each operation. See Authentication and access for the trusted-local policy and camera grants.

Move a PTZ camera

On a supported camera, the PTZ panel provides directional movement, optical zoom, Stop PTZ, and existing presets.

  1. Check the live image and select the intended camera.
  2. Press and hold a direction or optical zoom control. Release it to request a stop. Keyboard users can focus a movement button and hold Space or Enter.
  3. Use Stop PTZ to stop movement explicitly.
  4. Select an existing preset to recall that device position. Preset creation and deletion are not available through the shared KeepPeek controls; manage them in the camera’s own interface.

Continuous movement belongs to the controlling WebRTC connection. KeepPeek requests a stop on disconnect and after an uncertain movement acknowledgement. If a stop cannot be confirmed, it reports the failure and retains ownership instead of treating the camera as safely idle. Check the camera directly before another operation. Keep the device’s own movement timeout enabled.

Hikvision/Annke controls use ISAPI HTTP. The current Reolink control adapter uses Reolink HTTP even when the recording backend is reo-proto. Their movement speed scales differ. Reolink HTTP controls currently address channel 0; do not assume arbitrary NVR channel control is supported. A missing PTZ axis or unavailable preset is not supplied by software emulation.

For magnifying a picture without moving the device, use Digital zoom and pan. Digital zoom also works on recordings and event images.

Enable or disable motion detection

The camera detail page’s Motion detection > Enabled switch changes the camera’s detector configuration. It is independent of whether motion is occurring at this instant.

KeepPeek reads the current alarm configuration, changes the enabled field, and reads it back. Unrelated settings such as masks and sensitivity remain intact. Wait for the result before making another change. An error or mismatched read-back is a failed operation, and KeepPeek does not automatically reboot the camera to apply it.

Three settings have different purposes:

SettingEffect
Motion detection enabledTurns the device’s detector on or off where supported.
Store generic motion eventsControls whether KeepPeek stores unclassified motion notifications.
Camera recording policyChooses which stream KeepPeek records and whether events boost quality.

Turning off generic motion retention does not turn off the detector or native subscriptions. Smart person or vehicle classifications may still depend on those subscriptions. Configure detector regions, schedules and vendor-specific target filters in the camera interface. See Native camera events and Recording and evidence.

Audio and unsupported controls

Listening to an available camera audio stream is separate from sending microphone audio back to its speaker. Talk is not implemented in the browser application, including on devices that advertise a speaker or two-way-audio capability. KeepPeek’s reusable vendor libraries contain audio transport support, but this does not provide an application talk session.

Relative PTZ movement, preset save/delete and vendor-specific focus, guard, sensitivity or smart rule editing are not shared browser controls. Disabled controls describe unavailable application operations; they are not a reason to grant broader access or enable an unsupported protocol.

Troubleshooting

SymptomWhat to check
Controls are disabledThe operation’s role requirement, camera grant, capability evidence and current connection state.
PTZ works in the vendor app onlyThe configured HTTP port, device credentials, selected channel and supported KeepPeek adapter. RTSP playback alone does not validate these.
Motion is enabled but no events appearCamera schedule, regions, notification linkage and generic-motion retention; then inspect native event status.
A capability appears but its command failsReachability and the command’s error. Cached discovery evidence is not a current successful command.
Zoom changes only the displayed cropYou are using Digital zoom. Optical zoom is a separate PTZ operation.

The camera control reference details vendor differences and synthetic test coverage. Physical support still depends on the camera model, firmware, account permissions and channel mapping.

Demo videos

These audio-paced H.264/AAC recordings exercise KeepPeek’s production controls and are regenerated from the canonical Paper scenarios. Azure OpenAI narration controls each visual phase, and captions are available from every player.

These are recorded demonstrations, not a live preview of the current release. Use the source commit and generation timestamp in the manifest below to identify the version shown. Labels, layout, and available controls may differ from the current application; the feature guide and workflow chapters describe current behavior.

Add nine cameras in Settings

Begin with an empty KeepPeek server, enter nine real-time RTSP camera configurations through Settings, then inspect the completed live wall and one feed’s WebRTC evidence.

The virtual-camera source is Big Buck Bunny, © 2008 Blender Foundation, licensed under CC BY 3.0.

Review what just happened

Focus a live camera, then use History to open its timeline in Keep.

Delete and re-add a camera

Remove and restore the deterministic H.264 camera through the real KeepPeek server and production Settings UI.

Set up a camera with catalog context

Discover a model, review the embedded catalog facts, open CCTV Database for deeper manual research, then apply only the stream declarations you choose before the first configuration write.

The published manifest contains the source commit, generated timestamp, content hashes, and metadata links for every recording.

Live wall and kiosk display

The Dashboard is KeepPeek’s multi-camera live wall. Open Wall display settings with the sliders icon in the upper-right corner to change its presentation and resource policy. These choices belong to the selected dashboard on the server, not to camera recording or detection.

When no cameras are configured, Administrators see No cameras yet and an Add camera action that opens the camera wizard. Users with no available cameras see a message to ask their administrator for access.

Choose a dashboard or focus a camera

Use the dashboard-name selector to choose a saved layout. All cameras is the automatic fleet view; changing camera configuration updates its generated grid. A saved dashboard can arrange a subset of cameras for a room, entrance or display.

Select a camera tile to open the focused Viewer. Use its camera selector or filmstrip to change the focused camera. Live and History are viewing modes; pausing or leaving a browser view does not stop server recording. Open Keep for a recorded-day investigation, event context or evidence export. See Recording and evidence.

The information control on a tile opens camera evidence without making a paused browser preview look like a server outage. For detailed device settings and supported PTZ or detector operations, open the camera from Cameras. See Camera controls.

Arrange and share a dashboard

An Administrator can create, duplicate and edit dashboards. In the layout editor, add available cameras, select a tile, and move or resize it in the grid. Keyboard controls support moving the selected tile; the editor’s hints identify the available actions. Save the draft to publish the layout. Cancel or discard to leave the saved layout intact. The automatic All cameras grid cannot be rearranged like a custom dashboard; create a custom layout for that purpose.

The dashboard audience determines who can see a saved layout. It does not grant access to the cameras in it. Camera groups are labels used for organization and access grants; the server filters each viewer’s layout to the cameras that identity is allowed to see. A shared layout can therefore be empty for one User while containing cameras for another. Do not broaden camera permissions merely to make a layout match another person’s screen.

Layout export/import carries dashboard definitions, not camera credentials or media. Review the imported camera references and audience before use on another recorder. A stale save is rejected instead of overwriting a newer revision; reload the current saved layout before trying again. See Authentication and access and Configuration export and import.

Defaults and ownership

SettingDefaultSupported choices
Tile shape16:916:9, 4:3, Native
Media fitContainContain, Cover (cropped)
Gap10 pxWhole pixels from 0 through 24
Corner radius10 pxWhole pixels from 0 through 24
Streaming modeSmartSmart, Continuous
Stream limit12, further limited by the browser estimateWhole numbers from 1 through 12
Keep display awakeOffOpt-in Screen Wake Lock

Administrators can preview changes in the current wall and select Save to store them in the selected dashboard. Discard changes restores the saved values. A rejected or conflicting save leaves the preview intact; discarding after an error reloads the server’s latest values. Users without dashboard-edit permission see read-only controls.

Reset wall settings previews the defaults and releases any requested wake lock. Select Save to make the reset permanent. Shape, fit, and radius changes do not recreate the WebRTC session, restart recording, or alter exported media. Gap changes can naturally change which tiles are visible.

The server stores each dashboard’s versioned display object under [peek_layouts] in the existing config.toml. Settings follow the dashboard through duplication, layout export/import, configuration export/import, and restart. A kiosk and a phone can use different dashboards with different settings; loading the same dashboard uses the same saved choices, subject to each browser’s runtime limits.

Older dashboards without a display object use defaults. Writes from older clients that omit the object preserve the stored settings. Invalid versions, fields, types, and values are rejected before the configuration changes. Browser-local wall preferences are no longer read or written and are not automatically uploaded. See the layout registry reference.

Gap, corners, and presets

The appearance controls combine live sliders with exact pixel inputs:

PresetGapCorner radius
Current10 px10 px
Hairline2 px0 px
Flush0 px0 px

Adjust either value to create a custom appearance. The preset indicator follows the actual values; presets change only gap and radius, leaving shape, fit, streaming, and wake-lock settings intact. Zero radius gives square corners and removes the normal video-tile border. Health warnings retain an inset outline. Zero gap joins neighboring tile slots; Contain can still add letterboxing when source and tile ratios differ. No appearance setting stretches pixels or forces Cover fit.

The automatic All cameras dashboard retains its generated camera grid, but its display settings are editable. Other dashboards retain their own independent display settings.

Tile shape and media fit

16:9 and 4:3 select the wall’s presentation ratio. Native uses the first valid decoded dimensions for each camera, with 16:9 reserved until dimensions arrive. Native ratios from 1:16 through 16:1 are accepted. Invalid or missing dimensions retain the reserved frame.

Media frames fit inside stable grid slots. Native metadata can change the frame inside its slot, but it does not repack neighboring tiles. A later main/sub quality switch retains the camera’s latched presentation ratio for that mounted wall. Labels, health evidence, and diagnostics remain outside the resized media frame, including for portrait and panoramic cameras.

The wall reserves a compact toolbar strip above the camera grid. On narrow screens, tile slots retain at least 160 pixels of height for labels and status, while media keeps the selected ratio. The wall scrolls inside its viewport without extending under the surrounding navigation.

  • Contain preserves the complete source image. Black bars are expected when the source and presentation ratios differ.
  • Cover (cropped) fills the presentation frame without stretching. A Cropped label identifies the omitted edges. Select Contain to reverse the crop.
  • Selecting a tile opens the focused Viewer, which always offers uncropped video independently of the wall’s fit choice.

These settings do not add per-camera crop positions or alter the saved grid’s tile coordinates.

Smart and Continuous streaming

Smart prioritizes the focused camera, visible tiles, nearby offscreen tiles, and the existing audio/visibility signals. It permits bounded prefetch and a one-second release grace period for a tile that leaves the viewport.

Continuous attempts to keep every visible tile subscribed, independently of motion and events. It does not prefetch offscreen tiles. Both modes prefer the lowest browser-compatible quality rank for wall previews and preserve the focused Viewer’s separate quality selection. When ranks are unavailable, the existing resolution, bitrate, frame-rate, and stream fallback ordering applies. A codec rejected by the browser is not requested.

The settings panel shows requested streams and decoders, the effective budget, the active count, and any excess demand before Continuous is selected. The initial browser estimate is half of reported hardware concurrency, rounded down and bounded to 4-12 decoders. Missing hardware information uses four. The effective ceiling is the smaller of that estimate and Stream limit. The estimate is not a guarantee that every codec or camera will decode smoothly; lower the limit when this device cannot sustain the workload.

Admission uses batches of up to three cameras, with a 40 ms delay while more admissions are queued. Focused and warm background streams use the same ceiling. Existing subscriptions are released before their replacements are requested. The server still authorizes every subscription and enforces its negotiated transport and resource limits; the current capability snapshot does not advertise a numeric server quota.

A server-refused stream shows Server did not admit this stream, while other admitted streams continue. A failed eviction stops the local live session and reports an unavailable state instead of retaining unconfirmed media work. Lower the stream limit, inspect camera diagnostics, and retry the live view when the server or network is available.

Both modes suspend media subscriptions when the document becomes hidden. Wake lock does not override suspension. Returning to the visible wall resumes admission within the same budget. None of these actions stops camera ingest, recording, detection, or retention on the server.

Live video quality

The focused Viewer’s Auto quality uses native RTP bandwidth feedback to choose between compatible Main and Sub streams. It begins with Sub when available. A shared WebRTC connection has one network estimate for all of its tracks: KeepPeek reserves audio and explicitly selected video demand, then divides the remaining estimate among Auto video subscriptions. Adding another camera can therefore reduce the budget available to each Auto camera.

Auto waits for sustained headroom before upgrading and sustained pressure before downgrading. The existing picture remains active until the replacement stream supplies a compatible keyframe. Changing quality does not start a second peer connection or change camera recording settings. High and Low keep the requested quality when it is compatible and available; Auto does not override a manual choice. The panel reports an unavailable alternative rather than treating it as a completed handover. A manual choice can still exceed the available network capacity.

The bandwidth estimate describes the shared connection, not a camera’s received bitrate or capture-to-display latency. An unavailable estimate is unknown. Reliable data delivery uses a fixed variant and does not use native RTP Auto adaptation. Browser decoder limits and the wall’s stream admission budget remain separate from network quality selection.

Open the live video’s information panel to compare Session network estimate, Camera budget and Receive bitrate. The first describes the connection, the second describes an adaptive Auto camera’s allocation, and the third describes media arriving in this browser. Manual and fixed selections do not receive an adaptive budget. A zero allocation is different from an unknown estimate or unavailable diagnostics on an older server.

Requested quality, Active stream, Pending stream and Auto status distinguish the user’s choice from the server’s runtime selection. A pending change waits for a keyframe; the active source remains unchanged until that gate admits the replacement. An active source does not prove that this browser has decoded or displayed a new frame. The panel also identifies holds, missing network evidence, a single variant and unavailable alternatives. Reconnecting or removing a subscription clears its previous diagnostic values.

Auto keeps the current picture if an alternative lacks the codec configuration needed to verify compatibility. That alternative is reported as unavailable. It does not replace a working stream with an unverified H.264 profile.

Recorded playback does not use this live controller. A recording with a matching time range is not necessarily an aligned alternative view of the same captured frames.

Frame freshness and health

A camera’s server-reported health and a browser tile’s live admission are separate facts. A healthy camera can have a paused tile when the browser has exhausted its budget.

Paused tiles identify whether they are waiting for admission, over the device budget, offscreen, hidden, unsupported by the browser, or unavailable. A retained frame shows its age when the current viewer observed it. A cached frame without a capture timestamp says Frame time unknown. No frame received means there is no observed frame to present. A paused frame is not evidence that the camera itself stopped recording.

Use the camera-information control for transport, decoded video, and recording evidence. See camera and stream health for the authoritative health meanings.

Keep a display awake

Enable Keep display awake after interacting with the Dashboard, then save it for that dashboard. The setting uses the browser’s Screen Wake Lock API. Support requires a secure context, normally HTTPS or localhost, and permission from the browser, embedding policy, and device.

A saved enabled preference waits for a user interaction before its first request. Only one request is outstanding at a time. The status reports waiting, requesting, active, released, unsupported, or denied. A request that receives no response within ten seconds is also reported as denied.

Only the intent is stored on the server. The active wake-lock handle, browser permission, current visibility, and measured decoder capacity are runtime facts, not persisted settings. Switching to a dashboard with wake lock disabled releases the previous dashboard’s lock.

The lock is released when the setting is turned off, the document is hidden, the user leaves the Dashboard, or the view is destroyed. A late request result is released too. After a visibility return, KeepPeek may reacquire the lock if it is still enabled and the browser permits it. Rapid toggles and visibility changes reconcile to the latest choice after release completes.

The browser or operating system can release or deny a lock, including in battery-saving mode. KeepPeek does not repeatedly prompt or immediately reacquire a system-released lock. To try again after denial, turn the setting off and on. If Release failed appears, close the Dashboard tab to end its use of the browser resource. Wake lock is not native operating-system kiosk management and does not guarantee that a device can never sleep.

Recording and evidence

KeepPeek treats a recording as evidence only when the media is finalized, indexed, and playable. A connected camera or an MP4 filename alone does not prove that the requested interval can be reviewed later. The underlying live evidence dimensions are defined in Camera and stream health.

Find and play a recording

Open Keep, choose the camera and recorded day, then select footage or an event on the timeline. The four modes serve different investigations:

ModeUse it for
TimelineFollow footage and event intervals around the selected playback time.
StoriesReview available event stories and their associated media.
SwimlanesCompare the available camera histories around a common time.
ExportPrepare and inspect an evidence-export job; creation requires Administrator access.

Use play/pause, skip, the recording-position slider, volume/mute, speed and fullscreen controls below the image. Available stream quality depends on retained media and browser codec support. Changing the quality preference is separate from digital zoom. Stored playback does not provide automatic transcoding for an unsupported codec; retain a compatible H.264 stream when your viewing devices need one.

With the recorded player focused, Left and Right step frames. Space toggles playback; J, K and L provide shuttle/pause controls. [ and ] set export boundaries. Form fields keep their normal keyboard behavior, and arrow keys pan a focused digitally zoomed image instead of stepping frames.

Use Keep on a phone

In portrait, Keep keeps a compact camera/date row, the player and a horizontal history timeline in the main view. Tap the camera name or date to open Camera and date. Search for a camera or use previous/next camera; camera navigation wraps at the ends. The recorded-day selector and its arrows choose available days, with unavailable directions disabled.

Tap Playback options for volume, playback speed, quality, Copy link to this moment, and Refresh recordings. Changes apply immediately. Done, the close button, Escape or browser Back closes the sheet and returns focus to a visible control. Opening a sheet keeps the player open. In recording fullscreen, rate and volume remain available beside the player controls.

The compact controls do not remove Timeline, Stories, Swimlanes or Export. Rotate the device or close the sheet to return to the history view. The timeline changes recording time; the separate Digital controls above the image change only the inspection crop.

Keep the requested UTC time explicit

Recorded days and event time filters use UTC. Switching cameras preserves the selected absolute moment, including when the destination camera has no footage there. Choosing another recorded day keeps the UTC clock time on that chosen day. At midnight this means midnight of the selected day, not the following day.

A gap stays visible until you explicitly choose available footage. Use Previous recording or Next recording when offered, or select a recorded interval on the timeline. KeepPeek does not silently move an unavailable requested moment to the latest clip. Loading, no recorded days, unavailable footage and decode failure are distinct states; use the displayed action or refresh after checking the camera’s recording policy and retained coverage.

Choose what each camera records

Each camera has one recording policy:

PolicyRecorded media
offRecords neither video stream. This is intentional and is not reported as a gap.
subRecords the substream continuously.
mainRecords the main stream continuously.
bothRecords main and sub independently.
event-boostRecords the substream normally, switches to main at a keyframe after an event, then returns to substream.
event-onlyRecords the selected main or sub stream only for accepted events, including available pre-event history.

event-boost is the default. It writes one logical recording rather than recording main and sub at the same time. Transitions happen only at keyframes, so the stored file remains seekable. A new event extends the configured main-stream interval.

Event-only and event-boost can retain up to 30 seconds of encoded pre-event media in memory. Pre-recording defaults to zero, preserving existing camera behavior. Choose the duration in camera setup or the camera editor. Event-only also lets you choose main or sub. Repeated events extend the post-event window. Event-only excludes frames at or after its deadline.

With event-boost pre-recording enabled, KeepPeek delays the continuous substream by at most the selected duration so available main-stream history can replace that interval in one recording. A crash can lose this uncommitted interval. Memory pressure releases continuous sub media and shortens optional history. Disable pre-recording to restore the existing immediate admission path.

The requested interval is a ceiling, not a guarantee. Recordings begin at a retained, decodable keyframe. Startup, missing keyframes, long GOPs, and byte limits can shorten the interval. Camera health reports the requested and available history, retained bytes, and the current reason. Pending replay means media is queued; it does not prove a successful disk or catalog commit. Use Recording integrity and playback to inspect committed evidence. Audio packets crossing the selected video boundary are omitted without re-encoding.

If a runtime profile reports a camera as Degraded because storage rejected its recording settings or could not queue the change, new recording input for that camera stops. Live viewing can continue, and other cameras still apply their profile changes. Pending means the recording writer has not yet accepted the configuration. A missing writer acknowledgement also reports Degraded and stops new recording input for the current request. Correct the reported settings or restore storage availability, then open Settings → Runtime profiles and select Reapply active policy. This retries the accepted Base or named profile without switching to another policy. Save or discard profile edits before retrying. An authorized WebRTC client can also reapply the current policy, and restarting KeepPeek restores the saved selection. Applied confirms that the writer accepted the recording configuration; use Recording integrity and playback to verify retained footage.

An active recording write failure after acceptance can also change Applied to Degraded. This status uses the camera source identity even when its recording folder has a different name. Recording can continue to attempt writes. After repairing storage, use Reapply active policy to clear the failure observation after fresh configuration acceptance. A failed retry keeps the desired selection and shows its error; reload current state before trying again if another Administrator changed it. Fresh configuration acceptance does not prove media recovery, so check Recording integrity and playback. Idle event recording and ordinary write progress do not clear the failure status automatically. Use integrity checks, playback and server logs to assess cleanup, finalization, shutdown and storage-pressure failures.

The default limits are 64 MiB per buffered stream and 256 MiB globally. Administrators can change them in advanced storage settings. History is not saved across restart. Privacy activation, reconnect, and storage pause discard stale history; recording resumes from a valid keyframe. See the configuration reference for persisted fields and limits.

Before downgrading to a version without event-only support, change affected cameras to a supported policy and set pre-recording to zero. Save a configuration backup first. Existing committed media does not need conversion or deletion, and disabling pre-recording does not remove it.

Choose a browser-compatible H.264 substream even when the main evidence stream is H.265. KeepPeek stores the camera’s encoded media without re-encoding it, so browser support still determines which recordings can play directly.

Recording admission and media retention are separate. The current camera modes and event pre-recording do not implement independent continuous, motion, alert and detection retention deadlines. Storage pressure can shorten available coverage. The recording policy audit tracks verified behavior, missing retention rules and their implementation owners under #168.

Use the local archive tier

KeepPeek separates short-lived RAM buffering, active recording files, finalized primary media, and the fourth ARCHIVE tier. A fourth-tier archive is a verified replica. The named-volume archive placement role still relocates primary recordings; it does not create a second replica.

As an Administrator, open Settings → Storage → Local archive. Create a private destination directory owned by the recorder account on an already mounted local disk, then edit and save its target ID, root, limits, and optional source selection. The archive root must not overlap primary or metadata roots. Save requires a configuration revision; a conflict preserves your draft. Reload archive settings explicitly discards the draft after confirmation and loads current settings. Restart KeepPeek to apply the saved configuration, then test the destination and refresh archive jobs to observe the result. A pending probe is not a successful destination test.

You can queue a manual copy of finalized recordings or completed exports from the catalog object selector. Automatic mode selects eligible sources after the configured minimum age; an empty source list selects all. Copies use bounded background work and independently verify the retained replica before reporting success. Jobs show identity, state, bytes, attempts, failures, and any restore or eviction action. Refresh before retrying an uncertain result. A lost copy reply is reconciled using the same durable job identity.

Primary eviction defaults to off. A successful copy alone does not remove the primary file. Enable automatic eviction only deliberately, or confirm Evict primary for an individual job. The server checks the verified archive, current reader leases, and retention holds before retiring the primary. Timeline and seek metadata remain indexed. Open archived recordings through the normal Keep timeline; the player displays ARCHIVE — Playing from archive. Startup and seeking may take longer. It reads the required archive ranges directly, without a full restore or an artificial delay. Existing waiting, failure, seek, and cancellation controls still apply.

Restore primary queues a verified transfer back to the original primary destination. Cancellation stops cancellable copy or restore work and cleans up partial staging. Retry / finish cleanup retains the cancelled copy’s job identity. Copying resumes only after cleanup is acknowledged and current source evidence is validated. Refresh to distinguish pending cleanup from resumed copying. Once a primary eviction intent is durable, it must finish reconciliation; cancellation is unavailable for that state. Preserve the job evidence and restore the destination before retrying recovery.

An offline archive reports unavailable health without blocking new recording ingestion. Archived media whose primary was evicted can remain temporarily inaccessible until the mounted destination returns. Do not recreate or substitute a missing mount: the catalog binds the target ID to its root identity. Retain the catalog, archive tree, and configuration together for filesystem recovery. Broader offsite providers remain planned; the supported target in this phase is the local filesystem only.

Prove recording integrity

Open Recording integrity to inspect the fleet before footage is needed. The workspace reports:

  • whether recording is requested by policy;
  • current writer state and the last frame, write, finalization, and catalog commit;
  • oldest and newest retained media;
  • effective retained duration, attributed bytes, and estimated daily growth;
  • playable coverage for the selected 24-hour, 7-day, or 30-day interval;
  • exact recent gaps and the evidence that explains each one.

Fleet states have narrow meanings:

StateMeaning
Recording healthyEvery requested stream has current writer progress.
Recording degradedA requested writer is stalled or failed.
Paused by policyThe effective policy intentionally requests no recording.
Not configuredNo usable recording stream is configured.
UnknownKeepPeek lacks enough current evidence to make a truthful determination.

The Not configured summary appears only when its count is nonzero. Policy-disabled streams and unconfigured cameras remain distinct from unexpected recording loss.

Coverage comes from keyframe-indexed fragments, not from file presence. Current gaps remain open and never claim an end time. Causes include source silence, transport outage, stale frames, decode failure, writer failure, disk pressure, retention deletion, storage migration, catalog mismatch, and an explicit unknown state. Gap actions open nearby footage, camera health, or relevant logs.

Long histories remain bounded: fleet pages contain 25 cameras by default, recent exact detail keeps at most 256 ranges per stream, and longer periods use deterministic time buckets while retaining exact totals.

Review events consistently

Open Events to search across authorized cameras. Choose the UTC date and optional start/end times, then narrow by camera, event kind, origin, zone, confidence, image availability or text. Review and bookmark filters compose with the same search. If a filter reports unavailable support, check the server capability instead of assuming that an empty result means no events.

Select a card to inspect its canonical image, description, timestamps and revision. Use Open at this moment to investigate recorded footage or the export action to prepare a clip. The return link restores the selected event and supported filter context. For an empty result, check the effective filters and UTC date before broadening the search.

Mark events reviewed or dismissed without deleting evidence. Those flags belong to your authenticated identity, or to a persistent Local workspace in trusted-LAN use. Event updates do not reset them. Use the visible or selected count on bulk actions; undo applies only to the acknowledged targets. Review and bookmark filters use server-computed counts, not the current page length.

Reviewed and Dismissed are independent flags. Unreviewed means neither flag is set; dismissal removes an event from that review queue without deleting its attachments or recording. Mark N visible reviewed affects the rendered page, while Mark N selected reviewed sends the explicit selection, which may span pages. The selection limit is 128 events. A stale or unconfirmed write requires Reload and retry; note drafts and action intent remain available.

Named credentials retain their review identity across credential rotation. A trusted-local browser uses a persistent Local workspace identity rather than attributing work to a person. Clearing that site’s browser storage creates a new workspace; unavailable persistent storage disables its mutations instead of silently using a temporary identity.

Bookmarks are shared with authorized camera viewers. Their creator or an Administrator can edit the bounded note or remove the bookmark. Saved bookmarks retains honest metadata-only references when media, events, or sources are unavailable. A bookmark does not pin video through retention. Exports keep their source event/bookmark revision relationship without creating a retention hold.

Bookmark notes are plain text, limited to 1,024 UTF-8 bytes. The bookmark library has its own source, date and creator scope, independent of the main Events filters. Recording retention can remove footage without removing the bookmark. References for deleted events are retained for 90 days and explicitly identify missing event, source or media.

Events, Keep, notifications, MQTT, and export entry points use the same event revision and canonical preview. An authorized producer may name the canonical attachment. Otherwise KeepPeek chooses a supported snapshot, then a story frame, then a retained thumbnail using stable ordinal, capture time, and attachment ID ordering.

If canonical image bytes are unavailable, KeepPeek shows that state instead of silently choosing a different image. Bounding boxes appear only when their coordinate-space attachment matches the canonical image. Event type remains authoritative for filtering and automation; semantic icons are presentation-only and come from a fixed allowlist.

Notifications and integrations explains how those same event and operational identities remain stable across delivery, retry, and broker recovery.

Copy a recording moment

Use digital zoom and pan to inspect recorded frames and canonical event images without changing their source resolution or the evidence. Inspection crops are not included in copied moment links or exports.

Use the link icon in Keep’s command bar to copy the current recording moment. The command reads the playback clock when invoked; it does not pause, seek, reload the video, or change browser history. Event detail has the same command beside Open at this moment, using the event’s start time. Both event actions retain the selected event and active filters. Back to event returns to that context from any Keep mode.

These are authenticated navigation links, not public shares or exports. Recipients need access to the same KeepPeek server and camera, and remote recipients must sign in. A link does not contain an access key, session ID, temporary media URL, recording filename, or filesystem path. It does include the server-provided camera identifier and, for event links, event and filter context. Treat those details as private operational information. Changing camera identity or deleting footage can make an old link unavailable.

Links use the existing /keep route, including any configured application base path:

ParameterMeaning
cameraServer-provided source identifier, encoded without changing its value.
atAbsolute Unix timestamp in integer milliseconds.
dateUTC date derived from at; the timestamp wins if an incoming date conflicts.
streamRequested auto, high, low, main, or sub preference.
modeKeep view; omitted for Timeline, otherwise stories, swimlanes, or export.
eventOptional event identifier.
returnToOptional local Events route with supported filters and selected event identity.

The timestamp remains the same across browser timezones. For retained, supported media, playback restoration is verified within one second or one source-frame duration, whichever is larger. Browser codec support still applies. KeepPeek explains a compatible stream fallback and preserves the requested preference in copied links.

An exact link into a gap does not open nearby footage automatically. It keeps the requested UTC time visible and offers previous or next recordings found in the bounded five-minute window on each side, within that UTC day. Selecting one explicitly moves the playhead. If no retained footage is found, the view explains that it may have expired or never been recorded. Missing and inaccessible cameras share an unavailable-or-not-authorized message to avoid disclosing hidden sources. Malformed links fail visibly instead of selecting another camera.

Successful copying shows a checkmark and announces confirmation. If browser clipboard access is denied, unsupported, or takes more than 2.5 seconds, a dialog presents the same link in a selected, read-only field for manual copying. Escape closes that dialog and returns focus to the command.

Export evidence

An Administrator can select up to two minutes in Keep and create a standalone MP4. Event export opens the same editor with 15 seconds of context before and after the event while preserving the camera, stream, timestamp, event revision, filters, and return route.

Export jobs can be running, ready, partial, failed, cancelled, or expired. KeepPeek:

  • returns an identical running job instead of starting hidden duplicate work;
  • offers an existing ready artifact before creating a fresh one;
  • names every missing interval before a partial export proceeds;
  • stops jobs after 30 seconds without progress or five minutes total runtime;
  • removes partial output after failure or cancellation;
  • marks interrupted jobs failed and retryable after restart;
  • verifies SHA-256 again immediately before download.

Ready files remain available for 24 hours. Bounded job history remains for 30 days, up to 500 jobs, so an expired or missing artifact can still explain what happened and support an explicit retry. History and files remain scoped to the Administrator identity that created them.

Check the completed job’s interval, stream and missing-range explanation before downloading. A partial artifact contains only its available intervals and must not be described as continuous coverage of the whole selection. Cancellation and retry are explicit actions; opening another page does not turn a running job into a completed download. Download integrity failure or an expired artifact requires a fresh job if the original recordings are still available.

Timestamp burn-in requires a configured re-encoding worker and otherwise fails explicitly. The normal export path preserves source frames and timestamps without re-encoding.

For the availability and safety limits of manual deletion and catalog drift checks, see Recording maintenance. The Administrator workflow is under qualification; confirmed jobs can remove selected recordings permanently.

For the detailed coverage model and export lifecycle, see the recording integrity, event presentation, and evidence export references.

Digital zoom and pan

Digital zoom enlarges the media already decoded by your browser. It is available in the focused Peek Viewer, Keep recorded playback, and event detail images. It does not move the camera, request a different stream, or increase the source resolution.

Compact Dashboard tiles and the camera filmstrip do not capture inspection gestures. Open a camera in Viewer before using digital zoom.

Inspect a frame

The Digital controls sit at the top-left above the image without covering the picture. In Peek, they share one row and height with Live/History, quality, and camera information. The row scrolls horizontally when needed rather than wrapping. They show the current magnification. The minimum is 1.0x, which fits the complete image, and the maximum is 8.0x. The image keeps its original aspect ratio. Panning is bounded to the fitted image area, so a drag cannot move the image out of view.

InputResult
Zoom-in iconDouble the magnification, up to 8.0x.
Zoom-out iconHalve the magnification, down to 1.0x.
Reset iconReturn to the complete, centered frame.
Two-finger pinch inside the imageZoom around the midpoint between the fingers; moving that midpoint also pans.
Mouse, pen, or one-finger dragPan while magnification is above 1.0x.
Alt + wheel or trackpad scrollZoom around the pointer. Use Option on macOS keyboards.
Double-click or double-tapSwitch between 1.0x and 2.0x around the pointer or tap.
+ or =Zoom in when the inspection surface has focus.
-Zoom out when the inspection surface has focus.
0Reset when the inspection surface has focus.
Arrow keysPan while zoomed and focused.
EscapeReset digital zoom before the normal Viewer or event-detail exit action.

Tab reaches the named zoom buttons. Activating a zoom button focuses the inspection surface, where the keyboard commands work. Peek’s desktop control groups are 32 pixels high. Touch input uses at least 44-pixel button targets. All buttons have visible focus indicators and disabled states at the limits. The zoom value is also available to assistive technology.

Ordinary wheel scrolling does not change magnification. Control/Command zoom shortcuts still belong to the browser. Touch gestures are captured only inside the focused image; scrolling and browser gestures remain available elsewhere on the page. Zoom and reset do not require animated transitions.

Keep playback controls

Keep’s play/pause, skip, position, volume, mute, speed, and fullscreen controls remain outside the transformed video. Timeline scrubbing still selects recording time, not an image crop. Arrow keys pan when the zoomed image has focus; Keep’s normal frame-stepping shortcuts remain available at full-frame view or outside the inspection surface.

On a portrait phone, the Digital strip remains above the image while Playback options opens volume, speed and quality in a separate sheet. Opening that sheet does not replace the recording or move its playhead. Its quality choice can request different available media; the Digital strip only enlarges pixels already decoded. Close the sheet to inspect the horizontal recording history.

The fullscreen button expands the whole recorded player, including its controls. Zoom bounds update when entering or leaving fullscreen. Browser-native Escape handling can leave fullscreen before the page receives the key. A disabled fullscreen button indicates that the browser does not offer the Fullscreen API in that context; digital zoom remains available without fullscreen.

Reset and evidence

Changing the camera, recording, or canonical event image resets digital zoom. Play/pause and ordinary progress within the same selected media preserve the inspection position. Resizing the window, rotating the device, or changing decoded dimensions recomputes the bounds. Reset always returns to the complete frame at the current size.

Event bounding boxes use the canonical image’s coordinate space and move with that image. Player controls, camera information, and diagnostics do not scale. Live camera information reports digital magnification separately from decoded resolution.

The inspection crop exists only in the current browser view. It is not saved into configuration, recording metadata, bookmarks, copied moment links, or exported evidence. Exports still contain the original recorded frame. See Recording and evidence.

Digital zoom is not PTZ

Digital zoom is labelled Digital and works for any media you can already view, including recordings and offline-camera event images. It does not require PTZ permission or a PTZ-capable camera. Physical pan, tilt, and optical zoom remain separate camera commands with their existing capability and permission checks.

See Camera controls for physical PTZ, detector configuration and the current limits of browser Talk.

Magnifying a low-resolution stream does not reveal detail that the camera did not send. Choose a higher available stream quality separately when you need more decoded pixels.

Camera and stream health

KeepPeek computes camera and stream health on the server. Peek, Cameras, Health, camera diagnosis, fleet counts, the control API, and Prometheus metrics consume that same projection. Clients do not infer health from display text, local video playback, or issue severity.

Investigate an unhealthy camera

  1. Open Cameras and use Not healthy to narrow the fleet. Select the camera for its current state, primary reason, stream profiles and recording evidence.
  2. Open Health for fleet findings. Refresh to request a current snapshot, then follow a finding’s camera or timeline link to the affected source and time.
  3. Compare transport, advancing frames, keyframes and recording progress. A connected socket can still deliver stale or undecodable video; a decoded live picture does not prove recording.
  4. Use Recording integrity when the question is whether footage was retained. Its historical coverage and gaps answer a different question from current-process health counters.
  5. Check native event and external service status separately when only detections or alerts are missing. Their failures do not make otherwise healthy camera media offline.

An active health finding clears when new evidence shows recovery. It has no dismiss action that changes the underlying state. Acknowledging an alert or marking an event reviewed is a separate workflow. See Notifications and integrations and Recording and evidence.

Health and administrative diagnostics depend on your role. If a User can view video but cannot load fleet health, check access permissions before treating the unavailable diagnostic panel as a camera failure.

The current health contract version is 1. A client that cannot understand a state or reason must treat it as unknown, never as healthy.

Evidence dimensions

Health keeps these dimensions separate:

DimensionEvidence
Configured and expectedCamera configuration, recording policy, and expected stream IDs
TransportRouter lifecycle plus the expected and connected stream IDs
Report freshnessMonotonic age of the latest periodic ingress report
Frame freshnessMonotonic age of the latest advancing frame counter
DecodabilityMonotonic age of the latest advancing video keyframe counter
Ingress qualityMeasured frame rate and recent reconnect, drop, and error deltas
RecordingRequested stream IDs, writer attempts, successful MP4 appends, and bounded writer errors
Battery sleepFresh registration heartbeat plus no pending wake request from the optional battery wake service

Native events, external detectors, and other optional services have their own health. Their failure does not make otherwise healthy media or recording appear offline.

Canonical states

StateMeaning
startingKeepPeek is inside the initial evidence grace period.
healthyRequired transport, frame, keyframe, frame-rate, and recording evidence is current.
degradedMedia is current, but one or more quality, decodability, transport, ingress, or recording dimensions failed.
staleA report is missing or old, or frame counters stopped advancing.
reconnectingTransport is reconnecting and recent media evidence still exists.
offlineTransport is disconnected without recent evidence.
stoppedMedia is not expected, the camera is shutting down, or a registered battery camera is sleeping.
unknownThe server or client lacks enough authoritative evidence.

healthy is the only healthy state. starting, unknown, and stopped are not included in healthy counts. A health request failure makes the current view unavailable; it does not rewrite the last server camera state.

Precedence

The server applies this order. The first matching rule supplies the canonical state and primary reason code.

  1. Not expected, shutting down, or stopped: stopped.
  2. Fresh battery registration without a connected transport or pending wake request: stopped with battery_sleeping. An accepted wake remains pending until connected media clears it; failures therefore become reconnecting or offline, not sleeping.
  3. During initial grace, incomplete or transiently unhealthy evidence remains starting; a complete healthy snapshot may promote immediately.
  4. Reconnecting transport: reconnecting, or offline after recent evidence expires.
  5. No report, an old report, or frames that stopped advancing: stale.
  6. Missing keyframes, stalled requested recording, low frame rate, partial transport, recent errors, drops, or reconnects: degraded. The primary degraded reason is ordered as partial transport, missing keyframes, recording failure, low frame rate, errors, drops, then reconnects; all matching codes remain in reason_codes.
  7. Required evidence unavailable: unknown.
  8. All required evidence current: healthy.

A connected transport cannot override stale frames, missing keyframes, or failed recording. A decoded browser frame cannot promote server state. A fresh capability snapshot is not media evidence.

Thresholds

Ingress publishes every 10 seconds.

  • A stream report is stale after 30 seconds.
  • Reconnecting becomes offline when the latest stream evidence is older than 90 seconds.
  • Frame freshness is $\max(20\text{ s}, 3 / \text{expected fps})$, capped at 120 seconds, and also requires a fresh stream report.
  • Keyframe freshness is three configured GOP intervals, or three observed keyframe intervals when configuration is unavailable. It is clamped to 30 through 120 seconds.
  • Recording progress is fresh for the configured short-term buffer plus flush interval plus 30 seconds.
  • Initial startup grace is two ingress report intervals, currently 20 seconds.

Frame-arrival jitter measures the absolute difference between each observed frame gap and the configured frame interval. Its P99 is computed over the current 10-second ingress report window; it is not WebRTC network jitter. KeepPeek displays it as diagnostic evidence but does not create a finding from jitter alone. Stale frames, low sustained frame rate, decode failure, ingress drops/errors, recording failure, and a maximum observed frame gap above 2 seconds remain actionable health signals.

The first ingress report establishes the baseline for cumulative reconnect, drop, and error counters. An initial connection is therefore not reported as a recent reconnect. During startup grace, partial-window frame rates and other transient evidence remain starting; if they have not recovered when grace expires, the canonical degraded or offline reason becomes visible.

These thresholds are the bounded debounce window. One missed 10-second ingress report does not change a camera to stale; report evidence must age beyond 30 seconds. Recovery is intentionally asymmetric: a new advancing frame or keyframe counter can recover the state immediately. The API still returns the actual evidence ages, and clients do not add another timer or rewrite the state.

These ages use monotonic clocks inside the server. Wall-clock timestamps are included for display, but wall-clock changes do not make stale evidence fresh.

Health findings are live snapshot evidence rather than acknowledgements or retained alerts. They disappear automatically after a later ingress report no longer meets the condition. The refresh button requests a current snapshot immediately, but an active finding cannot be dismissed from the Health page. Acknowledging a delivered alert belongs to the notification inbox rather than changing server health evidence.

Operational intervals

KeepPeek projects four durable event kinds from the independent health dimensions:

Event kindScopeEvidence
camera_offlineCameraNo expected transport is connected, or aggregate transport is disconnected
stream_staleCamera streamThe stream report is missing or stale, or frames stopped advancing
decode_unavailableCamera streamNo recent decodable keyframe exists
recording_interruptedCamera streamRecording was requested but writer progress stopped

These event kinds remain independent. One stream can be stale and undecodable while another stays healthy, and recording failure does not rewrite transport evidence. Native event and external analysis failures are not camera, stream, or recording outages.

A candidate interval starts as soon as evidence fails. Monotonic elapsed time controls warning, critical, and recovery thresholds; server wall time supplies the displayed start and end timestamps. The default policy is:

[operational_events]
warning_hold_down_secs = 15
outage_hold_down_secs = 60
recovery_debounce_secs = 10
record_short_flaps = false

[operational_events.cameras.front-door]
warning_hold_down_secs = 30

Camera overrides may use the stable camera ID or IP address and inherit omitted global values. A zero duration requests an immediate transition. Durations cannot exceed 24 hours, and the warning hold-down cannot exceed the outage hold-down.

Recovery before the warning hold-down removes the candidate unless record_short_flaps is true. Once visible, an interval keeps one event ID. Cause or severity changes increment its revision; stable recovery increments the same event again, records the recovery timestamp, and reports total duration from the original evidence-loss instant. Open intervals are restored from the recording catalog after restart. Stable revision replay makes notification processing at least once while the notification store collapses duplicate revisions.

The stored timeline renders open and recovered intervals beside recording availability. Event payloads include severity, bounded cause and explanation, affected streams, recording consequence, evidence source, duration, and recovery state. Health findings link to the corresponding camera and timeline instant.

Session durations

Camera health reports current-process session durations separately from historical archive coverage:

  • session_duration_ms is the longest observed ingress-worker duration for the camera’s expected video streams;
  • recorded_main_duration_ms and recorded_sub_duration_ms accumulate video sample durations only after those samples are successfully appended to MP4 during the current process run;
  • recorded_total_duration_ms is the sum of all requested stream durations for that camera session.

The total can exceed camera session elapsed time when Main and Sub record concurrently. These counters reset when KeepPeek restarts or the corresponding recording-health registry is replaced; they do not claim lifetime or retained archive coverage. A recording indicator uses writer progress state, not a comparison between these counters and wall time.

Reasons and detail

Every camera and stream includes:

  • state: the canonical state;
  • reason: the primary stable reason code;
  • reason_codes: all applicable ordered reason codes;
  • detail: bounded human-readable text;
  • dimensions: the independent raw evidence and counts.

Stable reasons include transport_disconnected, transport_reconnecting, transport_partially_connected, no_stream_report, stream_report_stale, frames_not_arriving, frames_below_expected, keyframes_missing, ingress_reconnects, ingress_drops, ingress_errors, and recording_not_progressing.

Use reason codes for automation. Display text may change.

Fleet counts

Fleet summaries name the dimension they count:

  • configured cameras and expected video streams;
  • cameras and streams with connected transports;
  • cameras and streams with fresh frames;
  • cameras and streams with recent keyframes;
  • cameras and streams requested for recording;
  • cameras and streams with current writer progress;
  • cameras whose evidence is unknown.

Fleet aggregates count expected video streams. Audio freshness remains available on each stream snapshot and is not folded into the video decodability or recording totals.

Unknown evidence is not counted as connected, fresh, decodable, recording, or healthy. The Cameras Not healthy filter counts every state except healthy.

API and metrics

HealthCommand returns the versioned protobuf health snapshot. Version 1 includes the canonical state, stable reasons, camera and stream dimensions, media progress timestamps, and explicit fleet counts. Unknown future state or reason strings are presented as unknown.

GET /metrics exposes the same projection. Important gauges include:

  • keeppeek_cameras_connected, keeppeek_cameras_fresh, keeppeek_cameras_decodable, and keeppeek_cameras_recording;
  • corresponding keeppeek_video_streams_* gauges;
  • keeppeek_camera_info with the canonical state label;
  • keeppeek_camera_health_dimension and keeppeek_camera_health_dimension_known;
  • corresponding per-stream dimension and known gauges;
  • keeppeek_operational_event_active, labeled by camera, stream, event kind, and severity.

There is no separate online or degraded gauge. Consumers use the canonical state label and explicit dimensions.

Recovery

Recovery requires new positive evidence:

  • a newly advancing frame counter refreshes frame age;
  • a newly advancing keyframe counter refreshes decodability;
  • a successful writer append clears the previous bounded recording error;
  • drop, error, and reconnect reasons use per-report deltas, so old cumulative counters do not keep a recovered stream degraded;
  • a fresh report can recover stale state, while cached metadata cannot.

Operational interval recovery uses this canonical projection. Delivery failure cannot change or close an interval because catalog persistence happens before notification publication and provider work runs independently from camera media.

Use Recording and evidence to inspect retained coverage and explain gaps. Use Notifications and integrations to deliver these operational transitions without coupling providers or brokers to camera ingest.

Visual configuration management

Use Cameras to inspect and change shared camera defaults, effective inherited values, versioned templates, and explicit camera sets. These operations require Administrator access and the server’s keeppeek.configuration.v1 capability. If it is unavailable, the fleet remains readable and the unsupported actions stay disabled.

Choose the right editor

TaskLocation
Change one camera’s connection, streams, or recording policyOpen its Camera page and edit its configuration.
Change inherited policy, manage templates, or preview a fleet changeOpen the Cameras fleet configuration controls.
Change storage, retention, or listener settingsSettings. Use the storage editor’s move/restart review when changing paths.
Change a dashboard or its display and audienceThe live-wall layout controls.
Change User camera grants or credentialsSettings > Access & roles.
Change notification rules or MQTTThe notification and integration editors.
Transfer the complete settings and file-backed secretsConfiguration ZIP export and apply.
Configure file-only native event policyStop the server and edit the supported camera event table.

The browser has no general secret editor or raw TOML editor. Use the configuration reference for supported file fields. Preserve unrelated sections and existing secret references when editing a stopped installation.

Inspect inheritance before changing it

Every inheritable policy shows its configured default, camera override, final effective value, source, and current runtime state. Use inherited value removes an override so future default changes continue to flow to that camera.

A built-in value is the fallback shipped by KeepPeek. A shared default changes the fallback for cameras without that override. An explicit camera value takes precedence. Use built-in value removes a shared default. Removing an override does not freeze the current value into the camera.

For example, changing a shared recording mode affects cameras that inherit it. A camera with an explicit main override keeps that policy. Check both the effective value and runtime status after applying a change, especially if a camera is disconnected.

Preview and apply a fleet change

  1. Open the shared-default or bulk-change view and choose the fields to change.
  2. For bulk changes, select explicit camera IDs, the complete filtered result, one configured group, or all configured cameras. The all-cameras scope requires explicit confirmation.
  3. Request a preview. Confirm the server’s authoritative camera count, old and new values, skipped targets, validation issues, and reconnect or restart consequences.
  4. Resolve issues before applying. A missing target makes the plan invalid; an oversized preview requires a smaller selection or fewer changed fields.
  5. Apply the current preview, then check the per-camera activation result and camera health.

The filtered scope means the complete filter result, including rows outside the visible viewport. One plan includes at most 64 cameras and expires after ten minutes. The server retains at most 128 plans and bounds control responses to 64 KiB. A preview that cannot fit fails explicitly.

Template and bulk operations always produce a server-owned preview before apply. The preview lists the exact authoritative cameras, semantic old and new values, skipped targets, validation issues, and reconnect or restart consequences. Applying a template creates explicit overrides; later template edits or deletion do not silently change cameras.

Configuration writes use the edit-start revision, validate the complete candidate, preserve unrelated fields and secret references, and replace the configuration atomically. A conflict reloads current evidence without discarding the local draft. If a committed change cannot activate on one camera worker, KeepPeek reports that camera and the required restart recovery action.

Create and reuse a camera template

In the templates view, create a named template with supported connection or recording-policy fields you want to reuse. Templates can be edited, duplicated, deleted, imported, and exported. They do not include notification rules; manage those in their own editor.

Credential fields accept complete references such as {secret:CAMERA_PASSWORD}. Create the corresponding secret in the existing companion secrets.toml through your protected local workflow. A template does not carry the secret value to another recorder.

To use a template, choose it for a bulk target set, review the server preview, then apply. Applying creates explicit camera overrides. Later template edits or deletion do not change cameras that previously used it. To return a camera to shared defaults, remove the relevant overrides with Use inherited value.

Template exchange uses versioned JSON, not a configuration ZIP or raw TOML. Export the template document, choose it for import on the target, review its validated contents, and apply the preview. Unsupported versions, unknown fields, duplicate IDs or names, inline credentials, invalid values, and oversized documents are rejected before mutation. Documents are limited to 16 KiB and contain at most 64 templates. Template names are unique ignoring case.

Resolve conflicts and activation failures

ResultWhat to do
The configuration changed while you were editingRead the refreshed evidence, compare it with the retained draft, and request a new preview. There is no blind overwrite.
A preview expiredRequest and review a new preview. A ten-minute-old target set may no longer be valid.
A target is missing or the preview is too largeCorrect the target selection or split it into smaller explicit batches.
A value fails validationCorrect the indicated field. Check the reference for units, bounds, inheritance, and supported secret syntax.
Saved values differ from runtimeFollow the reported per-camera reconnect or restart action, then verify health and effective values again.
The capability is unavailableCheck the connected server version. The visible fleet does not imply configuration writes are supported.

Conflicts retain the complete local draft while reloading current evidence. Single-camera edits use the configuration revision; template transactions also account for the template document. A template-only edit therefore does not create a false conflict for an unrelated camera draft.

The detailed contract, limits, and ownership boundaries are in configuration engineering guide. Use configuration exchange for full-recorder settings recovery.

Configuration reference

KeepPeek stores settings in one application configuration file, named config.toml by default. The existing companion secrets.toml holds reusable private strings. Layouts, notification rules, credential metadata, and camera templates belong in the application configuration, not in separate settings files. Media files and recording catalogs are data, not additional settings stores.

Live-wall display settings belong to each saved dashboard in [peek_layouts]. Shape, fit, gap, corner radius, streaming mode, stream ceiling, and wake-lock intent are stored on the server and included in configuration and layout exchange. Browser permission and active media/wake-lock handles remain runtime state.

This chapter describes the supported on-disk sections and their serialized Rust types. It is not a list of every API response or runtime struct. Use visual configuration for normal administration and configuration exchange for validated export and import.

Location and editing

EnvironmentDefault application configuration
macOS~/Library/Application Support/keeppeek/config.toml
Linux${XDG_CONFIG_HOME:-$HOME/.config}/keeppeek/config.toml
Windows%APPDATA%\keeppeek\config.toml
Docker/config/keeppeek/config.toml

--config <path>, --config=<path>, or -c <path> selects another file. Its companion secrets file is always named secrets.toml in the same directory. These flags do not create a second settings store.

Default paths belong to the account running the process. A Windows service can resolve a different %APPDATA% from an interactive launch; the macOS package runs a per-user launch agent. Confirm the service identity and effective paths before transferring an installation. See Upgrades and migrations.

Use the UI and typed configuration operations while KeepPeek runs. Stop the server before manual edits so a concurrent settings update cannot overwrite them. Server writers validate candidates, preserve unrelated sections and secret references, and replace the file atomically under the shared configuration-update lock. Some changes apply live; listener or storage changes can require the restart or migration reported by the configuration plan.

On Unix, KeepPeek creates its default private directory with mode 0700 and private files with mode 0600. Restrict the directory to the service account on every platform. Secret values are plaintext at rest, not encrypted by TOML.

In the tables below, optional means omit the field when unset: TOML has no null. Integer types refer to the Rust model; values must also fit TOML’s integer representation. Vec<T> is an array, and a map is a table keyed by the indicated identity. Byte limits and character limits are different. Root fields must appear before a table header or they belong to that table.

Section index

TOML sectionOwning typeOwnership
Root fieldsConfigOperator settings
[access]AccessConfigAdministrator security policy
[direct_card]DirectCardConfigExact browser-origin allowlist
[storage]StorageTomlRecording paths, retention, and safety
[battery_wake]BatteryWakeConfigReolink battery-camera wake service
[logging]LoggingConfigService log destination
[operational_events]OperationalEventsConfigHealth-event timing
[operational_events.cameras."<camera-id-or-ip>"]OperationalEventOverridePer-camera timing overrides
[privacy.default]PrivacyScheduleInherited server-enforced recurring privacy
[privacy.cameras."<camera-id-or-ip>"]PrivacySchedulePer-camera server-enforced recurring privacy
[event_forwarder.mqtt]MqttForwarderConfig inside EventForwarderConfigMQTT configuration; server-owned revision
[camera_defaults]CameraCredentialDefaultsShared camera defaults
[<namespace>.<camera-key>]CameraConfigCamera settings
[<namespace>.<camera-key>.events]EventConfigNative camera event selection and filters
[access_credentials]PersistedAccessCatalogServer-managed credential records
[peek_layouts]StoredRegistryServer-managed layouts and per-user selection
[configuration_templates]StoredTemplateDocumentServer-managed camera templates
[notifications]NotificationConfigurationServer-managed rule drafts and active rules
[runtime_profiles]ProfileRegistryServer-managed profile definitions and selection
[storage_migration]StorageMigrationServer-managed pending storage move

homekit is a reserved root section, not an implemented HomeKit configuration schema. Do not use it as a camera namespace. Every other non-reserved root table is interpreted as a camera namespace; cameras is the conventional name, not the only allowed one. A typo in a section name can therefore be interpreted as camera configuration. Some structs ignore unknown fields, so successful TOML parsing alone does not prove a field has an effect.

[runtime_profiles] is server-managed internal data. Its registry is bounded to 32 profiles, and each profile can target at most 127 cameras with sparse runtime-safe overrides. Do not edit this section manually. The partial implementation supports Administrator management and activation through WebRTC StateStore, including bounded definition reads and staged bounded writes. Privacy, recording, notifications, and camera availability reach their current runtime owners. Delayed profile commands cannot replace newer pending runtime status; an already running camera stop is not preempted. An active profile’s saved target that is no longer configured remains degraded through startup, completion and live removal; re-adding the camera replaces that observation. Activating Base clears missing-target observations from the previously active profile. Events-only camera edits also remain in effect after a profile disables the camera and Base enables it again. Profile targets use the server-managed profile_identity described below. An Administrator can retry a pending or degraded selection with Reapply active policy in Settings. Complete privacy leak, asynchronous convergence, capacity and performance qualification remain open under #202.

Sources: configuration, cameras, and the subsystem sources linked below. Config.source is an in-memory copy of the original TOML table, marked serde(skip); there is no writable source field in the file.

Passwords and secret references

Secrets is a flat BTreeMap<String, String>. Its keys must match [A-Z_][A-Z0-9_]*. Nested tables, arrays, numbers, and booleans are not secret values. Enter real values directly on the server; do not put them in source control, screenshots, logs, or shell arguments.

Example companion secrets file, with placeholders that must be replaced locally:

CAMERA_USERNAME = "replace-with-camera-user"
CAMERA_PASSWORD = "replace-with-camera-password"
FRONT_CAMERA_PASSWORD = "replace-with-front-camera-password"
MQTT_PASSWORD = "replace-with-broker-password"

The application configuration refers to those keys:

host = "0.0.0.0"
port = 8081

[camera_defaults]
username = "{secret:CAMERA_USERNAME}"
password = "{secret:CAMERA_PASSWORD}"

[cameras.front_door]
ip = "192.0.2.10"
password = "{secret:FRONT_CAMERA_PASSWORD}"
main_rtsp_url = "rtsp://{secret:CAMERA_USERNAME|url}:{secret:FRONT_CAMERA_PASSWORD|url}@192.0.2.10:554/stream1"

[cameras.back_door]
ip = "192.0.2.11"

[event_forwarder.mqtt]
username = "keeppeek"
password = "{secret:MQTT_PASSWORD}"
  • {secret:KEY} substitutes the string directly. It can occupy the entire value or part of it.
  • {secret:KEY|url} percent-encodes a URL component. Use it for a password, username, or query value embedded in a URL, not for an entire URL. Bytes other than RFC 3986 unreserved characters are encoded, including @, :, /, spaces, and non-ASCII UTF-8 bytes.
  • KEEPPEEK_SECRET_<KEY> in the server process environment overrides the same key in the file. A literal value does not consult either source.
  • A missing referenced key, malformed reference, unsupported modifier, or invalid secrets file fails loading or the corresponding settings operation. Diagnostics identify the key without including its value.
  • Resolution substitutes strings, not TOML syntax. It does not convert a quoted numeric secret into an integer or expand table keys. Inserted secret values are not recursively expanded.
  • Reusing a key gives several cameras the same credential. A per-camera key overrides the shared default. Rotate an ordinary secret in the companion file and restart KeepPeek to reconnect with the new value.

Reference boundaries

SettingSupported resolution
Root settings, camera defaults, camera entries, and ordinary settings sectionsString values and strings inside arrays/tables are resolved before typed deserialization. The resolved string must still satisfy the field’s type and validation.
[event_forwarder.mqtt].passwordDirect or referenced string. The MQTT settings writer stores a supplied password in the companion file under MQTT_PASSWORD.
Notification active.actions[].destination and draft.actions[].destinationThe notification loader explicitly resolves destination references. Non-browser destinations saved through the rule store use managed KEEPPEEK_NOTIFICATION_... secret keys.
Camera-template username_secret_reference and password_secret_referenceComplete reference tokens only, not inline credentials or interpolated surrounding text. Values are resolved when applying camera configuration.
[access_credentials], [peek_layouts], other notification fields, and other template fieldsNot blanket-expanded as secret templates. These sections have dedicated loaders.
[storage_migration]Concrete paths written by the server, not a secret-template interface.

Configuration responses and unrelated settings updates preserve existing reference tokens instead of returning or persisting resolved credentials. Inline camera credentials remain supported but are not automatically moved to the secret file. Intentionally replacing a camera credential in the editor can replace its reference with an inline value. Ordinary layout exchange must not include credentials, verifiers, or unrelated user state. Full recovery archives have a different security contract; see backup and restore.

The first-run access key is generated as KEEPPEEK_ACCESS_KEY in the companion file and referenced from the root access_key field. Named credentials created later retain only verifiers in the application configuration and return their raw keys once. See authentication and the secret lifecycle guide.

Root settings

Type: Config. Nested section fields are listed in the section index.

FieldTypeDefaultMeaning
hostString"0.0.0.0"HTTP listener bind address.
portu168081HTTP listener port.
access_keyAccessKeyUnset in the model; generated on startupCompatibility initial Administrator key: a UUID string, a reference resolving to a UUID, or integer 0 as the unset marker. 0 is not a switch that disables authentication.

Access policy

Type: AccessConfig, section [access].

FieldTypeDefaultValidation and meaning
local_networksVec<IpNet>Networks belowAt most 64 CIDRs. Matches become trusted-local Administrators without sign-in. Setting the array replaces the defaults.
trusted_proxiesVec<IpNet>[]At most 64 CIDRs, checked separately from local_networks. Only these peers can supply the supported forwarded-client header contract.
require_secure_remotebooltrueRequire the declared secure boundary for remote traffic.
failed_authentication_limitu325Nonzero failed-attempt limit per address/window.
failed_authentication_window_secsu6460Nonzero authentication-rate window, in seconds.
session_idle_timeout_secsu641800Nonzero; cannot exceed the absolute timeout.
session_absolute_timeout_secsu6486400Nonzero maximum session lifetime.
max_sessions_per_principalu3264Nonzero concurrent-session limit per identity.
max_sessions_per_addressu32128Nonzero concurrent-session limit per address.

Default local networks are 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1/128, fc00::/7, and fe80::/10. A container bridge or VPN within those ranges is trusted unless the operator narrows the list. Forwarding headers and CORS do not replace authentication. Review authentication and access control before exposing the listener.

Direct card origins

Type: DirectCardConfig, section [direct_card].

FieldTypeDefaultValidation and meaning
allowed_originsVec<String>[]Unique canonical exact HTTP(S) origins, such as "https://ha.example". No credentials, path, query, fragment, wildcard, or trailing slash.

External authentication configuration

Type: access::external::Config, optional section [external_auth]. Configured OIDC providers use browser authorization-code login with PKCE. Configured identity proxies use exact assertions from their immediate trusted transport peer. Both methods use revocable browser sessions and the existing server-side camera grants. Omitting the section preserves existing local and bearer behavior.

FieldTypeDefaultValidation
allowed_originsVec<String>Required1–16 unique canonical exact HTTPS origins, without credentials, path, query, fragment, wildcard, or trailing slash.
providersVec<Provider>Required1–4 providers with unique IDs.
bearer_enabledboolfalseMixed mode requires an explicit transition deadline.
bearer_transition_until_msOptional i64AbsentPositive Unix milliseconds; present if and only if bearer_enabled is true.

Each [[external_auth.providers]] has id, name, mappings, and a method table. IDs contain 1–64 ASCII letters, digits, underscores, or hyphens. Names contain 1–64 bytes without control characters and cannot be whitespace-only. At least one mapping is required per provider; there are at most 128 across all providers. Unknown fields are rejected.

Each [[external_auth.providers.mappings]] contains claim (1–64 bytes), value (1–256 bytes), and role (administrator or user). Claim names and values cannot be whitespace-only or contain control characters. User mappings require an explicit camera_access table using the existing all_cameras, camera_ids, and group_ids fields and limits, with at most 128 camera and group IDs combined per external identity policy. Set all_cameras and camera_ids explicitly; group_ids defaults to an empty list. With all_cameras = false, empty camera and group lists grant no cameras. Administrator mappings must omit camera_access because Administrators are unrestricted. Unmapped identities and identities matching multiple rules are denied. Mapping claims must be strings or lists of at most 128 strings, each at most 256 bytes; the claims object is limited to 16 KiB serialized JSON.

For [external_auth.providers.method] with kind = "oidc":

FieldTypeDefaultValidation
issuerStringRequiredHTTPS URL without user-info, query, or fragment.
client_idStringRequired1–256 bytes, nonblank, no control characters.
client_secretOptional StringAbsentComplete {secret:KEY} or {secret:KEY|url} reference; resolved value is nonblank, at most 4,096 bytes, without control characters. Inline secrets are rejected.
redirect_uriStringRequiredAllowed HTTPS origin with exactly /auth/callback, no query or fragment.
scopesVec<String>["openid"]At most 16 unique OAuth scope tokens of 1–64 bytes; must include openid.
display_name_claimString"name"1–64 bytes, nonblank, no control characters.
endpoint_originsVec<String>[]At most 16 unique canonical HTTPS origins for additional provider endpoints.
private_networksVec<IpNet>[]At most 64 explicitly permitted private issuer networks.
logout_uriOptional StringAbsentHTTPS URL without user-info, query, or fragment.

All external-authentication URLs are limited to 2,048 bytes. A configured URL does not authorize arbitrary discovery redirects or other network destinations.

For [external_auth.providers.method] with kind = "proxy":

FieldTypeDefaultValidation
trusted_peersVec<IpNet>Required1–64 immediate transport-peer CIDRs, independent of forwarded-client classification.
subject_headerStringRequiredExact identity assertion header.
role_headerStringRequiredExact role assertion header; values still require explicit mapping.
name_headerOptional StringAbsentOptional display metadata header.
secret_headerOptional StringAbsentPresent if and only if shared_secret is present.
shared_secretOptional StringAbsentSame reference and resolved-value rules as client_secret.

Header names are distinct ignoring ASCII case, at most 64 ASCII letters, digits, or hyphens, start with X-, and cannot start with X-Forwarded-. Secret references remain unchanged in config.toml; only the runtime copy resolves them. Existing configuration validation rejects invalid candidates before the atomic writer runs.

Trusted peer networks from different identity proxy providers must not overlap. KeepPeek checks the immediate peer after normalizing IPv4-mapped IPv6 addresses; configure IPv4 CIDRs for those peers.

Durable external identities

[external_identities] has version = 1 and at most 1,024 records. Each record contains id (non-nil UUID), provider_id, subject_fingerprint (64 lowercase hexadecimal characters), display_name, role, camera_access, positive revision, enabled, and nonnegative created_at_ms. Provider and display names are nonblank, at most 64 bytes, and contain no control characters. Subject fingerprints and IDs are unique. Administrator camera access is unrestricted.

The optional admission_policy_fingerprint is a 64-character lowercase hexadecimal digest of the resolved provider policy at successful authentication. KeepPeek updates it after ordinary or replacement-Administrator authentication. An absent or stale digest cannot count as a retained Administrator path; the identity must authenticate under the current policy first. Legacy records remain readable and still trigger lockout protection. Raw subjects, claims, and tokens are not stored.

Camera defaults and camera entries

Source: camera types and the camera loader in configuration.

Type: CameraCredentialDefaults, section [camera_defaults].

FieldTypeDefault or inheritance
usernameStringEmpty string; accepts secret references.
passwordStringEmpty string; accepts secret references.
backendOptional CameraBackendNo shared override; camera model defaults to "auto".
transportOptional CameraTransportNo shared override; camera model defaults to "tcp".
record_generic_motion_eventsOptional boolNo shared override; camera model defaults to false.
recording_modeOptional CameraRecordingModeNo shared override; camera model defaults to "event-boost".
event_recording_duration_secsOptional u64No shared override; camera model defaults to 60 seconds.
event_pre_recording_duration_secsOptional u64No shared override; camera model defaults to 0; range 0..30 seconds.
event_recording_streamOptional EventRecordingStreamNo shared override; camera model defaults to "main"; "main" or "sub".

Username and password fall back to their shared defaults when the per-camera resolved value is empty, including when the field is omitted. The other shared defaults apply only when the per-camera field is absent. Ports and RTSP URLs are not fields of CameraCredentialDefaults.

Type: CameraConfig, section [<namespace>.<camera-key>], usually [cameras.front_door].

Each camera table also contains the server-managed profile_identity string. It is a unique, canonical, 36-character UUID v4 assigned at startup or when the camera is created. It has no operator-supplied default and does not accept secret references. The configuration owner preserves it across ordinary camera edits; changing the IP in the same saved entry keeps profile references. Deleting the camera and adding another at the same IP assigns a new identity. Saved profile targets for the deleted identity remain unavailable until an Administrator explicitly removes or retargets them. Other camera controls and media source IDs continue to use their existing IP identities.

Camera ip supports references such as {secret:CAMERA_IP}; the resolved value must be an IP address. Identity migration, the profile identity map and camera definitions use the same loaded disk secrets.toml snapshot. The raw reference stays in config.toml after migration instead of being replaced by the resolved address. Editing the secrets file does not change an already loaded configuration snapshot; the next configuration load uses the updated address and preserves the camera’s saved profile_identity.

On upgrade, KeepPeek assigns identities and converts existing IP-based profile references in one atomic configuration write before media starts. A current, unambiguous target becomes its saved identity. An absent target becomes an unavailable missing-ip:<address> reference that cannot bind to a later device at that address. Changed profile and registry revisions advance once, so stale clients must reload. Invalid or duplicate UUIDs and ambiguous active targets fail validation without writing the configuration. Keep the identity metadata when exporting or restoring the full configuration; do not edit it manually.

A migrated [runtime_profiles] section carries server-managed identity_schema = 1. Keep it with the profile definitions. Earlier KeepPeek binaries reject this section rather than starting with UUID targets that they cannot apply; restoring an older binary requires restoring a compatible pre-migration configuration as well. Unknown identity schema versions fail validation.

FieldTypeDefaultMeaning
ipIpAddrRequiredCamera IP address, not a DNS hostname.
nameOptional StringCamera table keyThe loader replaces this value with the table key. Use display_name to change the UI label without changing storage identity.
display_nameOptional StringFalls back to nameHuman-readable camera label.
manufacturerOptional StringNo overrideNonempty trimmed value takes precedence over discovery.
usernameStringShared default or emptyCamera username; literal or secret reference.
passwordStringShared default or emptyCamera password; literal or secret reference.
onvif_portOptional u168000 when unsetONVIF service port. Typed configuration operations require 1..65535.
http_portOptional u1680 when unsetDirect camera HTTP control port; typed operations require 1..65535.
main_rtsp_urlOptional StringDiscover streamExplicit main-stream RTSP URL takes precedence over ONVIF discovery. Blank values are not explicit URLs.
sub_rtsp_urlOptional StringDiscover streamExplicit sub-stream RTSP URL. Use URL-escaped references in credential components.
uidOptional StringNoneReolink P2P UID for direct BCUDP discovery.
backendCameraBackendShared default or "auto""auto", "retina", or "reo-proto".
transportCameraTransportShared default or "tcp""tcp" or "udp".
record_generic_motion_eventsboolShared default or falseOpt in to supported generic motion events.
recording_modeCameraRecordingModeShared default or "event-boost""off", "sub", "main", "both", "event-boost", or "event-only".
event_recording_duration_secsu64Shared default or 60Event-triggered recording duration; typed operations and TOML loading for event-only or pre-roll-enabled event-boost require 1..3600 seconds.
event_pre_recording_duration_secsu64Shared default or 0Range 0..30 seconds; 0 disables pre-roll allocation.
event_recording_streamEventRecordingStreamShared default or "main""main" or "sub"; selects the event-only recording stream. EventBoost always boosts to main.
eventsEventConfigDefaults belowCamera-native event policy, independent of video backend; configured through the file.

These are stored configuration types, not promises that every camera supports every backend, transport, or recording capability. Discovery results such as CameraCapabilities, CameraPorts, MediaProfile, VideoConfig, and AudioConfig are not additional editable camera tables.

Native camera events

Type: EventConfig, section [<namespace>.<camera-key>.events], for example [cameras.front_door.events]. Source: camera event policy. These settings are not part of CameraCredentialDefaults and are not exposed by the fixed protobuf camera-settings form. Ordinary camera API edits preserve the saved event table.

FieldTypeDefaultValues and limits
modeEventMode"auto""auto", "vendor", "onvif-pullpoint", "rtsp-metadata", or "disabled".
metadata_streamMetadataMode"auto""auto", "enabled", or "disabled". Enabling metadata does not override vendor or disabled event mode.
event_service_urlOptional StringNone; discover serviceHTTP(S) URL of at most 4,096 UTF-8 bytes using the exact configured camera IP. No hostname alias, wildcard/multicast address, port zero, credentials, query, fragment, backslash, whitespace, or control character.
source_tokensVec<String>[]At most 32 exact opaque tokens, each at most 256 UTF-8 bytes. Empty or control-character entries are rejected.
include_topicsVec<String>[]Expanded namespace root plus slash-separated local path. Up to 32 filters combined with exclusions, each at most 1,024 UTF-8 bytes.
exclude_topicsVec<String>[]Same syntax and shared count/byte limits as inclusions; exclusions take precedence.
snapshotsbooltrueAllow optional camera snapshots; a missing or failed image does not discard the event.

An empty inclusion list permits recognized topics. An example filter is {http://www.onvif.org/ver10/topics}VideoSource/MotionAlarm; unstable document prefixes such as tns1:VideoSource/MotionAlarm are rejected. Source tokens retain their exact text. Filters require nonempty namespace and path segments without whitespace, embedded prefixes, or control characters.

The loader resolves supported {secret:KEY} and {secret:KEY|url} references in event strings before validation. Preserve those references in config.toml and keep reusable private values in secrets.toml; URL escaping does not relax endpoint restrictions. Direct file edits require the existing configuration activation/restart path; there is no automatic file watcher. Generic motion recording still requires record_generic_motion_events = true, independently of event mode. The native event operator guide describes transport selection, health evidence, and verification.

Recording storage

Type: StorageToml, section [storage]. Optional paths are resolved by the runtime when absent; inspect the effective settings instead of assuming an omitted path is the current directory.

Both recording directories default to recordings/ under the default OS KeepPeek configuration directory, even when --config selects a configuration file elsewhere. An omitted catalog path becomes recordings.db under the effective long-term directory; the thumbnail directory defaults to .event-thumbnails under that same directory. See StorageConfig::from_toml.

FieldTypeDefaultMeaning
medium_term_pathOptional StringRuntime-selectedMedium-term recording directory.
long_term_pathOptional StringRuntime-selectedLong-term recording directory.
recording_catalog_pathOptional StringRuntime-selectedRecording catalog path.
event_thumbnail_pathOptional StringRuntime-selectedEvent-thumbnail directory.
event_thumbnail_max_mbu641024Thumbnail storage budget, in MiB.
short_term_secsu64120Short-term retention, in seconds.
medium_term_secsu641800Medium-term retention, in seconds.
flush_interval_secsu6460Recording flush interval, in seconds.
write_buffer_bytesusize8192Recording write-buffer size, in bytes.
long_term_max_gbu641024Long-term storage budget, in GiB.
minimum_free_gbu6410Minimum-free-space safety threshold, in GiB.
maximum_used_percentOptional u8No percentage thresholdWhen set, 1..99; filesystem-used percentage threshold.
warning_free_gbu6420Warning-free-space threshold, in GiB.
critical_free_gbu6410Critical-free-space threshold, in GiB.
cleanup_hysteresis_gbu645Cleanup recovery margin, in GiB.
pre_recording_stream_max_bytesu6467108864 (64 MiB)Positive encoded-byte limit per candidate stream; must not exceed the global limit.
pre_recording_global_max_bytesu64268435456 (256 MiB)Positive encoded-byte limit across all candidate streams, including pending replay; must fit the platform address space and TOML signed 64-bit integer range.

The effective critical threshold is the greater of critical_free_gb and minimum_free_gb. If warning_free_gb is zero while the effective critical threshold is nonzero, the warning threshold is derived as critical plus hysteresis. Otherwise, warning must be at least critical. Effective media paths come from named owners, and catalog paths come from the metadata binding. Use named-volume move controls for stored objects and the confirmed metadata restart workflow for the catalog. The general storage editor displays these paths read-only.

These values are policy defaults, not a capacity guarantee. Storage budgets and free-space thresholds must suit the actual disks and camera bitrates. A configuration ZIP preserves the target’s existing storage paths on apply; it does not move a recording archive. See storage migration and recording archive recovery.

event_thumbnail_max_mb applies to catalog-referenced image files in the configured thumbnail directory. Unindexed images and temporary files are excluded from this quota and are not removed at startup. Missing image files retain their catalog references so that restoring an unavailable thumbnail directory does not require rebuilding event metadata.

Recording retention rules

Optional [storage.retention] rules set independent recording lifetimes in days. Without this section, recordings keep the existing capacity-based cleanup behavior.

[storage.retention.default]
continuous_days = 1.0
motion_days = 7.0

[storage.retention.default.events]
person = 30.0

[storage.retention.cameras."192.0.2.10"]
continuous_days = 0.0

default supplies global rules. Each cameras entry overrides rules for the exact catalog camera identity, which is the camera IP address for RTSP recordings. Omitted fields inherit defaults; explicit zero disables that rule. Event entries use exact canonical event kind strings. Motion rules match canonical motion events. Stream-specific events apply to their matching logical recording stream; events without a stream apply to all recording streams for that camera.

Durations must be finite, nonnegative, and representable as whole milliseconds within a signed 64-bit lifetime. Settings allow at most 127 camera overrides and 16 effective rules per camera. Event selectors contain 1–128 ASCII letters, digits, underscores, hyphens, or periods. Unknown fields are rejected. Numeric durations do not accept secret references.

The longest matching rule sets a whole-file deadline from the recording end. Files can therefore retain unrelated footage beside a matching event. Protection blocks automatic deletion. Shorter settings, removed rules, or corrected events never shorten an already committed deadline. Files whose event evidence exceeds the bounded evaluation limit remain excluded from automatic cleanup until their evidence can be evaluated successfully.

Apply file edits through the existing configuration activation/restart workflow; there is no file watcher or separate retention settings file. Activation persists a bounded catalog sweep and fences new cleanup admissions until evaluation finishes. Finalized recordings and event changes then receive automatic reevaluation. Expiry removes eligible whole files even when storage has spare capacity. Interrupted cleanup resumes through the existing storage owner.

To diagnose activation, inspect structured log events retention_started and retention_activation_ready, correlated by run_id. The first reports whether retention is enabled and the settings request was accepted; the second reports whether initial evaluation encountered quarantined files. Per-file evaluation warnings identify the recording and cause. Expiry logs identify removed recordings and whole-file bytes. These answer whether activation finished, which evidence needs repair, and whether expiry made progress. The worker logs no serialized configuration or event payloads.

Named storage volumes

[storage.named_volumes] stores volume and placement definitions in config.toml. A fresh installation creates four private roots beneath storage/ beside the configuration: media for active and archived recordings, exports, images for thumbnails, and metadata for the catalog and export history. Global placement rules use these roots without fallback. The initial media and image caps use long_term_max_gb and event_thumbnail_max_mb; after initialization, edit each volume’s byte cap directly.

Saved volume and placement changes take effect after restart and affect future placement. Existing objects keep their catalog owner. Create custom root directories before enabling them. A missing bound root stays unavailable; startup does not recreate it or redirect writes.

The optional section has two arrays: volumes (at most 32) and placement (at most 256). Both default to empty. Ordinary settings updates that omit named_volumes preserve the section. An explicit empty section requests removal, subject to ownership and metadata checks. Unknown fields in this section and its entries are rejected.

Administrator runtime settings carry these definitions in the optional protobuf named_volumes field. Updates that include it require the current configuration revision. String fields, including IDs, roots, selectors, and candidate IDs, support existing secret references. Validation uses resolved values; saved settings and responses retain the references.

Administrators can edit volumes under Settings → Storage → Named storage volumes. Byte limits use exact whole-byte values. Refreshing status or a failed save preserves unsaved inputs. Removing a saved volume definition requires confirmation. Root probes inspect existing configured roots without creating directories; a successful probe does not establish write permission. Individual move preview, confirmation, status, and cancellation appear only when a volume runtime is available. The same runtime controls let administrators stop new writes to a volume or clear that operator drain, after confirmation. Already admitted writes finish normally. Clearing an operator drain does not override a draining or read-only saved configuration. Operator drain survives restart and does not itself move existing media.

The move controls preview up to 16 objects from the loaded page and show exact file and byte totals before confirmation. Each confirmed object has a durable move job. Stop dispatch after the current request or cancel admitted work under Move jobs. Refresh jobs to inspect progress; refresh the object page after completed batches. A lost confirmation reply is checked against the same job identity before further work is admitted.

Removing a bound definition requires stopped writes, zero owned and reserved bytes, and completed cleanup receipts. An offline enabled runtime must be stopped through operator drain, or disabled and restarted before removing an unbound definition. Remove archive-policy references and restart before removing their destination; already captured archive work must finish first. Removal keeps the catalog’s immutable binding and completed history and does not delete files. The removal check inspects up to 1,024 pending archive policies; finish a larger backlog before retrying removal.

[storage.metadata] is a server-managed binding, not an ordinary placement draft. It contains volume_id, catalog_file, history_file, catalog_id, generation, filesystem, and root_identity. The filenames use one shared canonical UUID with catalog-<uuid>.db and exports-<uuid>.json. The catalog ID is a UUID, generation is 1 through i64::MAX, and both filesystem identity strings contain 1–256 bytes without control characters. The volume must exist, have the metadata role, and be enabled; a separate recording_catalog_path override is incompatible with this binding. Startup verifies the saved directory and catalog authority. It does not create an empty replacement catalog if the selected owner is unavailable.

Ordinary settings cannot install, clear, disable, or relocate the metadata owner. Unrelated updates preserve its existing secret references. Use the administrator metadata preview and confirmation controls to schedule relocation; do not construct or edit this binding manually. The internal [storage.metadata_pending] restart record contains a 32-byte source-configuration digest and the target binding. It retains the previous effective paths until a stopped transfer verifies both the catalog snapshot and copied export history, then atomically commits the new binding. Failed or interrupted transfers retain their source files and pending state for retry. Pending handoffs reject effective storage-setting changes while allowing unrelated settings saves. Startup rechecks available space, the byte cap, and the larger minimum/critical free-space reserve before fencing the source. Managed export history must already exist, fit within 8 MiB, and contain valid, uniquely owned jobs; missing or invalid history is preserved and reported unavailable.

In Settings, add and save a disabled volume with only the Metadata role, then use Preview metadata move to check its root and required space. Confirm metadata move saves the plan without interrupting recording. The service restart copies the latest catalog and export history before workers resume. Cancel a pending plan before restarting if needed. Returning to an earlier metadata volume uses the same workflow and a new catalog generation; never point the service at a retained old catalog. An interrupted startup leaves the plan available for retry after its reported storage problem is corrected.

Each [[storage.named_volumes.volumes]] entry has these fields:

FieldTypeDefaultMeaning
idStringRequired1–64 lowercase ASCII letters, digits, _ or -; legacy- is reserved.
rootPath stringRequiredAbsolute UTF-8 directory path, at most 4096 bytes and 64 components; no traversal, control characters, duplicate or nested roots. Windows requires a local drive path and rejects device names and reserved characters.
rolesRole arrayRequired1–5 distinct values: active, archive, export, thumbnail, metadata. Only one bound metadata owner is active; other metadata-capable volumes can be relocation targets.
stateStateenabledenabled admits new writes; read_only and draining retain reads but stop new writes; disabled does not open the root.
priorityu160Smaller values rank first for priority placement.
capacity_bytesOptional u64No capPositive owned-data byte cap.
minimum_free_bytesu640Minimum filesystem free space to retain.
critical_free_bytesu640Placement retains the larger of minimum and critical free space.
warning_free_bytesu640Must be at least both minimum and critical free space.
sources, groupsString arraysEmptyAt most 256 unique exact selectors each, 1–256 bytes without control or surrounding whitespace. Empty lists allow all sources. When either list is populated, matching either a source or a group permits placement. Metadata volumes cannot restrict sources or groups.

All byte thresholds must fit a signed TOML 64-bit integer. A root can use the existing {secret:KEY} or {secret:KEY|url} resolver; unchanged root references are preserved during settings updates. Paths are redacted from the volume model’s debug output. Lexical validation does not establish filesystem identity, writability, or protection against path replacement.

On Windows, owned storage requires local NTFS volumes with persistent ACLs and directory metadata flushing. ReFS, FAT, and network shares are not qualified for owned writes or deletion; they remain unavailable rather than bypassing these checks. Keep the configuration directory on NTFS when using the automatically created default volumes. An unavailable bound root is never recreated.

Each [[storage.named_volumes.placement]] entry contains:

FieldTypeDefaultMeaning
roleRoleRequiredOne of the roles above.
source, groupOptional stringAbsentSelect either a source or a group, using the same string limits as volume allowlists. Absence selects the role default.
candidatesVolume ID arrayRequired1–8 distinct configured volumes supporting the role.
strategyStrategyprioritypriority ranks smaller priorities first; free_space ranks usable free bytes after reserve and cap limits. Ties use volume ID order.
allow_fallbackBooleanfalseFalse limits selection to the first candidate. True permits ranking all eligible candidates in the explicit pool.

Source rules take precedence over group rules, which take precedence over role defaults. A failed override never falls through to a broader rule. Duplicate selectors for a role are invalid. Metadata placement requires one global candidate and forbids fallback. Selection is a pure proposal; it does not reserve capacity, authorize writes, or alter existing object locations.

Source: named-volume model. Windows path validation follows the Windows filename rules.

Fourth-tier local archive

Optional [storage.archive] configures one verified local replica destination. Omitting it disables this tier. It is separate from named-volume archive placement, which relocates the authoritative recording. A verified fourth-tier copy retires its primary only through an enabled eviction policy or an Administrator’s confirmed per-job action. Local recording, live viewing and the first three tiers do not wait for archive availability.

[storage.archive]
target_id = "local-archive"
root = "{secret:ARCHIVE_ROOT}"
enabled = false
automatic = false
source_ids = []
minimum_age_secs = 0
evict_after_archive = false
maximum_object_bytes = 4294967296
maximum_pending_count = 256
maximum_pending_bytes = 68719476736
operation_timeout_secs = 120
maximum_attempts = 3
FieldTypeDefaultValidation and meaning
target_idStringRequiredStable ID with 1–64 lowercase ASCII letters, digits, hyphens or underscores; legacy- is reserved.
rootPathBufRequiredAbsolute local destination; at most 4,096 UTF-8 bytes and 64 path components. It must already exist when used. Named-volume path safety rules apply.
enabledboolfalseEnable archive work; configuration alone does not bind or create the destination.
automaticboolfalseAdmit eligible finalized recordings automatically when the archive worker is enabled.
source_idsVec<String>EmptyAutomatic-selection filter; empty selects all sources. At most 127 distinct, nonblank IDs, each at most 256 UTF-8 bytes without control or surrounding whitespace.
minimum_age_secsu640Minimum finalized recording age for automatic selection, at most 315,360,000 seconds (ten 365-day years).
evict_after_archiveboolfalseExplicitly permit policy-driven primary-copy eviction after verification; holds, leases and durable archive indexes still apply.
maximum_object_bytesu644,294,967,296Per-object admission limit, from 1 byte through 64 GiB; cannot exceed the pending-byte limit.
maximum_pending_countu32256Retained pending jobs, from 1 through 4,096.
maximum_pending_bytesu6468,719,476,736Pending-byte admission ceiling, positive and at most i64::MAX.
operation_timeout_secsu64120Finite archive operation deadline, from 1 through 3,600 seconds.
maximum_attemptsu323Maximum attempts for one durable job, from 1 through 10.

Roots cannot overlap named-volume roots or legacy media, thumbnail and metadata directories. Lexical checks do not establish filesystem identity; the bounded owned-object write/read/delete probe and persisted catalog binding detect offline or replaced destinations without recreating their roots. Keep the stable target ID with its binding; changing a configured path must not adopt an old manifest under a different root identity.

Destination availability requires a successful verified operation. Opening or binding the root alone does not establish availability; test the destination after startup and refresh its result.

String settings support existing secret references. Validation uses resolved values; ordinary settings rewrites and configuration export/import preserve raw references and unrelated sections. Older runtime settings updates that omit archive settings preserve the accepted archive section. Unknown archive fields are rejected; cloud provider selectors and credentials are not supported by this local implementation. Job journals, manifests, root bindings and integrity metadata are operational catalog state, not extra settings files.

Archive playback reuses the timeline and normal playback controls. Initial access and seeking may take longer than the first three tiers because target access and integrity checks can add work. Available local archive files can play directly without a full-file restore; unavailable or corrupt objects remain explicit errors. Restoring or removing a primary copy preserves the original media identity and archive timeline indexes.

Battery wake

Type: BatteryWakeConfig, section [battery_wake]. This is the Reolink wake middleman service, not a general Wake-on-LAN or scheduled privacy configuration.

FieldTypeDefaultValidation and meaning
enabledboolfalseEnable the wake service.
bindOptional Ipv4AddrAutomaticLocal IPv4 binding selection.
middleman_portu169999Nonzero middleman port.
register_portu1658200Nonzero registration port; must differ from the middleman port.
heartbeat_secsu6420Nonzero heartbeat interval.
stale_after_secsu6480Must cover at least one heartbeat interval.

Logging

Type: LoggingConfig, section [logging].

FieldTypeDefaultMeaning
serviceServiceLogDestination"file""file" or "event_log"; service logging destination, with Event Log relevant to Windows service operation.

This section does not define arbitrary tracing filters, log payloads, or a diagnostics archive.

The existing diagnostic log-filter control uses a separate sibling file named log-filter, not another TOML section. The logging service saves and reloads that filter atomically. At startup a valid saved filter takes precedence over RUST_LOG; the built-in fallback is info,keeppeek=debug. A bad saved or environment filter is reported and falls back to the next usable source. This diagnostic setting is outside the two-file configuration ZIP. Preserve it with the configuration directory if you need it during a recovery rehearsal. Source: logging service.

Operational events

Type: OperationalEventsConfig, section [operational_events].

FieldTypeDefaultMeaning
warning_hold_down_secsu6415Delay before recording a warning transition.
outage_hold_down_secsu6460Delay before recording an outage.
recovery_debounce_secsu6410Stable-recovery debounce period.
record_short_flapsboolfalseRetain supported brief-flap transitions.
camerasMap of OperationalEventOverrideEmptyOverrides keyed by stable camera ID, with camera IP as fallback.

Type: OperationalEventOverride, section [operational_events.cameras."<camera-id-or-ip>"].

FieldTypeWhen absent
warning_hold_down_secsOptional u64Inherit global warning hold-down.
outage_hold_down_secsOptional u64Inherit global outage hold-down.
recovery_debounce_secsOptional u64Inherit global recovery debounce.
record_short_flapsOptional boolInherit the global flag.

Each effective policy requires warning hold-down no greater than outage hold-down. Outage and recovery durations cannot exceed 86400 seconds. Override keys contain 1 to 256 bytes after the nonempty check. The stable camera ID takes precedence over an IP-keyed override.

Privacy schedules

Type: PrivacyConfig, section [privacy]. The optional [privacy.default] schedule is inherited by cameras without a per-camera entry. Per-camera schedules are keyed by stable camera ID; an IP-keyed entry is accepted for migration and the runtime maps it to that camera ID.

Types: PrivacySchedule, sections [privacy.default] and [privacy.cameras."<camera-id-or-ip>"].

FieldTypeDefaultMeaning
enabledbooltrueRetain the policy but disable its scheduled enforcement when false.
timezoneStringRequiredIANA timezone used for recurring local windows.
windowsArray of PrivacyWindowEmptyHalf-open weekly privacy windows; at most 64.
temporary_overrideOptional PrivacyOverrideNoneBounded persisted override that suspends matching windows.
keep_camera_connectedbooltrueKeep camera ingress warm while delivery is blocked.

Each PrivacyWindow has weekdays (ISO values 1 through 7), start, and end in HH:MM. Equal endpoints are rejected; an end earlier than the start is an overnight interval. Both occurrences of a repeated daylight-saving local time are protected. A PrivacyOverride requires nonempty actor and reason, RFC3339 accepted_at and expires_at, and expires within 24 hours. Control-channel configuration requests replace the override actor and acceptance time with the authenticated administrator and server time before the candidate configuration is validated. Invalid privacy policy data fails validation before the server accepts camera media.

MQTT event forwarding

Types: EventForwarderConfig has one field, mqtt: MqttForwarderConfig. Section: [event_forwarder.mqtt]. Source: MQTT configuration.

FieldTypeDefaultValidation and meaning
revisionu641Server-managed settings revision.
enabledboolfalseEnable forwarding.
broker_urlString"mqtt://127.0.0.1:1883"mqtt or mqtts URL with host; no user-info, non-root path, query, or fragment.
client_idString"keeppeek"Broker client ID.
instance_idString"home-nvr"KeepPeek instance identity.
forwarder_idString"mqtt"Forwarder identity.
topic_prefixString"keeppeek"1 to 512 bytes; no leading/trailing slash, NUL, +, or #. Interior slashes are allowed.
usernameOptional StringNoneBroker username, including supported references.
passwordOptional StringNoneRequires username; prefer a reference to the companion secret file.
tls_ca_pathOptional PathBufSystem trustCustom CA path; only valid with mqtts.
qosu810, 1, or 2.
retain_eventsboolfalseRetain event publications.
retain_healthbooltrueRetain health publications.
outbox_max_mbu64641 to 65536 MiB of bounded outbox storage.
retry_min_msu64250Nonzero retry minimum, no greater than maximum.
retry_max_msu6430000Retry maximum, at most 3600000 ms.

Client, instance, and forwarder IDs each contain 1 to 128 bytes without NUL, /, +, or #. Do not embed credentials in broker_url; use the dedicated fields. MQTT’s bounded outbox and deduplication state are in memory and reset on restart. outbox_max_mb is a runtime memory budget; it does not enable persistence. The retired mqtt-forwarder.db is removed during startup and is not replayed. See Notifications and integrations.

Credential records

Source: access persistence. Use the Administrator credential controls, not hand-written verifiers or revision changes.

Type: PersistedAccessCatalog, section [access_credentials].

FieldTypeRule
versionu32Required; supported value is 1.
credentialsVec<StoredCredential>Required array, at most 128 records with unique IDs.

Type: StoredCredential, array [[access_credentials.credentials]].

FieldTypeRule
idUuidStable named-credential identity.
nameString1 to 64 printable bytes after trimming.
descriptionOptional StringAt most 256 printable bytes; empty descriptions normalize to absent.
roleAccessRole"administrator" or "user".
camera_accessOptional CameraAccessPer-user group and camera policy. Absent is unrestricted; new credentials explicitly default to everything.
verifierAccessKeyFingerprintSHA-256 fingerprint serialized as 32 byte-valued integers, not the raw access key. Treat it as sensitive authentication material.
created_at_msi64Creation time, Unix milliseconds.
rotated_at_msOptional i64Last rotation time; absent if never rotated.
last_used_at_msOptional i64Accepted by the stored record type for compatibility, but cleared on load and omitted by the canonical writer. Activity is runtime state.
expires_at_msOptional i64Expiry time; expired credentials are denied.
disabledboolDisabled credentials cannot authenticate.
revoked_at_msOptional i64Permanent revocation time.
revisionu64Nonzero credential revision used to invalidate stale sessions.
legacyboolCompatibility initial-credential marker.
initial_secret_pendingboolInitial-key retrieval is still pending.

The runtime AccessCatalog.audit array is not part of PersistedAccessCatalog. Audit history, session state, and credential last-use activity reset on restart; credential identities, verifiers, roles, revisions, and lifecycle metadata persist. The legacy access.toml is a migration input, not an additional active settings file.

Type: CameraAccess, nested table belonging to the credential record, not to a camera. The all_cameras and camera_ids fields are required when the policy is present; group_ids defaults to an empty list for compatibility. Unknown fields are rejected.

FieldTypeRule
all_camerasboolExplicitly grant all current and future cameras. Must not be combined with a nonempty ID list.
group_idsVec<String>Camera configuration namespaces, such as outdoor for [outdoor.front_door]. Defaults to empty. At most 128 unique, nonblank names of at most 256 UTF-8 bytes without control characters.
camera_idsVec<String>At most 128 unique, nonblank IDs of at most 256 UTF-8 bytes without control characters. Use server-advertised camera IDs, not names.

With all_cameras = false, access is the union of cameras in group_ids and explicit camera_ids; both lists empty grants nothing. all_cameras = true requires both lists empty and includes future groups and cameras. New credentials and absent policies default to everything. Existing explicit restrictions are not broadened on upgrade. Administrators always have full access and cannot be restricted through this policy. There is no secret substitution inside grant records.

The Administrator’s user-access editor validates camera and group IDs against the current fleet and saves under the shared configuration lock. Each save increments the credential revision, rejects stale writes, and invalidates existing sessions. Grants survive configuration reload and export/import. Group grants are saved as group names, not expanded camera lists; effective access is resolved from the server’s camera group membership. available_group_ids in management responses is bounded discovery metadata for the editor, not a persisted field. The editor supports up to 128 available groups using the same ID limits; an invalid or oversized inventory rejects management requests before any policy mutation. Runtime group-membership changes close affected group-scoped sessions and cancel their queued work so playback and event subscriptions cannot retain an obsolete grant. Grid audiences do not grant camera permissions. Custom roles remain unsupported, and trusted-local clients still follow the Administrator policy described above.

Peek layout registry

Source: layout persistence. Layouts use the existing main configuration file and the keeppeek.peek-layouts.v1 capability. Use the layout controls and revision-checked operations rather than manually constructing this server-managed section.

Type: StoredRegistry, section [peek_layouts].

FieldTypeRule
schema_versionu32Required; supported value is 1.
revisionu64Nonzero registry revision. Stale writes conflict.
shared_layoutsVec<Layout>Nonempty server-owned layout array.
usersMap of StoredUserRegistryKeyed by principal ID; holds per-user active selection and legacy private layouts.

Type: StoredUserRegistry, section [peek_layouts.users."<principal-id>"].

FieldTypeRule
active_layout_idStringSelected layout identity. Missing selections are repaired by the registry.
layoutsVec<Layout>Legacy private layout records; migrated to server-owned shared layouts with restricted audiences when opened.

Type: Layout, under shared_layouts[] or legacy users.<principal-id>.layouts[].

FieldTypeRule
idStringUnique within the registry view; nonblank, at most 128 characters.
nameStringNonblank, at most 80 characters.
scopeLayoutScope"shared" for canonical layouts; "private" is retained for migration.
owner_idString"server" for shared layouts; private records must match their principal.
audienceLayoutAudienceDefaults to everyone with an empty credential list when the entire field is absent.
activity_focusboolWhether activity can affect layout focus.
displayOptional DisplaySettingsDashboard-owned display settings; absent objects use defaults. Older client writes that omit the object preserve saved values.
tilesVec<LayoutTile>Ordered tiles, at most 64 per layout; no duplicate camera IDs or overlaps.

Type: DisplaySettings, nested display table. The dashboard editor writes the complete object with the existing registry revision. Only Administrators can change it. A User can still change their selected dashboard but cannot alter its display settings. No field resolves secret references.

FieldTypeDefaultValidation and meaning
versionu321Required in present objects; only version 1 is supported.
tile_shapeTileShape"16:9""16:9", "4:3", or "native".
media_fitMediaFit"contain""contain" or "cover"; never stretch.
streaming_modeStreamingMode"smart""smart" or "continuous".
stream_limitu3212Whole number from 1 through 12; each browser also enforces its own capacity.
keep_awakeboolfalseSaved wake-lock intent; browser gesture, visibility, permission, and device policy still apply.
gap_pxu3210Whole pixels from 0 through 24 between tile slots.
corner_radius_pxu3210Whole pixels from 0 through 24; zero gives square video corners.

Display objects reject unknown fields, positional arrays, invalid types, and out-of-range values. The All cameras dashboard keeps immutable generated coordinates and identity, but its display object can be edited and survives camera-inventory synchronization. Preset names are derived from gap/radius values and are not an additional stored field.

Type: LayoutAudience, nested audience table.

FieldTypeRule
everyoneboolWhen true, credential_ids must be empty.
credential_idsVec<String>At most 128 unique canonical UUID strings. Save operations check that viewer identities exist. Empty with everyone = false gives no ordinary user access; Administrators retain management access.

Type: LayoutTile, nested tiles[] array.

FieldTypeRule
camera_idStringStable camera identity, not display name.
columnu32One-based grid column.
rowu32One-based grid row.
column_spanu32Positive horizontal span; tile must fit the 12-column grid.
row_spanu32Positive vertical span; tile must fit the 12-row grid.
pinnedboolTile pinning state.

The registry has a 256 KiB serialized bound and at most 32 layouts per validated view. Stored camera references are retained when a camera disappears; additions are validated against known cameras. The initial registry has revision 1 and an All cameras shared layout with ID default, activity focus enabled, and up to 64 configured cameras. Opening the store also migrates the legacy peek-layouts.json; that file is not an active persistence target.

The client-facing LayoutRegistry contains schema_version, active_layout_id, and layouts. It is not the on-disk StoredRegistry; do not paste a client exchange document over the main configuration. Grid audiences control grid visibility only. They must not be treated as permission to view, export, or control an otherwise restricted camera.

The server intersects a User’s returned tiles with their camera grants. Selecting a dashboard from that filtered view updates only the principal’s active selection; it does not remove other cameras from the canonical layout. Unknown or no-longer-visible dashboard selections are rejected.

Camera templates

Source: configuration templates. Templates are versioned, server-managed configuration records, not another settings file.

Type: StoredTemplateDocument, section [configuration_templates].

FieldTypeRule
document_versionu32Required; supported value is 1.
templatesVec<StoredTemplate>Required array, at most 64 templates. Missing section means an empty document.

Type: StoredTemplate, array [[configuration_templates.templates]].

FieldTypeRule
template_idStringUnique ID, 1 to 64 ASCII letters, digits, hyphens, or underscores.
versionu64Server-managed template version.
nameStringNonblank, at most 128 bytes, no control characters; names are unique ignoring case.
descriptionStringDefaults to empty and is omitted when empty; at most 1024 bytes, no control characters.
valuesStoredTemplateValuesAt least one supported setting must be present.
created_at_msi64Creation time, Unix milliseconds.
updated_at_msi64Last update time, Unix milliseconds.

Type: StoredTemplateValues, nested values table. Every field is optional and omitted when unset.

FieldTypeRule
username_secret_referenceStringA complete valid secret reference, not an inline username.
password_secret_referenceStringA complete valid secret reference, not an inline password.
onvif_portu161..65535.
http_portu161..65535.
backendCameraBackend"auto", "retina", or "reo-proto".
transportCameraTransport"tcp" or "udp".
record_generic_motion_eventsboolGeneric motion-event preference.
recording_modeCameraRecordingMode"off", "sub", "main", "both", "event-boost", or "event-only".
event_recording_duration_secsu321..3600 seconds.
event_pre_recording_duration_secsu320..30 seconds.
event_recording_streamEventRecordingStream"main" or "sub".

The template writer enforces a 16 KiB serialized document limit. The legacy configuration-templates.json is imported into this section and removed. Template references stay as tokens in the stored template; applying a template must validate the resulting camera settings.

Notification rules

Sources: rule persistence, rule model, and cooldowns.

Type: NotificationConfiguration, section [notifications].

FieldTypeDefault and bound
rulesVec<PersistedRuleRecord>[]; at most 128 rules, with unique rule IDs.

Type: PersistedRuleRecord, array [[notifications.rules]].

FieldTypeMeaning
idStringStable rule ID.
owner_idStringOwning principal ID.
activeOptional RulePublished rule; absent before first activation.
active_revisionu64Active revision used for conflict checks.
draftRuleSaved draft, which may be incomplete before activation.
draft_revisionu64Draft revision used for conflict checks.
created_at_msi64Creation time, Unix milliseconds.
updated_at_msi64Last update time, Unix milliseconds.

Rule identity, owner, and revisions must agree with the containing record. A draft is bounded to 64 KiB in its JSON representation. Activating it requires complete rule validation. Runtime RuleRecord.last_match_at_ms and last_delivery_at_ms are not persisted fields.

Type: Rule, under each record’s draft and optional active tables.

FieldTypeValidation or default
idString1 to 128 ASCII letters, digits, ., -, or _.
nameStringNonblank, at most 128 characters.
enabledboolRequired; disabled rules do not match events.
revisionu64Corresponding draft or active revision.
owner_idStringSame ID character and byte limits as id.
triggersVec<Trigger>Nonempty and unique; values below.
filterFilterDefaults to an empty filter.
scheduleScheduleRequired.
cooldownsVec<Cooldown>Defaults to []; scopes must be unique.
rate_limitsVec<RateLimit>Defaults to []; scopes must be unique.
critical_bypassOptional CriticalBypassBounded critical-event quiet-hours bypass.
enrichmentEnrichmentPolicyRequired.
actionsVec<Action>1 to 8 actions.
failureFailurePolicyRequired.

Trigger values are "event_created", "event_updated", "event_ended", "outage_started", "recovery", "storage_health", "recording_health", and "test".

Filters and schedules

Type: Filter, nested filter table.

FieldTypeDefault and rule
source_idsVec<String>[]; at most 128 nonblank values.
group_idsVec<String>[]; at most 128 nonblank values.
event_kindsVec<String>[]; at most 128 nonblank values.
zonesVec<String>[]; at most 128 nonblank values.
minimum_confidenceOptional f64Finite value in 0..1, inclusive.
attachment_requiredOptional boolRequire or exclude an available non-private attachment.
minimum_duration_msOptional u64Minimum matching event duration.
severitiesVec<Severity>[]; values "info", "warning", "critical".
reviewedOptional boolMatch the supplied review state.
bookmarkedOptional boolMatch the supplied bookmark state.

Empty filter arrays do not restrict that dimension. Filter fields describe matching inputs; their presence does not mean every camera or event producer supplies them.

Type: Schedule, nested schedule table.

FieldTypeDefault and rule
timezoneStringRequired recognized IANA timezone, such as "UTC".
active_windowsVec<WeeklyWindow>[], meaning no active-window restriction.
quiet_hoursOptional QuietHoursNo quiet-hours suppression when absent.

QuietHours has one required field, windows: Vec<WeeklyWindow>. WeeklyWindow fields are:

FieldTypeRule
weekdaysVec<Weekday>Nonempty and unique; "monday" through "sunday".
start_minuteu160..1439, local minutes after midnight.
end_minuteu160..1439, exclusive end; must differ from start.

An end earlier than the start describes an overnight window.

Cooldowns, limits, and delivery

All policy durations below are in milliseconds and must be between 1 and 2592000000 (30 days), inclusive, unless a tighter rule is stated.

TypeFieldsValidation
Cooldownscope: CooldownScope, duration_ms: u64Scopes: "event", "camera_event_kind", "group", "rule", "outage".
RateLimitscope: RateLimitScope, maximum: u32, window_ms: u64Maximum 1..10000; scopes "rule", "channel", "principal", "global".
CriticalBypassmaximum: u32, window_ms: u64Maximum 1..10.
EnrichmentPolicydeadline_ms: u64, maximum_revisions: u32, maximum_attempts: u32, maximum_attachment_bytes: u64, wake_after_deadline: boolRevisions 1..32, attempts 1..8, attachment bytes 1..4194304 (4 MiB). All fields required.
FailurePolicymaximum_attempts: u32, maximum_retry_interval_ms: u64, expiry_ms: u64Attempts 1..10. An enabled push action requires retry interval at least 5000 ms. All fields required.

Type: Action, nested actions[] array.

FieldTypeDefault and rule
enabledbooltrue.
channelChannel"browser", "push", "webhook", or "forwarder". Use channels advertised as available by the server.
destinationStringAt most 2048 bytes after resolution. Browser actions use the owner and require an empty destination. Push uses the Pushover destination format; webhook requires an HTTP(S) URL without user-info; forwarder requires a nonblank destination.
templateTemplateRequired title: String and body: String.
attachmentAttachmentPolicy"never", "when_available", or "required".
allow_second_deliveryboolRequired flag for staged delivery.

Template title and body limits are 256 and 4096 characters respectively; provider-specific limits can be tighter. Allowed {{field}} substitutions are source.id, source.name, event.id, event.kind, event.zone, event.confidence, event.duration, health.state, notification.stage, and notification.deep_link. These are notification template substitutions, not secret references.

Provider destinations can contain credentials, so the rule writer moves non-browser destination strings into the companion secret file and stores references. It keeps active and draft secret versions separate. Do not inline tokens into a book example or export them with a layout.

The legacy notifications.db is a rule-migration input. Delivery jobs, retry state, evaluation history, and counters are runtime state, not extra [notifications] settings. See notifications and integrations for provider setup.

Pending storage migration

Type: StorageMigration, section [storage_migration]. The server creates this journal for a validated move and consumes it during migration. Do not use it as an alternative storage editor.

FieldTypeMeaning
medium_termOptional StoragePathMigrationMedium-term recording move.
long_termOptional StoragePathMigrationLong-term recording move.
recording_catalogOptional StoragePathMigrationExplicit catalog move when not covered by a recording-root move.
event_thumbnailsOptional StoragePathMigrationExplicit thumbnail move when not covered by a recording-root move.
recording_catalog_after_moveOptional PathBufCatalog whose stored recording paths must be rewritten after moving roots.

Each StoragePathMigration has two required PathBuf fields: from and to. Paths must be nonempty, must not contain one another, and must satisfy the migration overlap checks. A single source cannot be split into conflicting destinations. StorageMigrationPaths is a runtime helper of borrowed paths, not another TOML section.

Compatibility and completeness

  • Preserve server-managed sections when editing ordinary settings. Serializing only Config would omit the separate credential, layout, template, and notification records.
  • The legacy access, layout, template, and notification files are migration inputs only. Do not create new instances of them for new settings.
  • There is no generic [users], [permissions], [groups], or persistent runtime-profile schema in this configuration. Do not invent those sections or assume a grid’s audience restricts camera access elsewhere.
  • Future layout and permission settings must extend the existing application configuration and its validation, atomic writes, and recovery coverage. Secret values remain in the existing companion secret store.
  • When a serialized struct, section name, default, limit, enum, or migration changes, update this reference in the same change and verify both load and write paths with synthetic fixtures.

For an upgrade from separate legacy stores, follow What startup migrates. For retention of runtime and recording data, see Backup and restore.

Event recording pre-roll

event-only stores only accepted event windows from the selected main or sub stream. event-boost keeps continuous sub recording and uses main quality during event windows. A nonzero event_pre_recording_duration_secs enables bounded encoded history for these modes. EventBoost then delays persistence by up to the selected duration so an event can replace pending sub coverage with main coverage. A crash can lose this uncommitted window. With zero pre-roll, existing recording modes keep their immediate admission behavior.

Changes persist in config.toml through the existing atomic writer. Camera changes use the existing source restart path when available; otherwise the response reports that a restart is required. Memory-budget changes require an application restart. Omitted updates retain existing values; cleared camera values inherit camera defaults. These settings contain no credentials. Existing supported secret references in camera credentials and URLs remain preserved. Whole-GOP eviction can shorten coverage under duration or memory pressure; health diagnostics report available coverage and the reason. Privacy, stream discontinuities and storage failures invalidate optional history.

Export and import configuration

Use a configuration ZIP to back up your settings, return to an earlier configuration, or transfer settings to another KeepPeek recorder. KeepPeek calls the import operation Apply configuration. You download one ZIP, upload that same ZIP to the target recorder, and restart to activate it.

This is a configuration backup, not a backup of your recordings or database.

Before you start

  • Connect to the source or target recorder as an Administrator. See Authentication and access control for access setup.
  • Export the target recorder’s current configuration before importing a different one. This gives you a ZIP you can apply later if you want to return to those settings.
  • Plan for a recorder restart when importing. Export does not require a restart or stop recording.
  • If you use remote access, make sure you can authenticate with the imported access configuration after restart. Its access credentials and camera/integration secrets replace the target’s values.

What the ZIP contains

Every export contains exactly two files at the root of the archive:

FileContents
config.tomlDurable settings, including cameras, storage policy, access credential metadata, layouts, templates, notification rules, and MQTT settings.
secrets.tomlThe file-backed secret values used by the configuration.

The ZIP contains plaintext secrets and is not encrypted. Restrict access to it as you would to camera passwords or access keys. Do not attach it to a public issue or share it in logs, and delete copies you no longer need. Values supplied only through environment variables are not exported; configure those separately on the target recorder.

The ZIP excludes recordings.db, recording media, thumbnails, sessions, caches, and notification or MQTT runtime work. Import keeps the target’s recording, catalog, and thumbnail paths. It neither restores nor moves the source recorder’s database or media. Other storage settings, such as retention policy, are part of the imported configuration and should be reviewed afterward.

Keep the ZIP as exported. The current format stores its versioned manifest and checksums in the ZIP comment, so there is no third file. Do not edit the TOMLs inside it, remove the comment, or repack the archive before importing. Apply accepts current format-3 exports, not arbitrary two-file ZIPs or older backup formats.

Export in Settings

  1. Open Settings, then Backup and restore on the source recorder.
  2. Choose Export ZIP.
  3. Keep the downloaded ZIP in a location with restricted access.

The download is the backup artifact. Export does not delete or replace either live TOML file, and KeepPeek does not retain a server-side copy or add it to a backup list.

Import in Settings

  1. Open Settings, then Backup and restore on the target recorder.
  2. Under Configuration ZIP, select the original exported ZIP. Do not extract it first.
  3. Select Replace config.toml and secrets.toml on restart.
  4. Choose Apply configuration and wait for the staged confirmation.
  5. Choose Restart to apply when you are ready for the recorder to restart.

Apply validates the archive, both TOML documents, target permissions, and available space before staging. A successful upload leaves the running configuration and live TOML files unchanged. The replacement takes effect during startup, before the configuration and database owners open.

Only one apply can be pending. Finish its restart and health verification before uploading another configuration. To return to earlier settings, use the same import steps with the ZIP you exported before the change.

Verify after restart

  1. Reconnect to the target recorder, using the imported access configuration if it changed.
  2. Confirm the expected camera definitions, notification rules, integrations, and storage policy.
  3. Check camera and stream health and confirm that the target’s existing recordings are available.
  4. Check the logs if startup or an integration fails. Environment-only secrets and external services must still be available on the target.

KeepPeek uses before-images and a crash-recovery journal to restore the prior configuration pair if configuration loading or startup health fails. Do not manually modify its journal or staging files. See Backup and restore for recovery details.

Use the CLI

The CLI performs the same export and apply operations as Settings. These examples connect to a recorder on the local machine:

keeppeek config --server http://localhost:8081 export --output keeppeek-config.zip
keeppeek config --server http://localhost:8081 apply keeppeek-config.zip --confirm

Export requires an output path and creates a new, owner-only file; choose a different filename if it already exists. Apply requires --confirm. Commands write machine-readable JSON to standard output and diagnostics to standard error.

Keep --server explicit: a new recorder defaults to 8081, while this CLI subcommand currently defaults to http://localhost:3000 when the option is omitted. Use the recorder’s actual address. On Windows, export protects the new file for the current user, SYSTEM, and Administrators and verifies those permissions before writing any ZIP bytes. On Unix, it creates the file with mode 0600. A destination whose required protection cannot be verified is rejected; choose a private filesystem destination. Browser downloads and direct HTTP clients use their own destination permissions.

Check the command’s exit status and returned state. A successful apply reports RESTORE_STATE_AWAITING_RESTART; restart the service through your normal service manager, or use Restart to apply in Settings. The CLI does not restart the recorder automatically.

For a remote recorder, use HTTPS and provide its Administrator credential through KEEPPEEK_ACCESS_KEY using your normal secure environment setup. Do not put the key in a URL or CLI argument.

Use HTTP

RequestResult
GET /config/exportA fresh application/zip download.
POST /config/applyValidate and stage a raw ZIP body; return 202 and RESTORE_STATE_AWAITING_RESTART.

For a local recorder, a POSIX shell example is:

umask 077
curl --fail http://localhost:8081/config/export --output keeppeek-config.zip
curl --fail -H 'Content-Type: application/zip' \
  --data-binary @keeppeek-config.zip http://localhost:8081/config/apply

Send the ZIP as the request body, not as a multipart form. Apply requires Content-Length; the curl command above supplies it for the file upload. Both endpoints require Administrator access and use Cache-Control: no-store.

HTTP 202 means staged, not active. Restart the service only after checking the successful apply response. There is no separate public dry-run, section selection, or path-mapping step, and the old /api/backups endpoints are not supported.

If an import fails

ResultWhat to do
Invalid or unsupported ZIP (400)Use an unmodified current KeepPeek export. A hand-made ZIP, missing comment, changed checksum, or invalid TOML is rejected.
Authentication or permission denied (401 / 403)Connect with Administrator access to the target recorder.
Apply already pending (409)Complete the staged apply and restart before submitting another. Do not delete its journal to bypass this check.
Missing length or wrong media type (411 / 415)Send a raw ZIP with Content-Length and Content-Type: application/zip.
Upload too large (413)Verify that you selected the configuration export, not a media archive. Each TOML is limited to 16 MiB; compressed and expanded ZIP totals are each limited to 1 GiB.
Archive operation busy (503)Let the active export or apply finish, then retry.

A rejected upload does not replace the live configuration. In Settings, the selected filename remains available after a validation error so you can correct the selection and retry. Do not restart in response to a failed upload as though it had been accepted.

If startup fails after a successful apply, preserve its logs and allow the documented automatic two-file recovery to run. If both activation and rollback fail, preserve the full configuration directory, including its journal and staged files, before manual recovery. Do not clear a pending apply by deleting the journal. See Safe activation.

For a recorder upgrade or disk failure, also use Upgrades and migrations and Recording archive recovery. Applying settings cannot restore missing recording bytes.

Backup and restore

KeepPeek can create and validate a configuration bundle while recording continues. A format-3 ZIP contains exactly config.toml and plaintext secrets.toml; it contains no database or media.

For a step-by-step walkthrough, see Export and import configuration.

Open Settings → Backup and restore as an Administrator. Export ZIP downloads the two files directly. Select a Configuration ZIP, confirm replacement, and choose Apply configuration. After the upload succeeds, choose Restart to apply.

The HTTP surface is GET /config/export for download and POST /config/apply for a ZIP upload. There are no retained server backups, upload reservations, or separate dry-run requests. The old /api/backups routes are retired.

Secrets and media

The ZIP includes the complete file-backed secrets.toml in plaintext. It may contain camera passwords, access keys, MQTT credentials, webhook URLs, and notification-provider destinations. Restrict the ZIP like the live secrets file and delete copies that are no longer needed. Environment secret overrides are not copied into the bundle.

The catalog, MP4 recordings, thumbnail JPEGs, sessions, caches, audit activity, and in-memory notification/MQTT work are excluded. The target recorder retains its named volumes, placement, and metadata authority during restore. Private root references stay in configuration; the uploaded secrets must resolve them to the same target owners. Conflicting or missing references reject the apply before mutation. Finish or cancel a pending metadata handoff before importing configuration. Protect all owned volume roots with a separate archive policy when they need recovery.

What survives a restart

StatePersistence and recovery boundary
Camera settings, access credentials and grants, dashboards and selections, templates, notification rules, MQTT settingsDurable configuration in config.toml; included in a configuration ZIP.
File-backed secretsDurable sibling secrets.toml; included in plaintext. Environment overrides remain outside the ZIP.
Recording metadata, events, coverage and maintenance intentRecording catalog data; recover with a consistent catalog and media archive.
MP4 recordings and event imagesFiles in configured storage locations; not part of a configuration ZIP.
Evidence-export history and artifactsHistory belongs to the metadata volume; artifacts belong to export volumes, with separate expiry; not part of the ZIP.
Diagnostic log filterThe existing sibling log-filter file persists separately and is not in the two-TOML ZIP.
Notification delivery jobs, retries, inbox/history, cooldowns and delivery countersRuntime memory; reset on restart. Saved rule drafts and active rules remain.
MQTT pending publications, retries and deduplication historyRuntime memory; reset on restart. Stored source events are not an automatic replay queue.
Access audit, credential last-use activity and active sessionsRuntime memory; reset on restart. Durable identities and grants remain.
Browser sign-in key, media handles and active wake lockPage/browser runtime state; reload or reconnect as required. Saved dashboard wake-lock intent remains configuration.

Restart is therefore a recording interruption and a runtime-state reset, even when every saved setting returns correctly. Collect required audit and delivery evidence before restart. A queue that was pending before shutdown is not proof of later provider delivery.

See Recording archive recovery for consistent offline copies and recovery rehearsals, and Upgrades and migrations for version changes.

Safe activation

Apply accepts a current format-3 ZIP, including its manifest comment, and validates paths, checksums, both TOMLs, permissions, and capacity before staging. It returns 202 with RESTORE_STATE_AWAITING_RESTART; this is not an immediate replacement of the live files.

Staging writes owner-only copies beside both target files. Recovery runs before configuration or databases open, and completion requires successful configuration, HTTP, and camera-worker startup. KeepPeek restores the prior pair automatically if startup health fails. A pending apply blocks a second upload from replacing it; a healthy completed restore can be superseded by the next apply. To return to an older configuration, apply a ZIP exported before the change.

Do not edit .backups/restore-journal.json or its staging files manually. The detailed HTTP, limit, crash-recovery, and CLI contract is in the backup and restore engineering guide.

Automation

The keeppeek config commands call the same Administrator-only HTTP endpoints. Commands print machine-readable JSON to stdout. Supply remote authentication only through KEEPPEEK_ACCESS_KEY; never put it in a URL or command argument.

CLI exports create a new private file before writing archive bytes. On Unix, the file mode is 0600. On Windows, a protected access list permits the current user, SYSTEM, and Administrators; the CLI verifies that protection before writing. A destination that cannot provide this protection is rejected. Existing files and Windows alternate data streams are not export destinations. Browser downloads use the browser’s destination permissions; restrict those copies separately.

keeppeek config --server http://localhost:8081 export --output keeppeek-config.zip
keeppeek config --server http://localhost:8081 apply keeppeek-config.zip --confirm

Use your recorder’s configured port if it differs from the default 8081. Export does not overwrite an existing file. Apply does not restart the recorder automatically; check its exit status and RESTORE_STATE_AWAITING_RESTART result before restarting. Exit code 2 means invalid CLI usage, 3 means a stable server 4xx rejection, and 4 means a transport, protocol, or server failure.

Keep --server explicit in automation: the config CLI currently uses port 3000 if it is omitted, which differs from a new recorder’s default listener port.

Upgrades and migrations

Upgrade the application separately from moving storage or importing configuration. Each operation has a different recovery boundary. KeepPeek is in Alpha qualification; keep a tested recovery copy and rehearse upgrades on an isolated installation before changing a recorder you depend on.

Prepare an upgrade

  1. Record the running version, installation method, service account, listener address, and effective configuration, catalog, recording, and thumbnail locations. A service account can use a different configuration directory from your interactive login.
  2. Read the target release notes and known limitations. Retain the exact previous package or container image so you can identify what was running.
  3. Export a configuration ZIP. Keep it private: it contains plaintext file-backed secrets. Record any environment-only secrets and external dependencies in your existing secure operations system; the ZIP does not include them.
  4. Arrange a consistent recording archive recovery copy, including the catalog and media. A ZIP alone cannot restore these after a failed disk or catalog migration.
  5. Complete any already-staged configuration apply or storage move before starting a separate upgrade. Finish or cancel active exports and maintenance jobs where possible. Preserve reports for any unresolved maintenance work. Record the expected recording interruption and notify the people who rely on the installation through your normal process.
  6. Stop the recorder through its service manager before replacing application files or taking an offline archive copy. Confirm it is stopped; do not run two versions against the same directories.

Do not upgrade by replacing config.toml with a sample. Server-managed credentials, camera grants, layouts, templates, and notification rules live in that file alongside ordinary settings.

Replace the application

Docker on Linux

Keep the existing /config mount and any additional recording mounts. Pull the selected release image, stop the old container, and create its replacement with the same mounts, ports, user access, and environment. Record the image digest if you need to reproduce the installation; latest can refer to a different build later.

Container replacement does not preserve data that was left only in the container’s writable layer. Do not delete a named volume or host recording directory during an application upgrade. The image runs as user 65532; the replacement must still be able to write its mounted directories. See Get started for the basic mount contract.

macOS

Download the Apple Silicon DMG and its matching checksum file. Verify the checksum, replace /Applications/KeepPeek.app, then open the new application once. The launcher reinstalls and starts the per-user com.keeppeek launch agent. It starts after that user logs in; it is not a system-wide boot daemon.

Check the loaded service and logs:

launchctl print gui/"$(id -u)"/com.keeppeek
tail -n 100 ~/Library/Logs/KeepPeek/keeppeek.log ~/Library/Logs/KeepPeek/keeppeek-error.log

Keep the existing configuration directory. The launcher writes its launch-agent definition again, so preserve and recheck any deliberate local service customizations. Package-signing and notarization details are in the macOS installation guide.

Windows

Run the installer for the host architecture. An existing KeepPeekService selects the service component by default. The installer stops and removes that registration before replacing binaries, then recreates and starts it when the service component is selected.

Record custom service identity, startup, recovery, and environment settings before upgrading; recreating the service is not a promise to preserve SCM customizations. Recheck them before relying on unattended operation. Configuration paths resolve in the process account’s environment.

Use an elevated prompt for service operations:

sc.exe query KeepPeekService
sc.exe qc KeepPeekService

For a planned manual stop and start, use sc.exe stop KeepPeekService and sc.exe start KeepPeekService, checking sc.exe query KeepPeekService between operations. A stop request can return before the service has finished stopping. The installer manages these steps when performing its own service upgrade.

If the installer cannot stop the old service, resolve that failure before retrying the installer. If startup fails, inspect the service account’s configuration and logs. The default destination is keeppeek-service.log beside its configuration; [logging] service = "event_log" uses the Windows Application log after the source has been registered. See the Windows service guide.

Standalone binary

Stop the current process, replace the executable with the intended build, and start it with the same configuration selection and environment. The --config <path> option selects the TOML file and its sibling secrets.toml. A fresh configuration initializes named storage beside that file; an existing metadata binding keeps its recorded owner.

What startup migrates

Current KeepPeek loads durable settings from config.toml. Older installations may still have separate subsystem files. Startup imports their supported settings when the corresponding current section is absent, writes the current configuration, and removes the retired file after successful migration. Preserve an offline pre-upgrade copy if you need the original inputs.

Legacy inputCurrent location or behavior
access.tomlCredential identities, verifiers, roles, revisions, and grants move to [access_credentials].
peek-layouts.jsonLayouts and per-user selection move to [peek_layouts]. Legacy private layouts become server-owned dashboards with restricted audiences.
configuration-templates.jsonCamera templates move to [configuration_templates].
notifications.db and its database sidecarsRule drafts and active rules move to [notifications]; delivery history and pending runtime work are not a replay queue.
mqtt-forwarder.db and its database sidecarsRetired and removed. Current MQTT pending work and deduplication are in memory and are not replayed from this file.

When a current configuration section already exists, it is authoritative; do not expect an old file to merge additional settings into it. Do not recreate retired files to change settings.

The recording catalog also performs schema and index migrations as it opens. There is no supported general-purpose downgrade command or guarantee that an older binary can read a catalog after a newer binary has used it. Keep the pre-upgrade catalog and media together if a downgrade might be needed.

See the configuration reference for current fields and Backup and restore for durable versus runtime state.

Move storage deliberately

Use Settings → Named storage volumes. Create a private destination directory on a supported local filesystem, add its stable ID, roles, limits, and placement rules, and save the draft. Restart to apply configuration changes. New placement rules affect future objects; existing objects retain their catalog owner.

To drain media, choose Stop new writes for the source. Existing fragments finish there. Load its stored objects, select a compatible destination, and preview an individual move or a batch of up to 16 objects. Review the file count and bytes before confirming. Move jobs show durable progress and offer cancellation before authoritative publication. Stopping a batch stops new confirmations; already admitted jobs remain visible. An expired or changed preview requires another review.

Moves copy into a confined temporary file, verify length and SHA-256, publish the verified catalog location, then retire the old copy after readers finish. A lost confirmation reply is checked using the original job ID. Interrupted work resumes from its durable journal. Removal requires no owned objects, active allocations, unfinished jobs, or cleanup receipts; removing a definition does not delete unrelated files or its directory.

Metadata uses Catalog and export history controls. Preview a disabled metadata-only destination, review downtime and capacity, then confirm the restart handoff. The old catalog and history remain available for recovery, and the new authority is verified before media workers start. A missing bound root is offline and is never recreated. Preserve both copies and the pending configuration if activation fails; do not edit journals, overwrite a conflicting destination, or clear fencing records to force a retry. See the configuration reference.

Verify and recover

After startup, check the version, expected camera count, credentials and grants, dashboards, storage locations, and provider settings. Test live playback, a finalized new recording, and an older known recording. Check recording integrity and any pending maintenance or export history. Reconnect remote clients; sessions and their browser keys are not restored across a reload or restart.

An accepted configuration ZIP has its own automatic two-file startup rollback. That mechanism does not roll back an application binary, catalog schema, or storage move. If an application upgrade fails, stop the recorder and preserve its failed state before recovery. Restore an isolated copy of the previous complete installation and verify it before replacing the failed deployment. Do not run the older binary against the only copy of a possibly migrated catalog.

Record the result, interruption, recovered data, and any remaining gap in the installation’s qualification record.

Recording maintenance

Recording maintenance provides Administrator previews, confirmed deletion jobs, and bounded catalog reconciliation over the WebRTC control connection. Implementation issue #133 closed on 2026-09-08. The synthetic-media workflow and native Windows, macOS, and Linux checks have passed. Deployment-specific recovery and the wider release decision remain part of the Alpha gate #145.

Automatic storage retention remains separate from manual deletion. Manual claims exclude their recordings from retention and new playback/export resolution until the claim reaches a terminal outcome. Never delete recording files directly as a substitute for maintenance: that can leave stale catalog entries and unavailable evidence links.

Named-volume deletion follows each recording’s current catalog owner, including recordings moved to another named volume. It requires the original bound root to be available and writable. Active readers and unfinished moves block removal. Failed or interrupted jobs retain capacity ownership until the durable deletion checkpoint completes. Restore the same root or finish the conflicting work before retrying; do not clear ownership rows or private staging to force progress.

Startup preserves .active files and their catalog rows. A temporary-looking filename does not establish ownership or authorize deletion, in either the medium-term or long-term root. Interrupted media remains available for inspection. Writers create new .active files exclusively. If the output path already exists, creation fails without truncating or replacing the existing file.

Review and delete recordings

  1. Sign in as an Administrator and open Recording integrity, then Maintenance. Review deletion in Keep’s export-range view carries its camera, stream, and interval into maintenance. Closing Keep also releases its playback cursor.
  2. Select the camera, stream, UTC start and end, and operator or privacy reason. Choose Preview deletion. A day or hour is an ordinary UTC time range.
  3. Review every whole-recording boundary, byte count, gap, bookmark, related export, and stated consequence. An active, protected, retention-pending, or identity-unavailable selection cannot be confirmed.
  4. Type the exact confirmation text, such as DELETE 1, and choose Delete permanently. Keep recordings closes the confirmation without starting deletion; Cancel job cancels the prepared intent.
  5. Follow the per-object results. Use Refresh job after a connection failure and Download report to retain the result. Reports contain no confirmation nonce or host filesystem path.

Before deleting irreplaceable footage, retain the required evidence export or archive recovery copy and verify it separately. A configuration ZIP contains neither the media nor its catalog. A bookmark does not protect media from deletion; check the preview’s explicit protection and relationship evidence.

One deletion worker runs at a time. Close stored playback and wait for active exports before confirming. The server rechecks authorization during execution; revoking the session can leave a recoverable staged object rather than granting an old request unlimited permission to continue. A failed confirmation consumes its private nonce. Choose Refresh preview, review the new result, and type the confirmation again. The textbox is disabled while the refresh is pending. Losing the Administrator role or maintenance capability discards the review, including late preview responses. Already-removed bytes cannot be restored by cancellation.

Select a complete scope

Maintenance uses stable source, stream, and recording identifiers, never a display name or a caller-supplied filesystem path. Catalog inspection accepts one recording or a nonempty half-open time range on one source and logical stream. The start is included and the end is excluded.

A range selects whole recording objects that overlap it. It does not trim media at the requested timestamps. The snapshot retains each object’s full start and end bounds so an eventual destructive preview can show any expansion explicitly. The confirmation uses those whole-object bounds; it does not rewrite partial boundary fragments. Inspect the listed boundaries even when the requested interval is much shorter than the selected recording.

Selections contain at most 128 recording objects. Time ranges span at most 31 days, and indexed history inspection is bounded to 4,096 rows. If a complete selection cannot be established within those limits, the request fails instead of returning a partial result that could be mistaken for the full scope.

Counts and bytes in a catalog snapshot are recorded metadata. They do not prove that a file is intact, that its media is playable, or that the selected interval has no gaps. Active, protected, and cleanup-pending objects remain visible rather than disappearing from the selection.

Understand durable intent

Preparation reads an authoritative snapshot and checks its catalog revision. It rejects empty selections, active or unfinalized recordings, protected recordings, pending automatic cleanup, and recordings without a known end time. The stored intent records the requester, scope, operator or privacy reason, and timestamps.

The snapshot also retains an opaque fingerprint of each recording’s catalog device/file identifier. Confirmation and restart preserve this preparation-time binding; a later catalog refresh cannot substitute another file into the plan. Missing preparation evidence stays unavailable even if the current catalog later gains an identifier. The fingerprint does not verify media contents or prevent filesystem identifier reuse.

Confirmation requires the same requester, a server-generated nonce, and the unchanged preview revision. The catalog stores a hash of the nonce rather than the nonce itself. Prepared plans expire after ten minutes using both wall-clock and monotonic time; restarting the catalog requires a new preparation.

StateMeaning
PreparedA short-lived intent is awaiting confirmation; no recording is reserved.
ExpiredPreparation must be repeated before confirmation.
QueuedConfirmation and a per-recording ledger are durable. Execution may reserve the selection.
WorkingA live execution attempt owns the object.
StagedThe selected file has moved into private maintenance storage, with a durable staging checkpoint.
DeletedFile removal and catalog completion have been recorded.
FailedRemoval did not finish; retained evidence and a retry or inspection are required.
CancelledAdmission of unstarted objects has stopped. Objects already deleted stay deleted.

Confirmation creates exactly one ledger entry per selected recording in snapshot order, in the same transaction as the queued state. Retrying the same confirmation after a lost reply or restart returns that job rather than duplicating work. Queued and cancelled intents survive restart. Cancellation is idempotent and cannot assign a time before creation or confirmation.

The catalog admits at most 128 prepared plans and 256 confirmed or retained cancelled jobs. Expired preparation and terminal history older than 30 days can be pruned; unresolved execution is not evicted to make room. Cancelling before confirmation does not create work entries.

Reservations and per-object checkpoints survive restart. An authenticated retry can resume an abandoned attempt; it cannot run concurrently with a still-live worker. A file that disappeared before any staging evidence is not reported as deleted. The staging checkpoint binds recovery to the original token directory’s filesystem identity. A missing or replacement directory, renamed staged recording, or unexpected entry requires inspection instead of a successful result. Once unlink has completed, recovery can still finish if the empty original source directory has been removed.

Cancelling interrupted work does not release a claim merely because staging is absent. The original selected file must be positively identified; otherwise the object remains failed and reserved for inspection or recovery. Retry failed objects is available for those failures even when the job is cancelled. After restart, a previous worker’s pending objects report failed instead of indefinitely working; the checkpoint and reservations remain intact for authenticated retry. Before recording pipelines start, a bounded recovery pass settles catalog completion only when the original staging-directory checkpoint and absence checks prove that unlink has already completed. It does not move or delete present media. Other pending objects remain failed and reserved for an Administrator to inspect or retry. Each settled outcome is atomic, respects the original request deadline, and leaves any live executor untouched. If the pass cannot finish within its two-second budget, the server records a warning and retains unresolved claims.

Audit and coordination

Maintenance admission shares the configuration-update lock used by restore and access configuration. An export retry cannot remove its existing job while maintenance is admitted. The worker retains storage coordination during execution and rechecks the current session before filesystem mutations.

The access audit links each execution outcome to its durable job ID. Structured recording_maintenance log events include requester, source and stream, requested and expanded interval bounds, operator or privacy reason, preview revision, object and byte counts, relationship counts, and the observed result. Hold override is always false: this workflow cannot release a hold. Results distinguish successful deletion, partial failure, incomplete work, completed cancellation, and cancellation with pending work. Paths, confirmation nonces, and raw failure details are excluded.

Reconcile the catalog

Open the Reconciliation view and choose Inspect catalog. The dry run reads at most 4,096 catalog rows and 4,096 archive entries and returns at most 128 findings. Scan limits are explicit: an incomplete report cannot authorize a remedy. Reports expire after ten minutes and belong to the requesting Administrator. At most sixteen reports are retained in server memory.

The inventory reports missing and unknown files, duplicate paths or catalog file identities, size or identity mismatch, rejected paths, temporary files, interrupted work, corrupt containers, and playback-index mismatch. Protected recordings are not mislabeled as interrupted jobs. Active recordings are not parsed while their writer can still change them.

Finalized cataloged files use bounded fragmented-MP4 validation: box boundaries, decode times, track identities, sample extents, and decoder metadata are checked. The parser accepts at most 4,096 fragments, 8 MiB of metadata and two tracks; each track fragment has at most 65,536 samples. This is not a full media decode or a stored-content checksum comparison. Unknown files are never automatically adopted, moved, or deleted.

  • Acknowledge records an explicit ignore decision in the retained report and leaves the file and catalog untouched.
  • Retain tombstone is available only for a missing, unprotected cataloged file. It rechecks absence and catalog revision, then removes the stale row and playback indexes while retaining existing coverage-derived deletion evidence. No filesystem bytes are deleted. A reappeared file or changed catalog rejects the operation; inspect again before making another change.
  • Rebuild index is available only for an existing unprotected catalog recording whose container validates and whose playback index differs. After confirmation, it rechecks the catalog revision, exact index fingerprint and retained file observation. It replaces fragment/keyframe indexes and derived coverage in one transaction. The media bytes are unchanged. Unknown files cannot use this action.

Category filters and Download reconciliation report preserve inspection results. A new scan is required after a catalog-changing remedy. Mutating remedies share storage coordination with deletion and restore. Quarantine, content-checksum comparison, and previewed deletion of owned temporary files are not implemented yet.

Read a preflight report

Preflight accepts an actor-owned queued job. Its caller must authenticate and authorize the Administrator and supply the configured archive. These internal storage methods are not themselves a network authorization boundary.

The job, ledger, current recording rows, and observed catalog revision share one read transaction on the catalog search worker. Filesystem checks run only after that transaction closes. Reports preserve snapshot order and do not expose host paths or confirmation secrets.

StatusMeaning
PresentPlanned and current device/file fingerprints match the observed file, and its size matches. Content is not verified.
IdentityChangedThe preparation-time fingerprint, current catalog identifier, and observed file do not all agree.
IdentityUnavailableThe preparation or current catalog lacks identity evidence; path and size cannot establish a match.
MissingFileThe catalog-resolved file could not be found during inspection.
MissingCatalogThe recording row is absent. Its file location and whether bytes remain are unknown.
CatalogChangedSource, logical stream, recording bounds, or recorded size no longer match the plan.
ActiveRecordingThe recording is no longer finalized.
ProtectedRecordingCurrent catalog protection blocks maintenance.
CleanupPendingAutomatic retention already has pending work for the recording.
SizeMismatchThe observed file size differs from the catalog byte count.
PathRejectedThe path is not eligible for inspection, including escapes, hidden paths, or links.
InspectionFailedAnother filesystem error prevented a usable observation.

Malformed identity evidence or an inconsistent ledger rejects the report rather than reconstructing work from current files. Known catalog blockers are reported before inspecting the affected path. Both the planned and observed catalog revisions are returned; a changed revision requires another preview.

Malformed persisted fingerprints also reject intent reads and confirmation retries with a redacted error. Rejected values and internal paths are not included in public diagnostic messages.

The entire request shares one two-second deadline across queueing, catalog reads, and at most 128 filesystem checks. Paths are limited to 4,096 bytes and sixteen components. The deadline is cooperative: it cannot interrupt an operating-system or database call already in progress. A timeout returns no partial report.

Know what file inspection proves

Inspection pins the configured archive directory and opens each descendant directory without following links. It rejects hidden directories such as .exports, traversal, alternate-stream syntax, leaf symlinks, nonregular files, and files with multiple hard links. Checks before opening do not replace the checks on the opened file.

Each observation retains a non-writable file handle until it is dropped. Unix uses nonblocking opens to avoid waiting for a writer if a regular file is swapped for a FIFO. Unix requires read permission; Windows requests file attributes. Inspection does not read media bytes or request write, create, or truncate access. Preflight releases each file observation as it advances to the next object.

A retained handle pins the observed object, not its pathname. Another handle can still change the file’s contents. Device/file identifiers can be reused outside the lifetime of an open handle, and the available 64-bit Windows file index is not a complete ReFS identity. These checks do not certify immutable recording ownership or qualify Windows reparse-point races without runtime verification.

Even an all-Present report with a matching revision is not permission to delete. Filesystem observations are not atomic with the catalog read or with one another. Revalidating a file and then unlinking its pathname still leaves a replacement race. Missing catalog metadata is never evidence that an unknown file is safe to remove.

The removal path uses separate private staging directories, revalidates the staged object, and synchronizes directory changes before recording completion. Unix directories must be owned by the process user and deny untrusted mutation; private staging denies group and other access. macOS and Linux use atomic no-replace rename, so a destination introduced before the move is not overwritten. Unix also checks the retained selected inode after unlink and rejects the attempt if that inode is still linked.

These checks do not isolate maintenance from another process running as the same operating-system account or root. Such a process can replace private staging names immediately before Unix pathname-based unlink. Post-unlink checks can detect some substitutions, but cannot prevent the replacement from being removed or establish where a relocated recording went. Do not treat retries after external staging manipulation as qualified recovery. A trusted service-account boundary or stronger process isolation must be resolved before this feature is qualified.

The native Windows implementation targets NTFS with persistent ACLs, checked directory flushes, and handle-based rename/disposition. Other filesystems, including ReFS, are rejected before source-file mutation. ReFS requires a full 128-bit file identity and a separately justified namespace-durability strategy; a successful directory-flush call alone is not sufficient. The PR CI run and extended Main run exercise the implementation, including Windows and synthetic deletion, on production tree 4df9a94e (equivalent to 4bc3ab18). This establishes automated coverage of the exercised fixtures, not every NTFS race, crash boundary, device, or deployment.

Qualification still required

For day-to-day operation, treat an unresolved reservation, missing staging identity, or incomplete reconciliation report as an inspection task. Preserve the report and the original files, close competing playback or exports when directed, and refresh the authoritative preview. Do not clear catalog rows or hidden staging entries manually to make a retry available. See Recording archive recovery for the difference between index repair, tombstones, and recovering missing bytes.

For a deployment and final release candidate, retain explicit evidence for:

  • exact object ownership and adversarial replacement at every mutation boundary;
  • Windows NTFS and Linux runtime tests, plus supported-filesystem failure behavior;
  • holds, shares, operational investigations, moves, restores, and current access coordination, including audit completeness;
  • partial failure, lost replies, cancellation, and crash recovery across all file/catalog/tombstone combinations;
  • the remaining reconciliation categories and remedies;
  • browser conflict regeneration, failed-job retry, access revocation, and truthful timeline results, beyond the existing desktop/mobile happy-path coverage;
  • final-commit canonical checks and reproducible performance evidence.

Unknown files must never be adopted or deleted automatically. A ready export must not be removed as a side effect of deleting its source, and a bookmark is not an evidence hold. The implementation and acceptance criteria are tracked in issue #133.

Use Recording and evidence for recording policies, playable coverage, and evidence exports. See Backup and restore before changing storage, and Release readiness and known limitations for the qualification status of the wider system.

Recording archive recovery

A configuration ZIP restores settings and file-backed secrets. Recording recovery also needs the recording catalog and the files that it indexes. KeepPeek does not provide a public recording-archive importer, a button to merge another recorder’s database, or automatic adoption of unknown MP4 files.

Use this chapter to plan and rehearse an operator-managed filesystem recovery. It describes the current boundaries; it does not qualify every filesystem, backup product, or crash scenario.

Inventory the recovery set

Open Named storage volumes as an Administrator and record every configured root and its stable ID. Fresh defaults live beside the selected config.toml; each role can have a separate destination. Record the operating-system account that owns and runs the recorder.

ItemWhy it matters
config.toml and sibling secrets.tomlCameras, stable identities, policies, credentials, grants, dashboards, and integration settings. A configuration ZIP can preserve this pair.
The metadata-bound recording catalog and any accompanying database sidecarsRecording identities, owned locations, indexes, event data, coverage/deletion evidence, and maintenance intent. Copy the database family consistently.
The runtime state-store database file and any accompanying sidecarsDesired-state documents, leases, namespace and entry revisions. A separate consistent set; excluded from the configuration ZIP. Copy the family consistently.
Settings-backed state entriesStored in the state_store section of config.toml; already covered by the configuration ZIP. Generic runtime entries are not.
Every active and archive volume rootThe MP4 bytes referenced by the catalog. Preserve relative structure, file names, and interrupted work.
The optional fourth-tier local archive rootVerified replica bytes and private staging entries. Manifests and the target binding remain in the recording catalog; preserve both together.
Every thumbnail volume rootEvent-image bytes and their catalog ownership.
Every export volume root and the metadata-bound export history fileExport artifacts and durable history. Artifacts still have independent expiry rules.
Service definitions, environment-only secrets, certificates, and external service configurationDeployment dependencies outside the two TOML files. Preserve them through your existing protected operations system.
The existing sibling log-filter and retained service logsDiagnostic configuration and investigation evidence outside the configuration ZIP.

Copy the entire selected recording roots, including hidden and interrupted-work entries, rather than selecting only files ending in .mp4. Maintenance checkpoints can depend on private staging entries. Copying them is preservation, not permission to retry destructive work after a move: filesystem identities can change when data is copied.

Do not include only the default paths if Settings shows overrides. Do not treat an exported evidence clip as a full recording backup. Clips cover selected ranges and do not contain the recorder catalog.

Make a consistent offline copy

  1. Finish any pending configuration apply or storage move, then export the current configuration ZIP and save it privately. Finish or cancel active exports and maintenance jobs where possible; retain reports for unresolved work. If this is failure preservation instead, keep the unfinished journals with the copy and do not treat it as an ordinary clean recovery point.
  2. Stop KeepPeek through its normal service manager. Confirm the process has exited and no second recorder is using the same paths. Recording stops for this maintenance window.
  3. Copy the configuration directory and complete archive set while they remain stopped. Include any database sidecars that exist, for both the recording catalog and the runtime state store; do not manually delete them to make the copy simpler.
  4. Preserve ownership and access restrictions, record the source version and paths, and verify the copied files using your backup tool’s integrity checks. Keep the copy separately from the source disk.
  5. Start the source recorder again and verify that new recordings finalize normally.

A live copy of just the catalog database can disagree with its journal or recording files. KeepPeek’s configuration export lock does not create an atomic snapshot of the catalog, media, and export tree. If you use storage snapshots instead of a stopped copy, establish and test a consistent capture procedure for every involved volume and writer. This book does not claim that an arbitrary live filesystem copy is recoverable.

Restore the runtime state store before accepting watches

Restore the state-store database family together with its counters: namespace revisions, entry revisions, and byte counts must return as one unit before the server accepts new watches. Restoring entries without their counters would let a fresh write reuse a revision that a previous writer already consumed, which breaks compare-and-set ordering. After the restore, clients establish fresh watches and reconcile the new snapshots against current capabilities; there is no resume across a restore. A configuration ZIP alone does not restore generic runtime state: it carries only the settings-backed entries stored in config.toml.

Rehearse on an isolated host

  1. Keep the original backup unchanged. Restore a working copy into an isolated environment with enough space and compatible file permissions. Start with the same KeepPeek build that created it.
  2. Prevent the rehearsal from reaching real notification providers, MQTT consumers, and cameras until you deliberately test those connections. Imported credentials and rules are real settings; a duplicate recorder can otherwise act on the same devices or send duplicate notifications.
  3. Restore the catalog family and recording tree together at the original configured paths where practical. Restore the configuration pair and any required environment separately. Do not start with missing archive mounts and expect later attachment to be harmless.
  4. Confirm the service account can access every path, then start KeepPeek. Preserve startup errors and stop if a catalog, path, or migration failure occurs.
  5. Verify the expected cameras and access grants. Review a known older interval in Keep, inspect its coverage and event images, and play a finalized recording. Create a small evidence export and verify that its downloaded media decodes independently.
  6. Inspect recording integrity and reconciliation, including missing, unknown, interrupted, and protected recordings. Check old maintenance jobs before any retry. Record discrepancies rather than treating successful server startup as proof that every recording was recovered.

Catalog paths are meaningful. A same-path recovery avoids inventing a path rewrite. For a deliberate move on a working installation, use the validated storage migration. Do not manually rewrite catalog SQL or assume a configuration ZIP maps old recording paths: configuration apply keeps the target’s current recording, catalog, and thumbnail paths.

Once the isolated copy is verified, plan the production cutover and repeat the checks. Keep the old copy until the new recorder has completed the installation’s recovery acceptance test.

Understand startup recovery

Startup can reconcile specific interrupted states for recordings, automatic cleanup, export jobs, and maintenance. It is not an exhaustive media scan or a replacement for a consistent backup.

  • An interrupted recording finalization can reconnect a catalog row to its finalized sibling. Existing .active files are preserved rather than truncated or deleted merely because of their extension.
  • Missing or unavailable named roots retain their catalog ownership. A replacement directory does not have the bound root identity. Restore the original root or use an explicit recovery procedure; creating an empty directory at its path does not make it the original volume.
  • Export work that was running becomes failed and retryable. Missing ready artifacts also become failed. Partial export artifacts are cleaned within the owned export area.
  • Event images retain their catalog references when the files are missing. Startup preserves unindexed images and temporary files; thumbnail quota cleanup only removes catalog-referenced images in the configured directory.
  • Maintenance recovery only settles outcomes supported by its retained identity and checkpoint evidence. Other interrupted objects remain failed and reserved for Administrator inspection or retry. Copying files can change their identity, so a restored checkpoint may properly refuse work.

See Recording maintenance for exact mutation and retry boundaries, and Recording and evidence for coverage and export behavior.

Diagnose incomplete recovery

SymptomNext action
Settings returned, recordings did notConfirm that a catalog and media archive were restored. The configuration ZIP excludes both.
Settings returned, runtime state did notConfirm that the state-store database family was restored with its counters. Re-establish watches with a fresh snapshot afterwards.
Recordings appear missing after a path changeCheck mount availability, service identity, effective paths, and the catalog’s original locations before changing files.
MP4 files exist but do not appear in KeepInspect reconciliation. Unknown files are not automatically adopted; Rebuild index only applies to eligible existing catalog recordings.
A cataloged recording has a valid container but a wrong indexUse the previewed Rebuild index remedy if offered. It changes indexes, not media bytes.
Reconciliation reports corruptionPreserve the original file and report. Structural validation is not a full decode or an end-to-end content checksum guarantee.
A copied maintenance job refuses retryPreserve the job report and original checkpoint evidence. Do not delete private staging or manually clear reservations.
An old ready export is unavailableCheck expiry and artifact presence. Export metadata can outlive its bytes; retry requires source recordings still to exist.
Both configuration activation and rollback failPreserve the complete configuration directory, its restore journal and staged files, and service logs before manual recovery.

Never delete unknown media, database sidecars, restore journals, or maintenance staging merely to clear an error. A missing file cannot be recovered by retaining a tombstone; that remedy records catalog cleanup, not restoration. Quarantine, arbitrary archive import, and automatic adoption of unknown files remain unsupported.

Record the recovery result

Keep the exact build, platform, filesystem, source and destination layout, backup time, interruption, commands, checksums, recovered interval, missing data, and observed errors with the recovery report. Include a known recording before and after the backup boundary and any interrupted work you tested. The Alpha qualification matrix still requires representative recovery and sustained-operation evidence. One successful rehearsal does not establish every deployment’s recovery behavior.

Native camera events

Native events are detections and status changes reported by the camera itself. KeepPeek consumes Reolink notifications, Hikvision/Annke ISAPI events, generic ONVIF PullPoint notifications and metadata carried by RTSP. These do not require an external inference service.

Supported observations enter the same event catalog as other events and can appear in Events, Keep’s history, notification rules and MQTT. Event processing is independent of media ingest: an event-service failure does not restart healthy video or audio. Compare its status with camera and stream health instead of treating them as one signal.

Enable and verify events

  1. Add the camera and verify its video and recording independently.
  2. In the camera’s own interface, enable the intended detection rule, schedule, region and event notification linkage. KeepPeek discovery does not configure these on the device.
  3. Use the default native event mode, auto, unless there is a reason to select a specific path.
  4. If the camera reports only unclassified motion, enable Store generic motion events in its KeepPeek settings. The default is off; explicit person or vehicle detections remain distinct.
  5. Trigger one known event, then open Events, choose that camera and the correct UTC date, and inspect its kind, source, timestamp and available image. Verify the corresponding recording in Keep before enabling an alert rule.

A detector-enabled switch is not an activity indicator. A camera reporting a motion rule with a human-only filter also does not prove that every notification contains a person classification. KeepPeek uses the explicit evidence in each notification.

Select the event transport

The optional [cameras.<name>.events] table belongs in the existing config.toml. These transport fields are file-only; ordinary camera edits preserve them. Apply file changes through the existing configuration/restart workflow. For example, to force generic ONVIF for an existing camera:

[cameras.front.events]
mode = "onvif-pullpoint"
metadata_stream = "auto"
snapshots = true

Replace front with the existing camera entry’s name. Do not add a second entry for the same device merely to change its event path.

ModeBehavior
autoPrefer an eligible Reolink or ISAPI adapter; otherwise use generic ONVIF and permit RTSP metadata. Unsupported ISAPI HTTP 404/405 can hand over to generic ONVIF.
vendorUse the eligible vendor adapter without generic fallback.
onvif-pullpointUse generic ONVIF independently of the video backend; metadata remains permitted unless disabled.
rtsp-metadataConsume metadata from the existing RTSP workers without contacting an ONVIF Event Service.
disabledDisable native events while leaving camera media independent.

metadata_stream accepts auto, enabled, or disabled; it does not override vendor or disabled mode. Authentication failure is reported rather than bypassed through another authentication policy. The full field bounds and secret-reference rules are in the Configuration reference.

Multi-channel devices need exact channel evidence. ISAPI main/sub URLs ending in 101/102 select input 1; 201/202 select input 2. Both URLs must agree. Generic ONVIF source_tokens are opaque camera-supplied identifiers, not profile indexes. Topic filters use expanded XML names, with exclusions taking precedence. Use the detailed protocol guides when narrowing either field.

Understand event times and images

Repeated active observations update a logical event instead of creating one event for each notification. An explicit clear closes the matching observation. Generic ONVIF initialization establishes a baseline without notifying the user; an already-active baseline must clear before opening a new interval. Matching PullPoint and metadata evidence is deduplicated.

Timestamp rules depend on the source. Generic ONVIF uses valid camera UTC from the preceding five minutes; invalid, missing or future notification times use receipt time with an explanation in the event metadata. ISAPI’s recording timeline uses server receipt time and retains the original camera time as metadata. Correct the device clock when investigating inconsistent event timing.

ISAPI disconnects or a 30-second active-observation timeout close an observed span at its last evidence. That is not proof of the instant physical motion stopped. Unknown topics and heartbeats do not become invented motion events.

An event can be useful without an image. KeepPeek attaches explicitly associated snapshots and retains metadata when image capture is unavailable. Boxes are drawn only on their matching image; an unavailable canonical image is reported instead of replaced with unrelated footage.

Troubleshooting

SymptomWhat to check
Video works, events do notNative event status, the device event service, credentials, rule schedule and channel selection. Video and event protocols are separate.
Only heartbeats arriveEnable the desired camera rule and notification linkage. Check generic-motion retention for unclassified VMD.
Generic service is unsupportedConfirm the camera exposes ONVIF events or RTSP metadata; an ONVIF video profile alone is insufficient.
Repeated authentication failureCorrect the saved credentials and device permissions. Do not weaken authentication or repeatedly guess passwords.
Events have no thumbnailInspect the attachment state and snapshot support; metadata delivery can succeed independently.
Duplicate-looking cardsCompare source, event ID, rule, object and revision. Similar text can describe independent observations.

For protocol details, see ONVIF events and Hikvision ISAPI. For model-generated detections, see External analysis. For delivery, see Notifications and integrations.

External analysis

An external analysis service consumes KeepPeek camera video, runs its own detector or model, and publishes events back through the public API. KeepPeek stores accepted event revisions and attachments and makes them available in Events, Keep, notifications and integrations.

KeepPeek does not install, host or select an inference product for you. Model execution, decoding, sampling, deployment and model licensing belong to the independent service. The repository’s Python object-detection example demonstrates the integration and supports CI; it is not a production detector product. Camera-native detections are covered separately in Native camera events.

Prepare the integration

  1. Verify that the camera is available in KeepPeek and that its configured stream can be decoded by the service. A low-resolution stream is usually sufficient for an initial interoperability check; choose it explicitly in the service.
  2. Obtain the camera’s stable source ID from its camera details. A display name, a current session ID or a Home Assistant entity ID is not a substitute.
  3. Provide a dedicated KeepPeek credential through the service’s supported secret mechanism. Event publication currently requires Administrator authority; a viewing-only User key cannot publish events. Apply the access policy appropriate to that service.
  4. Confirm the server advertises keeppeek.event-publication.v1, the intended source, a supported media variant and the event types the service will publish. An unadvertised event type is not accepted merely because a model can produce it.
  5. Start the service and verify one committed event before depending on its alerts or automation.

The browser has no general model-installation or external-service deployment wizard. Configuration for a particular service belongs to that client; do not invent extra KeepPeek TOML sections from the conceptual computer-vision scenario.

Try the reference example

The object-detection example includes the complete platform-specific setup and commands. It requires Python 3.12 or newer, FFmpeg on PATH, an active H.264 or H.265 source and a compatible KeepPeek server. Install its requirements into the selected interpreter directly; the repository does not use Python virtual environments. Generate its bindings from the checked-in protocol before running it.

Use KEEPPEEK_ACCESS_KEY_FILE with an owner-only key file or the supported environment variable for authentication. Keep the key out of command arguments, endpoint URLs and committed files. The example accepts non-secret settings including KEEPPEEK_URL, KEEPPEEK_SOURCE_ID, stream, model, confidence, cooldown and inference-frame-rate choices.

The default example loads Ultralytics yolo11n.pt after authentication and media subscription succeed. Missing weights may be downloaded by Ultralytics. Review that provider’s licensing and deployment requirements independently. KeepPeek does not distribute the model weights.

Verify the result in KeepPeek

Open Events and select the source and UTC date. Inspect the event kind, confidence, text, revision and canonical image. A later model result may enrich or close the same event; it should not be interpreted as a new detection solely because its revision increased.

Use Open at this moment to compare the event with recorded video, and Back to event to return to the investigation. Review state and shared bookmarks work the same way for external and native events. A JPEG, story or description is an event attachment; it does not prove that continuous video was retained. Check Recording and evidence.

An attachment upload becomes visible only after the publication commits. A failed or unfinished upload must not appear as a complete event. Consumers use stable source/event IDs and revisions to recognize updates and retries. The service must use the API rather than writing KeepPeek’s catalog or attachment directories directly.

Failure and capacity checks

SymptomWhat to check
Subscription is rejectedSource visibility, advertised stream/codec and the service’s media capability checks.
Publication is deniedAdministrator authority, enabled credential, current source identity and advertised event type.
Processing falls behindService decode/inference rate and bounded queues. Reduce sampling or use a suitable stream instead of accumulating old frames.
Events appear without imagesPublication/attachment completion and image limits; inspect the event’s explicit availability state.
Recording continues while detections stopCheck the external process and its session. Inference failure does not imply recording failure.
Reconnection produces duplicatesPreserve logical event identity and increment revisions correctly; inspect commit/retry handling in the service.

Source restarts change session identity. A service must rediscover the current source session and replace its media subscription, while preserving the stable source ID used for event history. The reference example’s checks demonstrate interoperability, not long-duration operation or certification of a third-party model deployment.

Developers should start with the public API and the computer-vision scenario. The latter explains the wider service architecture; check advertised capabilities and current examples before treating a described workload as an installed KeepPeek feature.

Notifications and integrations

KeepPeek evaluates notification policy on the server after an event or operational transition has been persisted. Camera workers never call a notification provider or MQTT broker directly. A slow or unavailable integration therefore cannot stop camera ingest, recording, event storage, health projection, or another delivery channel. See Camera and stream health for the evidence that creates operational transitions.

Notification controls are available only when the server advertises keeppeek.rules.v1.

Build and activate a rule

Open Settings > Notifications to create a rule. A rule can match:

  • event creation, enrichment, and completion;
  • camera offline, stale stream, unavailable decode, and interrupted recording transitions;
  • storage or recording health changes;
  • an explicit test send.

Filters can narrow a rule by camera or source, group, event kind, zone, confidence, image availability, duration, severity, review state, and bookmark state. Schedules use an IANA timezone and support active windows and quiet hours, including daylight-saving transitions.

Editing creates a draft. Activate validates and atomically promotes the complete draft; a conflict or invalid draft leaves the previous active revision unchanged. Use the test action before depending on a new destination. Test sends have separate rate accounting but still use the saved credentials and provider configuration.

Start with one camera, one event kind and one destination. Verify a real matching event in Events after the test send succeeds: a provider test proves the delivery path, not that your camera, schedule or rule filters will produce an alert. Rules and active revisions survive restart in config.toml; delivery queues and inbox history do not.

Browser alerts, inbox and webhooks

Settings > Notifications has Rules, Inbox and History views. Use Inbox to mark a notification seen, acknowledge it or clear it. Clear all requires a second confirmation. These actions change notification receipts, not the event’s reviewed/dismissed state or the camera’s health. History shows delivery attempts and suppression reasons.

For desktop notifications, add a Browser action and select Enable browser alerts when offered. Browser permission is separate from KeepPeek access and server inbox delivery. The browser’s current permission and an active frontend determine whether it can display a system alert; this is not an offline push service for a closed application. Use Pushover when you need the supported external push channel.

Webhook actions send to the configured URL with a five-second timeout. They do not follow redirects, accept arbitrary request headers or allow credentials in the URL. Configure an endpoint that can accept the supported payload and inspect History after testing it. Enrichment can create a second webhook delivery only when the action enables that behavior.

The rule editor also exposes a Forwarder action, but its current provider returns channel_unavailable. It is not the MQTT event forwarder. Configure MQTT independently under Settings > Integrations, as described below.

Avoid duplicate and noisy alerts

KeepPeek gives one logical notification a stable identity derived from its rule, source, event or outage, and lifecycle. Event revisions, retries, enrichment, and recovery retain that identity while KeepPeek remains running. Equal message text never collapses unrelated cameras or events.

Rules can apply cooldowns per event family, camera and event kind, group, whole rule, or outage interval. The cooldown starts when the logical notification is created and resets on restart. Rate limits can apply to the rule, channel, principal, or whole server and also reset on restart. Critical bypass is explicit, bounded, and logged.

A preliminary alert may be replaced by an enriched revision when a canonical image or final duration becomes available within the configured deadline. Missing imagery does not suppress a metadata alert unless the action explicitly requires an attachment. Late enrichment stays in history without waking the user unless the rule permits it.

The process-local inbox keeps unread, seen, acknowledged, and cleared state per authenticated principal. Provider delivery and acknowledgement remain separate from KeepPeek review state. Delivery history explains whether work was created, replaced, suppressed, collapsed, rate-limited, retried, expired, delivered, or failed until the next restart.

Deliver through Pushover

Pushover is the supported push channel. To configure it:

  1. Register an application at https://pushover.net/apps/build.
  2. Obtain the application token and a user or delivery-group key.
  3. Add a Push action to a notification rule.
  4. Enter the token and key directly in Settings > Notifications.
  5. Save, activate, and run the rule’s test action.

The two credentials are write-only. KeepPeek stores owner-only values in secrets.toml and keeps references with the rules in config.toml. It never returns them to the browser, health snapshots, logs, or delivery reasons. Editing non-secret settings preserves the configured values; replacing credentials requires entering both again.

Push actions support optional device names, sound, priorities -2 through 2, a public base URL for deep links, and one bounded JPEG attachment. Emergency priority 2 also requires retry and expiry values and records provider acknowledgement separately from KeepPeek acknowledgement.

Timeouts, connection failures, HTTP 408, 425, 429, and 5xx responses use the rule’s bounded in-memory retry policy. Invalid credentials and other permanent provider failures do not retry indefinitely. Restarting KeepPeek discards pending attempts. Disabling a rule or Push action stops new work and expires its pending attempts.

The notification outbox holds at most 10,000 pending/retrying actions in process memory. A full queue records expired with reason outbox_full. Each action also has its own attempt, retry and expiry bounds. Restart is not a recovery strategy for pending delivery: it discards that work and can allow replayed events to notify again after deduplication state is cleared.

Forward events through MQTT

Open Settings > Integrations to configure, test, observe, or disable the MQTT event forwarder. It requires an MQTT 5 broker; MQTT 3.1 and 3.1.1 are not fallback protocols.

Configure a stable client ID, KeepPeek instance ID, topic prefix, optional username and write-only password, QoS, retention policy, and optional CA certificate for mqtts://. Use TLS outside an explicitly trusted local development network. Credentials embedded in the broker URL are rejected.

The default topics are:

keeppeek/{instance_id}/sources/{source_id}/events/{event_type}
keeppeek/{instance_id}/forwarders/{forwarder_id}/status

Every event publication is a complete JSON snapshot with schema_version = 1. Consumers should deduplicate with (instance_id, event_id, revision). Camera outage and recovery records use the same envelope and carry bounded cause, affected streams, recording consequence, duration, and recovery evidence. This profile publishes attachment metadata, not image bytes.

QoS 1 is the default and provides at-least-once transport while KeepPeek remains running. A broker reconnect around acknowledgement can redeliver the same stable identity, so consumers must still deduplicate. QoS 0 has no broker acknowledgement; QoS 2 does not make queued work durable across a KeepPeek restart.

The bounded in-memory MQTT outbox defaults to 64 MiB and reconnect delay grows from 250 ms to a 30-second ceiling. A broker outage or configuration replacement does not reconnect camera streams. Restarting KeepPeek discards pending publications and deduplication history. When the outbox is full, forwarding fails visibly while event persistence and recording continue.

Settings and Prometheus expose connection state, bounded error detail, pending items and bytes, retry and duplicate counts, last received and delivered times, and oldest unacknowledged work.

Troubleshoot a missing alert

Check the chain in order: the source event exists, the active rule matches it, its schedule and cooldown permit delivery, the action is available, and the provider or broker accepted the work. A missing image, an acknowledged inbox item and a failed provider request are separate outcomes. Changing one does not repair the others.

EvidenceNext step
No event in EventsCheck native events or the external analysis service, camera scope and UTC date.
Rule is saved but not activeActivate the intended draft and resolve any validation or revision conflict.
suppressed, collapsed or rate_limitedInspect the active filter, schedule, cooldown and next eligible time.
Push fails permanentlyVerify both configured credentials and destination in a controlled test; do not repeatedly retry invalid credentials.
MQTT pending bytes keep growingCheck MQTT 5 broker availability, TLS trust, authentication and acknowledgements before the bounded outbox fills.
Inbox or queued work disappears after restartThis is process-local delivery state. The stored event and configured rule remain separate.

For Home Assistant, the direct camera card displays live video. It does not create entities or subscribe to these events for you. MQTT consumers and Home Assistant automations need their own topic, payload and deduplication configuration.

See the detailed notification rule, Pushover, and MQTT 5 forwarder references for every field, payload, metric, and failure state.

Home Assistant card

Use custom:keeppeek-card to put live KeepPeek cameras in a Home Assistant Lovelace dashboard. Home Assistant serves the card module and dashboard configuration, but the browser connects directly to KeepPeek for session creation and WebRTC video. No Home Assistant backend integration, media proxy, or embedded iframe is required.

flowchart LR
    H[Home Assistant] -->|Card module and configuration| B[Browser]
    B -->|Authenticated session requests| K[KeepPeek]
    K -->|Direct WebRTC video| B

The card supports live video, a visual editor, camera selection, grid and single-camera layouts, focus, and shared connections. It inherits Home Assistant theme colors and adapts to dashboard width. Audio, PTZ, event ribbons, timeline playback, and Home Assistant entity discovery are not implemented in this card. Use the KeepPeek application for the corresponding supported features.

The direct card and the MQTT event forwarder solve separate tasks. The card displays selected live cameras; MQTT lets an independently configured consumer react to events. Installing the card does not create Home Assistant camera entities, motion sensors or automation triggers. See Notifications and integrations for the event envelope and delivery limits, and Recording and evidence for recorded playback.

Prepare KeepPeek

  1. Confirm that the selected cameras play in KeepPeek.
  2. Create a dedicated User credential with the intended camera access in KeepPeek’s access settings. See Authentication and access control.
  3. Make the KeepPeek endpoint and its advertised WebRTC addresses reachable from the browser that displays Home Assistant. Home Assistant Cloud does not relay the card’s media.
  4. Add the exact Home Assistant origin to the existing KeepPeek config.toml:
[direct_card]
allowed_origins = [
  "https://home.example.net",
  "https://homeassistant.local:8123"
]

An origin consists of the scheme, host, and port, without a path or trailing slash. Include each origin used to open the dashboard. Restart KeepPeek after editing this configuration. See the Configuration reference for the server’s configuration contract.

Use browser-trusted HTTPS for normal deployments. The card permits HTTP only for local development on localhost, 127.0.0.1, or [::1]. Keep credentials out of the endpoint URL. A private VPN is preferable to exposing the recorder publicly.

KeepPeek allows credentialless CORS on /create and /delete for configured origins. The card sends its Bearer key in the Authorization header and never uses browser cookies. Browser preflights must allow POST, OPTIONS, Authorization, Content-Type, and Content-Encoding; do not substitute a wildcard origin or a Home Assistant media proxy.

Install the card

The distribution contains one self-contained JavaScript module, keeppeek.js, and a metadata file, keeppeek-card.json, with its version, SHA-256 checksum, and byte counts. The module bundles its runtime, icons, and CSS; it does not fetch third-party scripts or fonts.

HACS installation

  1. Choose a KeepPeek release containing the card artifacts. Earlier releases without keeppeek.js cannot install the card.
  2. In HACS, add https://github.com/xnorpx/keeppeek as a custom Dashboard repository.
  3. Download the selected release. If HACS does not register the resource automatically, add /hacsfiles/keeppeek/keeppeek.js as a JavaScript module dashboard resource.
  4. Reload the dashboard and select KeepPeek in the card picker.

The HACS manifest selects the release artifact and hides default-branch downloads. A locally successful build does not make that artifact available through HACS. Custom-repository installation is separate from inclusion in the HACS default store.

Manual installation

  1. Obtain keeppeek.js and keeppeek-card.json from the intended release, or build them locally.
  2. Verify the module’s SHA-256 against the metadata file.
  3. Put keeppeek.js in the www/ directory inside Home Assistant’s configuration directory. Restart Home Assistant if this is the first time you created www/.
  4. Register /local/keeppeek.js?v=VERSION as a JavaScript module resource, replacing VERSION with the artifact’s version.
  5. Reload the dashboard and add the card.

From the KeepPeek repository root, build the artifacts with:

bun run --cwd ui build:home-assistant

The output directory is target/home-assistant-card/dist/. Building from the checkout allows testing in real Home Assistant before publishing a release.

Configure a dashboard

For a YAML-mode dashboard, put the dedicated key under keeppeek_lovelace_token in Home Assistant’s secrets.yaml, then use this configuration:

type: custom:keeppeek-card
endpoint: https://keeppeek.example.net
token: !secret keeppeek_lovelace_token
title: Entrances
sources:
  - source_id: "192.168.1.20"
    title: Front door
    quality: auto
  - source_id: "192.168.1.21"
    title: Driveway
    quality: low
layout: grid
columns: 2
aspect_ratio: "16:9"
show_name: true

Replace the example IDs with stable source IDs advertised by KeepPeek. Camera display names and Home Assistant entity IDs are not substitutes. Home Assistant resolves !secret; the card does not parse YAML or resolve secret references itself. Home Assistant’s secrets.yaml is distinct from KeepPeek’s companion secrets.toml.

In the visual editor, enter the endpoint and access key, then select Load sources to discover available cameras. For storage-mode dashboards, enter the key through this editor instead of using a YAML secret tag. The existing key is never read back into the form: an empty password field with a configured placeholder preserves it. Enter a replacement or use the clear action to change it explicitly.

After saving, test from the actual dashboard browser and network used by the intended viewer. Confirm every selected source is visible to that credential and that an omitted or ungranted camera is not accessible. Start with one camera before increasing the grid size. A configuration that is valid for 16 sources does not guarantee the device can decode 16 streams smoothly.

OptionDefaultBehavior
sourcesRequiredBetween 1 and 16 unique source objects.
sources[].qualityautoSelect auto, low, or high from the server’s available variants.
sources[].titleCamera name or IDOptional title below the video.
titleKeepPeekOptional card heading.
layoutgridgrid displays the selected sources; single provides a camera selector.
columns2Integer from 1 to 4, limited to the visible camera count; the grid stacks below 440 pixels.
aspect_ratio16:916:9, 4:3, or 1:1; video is contained rather than cropped.
show_nametrueShow camera names below the video.
viewliveOnly live video is supported.

Source IDs and titles must be nonblank and at most 160 characters. The editor preserves source titles, quality selections, and unrelated Home Assistant layout metadata across edits. The detailed card reference lists all validation limits and transport details.

Understand the security boundary

The key necessarily reaches the browser. A YAML !secret reference keeps configuration organized, but does not conceal the resolved key from a person who can inspect Home Assistant’s dashboard configuration or runtime. Dashboard editors and installed third-party frontend modules are part of this trust boundary. Use a dedicated User key, not an Administrator key, unless every viewer should have that authority.

The card keeps credentials in memory and does not put them in URLs, browser storage, rendered diagnostics, or console output. That does not prevent Home Assistant from storing its dashboard configuration or browser developer tools from showing the authenticated request header.

The sources list is a display filter, not authorization. KeepPeek’s trusted-local policy still applies: a browser classified as local is an Administrator even when it sends a User key. Narrow access.local_networks when dashboard viewers must be authenticated and restricted. CORS neither changes that policy nor prevents a copied key from being used by another client.

Rotating, disabling, revoking, or expiring the credential invalidates its sessions according to server policy. Authentication failure stops automatic card retries; update the key and reconnect.

When changing camera grants, review the credential’s access in KeepPeek as well as the card’s display list. Sharing a saved KeepPeek dashboard or adding a camera to a group does not make the card an authorization boundary. Test restrictive grants from a client classified as remote; trusted-local Administrator access intentionally follows a different policy.

Connections and troubleshooting

Cards in the same browser document share a connection when their normalized endpoint and key match. Identical source and quality selections also share one subscription. Different credentials never share media. Limits are eight connection identities per dashboard, 64 consumers per identity, and 16 distinct source/quality subscriptions per connection.

Hidden tabs, offscreen cards, removed cards, and changed selections release obsolete demand. Removing the final consumer closes the peer and calls /delete. Reconnects have bounded waits and at most eight automatic attempts, and restore only current subscriptions. Theme changes do not reconnect. Lovelace can temporarily detach cards while rebuilding its responsive columns; those moves may require a clean reconnect before video resumes.

SymptomCheck
The card is missing from the pickerConfirm the installed module and JavaScript module resource URL, then reload.
Connection failsCheck the endpoint, certificate, exact allowed origin, and browser Local Network Access permission.
Access deniedCheck the dedicated key and its enabled/expiry state in KeepPeek.
Unknown source IDUse Load sources and select an ID visible to this credential.
One camera is offlineCheck that camera in KeepPeek; other cards and sources should continue.
Video does not playCheck browser codec support and reachability of KeepPeek’s advertised WebRTC addresses.

Browsers can report CORS denial, an unreachable server, certificate failure, and denied Local Network Access as the same fetch error. The card lists the relevant checks without claiming to identify the exact cause. Do not disable browser security checks to work around it.

Upgrade or roll back

Select the new version in HACS, or replace the manual module and update the resource’s ?v= suffix. Reload all open dashboard tabs to release old module instances. Keep the card and server on compatible releases because the protocol is pre-1.0. For rollback, restore the previous HACS version or manual artifact and reload. Do not register multiple versions of the resource at once.

Test locally and in CI

The lightweight preview uses the real KeepPeek server and synthetic cameras, but emulates the Home Assistant dashboard shell. From the repository root:

bun run --cwd ui test:e2e:prepare
bun run --cwd ui demo:home-assistant

The demo prints its local URL. It does not use the operator’s configuration or cameras.

Test inside real Home Assistant

With Docker running and the repository prerequisites, including Playwright Chromium, installed:

bun run --cwd ui test:home-assistant-container

This prepares the binaries and module, pulls the official Home Assistant 2026.9.1 image pinned by an immutable multi-architecture digest, and runs the tests. To reuse prepared artifacts:

bun run --cwd ui test:home-assistant-container:run

Each runtime scenario onboards a temporary user in actual Home Assistant. The container serves the built module and resolves YAML secret references. KeepPeek, the two synthetic RTSP cameras, and Chromium run on the host, so WebRTC media avoids Docker NAT. Tests cover fresh decoded frames on desktop and mobile, three cards sharing one session/subscription, reconnect, navigation cleanup, themes, source discovery, credential redaction, and saved changes in the real visual editor.

The container uses two CPUs, 2 GiB of memory, a loopback-only HTTP port, and a Docker-managed configuration volume without privileged or device access. Tests remove their own containers, volumes, temporary files, and fixture processes. They never seed internal authentication storage.

On macOS, this suite has been exercised with ARM64 Docker Desktop as a development test. That is not a supported Home Assistant production deployment recommendation. The official macOS installation guide describes Home Assistant OS in a VM; the container installation guide targets Docker Engine on Linux.

CI and release verification

The Home Assistant Container Ubuntu job is required by the UI gate. It reuses the Linux KeepPeek and card build artifacts and the pinned Home Assistant image. Its evidence includes screenshots, sanitized server logs, onboarding notices, and JUnit results, but excludes raw configuration, authentication storage, and credential-bearing browser traces.

The normal ./check.sh remains Docker-free and typechecks the container suite. Run both it and the container command when changing the integration. The container tests record Home Assistant’s exact setup-only WebSocket close notice separately; other runtime errors fail the tests.

Local real-Lovelace tests do not prove HACS delivery. A release check must still install the published module through HACS, exercise it in a disposable Home Assistant instance, and verify upgrade and rollback. A configured CI job is not evidence of a successful hosted run; check the workflow result for the commit being released.

Groups and two-way audio: current status

Live participant groups are not operational in the current server and browser runtime. Settings includes Groups & two-way audio, but its directory reports unavailable. The generated protocol defines list, join, and leave messages; the running implementation does not handle those commands. There are no real group names, participant counts, join sessions, or participant recording states to display. Creation and administration controls explain this limitation.

Do not add an invented [groups] section to config.toml or infer support from generated types. The configuration reference lists the supported settings.

Three different uses of grouping

ConceptCurrent purposeWhat it does not grant
Camera group labels and access grantsOrganize cameras and define which cameras an identity may access.Membership in a voice or conference session.
Saved dashboards and their audienceShare a camera layout and its wall presentation settings with selected viewers.Permission to see cameras outside the viewer’s grants.
Live participant groupsA protocol and design model for named camera collections with optional participant audio/video. The runtime is unavailable.A working walkie-talkie, conference, or group administration feature today.

Use Authentication and access control for camera grants and Live wall for saved layouts. These implemented features work independently of live participant groups.

What the group design describes

The group client scenarios describe server-owned definitions with static camera stream members, optional passwords, and optional live participants. Participant media would have a server-assigned identity and its own stream and recording policy. Camera membership would not change a camera’s ordinary recording policy.

The design is full duplex: it has no floor control, moderator, or server-enforced speaking turn. Push-to-talk means a client gates its own microphone while keeping its publication ready. It is not a server command or a promise that the current browser includes a working radio console.

Those scenarios explain the intended contract, including reconnect and per-participant recording behavior. They are not evidence that the handler, client capture, directory, or recording path has been implemented and qualified. A future implementation needs runtime and device evidence before this chapter can provide operational setup instructions.

Camera talkback is separate

Talkback sends audio to a supported physical camera. A live participant group would exchange media between clients through KeepPeek. Support for one does not imply support for the other.

The current browser Talk control is unavailable. Consult Camera controls for supported physical camera operations and the availability reason shown for the selected device.

How KeepPeek works

KeepPeek puts recording, live media, event storage, and investigation in one local service. The browser is a client of that service. Closing a tab ends its viewing work; it does not stop the server’s configured recording policy.

This chapter explains the current boundaries. Users and design choices contains the product rationale, while the feature chapters provide operational instructions.

Media and event paths

flowchart TB
    Camera[IP camera] -->|Encoded media| Core[KeepPeek]
    Core -->|Record| MP4[MP4 and catalog]
    Core -->|Live WebRTC| Browser[Client]
    MP4 -->|Playback via KeepPeek| Browser

The camera supplies encoded media. KeepPeek understands codecs and timing well enough to route, package, and record those streams without decoding and re-encoding their video in the core. The browser or an external service performs the decoding it needs. Camera control, native events, and media can use different protocol connections, so success on one path does not prove the others are healthy.

Supported native adapters and optional analysis services publish events through a separate path:

flowchart TB
    Source[Camera or analysis service] --> Events[Validate and store event]
    Events --> Review[Review in KeepPeek]
    Events --> Delivery[Rules and MQTT]

The server separates camera/media work, storage work, WebRTC sessions, and integration delivery. Health and queue evidence make stalls and pressure visible. Optional analysis and notification delivery do not become a prerequisite for ordinary recording. Resource exhaustion or failure of the recorder’s own storage and network still needs operational attention.

Persistent data and live state

DataOwner and purposeOperational consequence
Application configurationThe existing config.toml holds supported settings, policies, layouts, and access configuration.Use validated saves or configuration import and preserve the file across upgrades.
Reusable private stringsThe companion secrets.toml supplies supported secret references.Preserve references and protect configuration archives and private restore material.
Encoded recordingsMP4 files in the configured storage paths.Retention, recording policy, storage pressure, and file integrity determine what remains.
Recording and event catalogIndexed recording identity, coverage, events, and related durable investigation state.Recover it consistently with the media and the documented archive procedures.
Images and exportsFeature-owned data under configured paths and retention policies.A playable recording does not imply that an event thumbnail or export is retained.
Notification and MQTT runtime stateBounded process memory for pending work and related runtime history or deduplication.A restart does not replay this work. Persisted rules and provider configuration are separate from delivery state.
Active connections and subscriptionsThe running server and each client connection.Reconnect and resolve current capabilities; a stored preference does not prove a stream is live.

The configuration reference is the authority for serialized fields, defaults, and paths. Do not create separate settings files for layouts or permissions. For a consistent recovery plan, use Backup and restore; configuration export and a full recording archive serve different purposes.

Control, media, and access

HTTP creates and deletes API sessions and provides dedicated administration and diagnostic operations. WebRTC carries typed control messages, data messages, and media. Clients must use the negotiated capabilities and source identities instead of assuming that every camera has the same streams, codecs, events, or controls.

Authentication identifies the caller; authorization determines the operations and cameras that caller may use. The same restrictions apply through a dashboard, deep link, or integration. A hidden UI control is not the security boundary. See Authentication for local trust, named credentials, roles, camera grants, reverse proxies, and revocation.

The server exposes configuration export/apply, backups, recording coverage, logs, and metrics on dedicated HTTP routes. Their methods and authentication requirements are defined by the HTTP contract and implementation; they are not a general REST replacement for the WebRTC control protocol. Custom clients should start with the API overview and verify runtime support for each command they need. The API is pre-1.0 and does not yet promise stable compatibility across versions.

Recording truth and investigation

Live frames, stored coverage, event timestamps, and exported evidence answer different questions. A connected camera can fail to record; a retained event can outlive its image or video; a successful seek does not prove that the surrounding day has no gaps.

Keep combines recorded coverage, selected time, camera identity, and event context. Health screens explain stream progress and recorder evidence. Maintenance inspects persisted files and catalog state. Keep these layers separate when diagnosing an incident so that a browser playback problem does not become an unnecessary archive repair.

Start with Recording and evidence and Camera and stream health. Use Recording maintenance when stored-data evidence warrants it.

Extension boundaries

External services consume media and publish supported events without becoming part of the core recorder. Camera-native adapters provide another event source. Notifications and MQTT consume committed events and maintain their own delivery evidence. Home Assistant’s card connects the browser directly to KeepPeek rather than proxying video through Home Assistant.

Some broader protocol scenarios are implemented only in part. Live participant groups currently have declarations and a UI explanation without a runtime handler. Consult the feature’s availability statement before designing a deployment around it.

Implementation entry points include src/app.rs for startup, src/server.rs for request handling, src/webrtc.rs for sessions and media, and src/storage for recording and catalog behavior. Development and verification instructions are in Contributing.

Users and design choices

KeepPeek is a local-first network video recorder and media gateway. Its core job is to ingest camera media, record it reliably, make live and stored media available, and provide a stable place for events and integrations to meet. It can use analytics events produced directly by a camera, events from an optional external service, or no analytics at all.

That narrow center is deliberate. Inference frameworks, models, accelerators, and licensing change much faster than a recorder should. KeepPeek therefore accepts camera-native events and makes media available through an open protocol instead of deciding which models every installation must run.

This chapter records the product’s users and rationale, including intended directions. Use the feature guide for current screen availability and release readiness for qualification limits. The architecture overview connects these choices to the running service.

At a glance

UserStarting pointWhat they valueKeepPeek’s role
Software ConsumerCommon cameras and a problem to solve, not an integration projectSensible defaults and dependable resultsA complete first-party recording and viewing experience
Small Business SecurityA shop, workshop, office, or yard with needs generic analytics do not fully coverRelevant alerts, dependable evidence, and a solution fitted to the businessA reliable recorder plus a boundary for tailored inference and workflows
Open Source EnthusiastExisting tools and a willingness to connect themReuse, inspectability, and replaceable componentsA composable open core rather than another closed ecosystem
Home Automation EnthusiastHome Assistant, MQTT, HAP, automations, and devices that should cooperateLocal orchestration, useful events, and stable identitiesA camera and event source for the automation system already in place
Residential SysadminA home server, NAS, container host, or automation stackReliability, diagnostics, and repeatable operationA native service with stable interfaces and health evidence
Residential Power Optimization EngineerCameras, networks, and compute that can be arranged deliberatelyControl, measurement, and efficient placement of workAn observable media plane that can feed the right hardware

Software Consumer

I just want it to work.

Profile

  • Starting point: A set of common cameras and an expectation that recording, live view, and review form one product.
  • Primary goal: Install it, add cameras, and trust that footage will be available when needed.
  • Constraints: Little interest in protocols, model runtimes, or maintaining an integration stack.

How they use KeepPeek

  1. Set up: Follow a guided camera workflow with safe defaults.
  2. Operate: Use the Svelte web application for live viewing and recording review.
  3. Investigate: Receive a clear diagnosis and a next action instead of raw protocol state.
  4. Extend: Choose an optional free or paid service only when it adds something useful.

What success looks like

Core recording works without a cloud account, an AI subscription, or decisions about model and accelerator compatibility.

Product implications

The first-party experience must hide optional architecture until it matters, without removing the open interfaces that let other users go further.

Follow Get started from installation to a first camera, then verify both live stream health and recorded coverage.

Small Business Security

Custom detection for the way the business actually works.

Profile

  • Starting point: A shop, workshop, office, warehouse, or yard with existing cameras and a specific operational risk.
  • Primary goal: Combine dependable recording with alerts and search events designed around the way the business operates.
  • Constraints: Privacy, false alarms, existing equipment, available compute, response time, and a solution that staff can operate without becoming AI specialists.

How they use KeepPeek

  1. Establish the baseline: Deploy KeepPeek for local recording, live viewing, health, and portable MP4 evidence.
  2. Define the need: Identify the events, objects, zones, schedules, and responses that matter to the business rather than starting with a generic model feature list.
  3. Add tailored inference: Run a separate service that subscribes to selected WebRTC streams and publishes normalized events back to KeepPeek.
  4. Connect the workflow: Route useful events into the KeepPeek timeline, notifications, Home Assistant, MQTT, webhooks, or another business system.

What success looks like

The business receives relevant, reviewable events without making recording dependent on the custom model. The inference service can evolve with the business while KeepPeek remains the stable media and evidence layer.

Product implications

Custom business logic belongs in an external protocol plugin, not a private fork of the recorder. That keeps camera credentials and storage ownership inside KeepPeek while allowing a tailored free or paid service to use the hardware, models, and support arrangement appropriate for the customer.

Need a tailored solution? Contact KeepPeek to discuss custom AI, inference, integrations, and deployment for your specific business needs.

Open Source Enthusiast

Because someone else already wrote it.

Profile

  • Starting point: A collection of proven open-source tools, libraries, models, and devices.
  • Primary goal: Reuse the best available component for each job and replace it when something better appears.
  • Constraints: Avoid proprietary lock-in and duplicated implementations.

How they use KeepPeek

  1. Set up: Combine KeepPeek with existing cameras and familiar self-hosted infrastructure.
  2. Operate: Use the included viewer or another client that implements the public protocol.
  3. Investigate: Inspect the server, wire contract, and generated bindings.
  4. Extend: Build or adopt an independent detector, transcoder, event forwarder, or viewer.

What success looks like

The recorder remains a useful shared foundation while every fast-moving or specialized component can come from somewhere else.

Product implications

The licensing model protects improvements to the server while allowing independent clients and services to use their own licensing and business model.

Home Automation Enthusiast

Because the porch light should have opinions.

Profile

  • Starting point: A local automation system built around Home Assistant, MQTT, HAP/HomeKit, notification services, and a growing collection of devices.
  • Primary goal: Turn camera events and recording health into useful household actions without making the automation controller proxy video.
  • Constraints: Stable device identities, local control, understandable event semantics, and automations that recover cleanly after either system restarts.

How they use KeepPeek

  1. View: Put selected live streams, events, and timeline review in a Home Assistant dashboard.
  2. React: Forward normalized camera-native or external analytics events through MQTT or webhooks.
  3. Present: Use an independent HAP bridge when selected cameras should appear in Apple Home.
  4. Observe: Scrape Prometheus metrics and include recording or camera health in automations.

What success looks like

A meaningful camera event can turn on a light, send a notification, or update a dashboard while KeepPeek continues recording independently. Reconnects do not silently retarget an automation to a different camera, and media does not take an unnecessary trip through the automation hub.

Product implications

KeepPeek needs stable source IDs, normalized event types, backfill after disconnects, direct browser media, and clear integration boundaries. It should participate in a home automation system without trying to replace one.

Residential Sysadmin

Unnecessarily professional.

Profile

  • Starting point: A self-hosted environment with monitoring, backups, and perhaps Home Assistant, MQTT, or a reverse proxy.
  • Primary goal: Operate cameras like a small but well-run production service.
  • Constraints: Limited maintenance time and no separate operations team.

How they use KeepPeek

  1. Set up: Install one native service or container and validate each camera before trusting it.
  2. Operate: Use the web interface for normal work and inspect health evidence, reason codes, and bounded logs when something changes.
  3. Investigate: Distinguish camera, transport, recording, and external-service failures.
  4. Extend: Attach integrations without patching the recorder or sharing its storage internals.

What success looks like

The system can be monitored and recovered using explicit server, storage, camera, stream, ingress, recording, and WebRTC evidence instead of guesses based on whether a video tile happens to be playing.

Product implications

KeepPeek exposes stable health reason codes, bounded logs, and documented interfaces. Rust supports a predictable native service, while the open API and external-service boundary keep upgrades and failures isolated.

Residential Power Optimization Engineer

Profile

  • Starting point: A mixed set of cameras and compute devices, each with different power and performance characteristics.
  • Primary goal: Put capture, storage, decoding, and inference where each task runs best.
  • Constraints: Power, heat, bandwidth, accelerator availability, privacy, and equipment already owned.

How they use KeepPeek

  1. Set up: Run KeepPeek close to the cameras and storage.
  2. Operate: Record camera-native streams without requiring an inference workload on the server.
  3. Investigate: Use explicit health and media evidence to find transport, recording, or downstream-service problems.
  4. Extend: Subscribe to WebRTC media from whichever device should decode or analyze it.

What success looks like

Recording stays dependable while optional work can move between a server, GPU host, desktop, or repurposed edge device without redesigning the recorder.

Product implications

The server must remain efficient and observable, while its media boundary must not assume that inference runs in the same process or even on the same machine.

Cameras, brands, and protocols

KeepPeek assumes that a home or small business may contain cameras from several generations and manufacturers. Compatibility is therefore organized around working protocols and verified media, not a logo on the enclosure. A discovered camera is a candidate, not proof that authentication, streaming, controls, and events all work.

Brand integration paths

Camera familyIntegration pathCurrent meaning
ReolinkDedicated reo-proto backend for Baichuan over TCP or UDP, with ONVIF and RTSP fallback pathsFirst-class discovery, authentication, main/sub media, supported controls, and native camera events
Dahua and compatible OEM/rebrand modelsONVIF discovery and profile queries with RTSP media through Retina or explicit stream URLsA supported standards-based family with coverage for common H.264/AAC and H.265/G.711 stream patterns; each OEM model and firmware still requires validation
Tapo (supported models)ONVIF discovery and profile queries where available, with RTSP media through Retina or explicit stream URLsA supported mainstream vendor through the standards path; exact capabilities still depend on model and firmware
Hikvision / AnnkeSADP discovery, ONVIF/RTSP media, and a plain-Rust ISAPI event workerNative event support depends on model, firmware, channel mapping, and enabled camera rules; see the ISAPI guide
Other ONVIF camerasONVIF discovery and device/profile queries, then RTSP mediaSupport depends on the device’s implementation of the standards and must be validated per model
Generic or unknown camerasExplicit main and/or sub RTSP URLsRecording can work without a recognized brand; metadata, controls, and native events may be limited

Support a pragmatic subset, not every camera

KeepPeek does not aim to support every camera ever sold. The camera market contains too many firmware variants, incomplete protocol implementations, cloud-only products, and models that reuse a brand name while behaving differently. Claiming universal compatibility would create a large, fragile surface and make failures harder to diagnose.

The practical goal is excellent support for a well-known subset: dedicated integration where that creates real value, as with Reolink; tested standards paths for selected Dahua-family and Tapo models; and a clear ONVIF or RTSP route for other cameras that implement those protocols correctly. A brand row means KeepPeek deliberately tests and maintains a useful path for that vendor family. It does not mean every model, firmware version, codec, control, cloud feature, or native analytics event is supported.

Compatibility is described at three levels:

  1. Supported vendor and model: The documented firmware and configuration pass discovery or manual setup, authentication, main/sub streaming, keyframe detection, MP4 recording, and independent playback validation.
  2. Protocol-compatible camera: The camera exposes usable ONVIF and RTSP behavior, but its exact model has not yet earned a maintained compatibility claim.
  3. Manual RTSP camera: KeepPeek can record a validated stream URL even when discovery, controls, metadata, and native camera events are unavailable.

New camera support should be added from measured evidence and a maintainable protocol path, not by collecting one-off exceptions for every device. This keeps setup predictable for common cameras while leaving an honest manual path for experienced users.

Protocol responsibilities

ResponsibilityProtocol or componentWhat KeepPeek uses it for
DiscoveryONVIF WS-Discovery, Reolink discovery, Hikvision SADP, and bounded local port probesFind candidate devices and collect safe identity hints
Device queries and controlONVIF SOAP plus supported Reolink HTTP or Baichuan commandsRead device information, profiles, capabilities, imaging state, and supported PTZ controls
Generic media ingestRTSP/RTP through Retina over TCP or UDPReceive standards-based camera media, including explicit main and sub URLs
Reolink media ingestBaichuan through reo-proto over TCP or UDPReceive complete Reolink video and audio frames without requiring RTSP
Native camera eventsReolink Baichuan and compatible Hikvision/Annke ISAPINormalize supported alarms into stored/live events; classifications require event evidence, and image association is protocol-specific
Viewer and service deliveryWebRTC with RTP and reliable/unreliable data channelsForward live media, control, events, and publications without exposing camera credentials

WebRTC is the client-facing media gateway protocol, not a requirement imposed on cameras. Cameras can continue speaking their native protocol to KeepPeek while browsers and independent services use one consistent downstream API.

Media and transport policy

KeepPeek accepts H.264 and H.265 video and supported AAC, G.711 A-law, G.711 mu-law, and ADPCM audio. It can retain main and sub profiles and records camera-native streams without re-encoding. When backend selection is automatic, Reolink uses reo-proto and other cameras use Retina. TCP is the conservative default; UDP remains available when a camera requires it or validation shows it is the better path. Explicit RTSP URLs take precedence over URLs discovered through ONVIF.

Discovery alone is never the final compatibility test. The camera setup workflow verifies credentials, requested profiles, incoming frames and keyframes, finalized MP4 output, and independent FFmpeg decoding before recommending a backend or adding a camera to the live configuration.

How this fits the users

  • Software Consumer: Gets discovery and safe automatic defaults where possible, plus a validated manual RTSP path when the camera is not recognized.
  • Small Business Security: Can retain an existing mixed camera fleet and add custom inference only to the streams and locations that need it.
  • Open Source Enthusiast: Can reuse ONVIF, RTSP, Retina, and vendor-specific protocol work and add another adapter without changing the WebRTC client contract.
  • Home Automation Enthusiast: Gets stable camera and event identities across supported vendors without making the automation hub understand each camera protocol.
  • Residential Sysadmin: Gets one inventory and one observable recording path across a mixed fleet, with the selected backend visible in diagnostics.
  • Residential Power Optimization Engineer: Can choose main or sub media, compare transports, and keep camera-native inference on the camera when that is the most efficient location.

Key design choices

Keep inference outside the server

KeepPeek does not build object detection, face recognition, license-plate recognition, image description, or model management into the server. That does not mean inference must happen in a separate assisted service. A camera with built-in motion, person, vehicle, or other analytics can send those events directly to KeepPeek, which validates and stores them and presents them beside recorded media.

KeepPeek supports three independent modes:

  1. Recording only: Record and view media without enabling analytics anywhere.
  2. External analytics: Forward media to an optional service and accept the events it publishes.
  3. Camera-native analytics: Use events produced by the camera without another inference service.

The repository includes a Python object-detection service demo for the external analytics path. It uses aiortc to subscribe to encoded KeepPeek video over WebRTC, decodes selected frames with the user-installed FFmpeg executable, runs inference, and publishes normalized events back to KeepPeek. It is a reference implementation and CI example, not a supported detector product.

Forward encoded streams, not inference JPEGs

KeepPeek does not provide a one-image-at-a-time inference API as its detector boundary. A common integration with an image-oriented service such as CodeProject.AI uploads one JPEG for each inference request. That is convenient for a caller that already owns an image, but it is inefficient when the source is a continuous H.264 or H.265 camera stream.

For an ordinary encoded camera stream, a server-generated snapshot pipeline would be:

camera stream -> KeepPeek video decode -> JPEG encode -> HTTP upload -> detector JPEG decode -> model

That path requires KeepPeek to bundle video decoders, convert full-resolution frames into pixels, encode and allocate a new JPEG for every sample, and transfer it only for the detector to decode it again. Repeating that work across several cameras and inference frames per second adds CPU use, memory traffic, latency, and another codec and licensing surface to the core service.

KeepPeek instead uses the media gateway it already has:

camera stream -> KeepPeek encoded forwarding -> detector video decode and sampling -> model

Forwarding encoded access units is much cheaper for the core than decoding pixels and producing JPEGs. The inference service keeps one decoder running near the model, chooses its own sampling rate, drops stale frames, and can batch work across cameras. This preserves timing and stream continuity while letting the service select an H.264 or H.265 decoder suited to its hardware.

JPEG remains useful after inference. A service or camera can attach a deliberately selected snapshot to a durable event, but KeepPeek does not turn every candidate inference frame into a JPEG. Event evidence is bounded output, not the transport used to feed the model.

User fit: Consumers avoid hidden image work; small businesses can run efficient continuous inference; open-source developers keep decoding beside model scheduling; home automation enthusiasts receive normalized events without decoding video; sysadmins keep image codecs out of the recorder; power optimizers avoid repeated decode, JPEG encode, and JPEG decode work.

Inference changes quickly and brings hardware drivers, large model files, model-specific licenses, and different privacy decisions. Keeping it out of the recorder lets camera firmware or an external model change without coupling its lifecycle to recording and live viewing. It also avoids making extra AI compute and a cloud account mandatory costs of running an NVR.

User fit: The consumer can use intelligence already present in a camera; a small business can commission inference for its specific risks; the open-source enthusiast can swap model stacks; the home automation enthusiast consumes the resulting normalized events; the sysadmin gets a smaller failure domain; the power optimizer chooses the best compute target.

Keep recording and media routing at the center

KeepPeek owns the durable and slow-changing responsibilities: camera ingest, recording, stable media identity, timeline access, health evidence, authorization, and validation of events received from cameras or published back by services. Specialized inference can happen inside a camera or in a replaceable service around that center; transcoding likewise remains outside the core.

This boundary keeps the most important promise narrow: footage should still be recorded when an optional service is overloaded, incompatible, or offline. External services receive media through the gateway instead of opening camera sessions or writing KeepPeek’s database and recording directories directly.

User fit: Consumers get one accountable recorder; small businesses keep custom models independent from evidence; open-source enthusiasts compose services; home automation enthusiasts get a stable event source; sysadmins retain one recording authority; power optimizers can place each workload independently.

Why WebRTC fits KeepPeek

flowchart LR
  C1[Supported camera] -->|Encoded stream| K[KeepPeek<br/>Record, route, validate]
  C2[Supported camera] -->|Encoded stream| K
  K -->|MP4| S[(Recording storage)]
  K -->|WebRTC media| B[Browser or viewer]
  K -->|WebRTC media| I[Inference service]
  I -->|Events| K
  K -->|Normalized events| A[Home automation]

WebRTC is a mature, widely implemented protocol suite rather than one vendor’s media stack. It is built into browsers, and interoperable implementations are available for most common service languages. Examples include:

An independent viewer, inference service, or protocol plugin can therefore choose an implementation that fits its language and deployment instead of adopting KeepPeek’s Rust stack. The author of KeepPeek is also well versed in WebRTC protocols and browser behavior. That experience reduces risk in the difficult parts of a media gateway: SDP negotiation, ICE, RTP packetization, encryption, congestion handling, SCTP, and interoperability between native and browser clients.

WebRTC also gives KeepPeek two complementary delivery paths inside one session:

  • RTP over UDP carries latency-sensitive audio and video without forcing every frame through an ordered reliable stream.
  • Data channels over SCTP carry ordered reliable protobuf control plus reliable or discardable binary payloads. They can also deliver encoded media when RTP is not the right binding for a client.

This combination provides flexibility without inventing separate protocols for viewers, publishers, transcoders, and inference services. It also gives browsers clean integration through RTCPeerConnection, negotiated media tracks, and data channels rather than a custom framing and playback stack.

The protocol boundary separates camera access from compute placement. An inference client can run beside KeepPeek, on a GPU machine elsewhere on the private network, or on a repurposed device. An older iOS device, for example, could run a small client application that embeds a compatible WebRTC implementation, subscribes to an encoded stream, and runs a suitable local model without receiving camera credentials or direct access to the recording store. Results return through the same API as events.

User fit: Consumers receive browser-native live playback; small businesses can attach custom inference without exposing camera credentials; open-source developers can choose a language-native implementation; home automation enthusiasts get direct media and event paths; sysadmins manage one observable boundary; power optimizers can move workloads without moving storage.

Fit into home automation instead of replacing it

KeepPeek should be a focused recorder and media gateway inside a larger home or small-business system, not another general-purpose automation hub. Its open API, stable source identities, normalized events, and observability endpoints let established tools do the jobs they already do well.

IntegrationConnection boundaryIntended role
Home AssistantA Lovelace card connects directly to KeepPeek over WebRTC; an optional integration can expose entitiesPut live cameras, events, timelines, and recording health in an existing dashboard without proxying media through Home Assistant
MQTTA data-only event forwarder subscribes to normalized KeepPeek events and publishes them to the user’s brokerFeed automations with camera-native or external analytics events, optional snapshots, and disconnect backfill without decoding video
HomeKit Accessory Protocol (HAP)An independent bridge translates selected KeepPeek capabilities into HomeKit accessoriesPresent compatible camera streams, events, doorbells, and controls to Apple Home while keeping HAP-specific behavior outside the recorder core
PrometheusThe monitoring system scrapes GET /metricsBuild dashboards and alerts for server, storage, camera, stream, recording, and WebRTC health

Home Assistant and MQTT designs build on the public API and normalized event model. HAP is an intended bridge boundary rather than a currently implemented server feature. A HAP bridge can be free, community-maintained, or commercial without requiring HomeKit code or credentials inside the KeepPeek process.

User fit: The consumer can choose integrations by name; a small business can connect security events to operational workflows; the open-source enthusiast can reuse mature ecosystem projects; the home automation enthusiast gets stable events and direct media; the sysadmin can connect familiar dashboards, brokers, and monitoring; the power optimizer can place each bridge on suitable hardware.

Use Rust for the server

A recorder is a long-running, concurrent parser of untrusted network and media data. Rust provides native performance and control over allocation without a garbage collector, while its ownership and type systems prevent broad classes of memory and concurrency errors. It also supports native services on Linux, macOS, and Windows and has reusable libraries for the protocols KeepPeek needs.

The trade-off is a steeper contribution path and longer compile times than some managed languages. That cost is accepted in the core because predictable resource use and memory safety matter more than optimizing for the fastest first prototype.

User fit: Consumers benefit from reliability without needing to know the language; small businesses get a dependable evidence service; open-source contributors get compiler-checked interfaces; home automation enthusiasts get a dependable event source; sysadmins get a predictable native process; power users get low overhead.

Keep the core small and focused

KeepPeek is intended to be a recorder and media gateway, not a bundled appliance stack. The core service should not need a gigabyte or more of memory merely to start and perform ordinary recording and forwarding. That baseline is different from the configurable in-memory cache used for pre-event recording. KeepPeek retains recent encoded frames so an event-triggered recording can include what happened before the event began.

The cache grows with the number of buffered streams, their bitrates, the enabled main and sub profiles, and the configured pre-event duration. A longer window across many high-bitrate cameras can therefore use substantial memory by design. Keeping those frames encoded makes the cache much cheaper than retaining decoded images, but it does not make the cache free. A hard total-RAM guarantee must wait for representative measurements and must always state the camera, bitrate, profile, and pre-event-cache assumptions.

Keeping inference runtimes, model weights, video encoders and decoders, and a packaged desktop browser out of the server protects its binary size, memory footprint, startup time, and attack surface. An optional detector or transcoder pays for its own dependencies and resources without making every KeepPeek installation pay the same cost.

User fit: The consumer avoids unused runtime costs; a small business can scale custom inference separately; the open-source enthusiast can compose specialized tools; the home automation enthusiast keeps orchestration in the existing hub; the sysadmin gets a service suited to modest hardware; the power optimizer can account for each workload.

Run on Linux, macOS, and Windows

The recording, media gateway, health, and public API behavior should remain consistent across Linux, macOS, and Windows. Distribution should follow the convention that makes each platform feel native rather than forcing one packaging model everywhere.

PlatformFirst-class distribution
LinuxA minimal multi-architecture Docker image for linux/amd64 and linux/arm64, published to GHCR with a persistent configuration volume. Native archives remain useful, but Docker is the primary polished Linux deployment.
macOSA signed and notarized KeepPeek.app in a drag-to-install DMG, with a per-user launchd service. The current release target is Apple Silicon.
WindowsAuthenticode-signed x86-64 and ARM64 binaries plus a signed NSIS installer that can register, upgrade, and remove the Windows service.

Public macOS and Windows release installers should be signed, timestamped where supported, and verified by the release workflow before publication. Checksums remain useful for every platform, including Linux images and native archives.

An operations-friendly combination: A focused Rust service is straightforward to build across platforms, first-class Docker images make Linux deployment repeatable, and the optional Prometheus GET /metrics endpoint plugs into standard dashboards and alerts. Together these make KeepPeek a strong choice where portability, container deployment, and conventional observability matter.

The Svelte interface runs in the browser and provides the same administration, live view, and recording workflow regardless of which operating system hosts the server. Platform-specific code should stay behind narrow boundaries so a feature does not quietly become available on only one host.

User fit: Consumers avoid operating-system lock-in; small businesses can deploy on existing hardware; open-source contributors can port and test explicit platform boundaries; home automation enthusiasts can place bridges beside their existing controllers; sysadmins can use the machine already running the home; power optimizers can choose hardware and operating system independently.

Store recordings as MP4

KeepPeek stores encoded video and supported audio in ordinary .mp4 files rather than a proprietary media container or database blob. Finalized segments can be copied, inspected, and played with established tools such as VLC, FFmpeg, and ffprobe, provided that the tool supports the codecs used by the camera. Basic access to a recording should not require KeepPeek to remain installed or an export into a private intermediate format.

The files use fragmented ISO MP4. Each file starts with its initialization metadata and continues with keyframe-aligned media fragments. This structure lets KeepPeek flush recording progress, index exact byte ranges for timeline seeks, and serve only the required fragments while the file itself remains a standard media object. Active segments use a temporary .mp4.active suffix and become .mp4 files when finalized without re-encoding or remuxing them.

The recording catalog stores identity, time, paths, events, and fragment indexes; encoded media stays in the MP4 files on disk. The catalog makes review faster, but it does not hold footage hostage inside a KeepPeek-specific database format.

MP4 is a container, not a codec. An H.265 recording still requires an H.265-capable VLC build, browser, or other player. Choosing MP4 improves interoperability without forcing KeepPeek to bundle the video decoders and encoders deliberately kept outside the core service.

User fit: The consumer can open a finalized segment in VLC; a small business can hand over portable evidence; the open-source enthusiast can reuse the MP4 ecosystem; the home automation enthusiast can attach standard evidence files to downstream workflows; the sysadmin gets files that fit normal backup workflows; the power optimizer can inspect recordings with standard tools.

Keep video codecs out of the core service

KeepPeek is codec-aware but does not bundle a video decoder or encoder. It understands enough H.264 and H.265 structure to receive encoded camera frames, identify configuration and keyframes, write MP4, and forward or repacketize media. It does not decode those frames into pixels or re-encode them inside the core service.

Bundling codec implementations would introduce patent and distribution questions that vary by codec, build, use, and jurisdiction. It would also bring substantial native dependencies, security surface, CPU or accelerator policy, and binary and memory cost into every installation. Keeping that machinery outside the core avoids silently inheriting the licensing and packaging choices of a particular FFmpeg or codec-library build.

On desktop, the first-party experience relies on Chrome or Chromium and the operating system’s media stack for video decoding and rendering. The UI asks the browser which RTP and MP4 codecs it actually supports and selects a compatible camera stream. Svelte provides the application UI; the browser provides the desktop runtime, media pipeline, accessibility primitives, and hardware decode path, so KeepPeek does not need to ship an Electron-style browser or codec pack.

Browser support is still a real boundary. H.264 is the broad compatibility path, while H.265 availability depends on the browser build, operating system, and hardware. When a camera-native stream cannot be decoded by the viewer, the user can select an H.264 substream or run an independent transcoding service that publishes a compatible variant back through the media gateway. The transcoder owns its codec dependencies, resource cost, and licensing decisions.

User fit: The consumer opens the interface in Chrome or Chromium without a KeepPeek codec bundle; a small business can use standard workstations or commission a specific transcoder; the open-source enthusiast can choose codec tooling deliberately; the home automation enthusiast can consume event metadata without decoding video; the sysadmin keeps codec packages out of the recorder; the power optimizer can use viewer-side hardware decoding.

Use Svelte for the first-party interface

KeepPeek needs a responsive browser interface for live state, timelines, health, and configuration, but it does not need a second application server. Svelte 5 and SvelteKit can produce a static client, use compiled reactivity for frequently changing media state, and consume generated TypeScript protobuf bindings without introducing a separate client state framework.

The browser becomes the cross-platform user interface while Rust remains responsible for server truth. The trade-off is a separate frontend toolchain, but that separation also keeps presentation changes out of the media process and makes the interface approachable to web contributors.

User fit: Consumers get the complete graphical product; small businesses can adapt focused operational views; open-source contributors can work in a familiar web stack; sysadmins get the same UI on every host; home automation enthusiasts can use direct dashboard clients; advanced users remain free to implement another client against the API.

Make setup AI-assistable without making AI a dependency

KeepPeek should be easy for an AI assistant to configure, but the running recorder should not need an assistant or model. The repository’s KeepPeek Camera Setup Skill turns camera discovery, authentication checks, stream tests, MP4 validation, transport selection, and final verification into an explicit, reusable workflow.

The Skill is constrained rather than magical: credentials stay outside the conversation, generated configuration is staged, media is independently validated, and camera-side changes require human approval. Structured configuration, stable capability messages, reason codes, metrics, and focused command-line tools make those steps understandable to both people and assistants.

User fit: Consumers can receive guided setup; small businesses get a repeatable custom deployment process; open-source contributors can improve the workflow; home automation enthusiasts get repeatable integration setup; sysadmins get a documented runbook; power users can automate repeatable experiments without turning the recorder into an AI appliance.

Connecting users to choices

UserMost relevant choicesWhat those choices enable
Software ConsumerFirst-party Svelte UI, common cameras, MP4, browser decoding, setup SkillInstall, record, review, and diagnose without assembling an integration stack
Small Business SecurityStable recording core, encoded-stream inference, open API, protocol pluginsCommission tailored analytics and workflows without making evidence depend on the model
Open Source EnthusiastAGPL server, MIT API, documented WebRTC, replaceable external servicesReuse, inspect, replace, and improve each component
Home Automation EnthusiastStable identities, normalized events, Home Assistant, MQTT, HAP, PrometheusConnect cameras to local dashboards, alerts, and automations without proxying media
Residential SysadminRust, small core, native packaging, Docker, health evidence, standard MP4Operate and recover a predictable service using familiar infrastructure
Residential Power Optimization EngineerEncoded forwarding, external inference, browser codecs, flexible placementPut storage, decoding, inference, and integrations on the most suitable hardware

Boundaries and non-goals

  • KeepPeek is not a model host, training environment, detector marketplace, or GPU manager.
  • KeepPeek does not expose periodic server-generated JPEGs as its native inference API.
  • Camera-native analytics events must work without an additional assisted AI service.
  • Recording and ordinary viewing must not require camera AI, an external AI service, or a cloud service.
  • A protocol plugin does not receive direct access to internal databases, recording paths, or camera credentials.
  • The core service does not bundle a video decoder, encoder, codec pack, or desktop browser runtime.
  • MP4 storage improves file portability but does not guarantee that every player can decode every camera codec.
  • A numeric memory limit is not a published guarantee until representative workloads are measured. Baseline service overhead should remain modest, while total memory also includes the configured encoded pre-event cache and scales with stream count, bitrate, enabled profiles, and retention duration.
  • The MIT API permits independent commercial clients and services; it does not exempt private modifications to the AGPL server.
  • KeepPeek does not try to replace Home Assistant, an MQTT broker, HomeKit, or a monitoring system.
  • AI-assisted setup must remain reproducible by a person and must not expose secrets or silently change camera settings.

Release readiness and known limitations

KeepPeek is in Alpha qualification. POC and MVP gates are complete; Alpha implementation issue closure does not establish the early-adopter matrix or a feature freeze. Passing automated tests is necessary, but it is not enough to call a camera recorder production-ready. The promotion decision belongs to the tested release build and representative deployment recorded in the active Alpha gate #145. The roadmap describes the subsequent release phases.

What automated validation proves

The repository gate builds the Rust workspace and browser application, runs Rust, TypeScript, Svelte, browser, real-media, and Playwright tests, checks formatting and dependencies, and validates the versioned Paper scenario and visual-harness manifests.

Focused benchmarks cover latency and memory budgets for recording coverage, operational events, notifications, MQTT enqueue, storage, and event lookup. Some are ignored tests or standalone Cargo benchmarks and require separate explicit runs; a green ordinary check or CI run does not establish their results. Record the exact benchmark command, build, workload, host, and measurements with the qualification evidence.

Those checks prove deterministic contracts and regression fixtures. They do not prove that a particular camera firmware, network, disk, browser, reverse proxy, notification account, or broker will behave correctly under sustained load.

Paper reference integrity tests check the exported design bundle, scenario identities, assets, dimensions, hashes, and shared tokens. They do not by themselves compare every production page with every Paper artboard. Story-based visual checks and route-level browser checks cover their declared fixtures. Treat the accepted mobile Keep proposal as a design reference and verify the implemented workflow on the target device; a reference file is not deployment evidence.

Release and dependency integrity

Cargo.lock is a tracked release input. Direct Cargo requirements name a compatible version, and repository build, test, benchmark, container, and release commands use --locked. CI runs cargo audit --deny warnings without advisory exceptions.

The UI and visual harness intentionally do not track JavaScript lockfiles. Every direct Bun dependency uses an exact version, while transitive dependencies resolve from the public npm registry on each clean install. CI creates a separate temporary lockfile for each manifest with dependency scripts disabled, runs bun audit, and rejects high or critical advisories. This policy detects current ecosystem breakage but does not make JavaScript transitive resolution reproducible between runs.

The object-detection example intentionally tests the newest compatible Python packages. CI uses Python 3.12 and resolves requirements.txt through pip-audit without installing the full model runtime in the audit job.

The embedded CCTV Camera Database comes from the v2.8.0 release archive. The build verifies its SHA-256 digest before opening the ZIP. Container builds use Bun 1.4.0 and immutable base-image digests, then CI starts the production image and probes its HTTP listener. A version tag can create release artifacts only when it points to a commit on main and that exact commit has a successful push CI run.

Representative deployment matrix

Before promotion, record exact evidence for the deployment that will rely on KeepPeek:

  • at least one direct RTSP or ONVIF camera and the established native camera path;
  • main and sub streams, including browser-compatible and incompatible codec profiles;
  • desktop and mobile browser workflows;
  • local access and remote access through the intended VPN or reverse proxy;
  • healthy, stale, reconnecting, recording-failed, storage-pressure, and recovered states;
  • events with and without images, revised events, and a dense event day;
  • Pushover success, failure, and retry plus MQTT success, outage, and recovery;
  • normal, partial, failed, cancelled, retried, and independently decoded exports.

This matrix documents tested configurations. It is not a universal camera, browser, broker, or provider support claim.

Continuous recording soak

Run the final release build long enough to cross normal segment finalization, catalog maintenance, camera reconnect, provider retry, and storage cleanup cycles. During the soak:

  • account for every unexpected recording gap with camera, writer, storage, or catalog evidence;
  • verify primary recorded playback remains at source rate while timelines refresh and exports run;
  • monitor memory, recording databases, thumbnails, export jobs, the in-memory notification outbox, MQTT outbox, logs, threads or tasks, file descriptors, sessions, and browser object URLs for bounded growth;
  • restart and verify that notification runtime state, the MQTT outbox and deduplication history, access audit/activity, and active sessions reset; credentials and grants, durable operational events, export jobs, and recording catalog state recover according to their documented contracts;
  • rerun the complete workflow without manual database or recording-file edits.

Stop qualification on any silent recording-loss path, remote authentication bypass, secret leak, indefinitely running job, or open release-blocking defect.

MQTT restart rehearsal

Use a disposable broker and synthetic event on an isolated installation. Interrupt broker access, publish the event, and observe the pending count before restarting KeepPeek. After restart, verify that the pending outbox is empty. Restore broker access and confirm that the old pending publication is not replayed. Emit a new event and verify delivery and consumer deduplication by (instance_id, event_id, revision). Confirm that the original durable event is still available in KeepPeek and that recording continued independently. QoS does not make this outbox durable; downstream recovery must not assume replay across a KeepPeek restart.

Current limitations

Camera support is evidence-based

Discovery does not prove authentication, stable media, keyframes, or valid recordings. Validate each requested stream through a finalized MP4 and independent decode before relying on it. Prefer reo-proto over TCP for a Reolink camera only after that path passes; use RTSP over TCP for a generic ONVIF camera unless measured evidence supports another transport.

Browser codec support varies

KeepPeek stores camera media without re-encoding it. A browser may therefore record or index a main stream that it cannot decode. Keep a broadly compatible H.264 substream for live view and review, and treat a truthful incompatibility message as different from missing footage. Automatic transcoding remains separate work.

Configuration bundles do not archive recording media

KeepPeek provides direct ZIP export and validated application of config.toml and plaintext secrets.toml, with restart activation and automatic startup recovery. recordings.db, recording MP4s, and thumbnail JPEGs remain a separate archive responsibility. A recovery rehearsal must test both the sensitive KeepPeek configuration bundle and the recording archive. See Backup and restore and the recording archive recovery procedure. There is no public importer for an unrelated recording archive, no general catalog downgrade command, and no automatic adoption of unknown MP4s. A validated index rebuild applies only to eligible existing catalog recordings.

Restart resets runtime work

Notification inbox/history, retry work and delivery counters; MQTT pending publications and deduplication; and access sessions, audit history and last-use activity are not durable. Restart does not replay those queues. Capture required operational evidence before restarting and verify the intended behavior afterward. Durable recording events and settings have separate recovery contracts; see What survives a restart.

Maintenance has filesystem and recovery boundaries

Manual maintenance is destructive and uses confirmed whole-recording selections. Native Windows mutation targets NTFS with persistent ACLs; ReFS is rejected. Structural container inspection does not certify complete media decodability or immutable content. Unknown files are not adopted or deleted automatically. Review Recording maintenance for cooperative deadlines, same-account interference limits, interrupted-work handling, and unqualified scenarios.

Access roles are intentionally fixed

Remote access supports Administrator and User; custom roles are not available. Administrators can restrict a User credential to selected camera groups and individual cameras in Settings → Access & roles → User access. The server enforces these grants for media, events, coverage, and camera controls. Dashboard audiences remain separate and do not grant camera access. Saving a grant change closes the affected User’s existing sessions; stale saves retain the draft and report a conflict. The default grant includes all current and future cameras.

Trusted-local clients remain Administrators. Exclude an address from access.local_networks and require a User credential when that client needs camera restrictions. Hidden navigation is not an authorization boundary. See Camera and dashboard access.

An access change during dashboard initialization can leave an expired-session error visible until the page is reloaded. The invalid session remains denied by the server. Follow the reconnect steps and verify the new grants after sign-in; this does not establish that the UI race has been resolved.

Detection remains external

KeepPeek records and presents camera-native or externally published events. It does not own object, face, or license-plate detection, model management, zones, masks, or training. Recording and health must remain useful when an external analysis service is absent.

Promotion record

The owner records the exact release commit, platform and browser versions, camera and codec matrix, network topology, storage configuration, test commands, soak duration, observed limits, known workarounds, and a promote or reject decision. Unverified items stay explicit; a green build never silently checks a deployment-specific criterion.

Use a record with the following concrete results:

EvidenceRecord
Build and deploymentExact version/commit or image digest, host architecture, filesystem, service identity, browser versions, and proxy/VPN topology.
Camera matrixCamera model and firmware, transport, main/sub codecs, finalized recording and independent decode results.
AccessAllowed and denied camera checks, dashboard audiences, expired/revoked sessions, and local versus remote classification.
Sustained operationStart/end times, workload, recording gaps and explanations, memory/storage growth, cleanup and reconnect results.
RecoveryConfiguration ZIP apply, stopped catalog/media copy, restart, upgrade rehearsal, and recovered/missing intervals.
Integrations and evidenceProvider/broker outage and restart behavior, completed/failed/cancelled exports, and verified downloaded bytes.
DecisionRemaining issues and workarounds, evidence location, and an explicit promote or reject decision.

Use Upgrades and migrations to plan a version change, and Reporting bugs for reproducible failures. This guide does not close the Alpha gate, its feature-freeze decision, or the installation’s long-running qualification work.

Reporting bugs

GitHub Issues are for reproducible KeepPeek defects. The bug report form is the canonical submission path, and .github/ISSUE_TEMPLATE/bug.yml is the source of truth for its required fields.

Choose the right channel

What you needWhere to go
Existing KeepPeek behavior reproducibly failsBug report
Setup help, troubleshooting, or usage guidanceQ&A
New behavior or a feature proposalSuggestions
A security vulnerabilityPrivate security report

A support request is not yet a bug until the evidence shows existing KeepPeek behavior violating its contract. Feature requests belong in Suggestions even when they would solve a real problem. Search open and closed Issues before filing so evidence and discussion stay on one report.

Before submitting

Confirm all of the following:

  • The same defect is not already present in an open or closed Issue.
  • The report describes a reproducible KeepPeek defect, not setup help or a feature proposal.
  • The defect reproduces on the current release, or the exact affected build is known.
  • Relevant Health, camera diagnosis, server log, and browser console evidence has been checked.
  • Credentials, private addresses, tokens, cookies, private media, and sensitive data are removed.
  • A KeepPeek diagnostics package is attached, or the report explains why it is unavailable.

Reports that are duplicates, support requests, feature proposals, or missing the required reproduction and environment details may be closed or marked needs more info.

Describe one observable defect

Keep one report focused on one failure. State what fails, when it fails, and the effect on recording, review, evidence, security, or operation. Include the affected camera, stream, time range, or workflow only in sanitized form.

Select the primary affected area in the form:

  • Recording and ingest
  • Peek and live video
  • Events, search, and previews
  • Keep, timeline, and recorded playback
  • Export
  • Cameras, discovery, and configuration
  • Camera controls and vendor integration
  • Storage, retention, backup, and restore
  • Authentication, authorization, and privacy
  • Installation, upgrade, and service lifecycle
  • API, WebRTC, and external integrations
  • UI, mobile, and accessibility
  • Other

If several areas are involved, select the user-visible workflow that fails first and describe the secondary effects in the report.

Provide a deterministic reproduction

Start from a known state and give the smallest sequence that reproduces the defect:

Starting state: <configuration, page, camera state, or recording state>
1. Configure or open ...
2. Select ...
3. Perform ...
4. Observe ...

Frequency: <always, intermittent, or N of M attempts>

Separate expected and actual behavior. Make the difference observable through a state, response, error, file, timing, resource measurement, or visible result. “It does not work” is not enough to reproduce or verify a fix.

Record regression and impact

State whether the behavior previously worked. Include the last known good and first known bad versions when known, how frequently the defect occurs, its operational or data-loss risk, and any verified workaround.

Use unknown when a relevant fact cannot be established. Do not guess a regression version or claim data loss without evidence.

Describe the environment

Include every field relevant to reproduction, using N/A only when it genuinely does not apply:

  • KeepPeek version, commit, and build profile
  • operating system and architecture
  • installation method: native package, Docker, service, or other
  • browser or client and version
  • camera make, model, and anonymized firmware version
  • camera source: Reolink, ONVIF, RTSP, test-camera, or other
  • stream codec, resolution, frame rate, and audio
  • camera count and relevant catalog or date-range scale
  • storage type and free space

Hardware-specific and intermittent bugs are valid, but the report must distinguish direct evidence from a suspected cause and provide a bounded way to collect the next observation.

Attach sanitized evidence

After reproducing the defect, open Settings > Logs & diagnostics > Download diagnostics. Attach the generated keeppeek-diagnostics-*.json.gz file under Diagnostics package and sanitized evidence so the package contains the relevant retained server and browser logs.

If the package cannot be generated or attached, state the exact reason. Missing diagnostics do not block submission, but a report without enough evidence may be closed as needs more info.

Add focused timestamps, screenshots, stack traces, protocol fixtures, or small log excerpts when they make the defect easier to reproduce. Never publish:

  • credentials, access keys, tokens, cookies, or authorization headers;
  • camera IP addresses, hostnames, private filesystem prefixes, or stream and snapshot URLs;
  • faces, license plates, private recordings, notification text, or location details;
  • unsanitized logs, screenshots, packet captures, or diagnostics bundles.

Redact at the source. Do not attach evidence that cannot be confidently sanitized.

Define fix acceptance criteria

Describe the observable behavior that will make the bug fixed. Keep each criterion unchecked and name the test or procedure that will verify it:

- [ ] AC-1: <the reported reproduction produces the correct observable outcome>
  - Expected outcome: <exact state or result>
  - Verification: <regression test, command, or reproducible procedure>

- [ ] AC-2: <the relevant failure or boundary case behaves correctly>
  - Expected outcome: <exact state or result>
  - Verification: <test name, command, or reproducible procedure>

Acceptance criteria should describe corrected behavior, not prescribe an unverified implementation.

Include performance evidence

When the defect affects runtime, build time, memory, storage, network traffic, or interaction latency, include:

  • metric and unit;
  • current or before value;
  • expected budget or target;
  • workload or dataset;
  • hardware, operating system, browser, and build profile where relevant;
  • exact command or harness;
  • run count and statistic, such as median or p95.

For a correctness-only bug, write Performance: N/A and explain which performance paths the change cannot affect. A statement such as “slow” or “no regression” is not performance evidence.

Submit and follow up

Review the complete report once more for secrets and unsupported claims, then submit it through the bug report form. Respond to requests for focused evidence or clarification on the same Issue rather than opening duplicates.

A strong report lets another person answer what failed, how to reproduce it, what should have happened, which build and environment are affected, what evidence proves it, and how the eventual fix will be verified.

Open source and licensing

KeepPeek is built in an ecosystem shaped by other open-source camera projects and protocol libraries. This chapter gives those projects credit, explains where KeepPeek made different product choices, and documents the license boundary between the server and its public API.

Different products, different strengths

Scrypted and Frigate are great products with strong, distinct designs. KeepPeek is not an argument that those designs are wrong. Their choices may fit another user, home, or business better.

ProjectDistinct design centerA strong fit when
ScryptedA high-performance video integration platform and NVR with a rich plugin model, smart detections, and low-latency connections to HomeKit, Google Home, and AlexaBroad camera and smart-home integration, an extensible plugin ecosystem, and first-class consumer-platform bridges should live in one system
FrigateA local NVR built around real-time AI object detection, accelerator-aware processing, zones, focused event review, Home Assistant, and MQTTDetection is a central NVR responsibility and the user wants an integrated, highly configurable local AI and home-automation experience
KeepPeekA focused recorder and WebRTC media gateway that accepts camera events and keeps optional inference, transcoding, and ecosystem bridges outside the coreRecording should remain independent from analytics, and viewers or services should compose through an open media and event boundary

Thank you to the Scrypted and Frigate maintainers and communities. Their work has advanced local camera software, demonstrated what polished self-hosted systems can do, and made the trade-offs in this space much easier to understand.

Code foundations

KeepPeek depends on substantial protocol work from other Rust projects. The local forks retain their upstream authorship and licenses; carrying a fork is a maintenance decision, not a claim that KeepPeek created the original work.

  • Retina by Scott Lamb and its contributors provides the high-level RTSP client, RTP depacketization, codec framing, and much of the interoperability groundwork used by KeepPeek. KeepPeek carries a local Retina fork for its camera, recording, and compatibility needs.
  • ONVIF-rs by Chris Bruce, Lumeo, and its contributors did the difficult work of making ONVIF discovery, schemas, authentication, and camera operations usable from Rust. KeepPeek carries a local ONVIF-rs fork and continues that work for its supported camera surface.
  • str0m by Martin Algesten and its contributors is the upstream WebRTC implementation used by KeepPeek. It is the best Rust WebRTC implementation: its Sans I/O design, explicit time and network inputs, RTP and frame APIs, data channels, and lack of a hidden async runtime fit a focused media service especially well.

KeepPeek would be significantly harder to build without these projects. Thank you to every upstream author, maintainer, reviewer, and contributor whose work remains in the code.

Licensing model

KeepPeek uses two licenses at a deliberate protocol boundary:

AreaLicensePurpose
Server, first-party viewer, and most repository codeAGPL-3.0-onlyKeep the shared NVR and media-gateway core open, including modified versions operated over a network
Public definitions and documentation under api/, plus bindings generated solely from themMITLet independent clients and services implement the protocol without adopting the server’s license

The repository root license contains the complete KeepPeek licensing notice and AGPL terms. The api/ directory has its own MIT license.

Why the server uses AGPL

The server is a network service, so the AGPL is the appropriate copyleft boundary for the shared core. The intent is that users can inspect, modify, self-host, and retain access to the source of the KeepPeek server they use, including modified versions offered over a network.

KeepPeek’s NVR and first-party viewer remain open source and free to use without a camera-count limit. The license protects that common foundation from becoming a private server fork whose users cannot inspect the code serving them.

Why the API uses MIT

An open server is most useful when other software can talk to it without inheriting the server’s implementation choices. The public HTTP, WebRTC, protobuf, and SDP contracts under api/ therefore use MIT. Bindings generated solely from those definitions remain covered by that directory’s MIT license.

Implementing or using the public protocol does not, by itself, impose KeepPeek’s repository-wide AGPL license on an independent client or service. A service can choose the language, WebRTC implementation, deployment model, and license appropriate to its users.

Free and paid protocol services

This split creates a protocol-plugin model rather than an in-process plugin ABI. A free, community, or paid service can run as a separate process and subscribe or publish through the documented API. That service may be open or closed source without receiving a private extension point inside the KeepPeek server.

The boundary allows commercial integrations to fund specialized inference, transcoding, business workflows, viewers, or support while the recorder and media gateway remain shared open-source infrastructure. Camera credentials, recording paths, and internal databases remain owned by KeepPeek rather than exposed to an in-process plugin.

What the split means for users

  • Software consumers retain a free recorder and first-party viewer, with optional services only when they add value.
  • Small businesses can buy tailored services at the API boundary without depending on a private recorder fork.
  • Open-source enthusiasts can audit the server, share core improvements, and build permissively licensed clients.
  • Home automation enthusiasts can choose community or commercial bridges.
  • Self-hosters retain control of the server running on their hardware.
  • Service authors can build sustainable offerings without closing the shared core.

See Contributing for how licenses apply to submitted changes.

Contributing

KeepPeek welcomes focused contributions that strengthen recording, camera compatibility, media routing, observability, the public API, and the first-party experience. A contribution should make the supported product more dependable without silently turning the core into a different product.

Read Users and design choices before proposing a substantial change. It describes the users, boundaries, and trade-offs that guide review.

Use Reporting bugs for a reproducible defect. Use GitHub Discussions for setup help, troubleshooting, usage questions, and feature proposals.

Guard the product boundary

The feature set is deliberately and heavily guarded. Any proposal that expands product scope must be discussed and reviewed before implementation begins. A complete implementation is not, by itself, a reason to add a feature to the core.

Feature review asks:

  1. Does this belong in a focused recorder and media gateway?
  2. Can an independent service or protocol plugin provide it through the public API instead?
  3. Which documented users need it, and does it make another user’s core workflow worse?
  4. What permanent maintenance, security, storage, resource, compatibility, and licensing costs does it add?
  5. Can the behavior be tested without depending on one private device, account, or cloud service?

Open a GitHub Discussion before writing a feature that changes the product boundary, public protocol, storage model, supported platform contract, or long-term maintenance surface. Expect alternatives and non-goals to be reviewed as seriously as the proposed implementation. This review is how KeepPeek avoids accidental scope creep.

Contributions that fit well

Focused bug fixes, tests, documentation corrections, diagnostics, and interoperability improvements usually have a clear path when they preserve the existing product boundary.

Camera protocols and RTSP interoperability

Camera protocol fixes and bounded RTSP workarounds are especially welcome. They are often easier to submit and review than feature proposals because the expected behavior can be captured in a fixture and regression test.

A useful camera contribution includes:

  • manufacturer, model, firmware version, backend, and TCP or UDP transport;
  • the smallest reproducible failure with credentials, addresses, and private media removed;
  • sanitized protocol, SDP, or packet evidence when it can be shared legally and safely;
  • a regression fixture or deterministic fake-camera case;
  • a narrow parser, transport, or adapter fix instead of a product-wide brand check;
  • successful main/sub frame, keyframe, MP4 finalization, and independent decode validation when the change affects recording.

Never submit camera passwords, access keys, private stream URLs, identifiable recordings, or packet captures containing secrets. If private evidence cannot be published, file the bug with only the safe evidence and explain what is unavailable rather than attempting a speculative workaround.

One camera quirk should not weaken validation for every camera. Prefer a protocol-level correction when the device is standards-compliant, and a bounded, evidence-backed compatibility rule when it is not.

Public API and external services

API contributions should preserve typed messages, stable identity, explicit capabilities, bounded queues, and clear failure behavior. An example client or service should consume the canonical API definitions rather than copy handwritten wire types or depend on private server internals.

New detector, transcoder, automation, and commercial-service ideas generally belong outside the core first. A small reference implementation may demonstrate interoperability without becoming a promise that KeepPeek will own that service as a product.

AI-assisted contributions

AI is one of the tools used to develop KeepPeek. This section does not take a position on AI in general; it defines only how AI-assisted contributions to this repository are reviewed.

AI-assisted contributions are acceptable. The standard is the same as for code written without an assistant, and responsibility never transfers to the model.

The person submitting the pull request is its first reviewer. Before requesting another review, they must:

  • inspect every changed line, including generated tests and configuration;
  • understand and be able to explain every line in the pull request;
  • verify API, security, resource, failure, and compatibility assumptions against the repository;
  • remove invented behavior, unnecessary abstractions, unrelated cleanup, and generated commentary;
  • run the required validation and investigate failures rather than asking reviewers to debug raw model output.

“The AI wrote it” is not an explanation for a design decision and is not a reason to merge code the submitter cannot defend. Reviewers may ask the submitter to explain control flow, ownership, protocol behavior, tests, or individual lines. If they cannot, the contribution is not ready.

Pull request shape

Use the repository’s pull request template as the canonical shape for every pull request. Complete its summary, linked Discussion, acceptance criteria verification, performance evidence, validation, and review evidence before requesting review. A bug-fix pull request may link its reproducible bug issue instead of a feature Discussion.

Keep pull requests small enough to review as one coherent behavioral change. State the supported user and boundary affected, include focused regression coverage, update public documentation when a contract changes, and leave unrelated formatting, dependency updates, and refactors out.

Use draft pull requests for early feedback when the boundary is clear but the implementation is not ready. Do not use a large completed pull request as the first request for product-scope review.

Review cadence

Pull request reviews are done on Sundays. If a Sunday review window passes and a pull request is missed, leave a short, polite ping on that pull request to remind the maintainer. A reminder on the existing pull request is enough; do not open a second Discussion or duplicate pull request.

Development environment

Repository development uses stable Rust, Bun 1.4.0, and Python 3.12. The pinned Bun and Python versions live in ui/.bun-version and examples/object_detection_service/.python-version.

Formatting and validation require the Python packages in examples/object_detection_service/requirements.txt, including Black. The root fix and check scripts use an absolute interpreter path from KEEPPEEK_PYTHON when set. Otherwise, macOS/Linux scripts try python3.12 and then python3; Windows scripts resolve python from PATH. The selected interpreter must be Python 3.12. Set KEEPPEEK_PYTHON explicitly if another Python installation wins that lookup.

Do not use a Python virtual environment. fix.sh and fix.bat install the requirements into the resolved interpreter, retrying with --break-system-packages when that interpreter is externally managed.

The canonical checks also require Bun, cargo-nextest, and cargo-machete. Install UI dependencies with Bun from the public npm registry and do not create or commit an npm, pnpm, or Yarn lockfile.

Validation

Run the canonical repository check from the repository root before requesting review:

  • macOS or Linux: ./check.sh
  • Windows: .\check.bat

Run the same gate with opt-in slow Rust test bodies before submitting a pull request:

  • macOS or Linux: KEEPPEEK_RUN_SLOW_TESTS=1 ./check.sh
  • Windows PowerShell: $env:KEEPPEEK_RUN_SLOW_TESTS = '1'; .\check.bat

Pull-request CI keeps feedback fast by splitting static UI checks, unit tests, coverage, and Linux Playwright tests into non-overlapping jobs. The extended Main workflow runs after every merge and once per hour on main; it owns the opt-in slow Rust tests, ARM builds and tests, Windows installer lifecycles, Rust coverage and CodeQL, and full Playwright suites on macOS and Windows.

Windows pull-request core tests run only the runtime-profile regressions and native volume-root validation. Windows pull-request durable-state tests retain ordinary regressions and defer scale/quota/churn qualification. The full Windows core and durable-state suites run on every push to main in the CI workflow. Linux and macOS still run the full core suite before merge, and local canonical checks still run the complete suite. This split changes scheduling, not test assertions or the required post-merge Windows coverage.

During development, run the narrowest relevant test first. The final check covers repository formatting, Rust tests and lints, frontend checks, and the supported test suites. Document any test that cannot run in the pull request and explain why.

Camera changes should also be validated against the affected device and independent MP4 tooling. Passing a unit test does not prove that a real camera authenticates, advances frames and keyframes, finalizes recordings, and produces decodable output.

Update the book and design evidence

The book lives in book/src; SUMMARY.md controls its navigation. Document the current behavior and the practical task it enables, including authorization, persistence, limits, and failure recovery. Update the feature guide when adding a screen or changing where a task starts. Fill operational chapters with instructions rather than leaving empty placeholders.

Repository Markdown is the only editing source for the Wiki and the mdBook site. The Wiki also includes selected engineering pages from docs/; Pages remains the generated mdBook mirror. Submit documentation changes through a repository pull request. Do not edit generated Wiki pages directly. See Wiki publishing for page mapping, preview, credentials, publication status, and recovery.

Keep serialized fields, defaults, and examples synchronized with the configuration reference. Protocol declarations and engineering scenarios can describe capabilities that do not yet have a runtime handler. Check the server and client implementation before presenting those capabilities as available.

From the repository root, validate documentation with:

bun run --cwd ui format:markdown:check
mdbook build book
bun run --cwd ui wiki:test
bun run --cwd ui wiki:check
bun run --cwd ui wiki:build

Install mdbook and mdbook-mermaid and make both available on PATH; the book’s configuration uses the Mermaid preprocessor. After building, open the generated book, follow the changed navigation and cross-links, and check diagrams and long tables at desktop and narrow widths. These focused documentation checks complement the repository validation above.

The first-party interface has versioned Paper references and scenario-based browser evidence under ui/design/paper. Use the active reference’s README and manifests to identify its scope. The active NVR design is keeppeek-nvr-alpha; keeppeek-nvr-v34 remains the historical input to the existing visual harness and Linux Loki baselines. A new Paper export does not approve a replacement baseline. A reference image records design intent; browser interaction tests prove working behavior. Keep unavailable, loading, denied, empty, and error states explicit, and preserve the actual evidence when updating a reference. Do not infer current feature availability from a mockup or a demonstration video.

Licensing

See Open source and licensing for the project-wide license model. Contributions are accepted under the license of the area they modify. Submit only work you have the right to provide under that license, including fixtures, generated files, and AI-assisted output.