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
| Environment | Default 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 section | Owning type | Ownership |
|---|---|---|
| Root fields | Config | Operator settings |
[access] | AccessConfig | Administrator security policy |
[direct_card] | DirectCardConfig | Exact browser-origin allowlist |
[storage] | StorageToml | Recording paths, retention, and safety |
[battery_wake] | BatteryWakeConfig | Reolink battery-camera wake service |
[logging] | LoggingConfig | Service log destination |
[operational_events] | OperationalEventsConfig | Health-event timing |
[operational_events.cameras."<camera-id-or-ip>"] | OperationalEventOverride | Per-camera timing overrides |
[privacy.default] | PrivacySchedule | Inherited server-enforced recurring privacy |
[privacy.cameras."<camera-id-or-ip>"] | PrivacySchedule | Per-camera server-enforced recurring privacy |
[event_forwarder.mqtt] | MqttForwarderConfig inside EventForwarderConfig | MQTT configuration; server-owned revision |
[camera_defaults] | CameraCredentialDefaults | Shared camera defaults |
[<namespace>.<camera-key>] | CameraConfig | Camera settings |
[<namespace>.<camera-key>.events] | EventConfig | Native camera event selection and filters |
[access_credentials] | PersistedAccessCatalog | Server-managed credential records |
[peek_layouts] | StoredRegistry | Server-managed layouts and per-user selection |
[configuration_templates] | StoredTemplateDocument | Server-managed camera templates |
[notifications] | NotificationConfiguration | Server-managed rule drafts and active rules |
[runtime_profiles] | ProfileRegistry | Server-managed profile definitions and selection |
[storage_migration] | StorageMigration | Server-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
| Setting | Supported resolution |
|---|---|
| Root settings, camera defaults, camera entries, and ordinary settings sections | String 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].password | Direct 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[].destination | The 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_reference | Complete 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 fields | Not 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.
| Field | Type | Default | Meaning |
|---|---|---|---|
host | String | "0.0.0.0" | HTTP listener bind address. |
port | u16 | 8081 | HTTP listener port. |
access_key | AccessKey | Unset in the model; generated on startup | Compatibility 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].
| Field | Type | Default | Validation and meaning |
|---|---|---|---|
local_networks | Vec<IpNet> | Networks below | At most 64 CIDRs. Matches become trusted-local Administrators without sign-in. Setting the array replaces the defaults. |
trusted_proxies | Vec<IpNet> | [] | At most 64 CIDRs, checked separately from local_networks. Only these peers can supply the supported forwarded-client header contract. |
require_secure_remote | bool | true | Require the declared secure boundary for remote traffic. |
failed_authentication_limit | u32 | 5 | Nonzero failed-attempt limit per address/window. |
failed_authentication_window_secs | u64 | 60 | Nonzero authentication-rate window, in seconds. |
session_idle_timeout_secs | u64 | 1800 | Nonzero; cannot exceed the absolute timeout. |
session_absolute_timeout_secs | u64 | 86400 | Nonzero maximum session lifetime. |
max_sessions_per_principal | u32 | 64 | Nonzero concurrent-session limit per identity. |
max_sessions_per_address | u32 | 128 | Nonzero 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].
| Field | Type | Default | Validation and meaning |
|---|---|---|---|
allowed_origins | Vec<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.
| Field | Type | Default | Validation |
|---|---|---|---|
allowed_origins | Vec<String> | Required | 1–16 unique canonical exact HTTPS origins, without credentials, path, query, fragment, wildcard, or trailing slash. |
providers | Vec<Provider> | Required | 1–4 providers with unique IDs. |
bearer_enabled | bool | false | Mixed mode requires an explicit transition deadline. |
bearer_transition_until_ms | Optional i64 | Absent | Positive 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":
| Field | Type | Default | Validation |
|---|---|---|---|
issuer | String | Required | HTTPS URL without user-info, query, or fragment. |
client_id | String | Required | 1–256 bytes, nonblank, no control characters. |
client_secret | Optional String | Absent | Complete {secret:KEY} or {secret:KEY|url} reference; resolved value is nonblank, at most 4,096 bytes, without control characters. Inline secrets are rejected. |
redirect_uri | String | Required | Allowed HTTPS origin with exactly /auth/callback, no query or fragment. |
scopes | Vec<String> | ["openid"] | At most 16 unique OAuth scope tokens of 1–64 bytes; must include openid. |
display_name_claim | String | "name" | 1–64 bytes, nonblank, no control characters. |
endpoint_origins | Vec<String> | [] | At most 16 unique canonical HTTPS origins for additional provider endpoints. |
private_networks | Vec<IpNet> | [] | At most 64 explicitly permitted private issuer networks. |
logout_uri | Optional String | Absent | HTTPS 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":
| Field | Type | Default | Validation |
|---|---|---|---|
trusted_peers | Vec<IpNet> | Required | 1–64 immediate transport-peer CIDRs, independent of forwarded-client classification. |
subject_header | String | Required | Exact identity assertion header. |
role_header | String | Required | Exact role assertion header; values still require explicit mapping. |
name_header | Optional String | Absent | Optional display metadata header. |
secret_header | Optional String | Absent | Present if and only if shared_secret is present. |
shared_secret | Optional String | Absent | Same 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].
| Field | Type | Default or inheritance |
|---|---|---|
username | String | Empty string; accepts secret references. |
password | String | Empty string; accepts secret references. |
backend | Optional CameraBackend | No shared override; camera model defaults to "auto". |
transport | Optional CameraTransport | No shared override; camera model defaults to "tcp". |
record_generic_motion_events | Optional bool | No shared override; camera model defaults to false. |
recording_mode | Optional CameraRecordingMode | No shared override; camera model defaults to "event-boost". |
event_recording_duration_secs | Optional u64 | No shared override; camera model defaults to 60 seconds. |
event_pre_recording_duration_secs | Optional u64 | No shared override; camera model defaults to 0; range 0..30 seconds. |
event_recording_stream | Optional EventRecordingStream | No 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.
| Field | Type | Default | Meaning |
|---|---|---|---|
ip | IpAddr | Required | Camera IP address, not a DNS hostname. |
name | Optional String | Camera table key | The loader replaces this value with the table key. Use display_name to change the UI label without changing storage identity. |
display_name | Optional String | Falls back to name | Human-readable camera label. |
manufacturer | Optional String | No override | Nonempty trimmed value takes precedence over discovery. |
username | String | Shared default or empty | Camera username; literal or secret reference. |
password | String | Shared default or empty | Camera password; literal or secret reference. |
onvif_port | Optional u16 | 8000 when unset | ONVIF service port. Typed configuration operations require 1..65535. |
http_port | Optional u16 | 80 when unset | Direct camera HTTP control port; typed operations require 1..65535. |
main_rtsp_url | Optional String | Discover stream | Explicit main-stream RTSP URL takes precedence over ONVIF discovery. Blank values are not explicit URLs. |
sub_rtsp_url | Optional String | Discover stream | Explicit sub-stream RTSP URL. Use URL-escaped references in credential components. |
uid | Optional String | None | Reolink P2P UID for direct BCUDP discovery. |
backend | CameraBackend | Shared default or "auto" | "auto", "retina", or "reo-proto". |
transport | CameraTransport | Shared default or "tcp" | "tcp" or "udp". |
record_generic_motion_events | bool | Shared default or false | Opt in to supported generic motion events. |
recording_mode | CameraRecordingMode | Shared default or "event-boost" | "off", "sub", "main", "both", "event-boost", or "event-only". |
event_recording_duration_secs | u64 | Shared default or 60 | Event-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_secs | u64 | Shared default or 0 | Range 0..30 seconds; 0 disables pre-roll allocation. |
event_recording_stream | EventRecordingStream | Shared default or "main" | "main" or "sub"; selects the event-only recording stream. EventBoost always boosts to main. |
events | EventConfig | Defaults below | Camera-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.
| Field | Type | Default | Values and limits |
|---|---|---|---|
mode | EventMode | "auto" | "auto", "vendor", "onvif-pullpoint", "rtsp-metadata", or "disabled". |
metadata_stream | MetadataMode | "auto" | "auto", "enabled", or "disabled". Enabling metadata does not override vendor or disabled event mode. |
event_service_url | Optional String | None; discover service | HTTP(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_tokens | Vec<String> | [] | At most 32 exact opaque tokens, each at most 256 UTF-8 bytes. Empty or control-character entries are rejected. |
include_topics | Vec<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_topics | Vec<String> | [] | Same syntax and shared count/byte limits as inclusions; exclusions take precedence. |
snapshots | bool | true | Allow 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.
| Field | Type | Default | Meaning |
|---|---|---|---|
medium_term_path | Optional String | Runtime-selected | Medium-term recording directory. |
long_term_path | Optional String | Runtime-selected | Long-term recording directory. |
recording_catalog_path | Optional String | Runtime-selected | Recording catalog path. |
event_thumbnail_path | Optional String | Runtime-selected | Event-thumbnail directory. |
event_thumbnail_max_mb | u64 | 1024 | Thumbnail storage budget, in MiB. |
short_term_secs | u64 | 120 | Short-term retention, in seconds. |
medium_term_secs | u64 | 1800 | Medium-term retention, in seconds. |
flush_interval_secs | u64 | 60 | Recording flush interval, in seconds. |
write_buffer_bytes | usize | 8192 | Recording write-buffer size, in bytes. |
long_term_max_gb | u64 | 1024 | Long-term storage budget, in GiB. |
minimum_free_gb | u64 | 10 | Minimum-free-space safety threshold, in GiB. |
maximum_used_percent | Optional u8 | No percentage threshold | When set, 1..99; filesystem-used percentage threshold. |
warning_free_gb | u64 | 20 | Warning-free-space threshold, in GiB. |
critical_free_gb | u64 | 10 | Critical-free-space threshold, in GiB. |
cleanup_hysteresis_gb | u64 | 5 | Cleanup recovery margin, in GiB. |
pre_recording_stream_max_bytes | u64 | 67108864 (64 MiB) | Positive encoded-byte limit per candidate stream; must not exceed the global limit. |
pre_recording_global_max_bytes | u64 | 268435456 (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:
| Field | Type | Default | Meaning |
|---|---|---|---|
id | String | Required | 1–64 lowercase ASCII letters, digits, _ or -; legacy- is reserved. |
root | Path string | Required | Absolute 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. |
roles | Role array | Required | 1–5 distinct values: active, archive, export, thumbnail, metadata. Only one bound metadata owner is active; other metadata-capable volumes can be relocation targets. |
state | State | enabled | enabled admits new writes; read_only and draining retain reads but stop new writes; disabled does not open the root. |
priority | u16 | 0 | Smaller values rank first for priority placement. |
capacity_bytes | Optional u64 | No cap | Positive owned-data byte cap. |
minimum_free_bytes | u64 | 0 | Minimum filesystem free space to retain. |
critical_free_bytes | u64 | 0 | Placement retains the larger of minimum and critical free space. |
warning_free_bytes | u64 | 0 | Must be at least both minimum and critical free space. |
sources, groups | String arrays | Empty | At 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:
| Field | Type | Default | Meaning |
|---|---|---|---|
role | Role | Required | One of the roles above. |
source, group | Optional string | Absent | Select either a source or a group, using the same string limits as volume allowlists. Absence selects the role default. |
candidates | Volume ID array | Required | 1–8 distinct configured volumes supporting the role. |
strategy | Strategy | priority | priority ranks smaller priorities first; free_space ranks usable free bytes after reserve and cap limits. Ties use volume ID order. |
allow_fallback | Boolean | false | False 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
| Field | Type | Default | Validation and meaning |
|---|---|---|---|
target_id | String | Required | Stable ID with 1–64 lowercase ASCII letters, digits, hyphens or underscores; legacy- is reserved. |
root | PathBuf | Required | Absolute 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. |
enabled | bool | false | Enable archive work; configuration alone does not bind or create the destination. |
automatic | bool | false | Admit eligible finalized recordings automatically when the archive worker is enabled. |
source_ids | Vec<String> | Empty | Automatic-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_secs | u64 | 0 | Minimum finalized recording age for automatic selection, at most 315,360,000 seconds (ten 365-day years). |
evict_after_archive | bool | false | Explicitly permit policy-driven primary-copy eviction after verification; holds, leases and durable archive indexes still apply. |
maximum_object_bytes | u64 | 4,294,967,296 | Per-object admission limit, from 1 byte through 64 GiB; cannot exceed the pending-byte limit. |
maximum_pending_count | u32 | 256 | Retained pending jobs, from 1 through 4,096. |
maximum_pending_bytes | u64 | 68,719,476,736 | Pending-byte admission ceiling, positive and at most i64::MAX. |
operation_timeout_secs | u64 | 120 | Finite archive operation deadline, from 1 through 3,600 seconds. |
maximum_attempts | u32 | 3 | Maximum 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.
| Field | Type | Default | Validation and meaning |
|---|---|---|---|
enabled | bool | false | Enable the wake service. |
bind | Optional Ipv4Addr | Automatic | Local IPv4 binding selection. |
middleman_port | u16 | 9999 | Nonzero middleman port. |
register_port | u16 | 58200 | Nonzero registration port; must differ from the middleman port. |
heartbeat_secs | u64 | 20 | Nonzero heartbeat interval. |
stale_after_secs | u64 | 80 | Must cover at least one heartbeat interval. |
Logging
Type: LoggingConfig, section [logging].
| Field | Type | Default | Meaning |
|---|---|---|---|
service | ServiceLogDestination | "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].
| Field | Type | Default | Meaning |
|---|---|---|---|
warning_hold_down_secs | u64 | 15 | Delay before recording a warning transition. |
outage_hold_down_secs | u64 | 60 | Delay before recording an outage. |
recovery_debounce_secs | u64 | 10 | Stable-recovery debounce period. |
record_short_flaps | bool | false | Retain supported brief-flap transitions. |
cameras | Map of OperationalEventOverride | Empty | Overrides keyed by stable camera ID, with camera IP as fallback. |
Type: OperationalEventOverride, section [operational_events.cameras."<camera-id-or-ip>"].
| Field | Type | When absent |
|---|---|---|
warning_hold_down_secs | Optional u64 | Inherit global warning hold-down. |
outage_hold_down_secs | Optional u64 | Inherit global outage hold-down. |
recovery_debounce_secs | Optional u64 | Inherit global recovery debounce. |
record_short_flaps | Optional bool | Inherit 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>"].
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled | bool | true | Retain the policy but disable its scheduled enforcement when false. |
timezone | String | Required | IANA timezone used for recurring local windows. |
windows | Array of PrivacyWindow | Empty | Half-open weekly privacy windows; at most 64. |
temporary_override | Optional PrivacyOverride | None | Bounded persisted override that suspends matching windows. |
keep_camera_connected | bool | true | Keep 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.
| Field | Type | Default | Validation and meaning |
|---|---|---|---|
revision | u64 | 1 | Server-managed settings revision. |
enabled | bool | false | Enable forwarding. |
broker_url | String | "mqtt://127.0.0.1:1883" | mqtt or mqtts URL with host; no user-info, non-root path, query, or fragment. |
client_id | String | "keeppeek" | Broker client ID. |
instance_id | String | "home-nvr" | KeepPeek instance identity. |
forwarder_id | String | "mqtt" | Forwarder identity. |
topic_prefix | String | "keeppeek" | 1 to 512 bytes; no leading/trailing slash, NUL, +, or #. Interior slashes are allowed. |
username | Optional String | None | Broker username, including supported references. |
password | Optional String | None | Requires username; prefer a reference to the companion secret file. |
tls_ca_path | Optional PathBuf | System trust | Custom CA path; only valid with mqtts. |
qos | u8 | 1 | 0, 1, or 2. |
retain_events | bool | false | Retain event publications. |
retain_health | bool | true | Retain health publications. |
outbox_max_mb | u64 | 64 | 1 to 65536 MiB of bounded outbox storage. |
retry_min_ms | u64 | 250 | Nonzero retry minimum, no greater than maximum. |
retry_max_ms | u64 | 30000 | Retry 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].
| Field | Type | Rule |
|---|---|---|
version | u32 | Required; supported value is 1. |
credentials | Vec<StoredCredential> | Required array, at most 128 records with unique IDs. |
Type: StoredCredential, array [[access_credentials.credentials]].
| Field | Type | Rule |
|---|---|---|
id | Uuid | Stable named-credential identity. |
name | String | 1 to 64 printable bytes after trimming. |
description | Optional String | At most 256 printable bytes; empty descriptions normalize to absent. |
role | AccessRole | "administrator" or "user". |
camera_access | Optional CameraAccess | Per-user group and camera policy. Absent is unrestricted; new credentials explicitly default to everything. |
verifier | AccessKeyFingerprint | SHA-256 fingerprint serialized as 32 byte-valued integers, not the raw access key. Treat it as sensitive authentication material. |
created_at_ms | i64 | Creation time, Unix milliseconds. |
rotated_at_ms | Optional i64 | Last rotation time; absent if never rotated. |
last_used_at_ms | Optional i64 | Accepted by the stored record type for compatibility, but cleared on load and omitted by the canonical writer. Activity is runtime state. |
expires_at_ms | Optional i64 | Expiry time; expired credentials are denied. |
disabled | bool | Disabled credentials cannot authenticate. |
revoked_at_ms | Optional i64 | Permanent revocation time. |
revision | u64 | Nonzero credential revision used to invalidate stale sessions. |
legacy | bool | Compatibility initial-credential marker. |
initial_secret_pending | bool | Initial-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.
| Field | Type | Rule |
|---|---|---|
all_cameras | bool | Explicitly grant all current and future cameras. Must not be combined with a nonempty ID list. |
group_ids | Vec<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_ids | Vec<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].
| Field | Type | Rule |
|---|---|---|
schema_version | u32 | Required; supported value is 1. |
revision | u64 | Nonzero registry revision. Stale writes conflict. |
shared_layouts | Vec<Layout> | Nonempty server-owned layout array. |
users | Map of StoredUserRegistry | Keyed by principal ID; holds per-user active selection and legacy private layouts. |
Type: StoredUserRegistry, section [peek_layouts.users."<principal-id>"].
| Field | Type | Rule |
|---|---|---|
active_layout_id | String | Selected layout identity. Missing selections are repaired by the registry. |
layouts | Vec<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[].
| Field | Type | Rule |
|---|---|---|
id | String | Unique within the registry view; nonblank, at most 128 characters. |
name | String | Nonblank, at most 80 characters. |
scope | LayoutScope | "shared" for canonical layouts; "private" is retained for migration. |
owner_id | String | "server" for shared layouts; private records must match their principal. |
audience | LayoutAudience | Defaults to everyone with an empty credential list when the entire field is absent. |
activity_focus | bool | Whether activity can affect layout focus. |
display | Optional DisplaySettings | Dashboard-owned display settings; absent objects use defaults. Older client writes that omit the object preserve saved values. |
tiles | Vec<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.
| Field | Type | Default | Validation and meaning |
|---|---|---|---|
version | u32 | 1 | Required in present objects; only version 1 is supported. |
tile_shape | TileShape | "16:9" | "16:9", "4:3", or "native". |
media_fit | MediaFit | "contain" | "contain" or "cover"; never stretch. |
streaming_mode | StreamingMode | "smart" | "smart" or "continuous". |
stream_limit | u32 | 12 | Whole number from 1 through 12; each browser also enforces its own capacity. |
keep_awake | bool | false | Saved wake-lock intent; browser gesture, visibility, permission, and device policy still apply. |
gap_px | u32 | 10 | Whole pixels from 0 through 24 between tile slots. |
corner_radius_px | u32 | 10 | Whole 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.
| Field | Type | Rule |
|---|---|---|
everyone | bool | When true, credential_ids must be empty. |
credential_ids | Vec<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.
| Field | Type | Rule |
|---|---|---|
camera_id | String | Stable camera identity, not display name. |
column | u32 | One-based grid column. |
row | u32 | One-based grid row. |
column_span | u32 | Positive horizontal span; tile must fit the 12-column grid. |
row_span | u32 | Positive vertical span; tile must fit the 12-row grid. |
pinned | bool | Tile 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].
| Field | Type | Rule |
|---|---|---|
document_version | u32 | Required; supported value is 1. |
templates | Vec<StoredTemplate> | Required array, at most 64 templates. Missing section means an empty document. |
Type: StoredTemplate, array [[configuration_templates.templates]].
| Field | Type | Rule |
|---|---|---|
template_id | String | Unique ID, 1 to 64 ASCII letters, digits, hyphens, or underscores. |
version | u64 | Server-managed template version. |
name | String | Nonblank, at most 128 bytes, no control characters; names are unique ignoring case. |
description | String | Defaults to empty and is omitted when empty; at most 1024 bytes, no control characters. |
values | StoredTemplateValues | At least one supported setting must be present. |
created_at_ms | i64 | Creation time, Unix milliseconds. |
updated_at_ms | i64 | Last update time, Unix milliseconds. |
Type: StoredTemplateValues, nested values table. Every field is optional and omitted when unset.
| Field | Type | Rule |
|---|---|---|
username_secret_reference | String | A complete valid secret reference, not an inline username. |
password_secret_reference | String | A complete valid secret reference, not an inline password. |
onvif_port | u16 | 1..65535. |
http_port | u16 | 1..65535. |
backend | CameraBackend | "auto", "retina", or "reo-proto". |
transport | CameraTransport | "tcp" or "udp". |
record_generic_motion_events | bool | Generic motion-event preference. |
recording_mode | CameraRecordingMode | "off", "sub", "main", "both", "event-boost", or "event-only". |
event_recording_duration_secs | u32 | 1..3600 seconds. |
event_pre_recording_duration_secs | u32 | 0..30 seconds. |
event_recording_stream | EventRecordingStream | "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].
| Field | Type | Default and bound |
|---|---|---|
rules | Vec<PersistedRuleRecord> | []; at most 128 rules, with unique rule IDs. |
Type: PersistedRuleRecord, array [[notifications.rules]].
| Field | Type | Meaning |
|---|---|---|
id | String | Stable rule ID. |
owner_id | String | Owning principal ID. |
active | Optional Rule | Published rule; absent before first activation. |
active_revision | u64 | Active revision used for conflict checks. |
draft | Rule | Saved draft, which may be incomplete before activation. |
draft_revision | u64 | Draft revision used for conflict checks. |
created_at_ms | i64 | Creation time, Unix milliseconds. |
updated_at_ms | i64 | Last 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.
| Field | Type | Validation or default |
|---|---|---|
id | String | 1 to 128 ASCII letters, digits, ., -, or _. |
name | String | Nonblank, at most 128 characters. |
enabled | bool | Required; disabled rules do not match events. |
revision | u64 | Corresponding draft or active revision. |
owner_id | String | Same ID character and byte limits as id. |
triggers | Vec<Trigger> | Nonempty and unique; values below. |
filter | Filter | Defaults to an empty filter. |
schedule | Schedule | Required. |
cooldowns | Vec<Cooldown> | Defaults to []; scopes must be unique. |
rate_limits | Vec<RateLimit> | Defaults to []; scopes must be unique. |
critical_bypass | Optional CriticalBypass | Bounded critical-event quiet-hours bypass. |
enrichment | EnrichmentPolicy | Required. |
actions | Vec<Action> | 1 to 8 actions. |
failure | FailurePolicy | Required. |
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.
| Field | Type | Default and rule |
|---|---|---|
source_ids | Vec<String> | []; at most 128 nonblank values. |
group_ids | Vec<String> | []; at most 128 nonblank values. |
event_kinds | Vec<String> | []; at most 128 nonblank values. |
zones | Vec<String> | []; at most 128 nonblank values. |
minimum_confidence | Optional f64 | Finite value in 0..1, inclusive. |
attachment_required | Optional bool | Require or exclude an available non-private attachment. |
minimum_duration_ms | Optional u64 | Minimum matching event duration. |
severities | Vec<Severity> | []; values "info", "warning", "critical". |
reviewed | Optional bool | Match the supplied review state. |
bookmarked | Optional bool | Match 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.
| Field | Type | Default and rule |
|---|---|---|
timezone | String | Required recognized IANA timezone, such as "UTC". |
active_windows | Vec<WeeklyWindow> | [], meaning no active-window restriction. |
quiet_hours | Optional QuietHours | No quiet-hours suppression when absent. |
QuietHours has one required field, windows: Vec<WeeklyWindow>. WeeklyWindow fields are:
| Field | Type | Rule |
|---|---|---|
weekdays | Vec<Weekday> | Nonempty and unique; "monday" through "sunday". |
start_minute | u16 | 0..1439, local minutes after midnight. |
end_minute | u16 | 0..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.
| Type | Fields | Validation |
|---|---|---|
Cooldown | scope: CooldownScope, duration_ms: u64 | Scopes: "event", "camera_event_kind", "group", "rule", "outage". |
RateLimit | scope: RateLimitScope, maximum: u32, window_ms: u64 | Maximum 1..10000; scopes "rule", "channel", "principal", "global". |
CriticalBypass | maximum: u32, window_ms: u64 | Maximum 1..10. |
EnrichmentPolicy | deadline_ms: u64, maximum_revisions: u32, maximum_attempts: u32, maximum_attachment_bytes: u64, wake_after_deadline: bool | Revisions 1..32, attempts 1..8, attachment bytes 1..4194304 (4 MiB). All fields required. |
FailurePolicy | maximum_attempts: u32, maximum_retry_interval_ms: u64, expiry_ms: u64 | Attempts 1..10. An enabled push action requires retry interval at least 5000 ms. All fields required. |
Type: Action, nested actions[] array.
| Field | Type | Default and rule |
|---|---|---|
enabled | bool | true. |
channel | Channel | "browser", "push", "webhook", or "forwarder". Use channels advertised as available by the server. |
destination | String | At 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. |
template | Template | Required title: String and body: String. |
attachment | AttachmentPolicy | "never", "when_available", or "required". |
allow_second_delivery | bool | Required 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.
| Field | Type | Meaning |
|---|---|---|
medium_term | Optional StoragePathMigration | Medium-term recording move. |
long_term | Optional StoragePathMigration | Long-term recording move. |
recording_catalog | Optional StoragePathMigration | Explicit catalog move when not covered by a recording-root move. |
event_thumbnails | Optional StoragePathMigration | Explicit thumbnail move when not covered by a recording-root move. |
recording_catalog_after_move | Optional PathBuf | Catalog 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
Configwould 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.