Skip to content

Configuration

ReoVault reads reovault.toml (or the path in $REOVAULT_CONFIG_FILE, default ./reovault.toml), merged with REOVAULT_* environment variables. Env always wins over the file. Nested keys use a double underscore: REOVAULT_WEB__PORT=9090 sets [web] port.

Start from reovault.example.toml.

The path defaults below apply when running from a source checkout (a development setup). In Docker, the image points every path at its volumes (/config, /vault, /staging) through env vars, and compose.yaml mounts the folder holding your reovault.toml at /etc/reovault, so leave the path fields out of it there.

The file is the source of truth

Every setting lives in reovault.toml, and the dashboard's Settings page is just another editor of it:

  • Dashboard to file. Saving a setting rewrites the file with your comments and layout intact. The whole new file is validated first and replaces the old one atomically; the previous version is kept as reovault.toml.bak. If the file changed on disk since the page loaded, the save is refused instead of overwriting that edit.
  • File to dashboard. Edits to the file are picked up within about a second, no restart needed, and open dashboards refresh. A file that doesn't validate is reported on the Settings page while ReoVault keeps running on the last valid settings.
  • Precedence. REOVAULT_* env vars win over the file, which wins over the defaults. A field set by an env var shows a lock in the dashboard.
  • Restart-only settings. [storage], [reolink_cli] and the [web] bind, port, cookie, origin, proxy and login-identity fields are read once at startup and shown read-only in the dashboard; a change to them in the file is flagged "restart required". Only [web]'s session length, login attempt limits and page size apply live.

Nothing in config is a secret by itself. REOVAULT_MASTER_PASSPHRASE and the camera password are deliberately not config fields. See Security.

[[devices]]

One entry per camera. A list, so multi-camera setups just add more entries.

Field Type Default Notes
alias string required Must match a reolink-cli device add <alias> ... alias.
channel int 0 Camera channel (multi-channel NVR-style devices).
timezone string required IANA tz name the camera's clock runs in, e.g. America/New_York.
name string none Display name shown in the dashboard instead of alias; auto-backfilled from the camera's own name once discovered if left unset.
enabled bool true false stops archiving this camera; its archived footage stays browsable.
schedule table none The camera's own [devices.schedule] (same fields as [schedule]); unset means it follows [schedule].
provider "reolink-cli" or "fake" "reolink-cli" "fake" replaces the camera with a built-in pretend one, for trying the dashboard with several cameras. See below.

The [[devices]] list is the set of cameras. Adding a camera on the Devices page appends an entry, and turning one off writes enabled = false. A camera removed from the file is disabled, never deleted, so its footage stays. Cameras are matched to their archive by alias and channel, which is why those two can't be changed from the dashboard.

A provider = "fake" entry needs no reolink-cli alias. Its "SD card" is a nearly full card holding a sliding week of tiny clips, one every 15 minutes, so new clips appear and old ones roll off like on a real camera. About one clip in a hundred "rotates off" before it can be archived, so the at-risk and lost states have something to show. Its clips go through the real download, verification and encryption into your vault; removing the entry stops it, and what it archived stays like any other camera's footage.

[[devices]]
alias = "fake"
provider = "fake"
timezone = "Europe/Brussels"
name = "Test cam"
Field Type Default
binary string reolink-cli
pinned_version string 0.19.0
gateway_addr string 127.0.0.1:9000
search_timeout_secs int 30
download_timeout_secs int 300
registry_dir path $HOME/.config/reolink-cli

registry_dir must match reolink-cli's own actual config directory. It's used only so reovault doctor can preflight that the directory is writable.

[storage]

Field Type Default
config_dir path ./data/config
vault_dir path ./data/vault
staging_dir path ./data/staging
db_path path ./data/config/reovault.db
master_key_path path ./data/config/master.key

staging_dir and vault_dir must share a filesystem (archiving finalizes a download with an atomic rename); reovault doctor asserts this at startup. master_key_path must live outside vault_dir, or a downstream backup tool that treats the vault as its source would replicate the key alongside the ciphertext it unlocks.

[schedule]

Field Type Default
archive_cron cron string 0 5 * * *
archive_enabled bool true
archive_overlap_hours int 48
backfill_cron cron string 0 4 * * 0
backfill_enabled bool true
backfill_days int 30
reconcile_interval_hours int 6
reconcile_enabled bool true
integrity_scan_sample_pct float 5.0
integrity_scan_enabled bool true
timezone string or unset unset

