* add error boundary
on frontend errors, show a page-level recovery panel and a separate compact strip for the sidebar/status/bottombar so failures there don't take down the rest of the page
* tweaks
* fix test
* Rename face model to face recognizer
* Refactor face detection into own module
* Return face and face landmarks
* Align faces with 5 points instead of just the eyes
* Add landmark validation to throw out images which do not fit a landmark
* Fix circular import and lock face detector for concurrent runs across threads
* Fix mypy
* Support multi resolution exports
* Fix decoder text
* Add dropdown and ability to select export stream selection
* Fix for review comments
* Fix mypy
* Cleanup wording
* rename migration
* use ASC composite index for event camera and start_time
the planner still picks for newest-first single-camera queries but doesn't slow down full per-camera scans on a cold cache
* pause playback while the timeline range handles are up
* reduce multi camera seeded export range to 30m
allows both handlebars to fit within most desktop windows
* Add additional indicies on event and review tables. Every events or timeline endpoint filters on event start time and camera, this should speed things up by avoiding a range scan on the table.
* Rewrite to use a CTE to leverage speedups by using sqllite internal optimization to do a single query instead of a starter query to get distinct labels and a subsequent loop of querys per distinct event labels.
Frigate is currently shipping sqlite 3.46.1, which is above the minimum version 3.25 needed for CTEs.
* Collapse a few sequential queries into a single one.
* Use peewee instead of rw sql for the CTE query.
* Slightly simplify review logic and avoid duplicating the json response for empty review IDs.
* Rerun ruff formatting.
* Remove 2x unnecessary index on reviewsegment, remove reference to prior code implementation in comment in event.py
* Editor fail, re-ruff format.
* Remove the CTE and restore the generator with sub-queries, which is more performance (thanks Nick and Blake for testing against your larger DB!)
* Update peewee index migration description
* Add on_conflict_ignore, replacing the try/catch/pass on IntegrityError
* Add a testcase for validating that on_conflict_ignore bypasses what was formerly an IntegrityError
* Change testcase to clarify that it covers the peewee behavior of on_conflict_ignore
---------
Co-authored-by: Greg <{ID}+{username}@users.noreply.github.com>
* add a notice registry and System Health tab
Problems Frigate detects on its own (ffmpeg crash loops, stuck detectors, failed model downloads, recordings deleted before their retention period) only ever existed as log lines. This adds a `NoticeRegistry` in the main process backed by two tables, an `update_notice` IPC topic so producers in other processes can reach it through the dispatcher, an admin-only API and websocket topic, and a Health tab that lists them. Kinds declare their own mode, severity, and category in one place: state notices are resolved by their producer, event notices are dismissed by the user.
* treat a prerelease as behind its final release
* fix notices clearing early
* rename menu items and update docs
* don't resolve the update notice on a failed version lookup
isLoading and isBuffering only clear on playback progress, and scrubbing holds the player paused, so a source rebuild during a timeline selection left them set with nothing able to clear them. Cancelling made them visible as a spinner over an already-loaded frame, which stayed until the next manual seek. Leaving a scrub now clears them when the source is loaded and the element holds a frame.
* Refactor hardware stats to have consolidated ffmpeg, detector, and enrichments running.
* Cleanup hardware access that is not passed into the container
* Remove network stats from hardware refactor
`after` and `before` defaulted to `datetime.now()` in the function signature, so they were evaluated once at import. Requests that omitted them got a window ending at process start. They now resolve in the handler.
* Support read-only rootfs with self-signed certs in /config/tls
* Support read-only rootfs in s6 and pre-compile bytecode
* Assert read-only rootfs support in CI
* Document hardened read-only deployment
* keep certsync's cert selection identical to nginx's
* note the uid trade-off in user: mode
* fail fast when EXTRA_GROUPS or a missing media volume meets read_only
* keep nosuid and nodev on the /run tmpfs
* support read_only in the default mode
* don't take go2rtc down when the homekit file isn't writable
* lead with the hardware consequence of switching to user:
* refuse to write TLS material through a symlink as root
* note that memryx writes models to the root filesystem
* certsync watches whichever cert path nginx loaded
* Run the frigate service as the frigate user
* Run go2rtc as its own restricted user
* Run nginx as the frigate user with writable state in /tmp/nginx
* Disable bandwidth stats gracefully when not running as root
* Hand TensorRT model cache ownership to the runtime user
* Document non-root operation and per-hardware device access
* Create /media/frigate after the ownership sweep
* Assert non-root services, JWT migration, and escape hatch in CI
* only write the sweep sentinel when a media volume is mounted
* tolerate homekit config chown failures in the go2rtc run script
* chown the s6 log pipe so non-root nginx can reopen /dev/stdout
* set HOME to /config for non-root services
* run smoke nginx -t and the write probe as the runtime user
* re-own the nginx shm cache on service restart
* discard stdout for the unprivileged smoke nginx -t
* unwrap hard-wrapped prose in the installation docs
* report progress during the ownership sweep
* document EXTRA_GROUPS as the only device access path for dropped services
* expand the non-root device access docs with diagnosis steps and udev rules
* document network storage ownership and the remaining detector hardware
* skip lost+found during the ownership sweep
* hand /tmp/cache to the runtime user before services start
* make bundled models readable by the runtime user
* reload nginx by signaling the master instead of parsing its config as root
* harden root writes into unprivileged-owned paths
Restrict the sweep sentinel to a mount at or below /media/frigate so a
parent /media mount cannot bless a later-shadowed volume. Rebuild
/tmp/nginx root-owned each start so root's cp and tempio writes cannot
follow a symlink an unprivileged nginx planted in the previous run.
* collapse the duplicated sentinel comment
* add a service-runs-as-root helper for granular root services
* validate FRIGATE_ROOT_SERVICES and fail fast on unknown names
* let services listed in FRIGATE_ROOT_SERVICES skip the privilege drop
* record the root-services mode in the sentinel and sweep small trees each boot
* cache the runtime ids in the ownership helper
* chown recordings, previews, and exports to the runtime user at create
* chown the database files after init
* recommend FRIGATE_ROOT_SERVICES in the bandwidth stats warning
* assert granular root services in CI
* document FRIGATE_ROOT_SERVICES
* own every directory level created for a recording segment
* clear the cached runtime ids when ownership tests finish
* skip missing media paths in the per-boot ownership sweep
* clarify granular root services docs
* clean up
* install acl for device access grants
* grant runtime users access to mapped device nodes at boot
* assert device access grants in CI
* document automatic device access grants
* stop telling users device access needs host side setup
* clarify the non-root docs
* link the migration script to the repo
* group the manual device setup under one section
* harden against symlink attacks
/config is owned by the unprivileged runtime user after the ownership sweep, so root operations on files there could be redirected by a planted symlink.
- go2rtc HomeKit setup: replace the root yq/jq normalization and chown with an O_NOFOLLOW helper (prepare_homekit.py), so a symlink at go2rtc_homekit.yml can't redirect a root write or chown onto another file
- go2rtc binary override: ignore /config/go2rtc whenever the service runs as root, so a planted binary can't exec as root under FRIGATE_ROOT_SERVICES
- sweep sentinel: read and write it through safe-sentinel, which trusts only a root-owned regular file and never follows a symlink, so it can't be forged to skip the migration or symlinked to clobber a root file
- ownership sweep: chown with -execdir so a parent directory swapped for a symlink mid-walk can't redirect the chown out of the volume
- validate inputs: restrict DEVICE_ACL_PATHS to /dev, require nonzero numeric EXTRA_GROUPS, and reject PUID/PGID that collide with the go2rtc ids
- docs: correct the TLS key ownership note to match what actually happens
* tweak docs
* stop the ownership sweep chasing entries other mechanisms own
* keep custom binaries out of root services only under granular root
The `v1.x` line is the same muxed fMP4 code as Kaltura's 1.31 with fixes backported, so the mapping JSON, manifest routes, and ffmpeg consumers are unchanged. The `MAX_CLIPS` patch applies as-is and the HEVC workaround is rewritten for the fork's reformatted source.
`vod_hls_version 6` is now explicit because the fork replaced Kaltura's automatic version calculation with a directive that defaults to 4 and only warns when fmp4 needs 6, so playlists were being stamped `EXT-X-VERSION:4` while carrying `EXT-X-MAP`. The `error_page 502 =404` hack is gone: https://github.com/kaltura/nginx-vod-module/issues/468 is a `vod_mode remote` bug and we're `mapped`, so those 502s were really `_vod_response` returning 404 upstream. The fork maps that through now, and the hack was also turning real 5xx into "no recordings".
* use the camera's model for the lpr vehicle check
`FrigateConfig.model` became `models[]` in the detector refactor, so this didn't compile on 0.19. Each camera has its own detector/model pair now, so the check resolves the camera's scene with `getModelForCamera` instead of looking at every model.
* use camera config model
* fix export test
* add onboarding wizard for new users
* resolve hwaccel per camera and clarify recording retention
The hwaccel step listed every preset Frigate ships, so an Intel box was offered Raspberry Pi and Rockchip decoding, and the codec specific presets (`preset-intel-qsv-h264` vs `-h265`) were offered as global values that break as soon as two cameras use different codecs. `/hardware/hwaccel` now returns the decoding families the probed hardware can actually use, each carrying a preset per codec, and the wizard resolves the family against the detect stream codec the camera wizard already probed: one global `ffmpeg.hwaccel_args` when every camera agrees, per-camera `cameras.<name>.ffmpeg.hwaccel_args` when they don't. The global stays on `auto` in that case so cameras added later still resolve at startup. A gen13+ Intel machine keeps its QuickSync recommendation with mixed h264 and h265 cameras instead of dropping to vaapi.
The recording step's "Days to retain recordings" only wrote alert and detection retention, and the storage estimate under it assumed continuous recording. It now asks what to record in plain language, writes `record.continuous.days` to match, shows the estimate only for continuous, and drops the spinner arrows on the number input.
* clean up
* add light/dark mode icon switcher
* use yml as default config file extension when not found
* i18n tweaks
* gate the setup wizard on cameras instead of a config key
* render setup wizard steps by key
* share the setup wizard e2e helpers and mock users
* add an account step to the setup wizard
* add setup wizard account step e2e coverage
* cover the account step's restart behavior
* button consistency
* fix test
* docs
* fixes
* fix inconsistent export download filenames
Zip entries in a case download were named from `Export.name`, the friendly display name, while an individual download uses the file name on disk. The two have always been formatted differently, so one export came out as `front_door_20260823_020615-20260823_020734_abc123.mp4` on its own and `front door 2026-08-23 020615 2026-08-23 020734.mp4` inside a zip. Zip entries now use the on-disk file name, and renaming an export renames its file, so there's only one name to download under. The rename is blocked while ffmpeg still holds the file.
* cap filename length and catch duplicate names
* fix export rename and stop blocking the event loop
* move the rename rollback off the event loop
* no awaits