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

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.