timezone unset (the default) means each device's cron jobs evaluate in that device's own [[devices]] timezone. Setting it forces every device onto one timezone. This table is the default every camera follows unless it has its own [devices.schedule]. Setting any REOVAULT_SCHEDULE__* env var instead pins this schedule for every camera and makes the dashboard's schedule form read-only.

[retention]

Field Type Default
max_age_days int >= 1 or unset unset (no age limit)
max_vault_gb float >= 1 or unset unset (no size cap)

One global policy for the whole vault, all cameras together. When either limit is exceeded, the oldest archived clips (by recording start time, across every camera) are deleted from the vault until both limits hold again. The size cap is measured against the encrypted bytes ReoVault has archived, not the disk's free space, so other data on the same disk does not change how much footage is kept. Leave some headroom on the disk for the database, staging downloads and anything else that lives there.

Retention runs before each archive run and after every clip it archives, so even a long backfill overshoots the cap by at most one clip. A pruned clip keeps its database row in state pruned and is never downloaded again, even while it is still on the camera's SD card.

The Retention tab under Settings edits this table. Saving a policy that would delete footage right away asks for confirmation first, showing how many clips and bytes would go. Setting any REOVAULT_RETENTION__* env var pins the policy and makes the tab read-only.

[alerts]

Where alerts go. These fields are config-only, never stored in the database or editable from the dashboard: a webhook URL or an ntfy token is a credential, so anything secret is read from a file (mount it as a Docker secret).

Field Type Default Notes
ntfy_url string or unset unset Full topic URL, e.g. https://ntfy.sh/my-reovault. Choose an unguessable topic on a public server.
ntfy_token_file path or unset unset Access token for a protected topic, sent as a Bearer token.
webhook_url_file path or unset unset File holding the URL. Receives a JSON POST with title, message, resolved, plus text and content so Slack and Discord incoming webhooks work as-is.
smtp_host string or unset unset Email is sent only when this and smtp_to are set.
smtp_port int 587 465 uses implicit TLS; any other port uses STARTTLS when smtp_starttls is on.
smtp_starttls bool true
smtp_user string or unset unset
smtp_password_file path or unset unset
smtp_from string or unset smtp_user
smtp_to list of strings []

[alerts.rules]

Field Type Default
enabled bool true
run_failed bool true
problems bool true
coverage bool true
gateway_down bool true
gateway_down_minutes int, 1 to 1440 15
renotify_hours int, 1 to 720 24
notify_on_resolve bool true

What to alert on: a run that failed or was stopped, recordings that failed or were quarantined, clips at risk of being overwritten on a camera's SD card before they're archived (the same "At risk" state its SD card shows on the Overview and Health pages), and the camera gateway staying down longer than gateway_down_minutes. An open alert is sent once, repeated every renotify_hours while it stays open, and followed by a "Resolved" message when it clears. A send that fails on every channel is retried on the next evaluation, once a minute. Settings, Alerts edits this table and the channel fields above; any REOVAULT_ALERTS__RULES__* env var pins the rules.

[web]

Field Type Default Notes
host string 127.0.0.1 Bind 0.0.0.0 in a container. Loopback there is the container's own namespace, unreachable from a reverse proxy.
port int 8080
password_hash string or unset unset Set via REOVAULT_WEB__PASSWORD_HASH (Docker secret) or the password_file below. App still starts and /healthz still works if unset; the API answers 503 until set.
email string or unset unset Login identity field (password-manager autofill, not an authorization boundary). reovault web set-password refuses to run without it.
password_file path ./data/config/web_password
session_key_path path ./data/config/web_session.key
session_max_age_days int 30
cookie_secure bool true Set to false only for a direct-port, plain-HTTP deployment. The browser silently drops a Secure cookie sent over HTTP, which breaks login. Leave true behind a reverse proxy terminating TLS. See Security.
cookie_samesite string lax
check_origin bool true Compares the Origin header's netloc against Host on mutating requests, as CSRF defense-in-depth. A proxy preserves both headers, so this is safe to leave on in either deployment mode.
login_max_attempts int 5
login_window_secs int 900
login_lockout_secs int 300
forwarded_allow_ips string 127.0.0.1 Uvicorn only trusts X-Forwarded-* from these addresses. Behind a reverse proxy in Docker, set this to the proxy's own container IP/subnet, never "*" unless the port is genuinely unreachable except through the proxy.
stream_chunk_bytes int 1048576
page_size int 100