Skip to main content

Installing and commissioning an EverNVR recorder

One recorder per site, on the site's own LAN, recording and detecting locally. This guide covers a fresh install through to customer handoff. For a recorder that is already running, see RECOVERY.md for backups and restores and SUPPORT_MATRIX.md for what has and has not been qualified.

Contents: the host · preparing a release · running the installer · HTTPS · first run · object detection and Hailo · recording storage · adding cameras · zones and rules · notification delivery · accounts and handoff · the commissioning report · backups · updates · the dead-man check · the API

The host​

  • Ubuntu Server 24.04 LTS or 26.04 LTS, x86-64. The installer refuses anything else.
  • No Node.js. Each release brings its own (the official nodejs.org build, checked against its published SHA-256) at /opt/evernvr/releases/VERSION/node/bin/node, so a Node installed on the host, or none, makes no difference. A Node security fix reaches recorders as an EverNVR update. Releases up to 0.1.50 used /usr/bin/node; a recorder rolled back to one of those needs it again.
  • Python 3.12, 3.13 or 3.14 at /usr/bin/python3. The pinned detector wheels were built for 3.12.
  • ffmpeg, ffprobe and flock on the path, and systemd-notify (part of the systemd package; the installer refuses a host without it, because the recorder's unit arms a watchdog that it answers through that tool).
  • smartmontools if disk hardware health is wanted. It is optional: without it the disk-health probe reports every configured disk as "SMART data is not available" and says why, and the host check notes the package.
  • A static LAN address or a DHCP reservation, on a network that reaches the cameras. Where neither is possible, the recorder can follow an interface's address instead (--interface, below).
  • Enough free disk for the retention the site wants. Storage on the bench runs 20.3 GB per camera per day at 1080p and 43.9 GB at 4096×1856.

On a fresh Ubuntu Server only ffmpeg, Python's venv support and smartmontools are usually missing. The installer lists what is missing and why; a guided install offers to install them with apt-get, and --install-prerequisites installs them without asking, which is what the one-line installer needs:

curl -fsSL https://updates.harnessautomation.com/install/evernvr.sh | sudo sh -s -- --install-prerequisites --bind 192.168.101.10

It waits up to five minutes for a first-boot unattended upgrade to release the package lock. -- --check and updates only report missing packages and never install them. To install them yourself instead:

sudo apt-get update
sudo apt-get install ffmpeg python3 python3-venv smartmontools

The release carries the application, the models and its Node dependencies, native addons included, so the recorder never contacts the npm registry. The install still needs internet access for the detector's Python wheels, which pip fetches from the Python package index and installs only when their sha256 is in detector/requirements.lock; an update fetches them again only when that lock file or the interpreter changed. Recording and detection do not need the internet. Updates from the Settings page reach https://updates.harnessautomation.com; a site without that access installs updates from a file. The recorder's license is renewed through the same server with every update check; a site without that access is licensed from a license file instead (see the License page). The .run is not an offline bundle for a fresh install, and it still needs the system prerequisites above. Makeself is needed only on the build machine, not on the recorder.

Configure the host firewall for the site's client addresses, for ONVIF discovery, and for NTP (UDP port 123) from the camera network: the recorder is the cameras' time server. This guide does not set up an internet-facing listener, a VPN or router port forwarding.

Automatic Hailo-8 setup is available on both 24.04 and 26.04. Setup verifies hardware inference before reporting success; a failure leaves CPU detection available. See SUPPORT_MATRIX.md for the validation scope.

Preparing a release​

Releases are signed makeself files built with the Harness release tool, harness-release, from the @harness/build package on the company npm registry. Build on an x86-64 machine with Node 22, like the recorders: the release carries its Node dependencies, native addons included, as they were installed here. Point npm at the registry once, in ~/.npmrc:

@harness:registry=https://git.harnessautomation.com/api/packages/admin/npm/
//git.harnessautomation.com/api/packages/admin/npm/:_authToken=YOUR_TOKEN

Then install the dependencies, the model assets and the tools once from the source tree:

npm ci
npm run setup:detector
npm install -g @harness/build # provides harness-release, if the checkout does not
sudo apt-get install makeself # 2.5 or newer

Releases are signed with the EverNVR Ed25519 key, ~/Code/keys/evernvr-signing-private.pem (signing.key in harness.release.json). Keep it on the release authority's machine, outside the source tree and never on a customer recorder, and back it up securely: a recorder installs only releases signed by a key it already trusts. harness.keyring.json in the source tree holds the public half — the same key recorders installed by the old installer hold as /etc/evernvr/update-public.pem — and every release ships that keyring. Never generate a replacement key to get a build through; recorders would refuse releases signed with it. Rotating the key is a planned change: a release signed with the old key ships a keyring that adds the new one (harness-release keyring add --public-key FILE), and later releases are signed with the new key.

Signing covers the release manifest, which lists every shipped file with its hash — the model, migrations, lockfiles, the vendored node_modules, the unit templates and the hooks — and, separately, the whole .run file including its executable header. An unexpected, modified or missing file fails verification.

dist/compliance/inventory.json holds the artifact hashes and collected third-party notices; a valid signature proves the bytes, not distribution rights.

Cutting a release​

One command bumps the version, pushes it, builds the installer and publishes it:

npm run release # the next patch version
npm run release -- minor # or major, or an explicit 0.2.0
npm run release -- --no-publish # bump, push and build without uploading

It refuses a working tree with uncommitted changes, a branch other than master, and a tree behind origin/master. npm version makes the version commit and tag and runs the documentation lifecycle; the commit and tag are pushed before anything is built, so the tag always names the released bytes. Then harness-release build runs npm run build, installs the production Node dependencies into the release from dist/package-lock.json, adds packaging/ (the unit templates and the install hooks), signs the tree and packages it:

  • releases/evernvr-VERSION-linux-x64.run
  • releases/evernvr-VERSION-linux-x64.run.sig
  • releases/evernvr-VERSION-linux-x64.run.sha256

Finally harness-release publish uploads it to https://updates.harnessautomation.com, where recorders find it. The EverNVR publish token is read from HARNESS_PUBLISH_TOKEN, in the environment or in a gitignored .env beside harness.release.json.

The configuration is harness.release.json. Do not reuse a version for different bytes: the release server refuses a second, different upload of the same version, and a recorder refuses to reinstall the version it runs. To retry an upload that failed after the build, npm run release:publish; to build without bumping, npm run installer; to publish a built release to another channel, npx harness-release publish --channel beta --rollout 10.

To check the built production release, run node scripts/check-production.mjs. npm run test:production rebuilds dist/, so run it before building the installer.

The server checks every upload against its own copy of the EverNVR keyring. Which recorders are offered a release — its channel, a staged rollout, promotion, withdrawal, or holding one site on a version — is decided on the release server by the release owner, not on the recorder.

Verifying the download​

Copy the .run file and its .run.sig to the recorder, and check them before executing the .run, even for help or extraction. With the release tool and the EverNVR keyring from a trusted source:

harness-release verify evernvr-VERSION-linux-x64.run --keyring harness.keyring.json

Without it, compare the file's checksum with the published evernvr-VERSION-linux-x64.run.sha256, obtained through a trusted channel:

sha256sum -c evernvr-VERSION-linux-x64.run.sha256

Makeself's built-in checksum detects archive corruption; it does not replace either check. On a recorder that is already installed, evernvr-update apply and evernvr-update verify check the signature with the installed code against the keys the recorder already trusts, before anything in the file runs.

Running the installer​

Run a verified .run from a terminal on the recorder and answer its questions:

chmod +x evernvr-VERSION-linux-x64.run
sudo ./evernvr-VERSION-linux-x64.run
sudo systemctl status evernvr
sudo journalctl -u evernvr -n 100 --no-pager

The guided run checks the host and the release, then lists this host's own addresses and asks which one the recorder should answer on. The interface the host routes through is offered first and is the usual answer; virtual bridges such as docker0 are offered last, and a DHCP lease is called one so it can be reserved on the router. 0.0.0.0 listens on every interface, including any added later. A busy port 3099 or an address this host does not hold is reported at the question, not after it.

Automation, and anyone who already knows the address, passes it instead. This is the unattended form; without a terminal to ask on, --bind is required:

./evernvr-VERSION-linux-x64.run -- --check -- --bind 192.168.101.10
sudo ./evernvr-VERSION-linux-x64.run -- --install --unattended -- --bind 192.168.101.10 [--port 3099]

To listen on whatever address a network interface has, rather than a fixed one, pass --interface instead of --bind:

sudo ./evernvr-VERSION-linux-x64.run -- --install --unattended -- --interface eno1

The installer writes EVERNVR_INTERFACE=eno1 instead of an address. The recorder takes that interface's IPv4 address, or a global IPv6 one when it has no IPv4, and checks it every 10 seconds; when a DHCP renewal changes it, the web interface moves to the new address without a restart and without a gap in recording. If the interface has no address when the recorder starts (cable out, DHCP still pending), it records anyway, listens only on 127.0.0.1, and moves to the interface once the address arrives. If the address goes away while the recorder runs, it keeps listening where it was until a new one appears. Link-local IPv6 addresses are never used. The installer itself still needs the interface to have an address, and refuses with "Network interface NAME has no address" otherwise. Browsers and bookmarks still need the new address, so a DHCP reservation remains the better answer where the router allows one. --bind and --interface cannot be combined.

The first -- separates makeself options from the installer's; the second passes EverNVR's own options (--bind, --interface, --port, --install-prerequisites) to its install step.

To license the recorder as it is installed, give the installer its key before the second separator:

sudo ./evernvr-VERSION-linux-x64.run -- --install --unattended --license-key EVR-XXXX-XXXX-XXXX-XXXX -- --bind 192.168.101.10

The installer keeps the key in /etc/evernvr/license-key, and the recorder activates it the first time it reaches the license server; if the server cannot be reached, it tries again after the next update check. Without a key, a new recorder starts a trial. A key can be entered later in Settings → License. -- --check runs the host preflight without installing anything; with -- --bind ADDRESS or -- --interface NAME after it, the preflight also checks that the recorder could listen there. --check before the first separator only checks the archive. Use -- --help for the installer's help or --help for archive options.

If the release server serves EverNVR's one-line install script (it does only when public installers are turned on for the product), the same unattended install is:

curl -fsSL https://updates.harnessautomation.com/install/evernvr.sh | sudo sh -s -- --bind 192.168.101.10
curl -fsSL https://updates.harnessautomation.com/install/evernvr.sh | sudo sh -s -- --interface eno1

That script is pinned to the checksum of the current stable release. It has no terminal to ask on, so on a host still missing ffmpeg, Python's venv support or smartmontools add --install-prerequisites before --bind or --interface, as under the host, or install them first.

Extraction needs enough temporary disk space for the full release. To inspect the payload without installing, extract it into a new directory:

./evernvr-VERSION-linux-x64.run --noexec --target /tmp/evernvr-release

The release's trusted keys go to /etc/evernvr/keyring.json on the first install. Every later update is checked against that file by the release already installed, and a new release's keyring is merged in only once it is running healthy, so keys are added or revoked, never silently replaced.

For an existing recorder, run the new .run the same way: a guided run on a host that already has EverNVR names the installed version and the version in the file and asks whether to upgrade. sudo ./evernvr-VERSION-linux-x64.run -- --upgrade --unattended does it without asking. The update procedure describes what an upgrade does; the usual way to run one is from the Settings page or with evernvr-update apply.

A recorder installed by the old installer (a plain /opt/evernvr tree, releases up to 0.1.33) is moved to managed releases by running the new .run once, as above. It is adopted, not reinstalled:

  • the old tree is kept at /opt/evernvr.legacy-<timestamp>; /etc/evernvr (bind address, port, Hailo, NAS and disk settings) and /var/lib/evernvr stay where they are,
  • the new release's detector environment is prepared while the old recorder is still recording,
  • the recorder stops, the database and configuration are backed up to /opt/evernvr/backups/pre-adopt-<version>-<timestamp>, and the units are replaced (evernvr-admin.service and evernvr-nas.service are retired),
  • the old updater's leftovers (/opt/evernvr-update.*, /opt/evernvr-build.*, /var/lib/evernvr/staged-release) are removed.

If the new release does not pass its health check, the old tree and its units come back and the old recorder starts again. Adoption refuses to start while /etc/evernvr/update-in-progress exists — an update by the old updater did not finish; recover it first as the installed release's deploy/RECOVERY.md describes — and refuses a release that is not signed by the key in /etc/evernvr/update-public.pem. Once satisfied, remove the /opt/evernvr.legacy-* tree to reclaim the space.

The fresh install checks the OS, architecture, Python, ffmpeg, the bind address and that port 3099 is free before it writes anything (it needs no Node on the host). Missing ffmpeg or Python venv support is offered from apt-get on a guided install, installed without asking with --install-prerequisites, and otherwise refused with the command that installs it. It refuses without root; -- --install refuses a host that already has a managed EverNVR, which takes the upgrade instead. It then:

  • installs the trusted keys to /etc/evernvr/keyring.json and verifies the release against them,
  • copies the release to /opt/evernvr/releases/VERSION, makes it root's, and points /opt/evernvr/current at it,
  • creates the system account evernvr, which is used only to run pip,
  • writes /etc/evernvr/evernvr.env with EVERNVR_BIND (or EVERNVR_INTERFACE) and PORT, an empty /etc/evernvr/disks.json, and /etc/evernvr/install-info.txt with the versions and hashes it installed,
  • builds the detector environment unprivileged, in a scratch directory, with an empty root-owned HOME so no pip.conf can redirect it, installing only wheels whose sha256 is in detector/requirements.lock,
  • keeps that environment in /opt/evernvr-venv, under a name taken from the lock file and the interpreter, and links /opt/evernvr-venv/release-VERSION to it, so an update that changes neither reuses it instead of downloading the wheels again; the release tree itself never contains an environment,
  • configures a supported Hailo-8 card if one is present on 24.04 or 26.04,
  • installs and enables evernvr.service and the disk-health service and timer, and the host tool /usr/local/sbin/evernvr-update,
  • starts the service and waits until it is running from this release and its API answers.

The recorder runs as root under a unit that keeps the protections which do not depend on the account: ProtectSystem=full, NoNewPrivileges, and the kernel, cgroup and namespace restrictions. /etc is read-only in its namespace apart from three directories it is granted by name (ReadWritePaths=-/etc/evernvr -/etc/systemd/system -/etc/udev/rules.d), because the recorder writes its own configuration and NAS credentials, the mount units for NAS shares and for recording drives, and the udev rules for recording drives. The appliance is the box, so it reaches any recording folder an operator picks and starts the host operations Settings can offer itself, from the root-owned release tree, with no broker in between. Those operations do not run inside that sandbox, though: installing the Hailo stack and installing the NAS packages each run in a transient systemd unit of their own (evernvr-install-hailo, evernvr-install-nas), started with systemd-run and waited on by the recorder, because apt-get, DKMS, firmware under /lib/firmware, /etc/modules-load.d and modprobe all need the real /usr, /etc and module loading that ProtectSystem=full and ProtectKernelModules take away from the recorder on purpose. An update goes the same way, through evernvr-update in evernvr-apply-update-*. The installer units keep the recorder's protections that do not change the filesystem view (PrivateTmp, the kernel log, clock and hostname protections, KillMode=control-group), and their stdout is the recorder's pipe, so progress reaches Settings as it did. systemctl status evernvr-install-hailo and journalctl -u evernvr-install-hailo show a unit that did not get going; a host without systemd-run is told so in Settings and runs the scripts from a shell instead. Nothing a browser supplies becomes a command: those scripts take fixed arguments, the unit names and the variables handed to them are fixed in the code, and an uploaded release is verified against the trusted keys before anything in it runs. The evernvr account is kept for one job, the unprivileged detector build, because pip runs code from the network.

Because ffmpeg parses untrusted RTSP and H.264 from the cameras in this privilege domain, a demuxer bug is root on the recorder. Keep cameras on a VLAN that cannot reach the internet, and the recorder's own network reachable only to the operators who need it.

If the install fails after copying the release, it stops there and says so; /opt/evernvr/releases/VERSION stays for inspection. Do not delete a data directory to get past a check.

When it finishes it prints the URL to open. The installer's full output, including pip's, is kept in /var/log/evernvr/install-<timestamp>.log rather than on screen; if a step fails, the installer shows the last lines of that log. The first page in the browser creates the administrator account.

The listening address and port live in /etc/evernvr/evernvr.env; restart the service after editing it. Updates leave that file alone. The service starts on boot and restarts after exits, and stopping it terminates its detector and FFmpeg children.

On the recorder, after installing​

/opt/evernvr/current -> releases/VERSION the running release
/opt/evernvr/previous -> releases/VERSION the rollback target
/opt/evernvr/releases/VERSION/ release trees (the last three are kept)
/opt/evernvr/backups/pre-update-* pre-update backups (the last three)
/opt/evernvr/state/update-status.json the updater's progress, for Settings
/opt/evernvr-venv/ detector environments, release-VERSION links
/etc/evernvr/evernvr.env EVERNVR_BIND or EVERNVR_INTERFACE, PORT, others
/etc/evernvr/keyring.json the release keys this recorder trusts
/var/lib/evernvr/ database, key, storage map, logs, backups
/var/log/evernvr/{install,update,rollback,adopt}-*.log
/usr/local/sbin/evernvr-update the host tool

HTTPS​

The recorder serves HTTPS from its first start; nothing needs configuring. It makes its own certificate authority (CA), kept in /var/lib/evernvr/tls/, and issues itself a server certificate covering its hostname, hostname.local, localhost and every address it has. When it gains an address, or 30 days before the certificate's 397 days run out, it issues a new one and switches to it without a restart; the CA itself lasts ten years. TLS 1.2 is the minimum.

The port (3099 unless the installer was given another) answers both protocols. HTTPS and WSS are served as usual. Plain HTTP on the same port redirects to HTTPS, except for the CA itself, which downloads over either:

  • http://recorder:3099/trust/ca.crt (DER, for Windows, Android, iPhone and iPad)
  • http://recorder:3099/trust/ca.pem (PEM, for Linux and anything else)

Until a device trusts the CA, its browser warns about the connection. Install the CA once on each device and the warning is gone for good, including after the recorder's address changes. The login page has the steps for each kind of device under Did your browser warn about this connection? Trust this recorder, with the CA's SHA-256 fingerprint to compare against what the device shows before trusting it. A managed site can push ca.crt through its device management instead. See Trusting the recorder's certificate for the steps.

The CA goes into every configuration backup, its private key encrypted under the credential key (and so behind the backup passphrase when one is set). A recorder restored from a backup, or a replacement restored onto new hardware, keeps the CA devices already trust and issues itself a fresh server certificate for its own addresses. A factory reset keeps the CA too.

Using the site's own certificate​

A site with its own CA can serve a certificate from it instead. Supply a PEM chain and a matching unencrypted PEM key, with Subject Alternative Names covering every DNS name or IP address phones and browsers will use:

sudo install -d -m 0750 -o root -g evernvr /etc/evernvr/tls
sudo install -m 0640 -o root -g evernvr server-chain.pem /etc/evernvr/tls/server-chain.pem
sudo install -m 0640 -o root -g evernvr server-key.pem /etc/evernvr/tls/server-key.pem
sudoedit /etc/evernvr/evernvr.env

Add, using the site's real name and port:

EVERNVR_TLS_CERT=/etc/evernvr/tls/server-chain.pem
EVERNVR_TLS_KEY=/etc/evernvr/tls/server-key.pem
EVERNVR_PUBLIC_ORIGIN=https://recorder.example.internal:3099

Leave EVERNVR_BIND (or EVERNVR_INTERFACE) as the installer wrote it. With EVERNVR_INTERFACE, the certificate must cover every address the interface may be given, or browsers should use a DNS name. Restart during a planned recording gap. Plain HTTP on the port still redirects, but there is no CA to download and the login page shows no trust steps.

A configured certificate is read at startup, so a renewal needs a restart. Setting one of EVERNVR_TLS_CERT and EVERNVR_TLS_KEY without the other is fatal, and so is a key that does not match the certificate — that handshake could never complete. An expired or not-yet-valid certificate is different: the recorder serves it, logs it and raises an alarm, because a recorder that refuses to start is a recorder that is not recording. Health warns 14 days ahead of expiry. These files are not in the configuration backup either.

EVERNVR_TLS=0 serves plain HTTP only. It exists for development; a recorder holding camera passwords should not run that way.

Trusting the recorder's certificate​

Download ca.crt from the login page or the address above, check that its SHA-256 fingerprint matches the one on the login page, then follow the steps below. The login page downloads it over plain HTTP: Chrome on Android fails a download from a page whose certificate warning was clicked through, with a message to check the internet connection. If a download fails anyway, type the http:// address into the browser.

DeviceSteps
WindowsOpen the file, choose Install Certificate, Current User, Place all certificates in the following store, Trusted Root Certification Authorities, then Finish. Restart the browser.
MacOpen the file to add it to the login keychain. In Keychain Access, open the EverNVR CA, and under Trust set When using this certificate to Always Trust.
iPhone, iPadDownload in Safari and Allow. Settings → General → VPN & Device Management, install the EverNVR profile, then turn the CA on in Settings → General → About → Certificate Trust Settings.
AndroidSearch Settings for CA certificate (usually Security → Encryption & credentials → Install a certificate), choose CA certificate, Install anyway, and pick the file.
LinuxChrome: Settings → Privacy and security → Security → Manage certificates → Installed by you, Import beside Trusted certificates (the Authorities tab on older versions). Your certificates is for client certificates and asks for a password; the CA does not go there. From a terminal instead: certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "EverNVR CA" -i evernvr-ca.crt (package libnss3-tools). For other tools: save ca.pem as /usr/local/share/ca-certificates/evernvr-ca.crt and run sudo update-ca-certificates.
FirefoxSettings → Privacy & Security → View Certificates → Authorities → Import, and tick Trust this CA to identify websites. Firefox keeps its own list on every system.

Web push, the installable app and picture-in-picture need a connection the device trusts, so they work only once the CA is installed.

First run​

Open https:// and the recorder's address with port 3099 (a plain http:// address redirects there). Until the device trusts the recorder's CA the browser warns once; see HTTPS. There is no default login: the first page is Set up this recorder, which creates the customer's owner administrator account. The customer chooses and keeps that password. The installer's own access comes later, from installer handoff.

After account creation, the Setup wizard guides the administrator through:

  1. Check detection — see Hailo hardware and the running detector, install the Hailo stack when offered, and choose automatic detection, CPU, or Hailo. Hailo is optional; automatic detection allows CPU fallback. A failed check remains visible and does not prevent continuing setup.
  2. Choose storage — pick a mounted volume or enter an absolute recording folder, and set how many days to keep footage. Continuing checks write access. New locations need an empty folder with an existing parent; the wizard never formats a disk. Existing footage stays in its original location. Connect a new NAS share in Settings → Storage & retention; already-mounted shares also appear in the wizard's volume list.
  3. AI settings — optionally save an Anthropic, OpenAI, or compatible endpoint, its model and API key, and spending limits. Keys use the encrypted credential vault. Save and test connection saves these settings and sends a short text request, which may incur a provider charge; it sends no camera images. Finish without changing AI skips this step. The AI agent stays off on a new recorder until you enable it and select cameras in Settings → AI agent. Local detection needs no API key.

Each Save and continue saves that step immediately. Finish later keeps saved progress and shows a setup reminder. Reloading or signing in again resumes the saved step. Unsaved edits are not retained. Administrators can also open Settings → Setup at any time. Upgrading an existing recorder does not automatically open the wizard or reset its settings.

Settings → This recorder shows the software version, the time zone used for times on screen, and the camera count.

The Settings page​

Settings is a sidebar of groups with one page open beside it. Administrators see seven groups: Recorder (This recorder, Updates, License, Storage & retention, Backups), Detection (Object detection, Recognition, AI agent), Deterrence (Spoken messages), Notifications, Integrations (MQTT, API tokens), Access and Setup. Operators and viewers see only Notifications and Access. Each page has its own address, /settings/<page>, so it can be bookmarked or linked from a runbook; the sidebar label is the page heading. On a phone the sidebar is a screen of its own: choose a page, and All settings at the top of it goes back to the list. Addresses from before this layout (/settings?tab=…) open the page that took over that content.

Object detection and Hailo​

Detection runs locally on DAMO-YOLO-T, either on the CPU through ONNX Runtime or on a Hailo-8. Under Settings → Object detection an administrator picks:

  • Automatic (the default) — try Hailo, fall back to CPU if it cannot start.
  • CPU (ONNX) — use the processor even when a Hailo is present.
  • Hailo accelerator — request Hailo, with a visible CPU fallback if it fails.

The page shows the hardware it found, the backend actually in use and any fallback reason. Select Save to apply a selection and restart detection. Recording and camera connections carry on, tracking identities start fresh and zones are resent. The choice is stored in the database and survives restarts. EVERNVR_DETECT_BACKEND=auto|onnx|hailo sets the default until a preference is saved; a saved preference wins.

CPU fallback latches only when the failure is the accelerator's — an inference error from the Hailo backend, a fault naming the HailoRT stack, Python choosing CPU at startup, or the device backend failing to start twice running. A stall or a generic non-zero exit restarts on the requested backend instead, so CPU contention cannot quietly turn an accelerated recorder into a CPU one. After a latch the recorder retries the preferred backend once, 30 minutes after the CPU detector has been running cleanly; Save retries at once.

Installing the Hailo stack​

On Ubuntu 24.04 and 26.04 a fresh install configures a Hailo-8 it finds. For a card fitted later or a setup that failed or needed a reboot, Settings → Object detection offers Install Hailo stack to administrators when PCI hardware is present but not configured. It works even before the kernel driver has created /dev/hailo0. The recorder starts the installed script itself, with arguments it fixes, in the transient unit evernvr-install-hailo (outside the recorder's own sandbox, which mounts /usr read-only and blocks module loading); nothing from the browser reaches it. Its stage lines come back to Settings through the unit's stdout; the full output is in the root-only log named below and in journalctl -u evernvr-install-hailo.

Setup downloads SHA-256-pinned HailoRT and driver 4.24.0 sources, Hailo-8 firmware 4.24.0 and the Model Zoo DAMO-YOLO-T HEF, installs build dependencies and the running kernel's headers, builds the native runtime and the Python bindings, and installs the bindings into the detector environment without replacing its CPU packages. It then caches those wheels under /etc/evernvr/hailo/wheels and records them in /etc/evernvr/hailo.json so a later update can reinstall them with no network. It makes the runtime packages readable by the recorder and installs a udev rule giving the evernvr group device access across reboots, including devices with existing access ACLs. It needs root, x86-64, systemd, Ubuntu 24.04 or 26.04 and internet access, and takes several minutes. Build dependencies match the detector's Python version (standard CPython 3.12–3.14). Setup uses SHA-256-pinned pybind11 3.0.1 for Python 3.14 compatibility and adapts the vendor build for CMake 4. Full fresh-install Hailo qualification on a 26.04 recorder is still outstanding; accepting the OS does not establish hardware qualification.

While setup runs, detection moves to CPU and backend changes are blocked; recording continues. The job verifies the device and runs a blank frame through the real Hailo backend as the recorder account before reporting success, then restores the previous preference. A failure or a pending reboot leaves detection on CPU and is retryable; diagnostics are in the root-only /var/log/evernvr-hailo-install.log. Secure Boot may need module-key enrollment first. An older loaded driver needs a host reboot — setup never unloads the device or reboots the recorder itself.

If an earlier installation reports a device permission error or cannot import HailoRT, a release containing the permission-repair command can repair the existing installation without downloading components or reinstalling the driver:

cd /opt/evernvr/current
sudo env EVERNVR_PYTHON=/opt/evernvr-venv/release-VERSION/bin/python \
python3 deploy/install-hailo.py --repair-permissions

VERSION is the installed release (evernvr-update status); the unit's EVERNVR_PYTHON names the same interpreter. The command defaults to the evernvr user and group, refuses to run alongside another Hailo setup job, repairs the runtime file permissions and persistent device ACL rule, and checks imports and device access as that account. It does not open the accelerator or restart recording. After it succeeds, select Automatic or Hailo accelerator under Settings → Object detection and select Save to retry detection. Its log is /var/log/evernvr-hailo-permissions.log.

From a shell in /opt/evernvr/current, with nothing else using the accelerator (pass EVERNVR_PYTHON through sudo env as above, so each command finds the release's detector environment):

sudo python3 deploy/install-hailo.py --check # read-only preflight
sudo python3 deploy/install-hailo.py # install and verify
sudo python3 deploy/install-hailo.py --if-present # skip when no hardware
python3 deploy/install-hailo.py --check-cache # is an offline reinstall possible?
python3 deploy/install-hailo.py --refresh-venv # reinstall cached wheels

Exit status 20 asks for a reboot and another attempt; zero means verified (or no hardware, with --if-present). The shell form does not manage a running detector — use Settings for that.

Recording storage​

A local drive​

Mount the drive at a stable system path such as /srv/evernvr-media and make the mount persistent. The recorder is root, so it needs no ownership change on the recording folder. A desktop automount path under /run/media or /media is readable but is not a deployment location: it exists only while that desktop user is signed in, so recording stops after a reboot until someone logs in. Then set it under Settings → Storage & retention → Recording location: pick a mounted drive from the list or type the folder, and Use this folder.

New recordings and event images go to the new folder. Footage already indexed elsewhere stays playable in its original folder and keeps its own retention. Switching briefly restarts camera streams. A registered location remembers its mount source, filesystem and hidden .evernvr-storage marker; a missing or replaced mount blocks recording rather than silently falling back to the system disk.

Retention is on the same page: Keep footage for (days), a disk watermark, and Keep event history for (days) which can follow the footage. A camera may override the site window from its own card. The page also projects how many days the current disk holds at the current rate and warns when that is under the retention you asked for. Event footage keeps the longer of its protected window and the camera's own.

To remove a location, choose Remove… and say what happens to the recordings already there: Move to another location, Keep files on disk or Permanently delete files.

A NAS share​

Open Settings → Storage & retention → NAS / network storage, add an SMB 3 or NFS 4 share, and choose Use for recording. Recordings and event images go to the share's evernvr folder, which must be empty or already registered with this recorder. Use a dedicated share with room for the chosen retention.

The recorder writes the mount units and credential files itself and remounts a share that drops out. All the host needs is the packages that mount SMB and NFS; if they are missing, Settings offers Install NAS support, which installs cifs-utils and nfs-common through apt, in the transient unit evernvr-install-nas (apt writes /usr and /etc, which the recorder's own unit mounts read-only). Package installation starts only when the administrator presses the button; journalctl -u evernvr-install-nas has apt's output. The same thing from a shell:

sudo bash deploy/install-nas.sh --install-packages

Nothing from the browser reaches a mount: the settings are validated into a fixed shape, the mountpoint under /mnt/evernvr-nas is named by the recorder, and the password is written to a 0600 file that mount.cifs reads by path, so it never appears in a unit file, an API response or ps.

SMB needs a username and password with read, write and delete access; SMB 3.0 is used, and guest or SMB1/2-only shares are not accepted by the form. Passwords are stored in root-only credential files under /etc/evernvr/nas and never appear in API responses or command arguments. NFS needs an NFSv4 export that permits this recorder's address and does not squash root: with the default root_squash the recorder is mapped to nobody and refused, so export with no_root_squash or use SMB. A successful mount does not prove write access, so Use for recording verifies a write of its own.

Mounts are saved as systemd mount units, enabled at boot, and retried every 30 seconds while absent; Reconnect retries one now. NFS uses hard mounts, so a share that stops answering would park any thread that touched it — EverNVR probes each recording location from its own worker thread, reports the share as Not responding within a few seconds, and skips it for indexing, retention and recorder restarts while the other locations carry on. It resumes by itself when the share answers. Nothing is buffered or backfilled while a share is away.

To disconnect a share, first remove its recording location, then Remove connection. A connection holding registered or preserved footage cannot be removed. Never force-unmount a share with active recordings.

The configuration backup covers the recorder's own storage.json but not the host's NAS units or credentials. Protect /etc/evernvr/nas and the mnt-evernvr\x2dnas-*.mount units in /etc/systemd/system with a separate root-only backup. When a mount misbehaves, check the unit itself: systemctl status 'mnt-evernvr\x2dnas-*.mount'.

Disk hardware health​

The installer writes an empty /etc/evernvr/disks.json and enables the probe timer, so the "not configured" warning means the list is empty, not that the probe is broken. Install smartmontools (the host check notes when it is missing; without it every configured disk reads "SMART data is not available: smartmontools is not installed"), then name the physical recording disks by stable device path, as root, keeping the file mode 0644:

["/dev/disk/by-id/REPLACE_WITH_RECORDING_DISK"]

Include only the physical disks this recorder uses. Then check that the timer is active and run the probe once instead of waiting up to five minutes for it:

systemctl status evernvr-disk-health.timer
sudo systemctl start evernvr-disk-health.service

If the timer is missing, reinstall or update the release; it puts the units back. Select Refresh disk status when the service finishes; the warning clears within ten seconds of fresh, healthy results for every configured disk. The shipped service writes /var/lib/evernvr/disk-health.json; a custom installation must adapt the service and probe to its own data directory and service account.

If results are missing or stale, check that the configured device paths exist, that smartctl is installed, and that the service can write results the recorder can read:

sudo systemctl status evernvr-disk-health.timer evernvr-disk-health.service
sudo journalctl -u evernvr-disk-health.service -n 50 --no-pager

Some USB bridges, virtual disks and network storage do not expose SMART data; configure the physical disk on the host where it is accessible. If a disk reports a hardware failure, preserve the footage you need and service or replace the disk. The probe reads SMART status and attributes only and never runs self-tests. Acknowledging an alarm does not resolve the health warning.

Settings → This recorder shows the last probe time and each disk's result, with a link to these instructions. The privileged probe runs smartctl -j -H -A on the configured devices only and publishes health, temperature and NVMe wear counters — not serials, WWNs or raw device logs. Unsupported USB bridges and virtual disks report unknown, never healthy. Results older than fifteen minutes stay visible as warnings. An administrator can turn off Alert when disk health results are missing and select Save in that form to apply it for everyone; disk failure, stale result and storage alerts stay on.

Adding cameras​

Before discovery, open Settings → Setup → Commissioning and save the site name, the default recording mode, and the number of connections reserved for the incumbent recorder and its operators. At least one is always reserved. Those defaults apply to newly adopted cameras; existing cameras keep theirs.

EverNVR changes no camera configuration except the clock. The only writes it makes are clock correction (the time, the recorder's time zone and the recorder as the camera's NTP server, set by the recorder itself or by an administrator), operator PTZ, preset recall (including an administrator's patrol stepping through presets), the moves of an administrator's auto tracking (a PTZ camera following a person or vehicle its own detector saw, audited once per follow), preset save, and switching a camera's deterrence output (a relay, light or siren) on or off when an operator or a rule asks — each audited — and it never takes the last RTSP session a camera has.

On the Add cameras page, save the site's camera passwords first, then scan. EverNVR tries every saved password on each device it finds and shows the result beside it; a password added later is tried at once on the cameras still locked. A camera the scan misses is added under Camera not found? by IPv4 or IPv6 address. An optional ONVIF port targets a camera on a non-standard port; leaving it blank tries the common ones.

Each camera starts on one connection, and adoption probes streams one at a time. Nothing asks for a connection limit, and a customer's live camera is never saturation-probed to find one. If a camera's specification gives its simultaneous connection limit, enter it afterward under Streams & connections so EverNVR can use the second stream.

A camera or encoder with no ONVIF service is added under Add by stream address with its main stream address and an optional second address for a smaller detection and viewing stream. It is opened and verified exactly like a discovered camera. Afterwards it has no clock correction, no PTZ and no refreshable stream list.

Review what the probe actually found. A profile ONVIF advertises is a claim; the catalog marks the streams that were really opened. A successful probe establishes stream facts and nothing else — not recording, playback, audio, browser compatibility or a safe session ceiling.

A stream address a camera answers with is opened only if it is rtsp:// or rtsps://, carries no credentials of its own (the recorder adds the ones it holds), and points at the camera's own address or a name that resolves to it. Anything else — an http:// address, a stream on another host — is refused with the address the camera offered, before ffprobe ever sees it. A camera behind a NAT or a proxy that advertises an unreachable internal address fails here too; add it by stream address instead.

On the Cameras page, pick a camera from the list on the left. Its settings open beside it, on tabs:

  • Overview — what the camera is doing now: the streams in use, the last frame, detector statistics, the camera clock, and anything it needs from you.
  • Setup — Camera name, whether EverNVR records or only watches, Record audio (off by default; it never changes a camera encoder or opens an extra stream, verified AAC is packet-copied and verified G.711 is converted to AAC, anything else records video only with a stated reason, and enabling it restarts that camera's ingest and affects future recordings only), the live view mode, Camera's own events, the camera's Deterrence outputs and whether its speaker can be talked through (see below), and removing the camera.
  • Streams & connections — Connection limit (optional) and Reserved connections, then Recording stream and Viewing & detection stream. An empty limit means one connection, shared by recording, viewing and detection. The wall, the maximized view and detection share the viewing stream. Checking a new stream briefly pauses that camera's ingest. A camera whose limit leaves no connection beyond the reserved ones opens on this tab.
  • Detection — floors: Ignore detections below a confidence and objects shorter than a height, applied in the detector before tracking. The size floor drops a detection outright. A detection under the confidence floor, or under the detector's own starting threshold, cannot start a track but can keep an object already being tracked in the same place, so a person who sits down or an animal that lies still does not end its visit. A box mostly inside a stronger box of the same kind counts as part of that object, not a second one, when it is under that threshold or is an animal; two people close together stay two; a detection schedule, Watch only from / to on chosen days, outside which nothing is detected while recording carries on. Once the operators' "Not a person" marks and confirmed visits are numerous enough, What do the marks propose? suggests a floor. Recognition floors for known people, vehicles and pets are proposed from your own confirmations and rejections on that camera and applied only when an administrator says so.
  • Recording — a recording policy: Everything, Only around detected activity, or Anything that moves, optionally Only at certain times in weekly windows. The recorder never stops; the index deletes finished minutes the policy does not keep, so the minute before an event is always there. When the detector was not watching, the minute is kept rather than guessed about. Keep this camera's footage for a number of days overrides the site window, and Box alignment is how far detection trails the recording; the recorder takes it off every event's time and picture, so they line up with the footage. With Measured by the recorder the recorder keeps every measurement, and boxes over a recorded clip use the ones taken nearest that footage, since the lag can change after a camera reconnects; the value shown is the latest. Set by hand applies one value to all of the camera's footage.
  • PTZ patrol and Auto tracking, on cameras added over ONVIF — the presets a PTZ camera tours and how long it stays at each, and whether it follows People or Vehicles its detector sees travel, for at most a set time, returning to a preset afterwards or staying where it stopped. Auto tracking needs the camera's PTZ geometry calibrated or trained under Privacy masks and a camera that takes absolute positions; the tab says which is missing. Any operator's PTZ command ends a follow and pauses tracking for the wait set on the tab. Commission a follow with a real walk-through at the site: it has been exercised on one camera model on the development bench, with a stationary vehicle, and nowhere else.

Clock correction​

A camera whose clock is wrong produces footage nothing can correlate. The recorder owns the clocks of the cameras attached to it. It reads each ONVIF camera's clock every 15 minutes and once more when a camera comes back online (Settings → This recorder → Camera clocks → Keep camera clocks correct, on by default, including on recorders updated from a release without the setting). When the time is more than a few seconds out, when the camera's picture shows a different UTC offset from the recorder's time zone, when the camera keeps its own time instead of taking it from the recorder, or when it takes its time from an NTP server other than the recorder, the recorder sets all three: the time now, its own time zone (the one EVERNVR_TZ or the host sets), and itself as the camera's NTP server. The picture time decides: a camera that reports its local time, and whose local time is within a minute of the recorder's, is left alone whatever UTC time or offset it reports, because some firmware keeps daylight saving by shifting its reported UTC time an hour while the picture stays right. A camera already on NTP is asked which server it uses; once it names the recorder it is asked again at most once a day, and a camera that cannot say is left as it is. A camera already showing the right offset keeps its own zone string, sent back with daylight saving on when its picture shows daylight saving, whatever flag it reports. A camera that refuses an NTP server keeps the corrected time and zone, and the refusal is recorded. Each correction is verified against the camera afterwards, by its clock or by what its local time now reads, and is recorded permanently on History → Camera changes as a write made by the recorder, with what was set and what the camera kept. One camera is checked at a time, a camera is corrected at most once every ten minutes, and a camera whose time is right but which did not keep the zone or NTP server from an earlier attempt to set them is set again at most once a day; an ordinary correction of the time alone does not start that daily wait. When a camera refuses a request because of its security timestamp, the recorder tries the request once more stamped with its own clock and keeps doing so if the camera accepts that, so a camera whose reported clock differs from the one its security check uses still takes PTZ, event and clock requests. Turning the setting off stops the writes; the clock is still read on the same schedule so the drift shown on the camera's Overview tab stays current. An RTSP-only camera has no ONVIF clock and is never checked.

The recorder answers NTP itself, on UDP port 123, from the host's clock, so a camera without a battery-backed clock that restarts reading 2000 puts itself right at its next NTP poll. Allow UDP 123 from the camera network to the recorder. Keep the host clock synchronized: while the host reports it synchronized (through systemd-timesyncd; Settings → This recorder → Recorder clock shows whether it is, and sets the zone, network time on or off, and the time by hand) the recorder serves its time at stratum 3, and otherwise as a local clock at stratum 10, which still gives every camera on a site without internet the same time as the recordings. If another time service on the host already holds port 123, the recorder logs not serving NTP to cameras at startup, leaves the cameras' NTP settings alone, and keeps their clocks right with its regular checks instead. EVERNVR_NTP=0 turns the recorder's time server off.

A correction of a minute or more, automatic or from Correct the clock, also reconnects the camera's streams, which costs a few seconds of recording. Some cameras stop advancing the timestamps of a stream that was open while their clock jumped, and that stream records footage that plays several times too fast until it reconnects. A camera without a battery-backed clock that comes back reading 2000 after a power cut does this once per outage.

Whether or not the setting is on, the recorder also compares each stream's timestamps with its own clock. A stream whose timestamps run well fast or slow for a minute is reconnected; if it happens again within 15 minutes, that camera is recorded with the recorder's own arrival timestamps until the recorder restarts. Each time leaves a resolved warning on History → Recorder health. Only H.264 and H.265 streams that play in the browser are measured. A stream whose timestamps jump backwards, as when a camera's clock is set back under an open stream, is reconnected at once, and a camera that has been delivering live video and then delivers none for 25 seconds is reconnected however busy its connection looks.

Two corrections leave a trace in recorder health. A clock that was a day or more out leaves a resolved warning on History → Recorder health, such as Lobby clock was 26 years out; corrected, so somebody learns it happened. A camera corrected three or more times in a day raises an open warning, Lobby clock keeps drifting; its NTP source may be wrong, which clears by itself after a day without a correction. Neither raises the problem banner.

The camera's Overview tab shows the camera clock and its drift, and offers Correct the clock to an administrator when the clock is more than a minute out — a camera the automatic correction could not set, or a recorder with the setting off. Correct the clock sets the zone and NTP server too. A camera on NTP whose clock stays wrong has an NTP server it cannot reach. Switch to manual time and set the clock is a second, separately approved action, never automatic; it sets no NTP server.

Deterrence outputs​

Some cameras can switch something on: an alarm output over ONVIF (a strobe, a door strike or a third-party siren wired to the camera's terminals), or the white light and siren built into an active-deterrence camera. Deterrence on the camera's Setup tab lists what the recorder has found and offers an administrator Find outputs, Look again once it has looked, and Test for 3 s beside each output. The recorder asks the camera rather than assuming from its model, and remembers the answer, so look again after changing the camera's wiring or account.

Each kind is reached the way its maker allows: ONVIF relay outputs through the camera's DeviceIO service; a Dahua or Amcrest white light and siren through the camera's own web interface with the camera account EverNVR already has; a Tapo siren and spotlight through the camera's local interface, which answers only to the Tapo app account. For a Tapo camera, enter that account's password under Deterrence and select Save and look; it is kept encrypted with the camera's other credentials and checked against the camera before it is used, so a wrong password costs no sign-in attempts. Forget removes it.

A found output appears in Live view beside the PTZ controls for operators and as the Switch a light, siren or output rule action. An output switched on for a while (up to five minutes) is switched off on time even if the recorder restarts in between; the recorder keeps trying for ten minutes past the time if the camera does not answer. Every switch, and every attempt the camera refused, is on History → Camera changes as Output switched, naming the rule when a rule asked. A siren is heard beyond the site, so give such a rule a schedule and a cooldown, and tell the customer what will sound.

What has been seen so far: an Amcrest active-deterrence camera's white light switched on and off from the recorder on the development bench, and its siren found but not yet sounded. The ONVIF relay path and the Tapo path are implemented with automated tests only; no camera with relay outputs and no Tapo camera with its app password entered has been on the bench. Commission each output at the site and record it.

Talk and spoken messages​

A camera's speaker is used only through the ONVIF audio backchannel (Profile T). The recorder asks each enabled camera once, in the background, with an RTSP request for its recording stream, and keeps the answer; it does not trust a camera's capability claims, since some claim an audio output they cannot be sent sound on. Deterrence on the Setup tab reads Talk: followed by whether talk is available and in which format, or why not; Look again asks again. The recorder sends G.711 mu-law, and converts to A-law for a camera that takes only that.

Operators and administrators talk from Live view with Hold to talk, in a browser that trusts the recorder's certificate (the microphone is refused otherwise; see trusting the certificate). One person talks on a camera at a time; a talk ends after five minutes, or after ten seconds with no sound from the page. API tokens cannot talk. While someone talks, the camera carries one more RTSP connection than usual; a camera with none free refuses, so leave room in its connection budget if talk matters at the site.

Settings → Spoken messages (administrators) holds messages for a speaker to play, recorded in the browser or typed and spoken by Piper on the recorder. Piper is installed with the detector's Python wheels. The first typed message in a voice downloads that voice (about 60 MB) from the Piper voices repository, checked against a pinned checksum; a site without internet access can still use recorded messages. Messages live in the database and travel in its backups. Operators play them from Live view, and the Play a message rule action plays them when something is detected. Every talk and every play is on History → Camera changes as Talked through the speaker.

What has been seen so far: the backchannel detected on Amcrest cameras on the development bench and correctly found absent on Tapo cameras, a browser's microphone heard through an Amcrest camera's speaker, and a Piper message played there from the API and by a rule. No other make has been tried. Commission talk on each camera at the site and record it.

While cameras are being adopted​

Watch the incumbent NVR's own footage while EverNVR runs. Confirm it has no gaps, no session eviction and no frozen pictures, including while its operators are viewing.

Zones and rules​

Detection zones: an administrator draws the areas to watch on each camera; everyone else sees them read-only. Zones are pushed to the detector on every change and resent after every detector restart.

Action rules holds the rules and the buttons. A rule has a trigger, a list of actions, and a schedule with a cooldown and an optional cap:

TriggerFires when
Object detectedA chosen object is seen in the area, above a confidence and after a minimum dwell
Object leftIt leaves the area, having stayed at least that long
OccupancyThat many of the chosen objects are in the area at once
AbsenceNone of them have been there for that many minutes
ButtonAn operator presses it in Live view
Recorder healthA fault that pages administrators has been open two minutes, and again when it clears
Something unusualA visit departs from the site's own normal for that camera and time of day
Camera reportsThe camera's own ONVIF event begins: motion, a line crossing, an intrusion, a person, tampering or another event

Something unusual compares against the last 28 days per camera and time of day, and stays quiet until the recorder has two weeks of history, so do not expect it to fire during commissioning. It reports at most once an hour per camera and object, and every departure reaches the Alarms page and the morning briefing whether or not a rule sends it.

Camera reports needs Camera's own events turned on under the camera's Setup tab. EverNVR then holds one ONVIF pull-point subscription on the camera, renewed a minute at a time. It uses no RTSP connection and changes nothing in the camera's configuration. The camera's tripwires and intrusion areas are set up in its own web interface. What the camera reports is not checked against the picture. Commission it by walking through the camera's own rule area, then check History → Camera events and the rule's run.

Object, left and occupancy rules can be scoped to a zone and to Who — anyone, someone known, or unrecognized — once people, vehicles or pets have been tagged on events. Where face or plate recognition is on for the camera (Settings → Recognition), object and left rules can also ask for someone known by face, a face nobody enrolled, a vehicle known by its plate, or an unknown license plate; see face and plate recognition.

A rule runs a list of actions, up to ten, in order. Every run is recorded in the alert history whatever the list holds, so a rule with no actions is a log.

ActionWhat it does
Send notificationEmails, texts or pushes the named people
Send webhookPOSTs the trigger details, with X-EverNVR-Timestamp and an HMAC-SHA256 X-EverNVR-Signature when a signing secret is set
Move a camera to a presetRecalls a preset the camera already holds, once, within a minute of the trigger or not at all; may name another preset to return to after a wait of 15 seconds to 30 minutes, cancelled if anybody drives the camera first
Aim a camera at the triggerTurns a PTZ camera whose heading is measured on the site plan toward where the detected object stood, worked out from the triggering camera's matched picture, with an optional zoom and the same optional return; within a minute of the trigger or not at all
Switch a light, siren or outputSwitches a deterrence output found on the camera's Setup tab (an ONVIF relay output, or a Dahua, Amcrest or Tapo light or siren) on for a number of seconds, on, or off, within a minute of the trigger or not at all
Play a messagePlays a message saved under Settings → Spoken messages through a camera's speaker (ONVIF audio backchannel), one to three times, within a minute of the trigger or not at all; skipped while somebody is talking on that camera
Preserve footageHolds the footage from a chosen time before the trigger to a chosen time after it, up to an hour, as a preserved investigation in the rule's name; a trigger with no camera holds every camera
Arm or disarm rulesEnables or disables the chosen rules for a number of minutes or until changed; a timed change reverts on its own, and saving the rule by hand ends it

An action can appear more than once in the list: two webhooks are two deliveries, each with its own secret, and each webhook keeps its secret across a save that does not mention it. Preserve footage holds one minute before and five after unless changed; runs close together on the same camera extend the hold rather than opening another, and the hold is released on the Investigation page like any other. Arm or disarm rules runs within a minute of the trigger or not at all, like a preset recall, and a timed change lasts at most a week; a rule under one shows Armed until or Disarmed until in the rules list. A disarmed rule's events are still recorded. Switching a rule off by hand drops its messages still waiting to go out; a timed disarm by another rule does not, so a button that notifies someone and then quiets its own rule still sends. Switch a light, siren or output names a Camera whose outputs have been found, one of its Outputs, and Switch it: On for a while for a number of Seconds (1 to 300, 10 unless changed), On, until switched off, or Off. It runs within a minute of the trigger or not at all, and the recorder switches a timed output off on time whatever happens to it in between. "Person at night: light for thirty seconds and siren for ten" is two actions on one rule. Every action of a run is on the History page under Notifications with its outcome, labeled Webhook, Camera preset, Camera aimed, Camera output, Camera message, Preserve footage, Arm or disarm rules, or Logged for a rule with no actions.

Webhook targets may be anything on the internet or the site network except this recorder, its own cameras and link-local addresses. EverNVR never creates presets; make them in the camera's own interface, or save the current position from the PTZ panel as an administrator. A preset a rule recalls, a patrol visits, or auto tracking returns a camera to cannot be deleted until the rule, patrol or auto tracking is changed. A rule's recall or aim ends a follow in progress on that camera without pausing auto tracking, so the camera can take up whatever it finds where the rule pointed it.

Schedule, cooldown & cap uses the recorder's local time — confirm the time zone before writing an overnight rule. Cooldown is the minimum time between runs of a rule, counted per camera; the cap is "at most N per window", counted the same way.

With the AI agent enabled, a detection rule can also Ask the AI agent first: its deliveries wait a few seconds for the recording still, the agent looks once, and a concern below the rule's line holds every action with the agent's sentence as the reason. Any failure to answer — off, camera not shared, over budget, provider error, no answer in two minutes — runs the actions as they would have without asking.

On Detection zones, with the agent on and the camera shared, Propose zones and rules reads a still and offers zones and the two or three rules a site like this usually wants, each approved, edited or skipped one at a time.

With the agent on, the Action rules page also takes a rule as a sentence — "text me when someone I don't know is in the garage after 10pm" — and Write the rule fills the editor from it. Nothing is saved until an administrator reviews the trigger, camera, zone, schedule and actions and selects Save, so treat it as a first draft, not a finished rule.

Notification delivery​

Three channels, all opt-in, none on by default: email, text messages, and push. Push needs no setup here; see below.

An administrator configures the services under Settings → Notifications, in the Email, push and text section (shown to administrators only, below Your notifications):

  • Email relay (SMTP) — server, port, sender address, and optional user and password. Non-loopback SMTP requires certificate-verified TLS: implicit TLS on 465, required STARTTLS elsewhere. A loopback relay may go without TLS. There is no OAuth enrollment.
  • Text messages (Telnyx) — the sending number and the API key.

Secrets saved there go to the credential vault and are never returned. The same values can come from /etc/evernvr/evernvr.env instead, in which case the page shows them read-only:

EVERNVR_SMTP_HOST=mail.site.example
EVERNVR_SMTP_PORT=587
EVERNVR_SMTP_FROM=recorder@site.example
EVERNVR_SMTP_USER=recorder
EVERNVR_SMTP_PASSWORD=replace-with-relay-secret
EVERNVR_TELNYX_FROM=+15551234567
EVERNVR_TELNYX_API_KEY=replace-with-telnyx-api-key

Set the Recorder address for links under Settings → Notifications (or EVERNVR_PUBLIC_ORIGIN) before sending anything: it is the HTTPS address staff open the recorder at, and every email and text message links to it. Email and texts cannot be sent without one.

Each person then opens Settings → Notifications → Your notifications and fills in their email address and their phone number, and enables the channels they want. An email address and phone number can also be set for them by an administrator under Settings → Access. Push is turned on from each device with Turn on for this device: the recorder sends it straight to that browser's push service (Apple, Google, Mozilla or Microsoft), so the recorder needs outbound HTTPS, and the device must open the recorder over HTTPS with a certificate it trusts: install the recorder's CA first (see HTTPS). On iPhone and iPad the recorder must first be added to the Home Screen and opened from there. Upgrading from a release that used ntfy turns push off; EVERNVR_NTFY_* settings are ignored. Send test email, Send test push and Send test text send a labeled test with a synthetic one-pixel image, at most one per person per minute. Snooze for 1 hour pauses everything; suppressed alerts do not replay.

An administrator creates the rule: Action rules → New rule → Send notification, choose each person, then tick email, text or push for them. A channel a person has no address for cannot be ticked. A rule that names a channel directly reaches that address whether or not the person's own switch is on; the switches cover rules that leave the choice to the recipient, and recorder problems. Administrators can also tick Tell me when the recorder itself has a problem for one message per fault and one all-clear.

Email and push carry the event image; text carries the rule, one sentence and the link. A missing or oversized image becomes a message that says the picture is unavailable. Images are copied to the chosen provider, which may retain them or show them on a locked phone; they carry no token to recorder footage.

Delivery is at least once. At most five attempts are made, 30 seconds, 2 minutes, 10 minutes and 30 minutes apart, and a job expires an hour after it was queued. HTTP 4xx other than 408 and 429, and permanent SMTP rejections, stop retries. Statuses are pending, sending, retry, accepted, failed, expired and suppressed; accepted means the provider took the message, not that a phone received it. Every message, its status and its retries are on the History page under Notifications.

The morning briefing​

At the foot of Email, push and text, Morning briefing sends one message a day summarizing the night: what each camera saw, what was unusual against the site's own normal for the last four weeks, and camera and recorder health with what to check. It is off until an administrator turns it on, picks a time — 07:00 by default, in the recorder's local time — and names at least one person.

Each person hears it through whichever channels they already switched on under Settings → Notifications; a text carries only the headline. Someone chosen with no channel on receives nothing. The briefing covers every camera on the recorder, so choose people meant to see the whole site.

The AI agent writes it when it is on; when it is off, over budget or unreachable, the recorder writes a plainer one from the same records, so the briefing does not depend on a provider subscription. Send one now produces one immediately for checking at handoff. A recorder that was off at the chosen hour sends the briefing when it comes back. Past briefings are on the History page under Briefings.

Accounts and installer handoff​

Roles are Administrator, Operator and Viewer. Administrators always have every camera and export authority. New Viewers and Operators start with no cameras and no export permission.

Settings → Access → Camera access and footage export: choose an account, select cameras or opt into all current and future cameras, and separately enable footage export. Saving signs that account out on every device. Operators can move PTZ only on cameras they have.

Reviewing is not exporting. Anyone with a camera may pull up to 120 seconds of its recording per request. Longer pulls are extractions: they need the export permission and are audited as clip.export.started and clip.export.completed. The same applies to a recorded frame from a moment older than that window. Timeline playback stays inside the window for every account, administrators included, so ordinary review is never on the record as an export; the Save dialog is the audited path. A read the viewer stopped early, by scrubbing or leaving the page, is not recorded as a failed request; a change cut short still is.

Settings → Access → Two-step sign-in offers optional authenticator-based two-step sign-in for any account: a time-based account added by scanning the displayed QR code or by hand from the setup key (SHA1, six digits, 30-second period), confirmed with a live code, and ten single-use recovery codes shown once. A wrong authenticator or recovery code is reported as an invalid verification code, not a wrong password. If a staff member loses both, a permanent owner resets that account's second factor from the same page; the account, its role and its camera grants survive. If the owner loses both, use the offline procedure in RECOVERY.md.

Failed sign-ins are counted per source address first, so a stranger guessing at a username cannot lock the owner out of their own recorder. Active lockouts are listed and clearable under Sign-in lockouts.

Temporary installer access​

The customer creates and keeps the first administrator account. For the installer's own access, the customer opens Settings → Access → Installer access and customer handoff and Authorize temporary access: a unique installer username, a password, the authorized work, and a duration from 1 to 168 hours (24 by default). The customer confirms with their own password and, if two-step sign-in is on, an unused authenticator or recovery code. Nothing is emailed; share the credentials securely.

The grant starts on authorization. Every session and future sign-in is capped at the original expiry, through password and MFA changes. Grants cannot be extended or made permanent — more work needs a new authorization and a new username.

An installer can configure cameras, recording, detection and the rest of the recorder. They cannot create permanent accounts, reset anyone else's password, change roles or camera permissions, revoke another user's sessions, grant installer access, accept their own handoff, or move the nightly backup folder — that folder holds the key that decrypts every camera password, so where it is written is a permanent owner's decision. They may still set the backup hour and how many are kept. They see notification recipients as ids and roles, never permanent usernames.

When the work is done, the customer reviews camera coverage, retention, recording status, alerts, notification recipients and any webhook destinations, then chooses Accept handoff or revoke → Accept customer handoff, writes what was accepted, and re-enters their password and an unused second-factor code. Acceptance ends that installer's access on every device at once. Revoke access stops work immediately and needs a reason; a signed-in permanent owner does not have to re-enter a password for it. Expiry ends access on its own even if the recorder restarts.

Either ending appends to the record whatever the installer changed of the settings that decide where a copy of the site's data can go — the backup folder, network shares connected or disconnected, the recording location, the email relay and SMS account, the MQTT broker, and the published address — with when each was last changed and where backups are written now. The history is append-only and survives user deletion.

The commissioning report​

Settings → Setup → Commissioning holds the site defaults, the commissioning checks, and the per-camera evidence.

Defaults for newly added cameras sets the site name, the mode (recording and detection, or detection only), and how many camera connections are reserved for the existing recorder and operators. Defaults apply only to cameras added afterward. Reservations matter only for a camera whose connection limit you have entered; without one a camera uses a single connection.

Refresh checks re-reads the state and lists what is unresolved: missing live frames, no recording progress, stale detection, an unknown or stale camera clock, an unknown session budget, missing playback evidence. Follow each camera's instructions rather than dismissing them.

For each camera, open recent playback, export a clip and decode it independently. Check the timestamps, seeking, image quality and that it is the camera you think it is. For an intelligence-only camera, verify the footage in the incumbent NVR instead and record the channel and clock offset — EverNVR has no qualified adapter for any incumbent NVR.

Record test evidence saves an installer attestation for the selected camera: physical or synthetic environment, playback and export results, audio result, the incumbent-recording outcome, the source and date of the session limit, and the procedure and its limitations. These are attestations, not tests EverNVR ran. Each entry is append-only with the actor, the timestamp and a snapshot of the camera, profile and clock. A firmware, stream, mode or budget change invalidates the current evidence, and evidence older than 30 days needs renewal. Do not type passwords, provider destinations or customer secrets into free text.

Download report saves the JSON report: model, firmware and port, the codec and audio facts of the selected and catalog streams, the declared budget and reservations and the resulting strategy, clock drift and age, the latest evidence with its stale flag, recent segment counts and recording progress, detector results, storage availability and capacity, retention policy, notification test states, global health and the unresolved warnings. Credentials, stream addresses, provider destinations and storage paths are not exported. A report can be full of failures; downloading it is not acceptance. Record its date and its warnings in the site handoff, and repeat it after significant changes.

The disk headroom figure is indexed bytes per recorded second over the last 24 hours, summed over the enabled recording cameras, against free space up to the disk watermark. It estimates the days before eviction pressure, not promised retention.

Backups​

The running recorder backs its own configuration up every night without a recording gap. Settings → Backups sets the Backup folder, the hour, and how many to keep (seven by default), and Back up now takes one on demand. The default folder is /var/lib/evernvr/backups, beside the database, which survives a failed update or a corrupted database but not a failed disk — point it at a NAS share or a second drive for both. The folder must be the recorder's data directory or a registered storage location. A failed backup, or two nights without one, is a recorder health fault and reaches administrators like any other.

That default folder is on the system disk, beside the database, so it also keeps to a size: nightly backups and the pre-migration snapshots an update takes stay within 4 GB together (EVERNVR_BACKUP_BUDGET_GB in /etc/evernvr/evernvr.env changes it), or within the configured count, whichever is reached first. The oldest go first and the newest of each kind is never removed. A folder on a NAS or a second drive is bounded by its count alone.

The database itself runs in write-ahead-log mode with synchronous=NORMAL: the log is synced at each checkpoint rather than at every commit, so a power cut can lose the last moments of recorded events (the segment index is rebuilt from the footage on disk anyway) but cannot corrupt the file, and the recorder does not fsync every one of its many small writes on a disk that is also taking footage. A passive checkpoint runs every fifteen minutes and the log is truncated to 64 MB afterwards. If the system disk fills, database writes fail with SQLITE_FULL; the recorder reports that as one critical fault, The recorder's system disk is full, keeps recording to the footage drive, and clears the fault once there is room again. Free space on the system disk (old backups and logs first) or move the backup folder to another drive.

A backup holds the database, the credential encryption key, the storage settings, the recorder's own certificate authority and an optional service environment file. It does not hold footage, thumbnails, a certificate of the site's own or OS configuration. It contains the key that decrypts every camera password: keep it on access-controlled, encrypted storage. A permanent owner can set a Backup passphrase on the same page, of at least 12 characters, so every backup from then on carries that key wrapped and a restore asks for the passphrase; without one the page says in plain words that the key travels in clear and will not save the settings until the owner ticks I understand. Set it at commissioning and keep it with the recovery material, not in the backup folder: a backup made under a passphrase cannot recover a stored credential without it. The details are in RECOVERY.md.

Restoring, replacement recorders, owner recovery and every other recovery path are in RECOVERY.md.

Updates​

An update has a recording gap of a minute or two while the recorder restarts. Plan it.

Updates come from the release server, https://updates.harnessautomation.com. The recorder checks it on the server's schedule and shows what is available; nothing installs by itself unless the site has turned automatic updates on.

From the Settings page​

Settings → Updates, as an administrator, shows the installed version and channel, when the recorder last checked, and any release on offer with its notes. Check for updates asks now; Download fetches and verifies the release; Install installs it (downloading first if needed). The page shows each step of the install. When the updater stops the recorder to switch releases, the page goes dark behind an Installing the update notice rather than reporting an outage; it polls the recorder every few seconds and reloads itself once a different version answers, or once the restart has ended the session, so it comes back on the new release's own pages. The reloaded page picks the install's step list up where it was and follows it to the end without reloading again, then reports the outcome: updated, or rolled back and why. A rollback that restored the database also signs everyone out. Leaving Settings during the install does not lose this: the notice appears on whichever page is open when the restart comes. The version label at the foot of the side rail opens this page for an administrator.

Install updates automatically is off by default. Turned on, the recorder installs what the server offers without waiting for an administrator, optionally only inside a daily window (set through PUT /api/update/settings with {"window": {"start": "02:00", "end": "05:00"}}, local time).

A site without access to the release server uses Install from file: choose the .run file and its .run.sig. They are checked against this recorder's trusted keys before anything is installed; a file signed by any other key is refused.

The install runs in its own transient systemd unit, because the updater stops the recorder partway through and anything in the recorder's own control group would be killed with it. Its full output is in /var/log/evernvr/update-<timestamp>.log, and its progress in /opt/evernvr/state/update-status.json, since the page that asked for it is gone by then.

A recorder that was not installed from a .run — a source checkout or a development server — has no updater, and the page says so.

From a shell​

sudo evernvr-update apply evernvr-VERSION-linux-x64.run # the .run.sig must sit beside it
evernvr-update verify evernvr-VERSION-linux-x64.run # check only
evernvr-update status # installed, previous, last update
sudo evernvr-update rollback # back to the previous release

apply copies the file and its signature somewhere only root can write, checks the signature with the installed code against /etc/evernvr/keyring.json, and only then unpacks it. It asks before starting; --unattended does not. --backup-dir /absolute/DIR puts the pre-update backup somewhere other than /opt/evernvr/backups, in a directory that does not exist yet. A downgrade needs --allow-downgrade, and reinstalling the running version --reinstall.

The pre-update backup is for undoing this update, on this machine: the copy that survives the machine is the nightly backup, which is where a mounted share or a NAS folder belongs. Confirm there is room for the new release, a new detector environment if its lock file changed, and the backup.

What an update does, and in what order​

The recorder keeps recording through the first part:

  1. Take the lock and check the host: architecture, Python 3.12–3.14, ffmpeg. An interrupted earlier update must be recovered first (evernvr-update rollback).

  2. Verify the release with the installed code against the trusted keys.

  3. Copy it to /opt/evernvr/releases/VERSION, make it root's, and verify the copy — the copy is what runs.

  4. Prepare the detector environment for the new release. An environment is named after detector/requirements.lock and the interpreter, and finished with a .evernvr-complete marker; when the lock file and interpreter are unchanged the running release's environment is reused — no download — after checking that it still passes pip check and loads the new model. Otherwise it is built from the hash-pinned wheels as the unprivileged evernvr account, with nothing of anyone's environment (HOME an empty root-owned directory, PIP_CONFIG_FILE=/dev/null, --require-hashes). On a recorder with Hailo, the bindings are installed into it from the cached wheelhouse, offline; without a complete cache the update stops here with the recorder untouched — re-run Hailo setup with network access and retry. The environment is linked as /opt/evernvr-venv/release-VERSION.

    Everything that may need the network happens here, while the site is still being recorded: an index that cannot be reached costs nothing, where the same failure after the switch would be a stopped recorder and a rollback.

Then the switch:

  1. Stop the recorder.
  2. Back up /etc/evernvr and, with the running release's maintenance.js backup, the database, key and storage map, into /opt/evernvr/backups/pre-update-<old version>-<timestamp>.
  3. Switch: current points at the new release and previous at the old one. The units are rendered from the new release's templates (evernvr.service gets that release's EVERNVR_PYTHON), and detector environments no remaining release uses are removed.
  4. Start the recorder and gate on its health: the unit is active, the process systemd runs is the new release, /api/status answers (deploy/check-running.py), and after ten more seconds it is still running with no restarts. The gate allows three minutes for the first start, which may include database migrations.
  5. Finish: merge the new release's keyring, keep the last three releases and backups, and record the result for Settings.

The new release, when it starts, saves the database before changing its schema (a pre-migration-* snapshot in /var/lib/evernvr/backups). If the backups folder has no room for that copy plus a 512 MB margin, the snapshot is skipped with a plain log line and a warning fault, The database was migrated without a safety copy, and the migration goes ahead: a recorder that starts without a snapshot is better than one that restarts every five seconds copying a database that does not fit. The pre-update backup the engine took (step 6) is the fallback in that case.

Any failure, or an interrupt, before the gate passes rolls back by itself: the previous release, its units and its detector environment come back, and if the new release had started (and may have migrated the database) the database and key are restored from the backup with the previous release's maintenance CLI. What the failed release left is kept beside the backup. There is no separate accept step: the previous release stays installed for sudo evernvr-update rollback until a later update replaces it, and the backup stays under /opt/evernvr/backups.

After an update, verify owner login, detector readiness, storage, live recording, historical event links and playback and export, and record the release, the outage and the results with the site's records.

Footage kept on another drive needs no ownership change; root reads it whoever owns it. The exception is an NFS share exported with root_squash, which maps the recorder to nobody: re-export it with no_root_squash, or add it as SMB under NAS storage.

Rollback, interrupted updates and a failed migration are in RECOVERY.md.

Health and the dead-man check​

The recorder samples camera, recording, storage and detector state every ten seconds. Settings → This recorder shows current issues and recent incident history, and an active issue also appears above the main view. Incidents survive restarts, resolve when their condition clears, and recur as new incidents. The Alarms page opens on what needs attention: the faults, failures and anomalies waiting for a person. Detections are records, not alarms; they and every recovery are under All history, one click away, for correlating what a camera saw when something fired.

After a sixty-second startup grace, a camera with no recent image or recording progress for thirty seconds raises an issue. A camera added or turned on while the recorder runs gets the same sixty seconds from that moment, and its first recordings the same time to reach the index, before it can raise a camera fault. A ready detector with no recent per-camera result raises a detection stall. Storage loss and critical free space are reported at the next sample. A covered, blinded or moved camera is a tamper fault, judged by the detector on frames it already has. A camera cutting to its night mode is not one: the same scene at a fraction of its contrast, or under infrared, is recognized by its edges and adopted as the camera's normal view. A tamper fault that stands against a new, steady picture for ten minutes clears by itself, since that picture is the view the camera now has; a flat or changing picture never clears one.

Set a Dead-man check URL on the same page: a full https URL with no credentials, from a monitoring service. The recorder fetches it once a minute while no critical fault is open, so the monitoring service raises the alarm when the pings stop — including when the recorder is dead, the disk is full or every camera is dark. Healthchecks, Cronitor and Uptime Kuma all hand out such URLs. Nothing is sent but the request.

Health checks cover camera images, recording progress, recent indexed footage, storage capacity and detection results. They do not prove that every incident was recorded or detected. Keep alarm history for sets how long the Alarms page keeps faults, outages and who dealt with them; a fault that is still open is kept whatever its age.

Download support diagnostics produces a JSON bundle assembled from an allowlist: runtime versions and counters, schema names, record counts, camera and detector health, incident timing. It excludes accounts, camera names, addresses, credentials, keys, cookies, raw logs, recordings and thumbnails. Review it before sharing.

Download the recorder log gives the structured JSONL log from /var/lib/evernvr/logs/recorder.jsonl (five rotated files, at most 10 MiB each, about 50 MiB in all). Credential fields and URL credentials are redacted, but the log still contains site-specific detail, which is why it is not in the support bundle.

The API​

Another system talks to EverNVR through the same HTTP routes the pages use, under the same permissions, signed in with an API token minted under Settings → API. A token stands for one account, may be read-only, may carry an expiry, and is revoked on its own; revoking it closes its open streams and feeds. The route list is generated from the running recorder at GET /api/openapi.json.

See API.md.