Merge pull request #214428 from mweinelt/frigate-init

frigate: init at 0.12.0

0x4A6F afad0c15 6972da14

Changed files
+645 -5
nixos
doc
manual
release-notes
modules
services
video
tests
pkgs
applications
video
servers
http
nginx
top-level
+2
nixos/doc/manual/release-notes/rl-2305.section.md
···
- [system-repart](https://www.freedesktop.org/software/systemd/man/systemd-repart.service.html), grow and add partitions to a partition table. Available as [systemd.repart](options.html#opt-systemd.repart) and [boot.initrd.systemd.repart](options.html#opt-boot.initrd.systemd.repart)
+
- [frigate](https://frigate.video), an open source NVR built around real-time AI object detection. Available as [services.frigate](#opt-services.frigate.enable).
+
- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
- [readarr](https://github.com/Readarr/Readarr), Book Manager and Automation (Sonarr for Ebooks). Available as [services.readarr](options.html#opt-services.readarr.enable).
+1
nixos/modules/module-list.nix
···
./services/ttys/kmscon.nix
./services/video/epgstation/default.nix
./services/video/go2rtc/default.nix
+
./services/video/frigate.nix
./services/video/mirakurun.nix
./services/video/replay-sorcery.nix
./services/video/mediamtx.nix
+368
nixos/modules/services/video/frigate.nix
···
+
{ config
+
, lib
+
, pkgs
+
, ...
+
}:
+
+
let
+
inherit (lib)
+
literalExpression
+
mkDefault
+
mdDoc
+
mkEnableOption
+
mkIf
+
mkOption
+
types;
+
+
cfg = config.services.frigate;
+
+
format = pkgs.formats.yaml {};
+
+
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! lib.elem v [ null ])) cfg.settings;
+
+
cameraFormat = with types; submodule {
+
freeformType = format.type;
+
options = {
+
ffmpeg = {
+
inputs = mkOption {
+
description = mdDoc ''
+
List of inputs for this camera.
+
'';
+
type = listOf (submodule {
+
freeformType = format.type;
+
options = {
+
path = mkOption {
+
type = str;
+
example = "rtsp://192.0.2.1:554/rtsp";
+
description = mdDoc ''
+
Stream URL
+
'';
+
};
+
roles = mkOption {
+
type = listOf (enum [ "detect" "record" "rtmp" ]);
+
example = literalExpression ''
+
[ "detect" "rtmp" ]
+
'';
+
description = mdDoc ''
+
List of roles for this stream
+
'';
+
};
+
};
+
});
+
};
+
};
+
};
+
};
+
+
in
+
+
{
+
meta.buildDocsInSandbox = false;
+
+
options.services.frigate = with types; {
+
enable = mkEnableOption (mdDoc "Frigate NVR");
+
+
package = mkOption {
+
type = package;
+
default = pkgs.frigate;
+
description = mdDoc ''
+
The frigate package to use.
+
'';
+
};
+
+
hostname = mkOption {
+
type = str;
+
example = "frigate.exampe.com";
+
description = mdDoc ''
+
Hostname of the nginx vhost to configure.
+
+
Only nginx is supported by upstream for direct reverse proxying.
+
'';
+
};
+
+
settings = mkOption {
+
type = submodule {
+
freeformType = format.type;
+
options = {
+
cameras = mkOption {
+
type = attrsOf cameraFormat;
+
description = mdDoc ''
+
Attribute set of cameras configurations.
+
+
https://docs.frigate.video/configuration/cameras
+
'';
+
};
+
+
database = {
+
path = mkOption {
+
type = path;
+
default = "/var/lib/frigate/frigate.db";
+
description = mdDoc ''
+
Path to the SQLite database used
+
'';
+
};
+
};
+
+
mqtt = {
+
enabled = mkEnableOption (mdDoc "MQTT support");
+
+
host = mkOption {
+
type = nullOr str;
+
default = null;
+
example = "mqtt.example.com";
+
description = mdDoc ''
+
MQTT server hostname
+
'';
+
};
+
};
+
};
+
};
+
default = {};
+
description = mdDoc ''
+
Frigate configuration as a nix attribute set.
+
+
See the project documentation for how to configure frigate.
+
- [Creating a config file](https://docs.frigate.video/guides/getting_started)
+
- [Configuration reference](https://docs.frigate.video/configuration/index)
+
'';
+
};
+
};
+
+
config = mkIf cfg.enable {
+
services.nginx = {
+
enable =true;
+
additionalModules = with pkgs.nginxModules; [
+
secure-token
+
rtmp
+
vod
+
];
+
recommendedProxySettings = mkDefault true;
+
recommendedGzipSettings = mkDefault true;
+
upstreams = {
+
frigate-api.servers = {
+
"127.0.0.1:5001" = {};
+
};
+
frigate-mqtt-ws.servers = {
+
"127.0.0.1:5002" = {};
+
};
+
frigate-jsmpeg.servers = {
+
"127.0.0.1:8082" = {};
+
};
+
frigate-go2rtc.servers = {
+
"127.0.0.1:1984" = {};
+
};
+
};
+
# Based on https://github.com/blakeblackshear/frigate/blob/v0.12.0/docker/rootfs/usr/local/nginx/conf/nginx.conf
+
virtualHosts."${cfg.hostname}" = {
+
locations = {
+
"/api/" = {
+
proxyPass = "http://frigate-api/";
+
};
+
"~* /api/.*\.(jpg|jpeg|png)$" = {
+
proxyPass = "http://frigate-api";
+
extraConfig = ''
+
add_header 'Access-Control-Allow-Origin' '*';
+
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
+
rewrite ^/api/(.*)$ $1 break;
+
'';
+
};
+
"/vod/" = {
+
extraConfig = ''
+
aio threads;
+
vod hls;
+
+
secure_token $args;
+
secure_token_types application/vnd.apple.mpegurl;
+
+
add_header Access-Control-Allow-Headers '*';
+
add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range';
+
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
+
add_header Access-Control-Allow-Origin '*';
+
add_header Cache-Control "no-store";
+
expires off;
+
'';
+
};
+
"/stream/" = {
+
# TODO
+
};
+
"/ws" = {
+
proxyPass = "http://frigate-mqtt-ws/";
+
proxyWebsockets = true;
+
};
+
"/live/jsmpeg" = {
+
proxyPass = "http://frigate-jsmpeg/";
+
proxyWebsockets = true;
+
};
+
"/live/mse/" = {
+
proxyPass = "http://frigate-go2rtc/";
+
proxyWebsockets = true;
+
};
+
"/live/webrtc/" = {
+
proxyPass = "http://frigate-go2rtc/";
+
proxyWebsockets = true;
+
};
+
"/cache/" = {
+
alias = "/var/cache/frigate/";
+
};
+
"/clips/" = {
+
root = "/var/lib/frigate";
+
extraConfig = ''
+
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+
add_header 'Access-Control-Allow-Credentials' 'true';
+
add_header 'Access-Control-Expose-Headers' 'Content-Length';
+
if ($request_method = 'OPTIONS') {
+
add_header 'Access-Control-Allow-Origin' "$http_origin";
+
add_header 'Access-Control-Max-Age' 1728000;
+
add_header 'Content-Type' 'text/plain charset=UTF-8';
+
add_header 'Content-Length' 0;
+
return 204;
+
}
+
+
types {
+
video/mp4 mp4;
+
image/jpeg jpg;
+
}
+
+
autoindex on;
+
'';
+
};
+
"/recordings/" = {
+
root = "/var/lib/frigate";
+
extraConfig = ''
+
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+
add_header 'Access-Control-Allow-Credentials' 'true';
+
add_header 'Access-Control-Expose-Headers' 'Content-Length';
+
if ($request_method = 'OPTIONS') {
+
add_header 'Access-Control-Allow-Origin' "$http_origin";
+
add_header 'Access-Control-Max-Age' 1728000;
+
add_header 'Content-Type' 'text/plain charset=UTF-8';
+
add_header 'Content-Length' 0;
+
return 204;
+
}
+
+
types {
+
video/mp4 mp4;
+
}
+
+
autoindex on;
+
autoindex_format json;
+
'';
+
};
+
"/assets/" = {
+
root = cfg.package.web;
+
extraConfig = ''
+
access_log off;
+
expires 1y;
+
add_header Cache-Control "public";
+
'';
+
};
+
"/" = {
+
root = cfg.package.web;
+
tryFiles = "$uri $uri/ /index.html";
+
extraConfig = ''
+
add_header Cache-Control "no-store";
+
expires off;
+
+
sub_filter 'href="/BASE_PATH/' 'href="$http_x_ingress_path/';
+
sub_filter 'url(/BASE_PATH/' 'url($http_x_ingress_path/';
+
sub_filter '"/BASE_PATH/dist/' '"$http_x_ingress_path/dist/';
+
sub_filter '"/BASE_PATH/js/' '"$http_x_ingress_path/js/';
+
sub_filter '"/BASE_PATH/assets/' '"$http_x_ingress_path/assets/';
+
sub_filter '"/BASE_PATH/monacoeditorwork/' '"$http_x_ingress_path/assets/';
+
sub_filter 'return"/BASE_PATH/"' 'return window.baseUrl';
+
sub_filter '<body>' '<body><script>window.baseUrl="$http_x_ingress_path/";</script>';
+
sub_filter_types text/css application/javascript;
+
sub_filter_once off;
+
'';
+
};
+
};
+
extraConfig = ''
+
# vod settings
+
vod_base_url "";
+
vod_segments_base_url "";
+
vod_mode mapped;
+
vod_max_mapping_response_size 1m;
+
vod_upstream_location /api;
+
vod_align_segments_to_key_frames on;
+
vod_manifest_segment_durations_mode accurate;
+
vod_ignore_edit_list on;
+
vod_segment_duration 10000;
+
vod_hls_mpegts_align_frames off;
+
vod_hls_mpegts_interleave_frames on;
+
# file handle caching / aio
+
open_file_cache max=1000 inactive=5m;
+
open_file_cache_valid 2m;
+
open_file_cache_min_uses 1;
+
open_file_cache_errors on;
+
aio on;
+
# https://github.com/kaltura/nginx-vod-module#vod_open_file_thread_pool
+
vod_open_file_thread_pool default;
+
# vod caches
+
vod_metadata_cache metadata_cache 512m;
+
vod_mapping_cache mapping_cache 5m 10m;
+
# gzip manifest
+
gzip_types application/vnd.apple.mpegurl;
+
'';
+
};
+
appendConfig = ''
+
rtmp {
+
server {
+
listen 1935;
+
chunk_size 4096;
+
allow publish 127.0.0.1;
+
deny publish all;
+
allow play all;
+
application live {
+
live on;
+
record off;
+
meta copy;
+
}
+
}
+
}
+
'';
+
};
+
+
systemd.services.frigate = {
+
after = [
+
"go2rtc.service"
+
"network.target"
+
];
+
wantedBy = [
+
"multi-user.target"
+
];
+
environment = {
+
CONFIG_FILE = format.generate "frigate.yml" filteredConfig;
+
HOME = "/var/lib/frigate";
+
PYTHONPATH = cfg.package.pythonPath;
+
};
+
path = with pkgs; [
+
# unfree:
+
# config.boot.kernelPackages.nvidiaPackages.latest.bin
+
ffmpeg_5-headless
+
libva-utils
+
procps
+
radeontop
+
] ++ lib.optionals (!stdenv.isAarch64) [
+
# not available on aarch64-linux
+
intel-gpu-tools
+
];
+
serviceConfig = {
+
ExecStart = "${cfg.package.python.interpreter} -m frigate";
+
+
DynamicUser = true;
+
User = "frigate";
+
+
StateDirectory = "frigate";
+
UMask = "0077";
+
+
# Caches
+
PrivateTmp = true;
+
CacheDirectory = "frigate";
+
+
BindPaths = [
+
"/migrations:${cfg.package}/share/frigate/migrations:ro"
+
];
+
};
+
};
+
};
+
}
+1
nixos/tests/all-tests.nix
···
freeswitch = handleTest ./freeswitch.nix {};
freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
freshrss-pgsql = handleTest ./freshrss-pgsql.nix {};
+
frigate = handleTest ./frigate.nix {};
frr = handleTest ./frr.nix {};
fsck = handleTest ./fsck.nix {};
fsck-systemd-stage-1 = handleTest ./fsck.nix { systemdStage1 = true; };
+60
nixos/tests/frigate.nix
···
+
import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+
{
+
name = "frigate";
+
meta.maintainers = with lib.maintainers; [ hexa ];
+
+
nodes = {
+
machine = { config, ... }: {
+
services.frigate = {
+
enable = true;
+
+
hostname = "localhost";
+
+
settings = {
+
mqtt.enabled = false;
+
+
cameras.test = {
+
ffmpeg = {
+
input_args = "-fflags nobuffer -strict experimental -fflags +genpts+discardcorrupt -r 10 -use_wallclock_as_timestamps 1";
+
inputs = [ {
+
path = "http://127.0.0.1:8080";
+
roles = [
+
"record"
+
];
+
} ];
+
};
+
};
+
+
record.enabled = true;
+
};
+
};
+
+
systemd.services.video-stream = {
+
description = "Start a test stream that frigate can capture";
+
before = [
+
"frigate.service"
+
];
+
wantedBy = [
+
"multi-user.target"
+
];
+
serviceConfig = {
+
DynamicUser = true;
+
ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -f mpegts -listen 1 http://0.0.0.0:8080";
+
};
+
};
+
};
+
};
+
+
testScript = ''
+
start_all()
+
+
machine.wait_for_unit("frigate.service")
+
+
machine.wait_for_open_port(5001)
+
+
machine.succeed("curl http://localhost:5001")
+
+
machine.wait_for_file("/var/cache/frigate/test-*.mp4")
+
'';
+
})
+179
pkgs/applications/video/frigate/default.nix
···
+
{ lib
+
, callPackage
+
, python3
+
, fetchFromGitHub
+
, fetchurl
+
, fetchpatch
+
, frigate
+
, opencv4
+
, nixosTests
+
}:
+
+
let
+
version = "0.12.0";
+
+
src = fetchFromGitHub {
+
#name = "frigate-${version}-source";
+
owner = "blakeblackshear";
+
repo = "frigate";
+
rev = "refs/tags/v${version}";
+
hash = "sha256-kJ0MnmWThiFbXvrN+zL5pZHq+Ig3DhCc8wPlWX2+nP8=";
+
};
+
+
frigate-web = callPackage ./web.nix {
+
inherit version src;
+
};
+
+
python = python3.override {
+
packageOverrides = self: super: {
+
# https://github.com/blakeblackshear/frigate/blob/v0.12.0/requirements-wheels.txt#L7
+
opencv = super.toPythonModule ((opencv4.override {
+
enablePython = true;
+
pythonPackages = self;
+
}).overrideAttrs (oldAttrs: rec {
+
version = "4.5.5";
+
src = fetchFromGitHub {
+
owner = "opencv";
+
repo = "opencv";
+
rev = "refs/tags/${version}";
+
hash = "sha256-TJfzEAMh4JSshZ7oEZPgB59+NBACsj6Z5TCzVOBaEP4=";
+
};
+
contribSrc = fetchFromGitHub {
+
owner = "opencv";
+
repo = "opencv_contrib";
+
rev = "refs/tags/${version}";
+
hash = "sha256-skuH9GYg0mivGaJjxbggXk4x/0bbQISrAawA3ZUGfCk=";
+
};
+
postUnpack = ''
+
cp --no-preserve=mode -r "${contribSrc}/modules" "$NIX_BUILD_TOP/source/opencv_contrib"
+
'';
+
}));
+
};
+
};
+
+
# Tensorflow Lite models
+
# https://github.com/blakeblackshear/frigate/blob/v0.12.0/Dockerfile#L88-L91
+
tflite_cpu_model = fetchurl {
+
url = "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite";
+
hash = "sha256-kLszpjTgQZFMwYGapd+ZgY5sOWxNLblSwP16nP/Eck8=";
+
};
+
tflite_edgetpu_model = fetchurl {
+
url = "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite";
+
hash = "sha256-Siviu7YU5XbVbcuRT6UnUr8PE0EVEnENNV2X+qGzVkE=";
+
};
+
+
# OpenVino models
+
# https://github.com/blakeblackshear/frigate/blob/v0.12.0/Dockerfile#L92-L95
+
openvino_model = fetchurl {
+
url = "https://github.com/openvinotoolkit/open_model_zoo/raw/master/data/dataset_classes/coco_91cl_bkgr.txt";
+
hash = "sha256-5Cj2vEiWR8Z9d2xBmVoLZuNRv4UOuxHSGZQWTJorXUQ=";
+
};
+
in
+
python.pkgs.buildPythonApplication rec {
+
pname = "frigate";
+
inherit version;
+
format = "other";
+
+
inherit src;
+
+
patches = [
+
(fetchpatch {
+
# numpy 1.24 compat
+
url = "https://github.com/blakeblackshear/frigate/commit/cb73d0cd392990448811c7212bc5f09be411fc69.patch";
+
hash = "sha256-Spt7eRosmTN8zyJ2uVme5HPVy2TKgBtvbQ6tp6PaNac=";
+
})
+
];
+
+
postPatch = ''
+
echo 'VERSION = "${version}"' > frigate/version.py
+
+
substituteInPlace frigate/app.py \
+
--replace "Router(migrate_db)" 'Router(migrate_db, "${placeholder "out"}/share/frigate/migrations")'
+
+
substituteInPlace frigate/const.py \
+
--replace "/media/frigate" "/var/lib/frigate" \
+
--replace "/tmp/cache" "/var/cache/frigate"
+
+
substituteInPlace frigate/detectors/detector_config.py \
+
--replace "/labelmap.txt" "${placeholder "out"}/share/frigate/labelmap.txt"
+
+
substituteInPlace frigate/detectors/plugins/edgetpu_tfl.py \
+
--replace "/edgetpu_model.tflite" "${tflite_edgetpu_model}"
+
+
substituteInPlace frigate/detectors/plugins/cpu_tfl.py \
+
--replace "/cpu_model.tflite" "${tflite_cpu_model}"
+
+
substituteInPlace frigate/ffmpeg_presets.py --replace \
+
'"-timeout" if os.path.exists(BTBN_PATH) else "-stimeout"' \
+
'"-timeout"'
+
'';
+
+
dontBuild = true;
+
+
propagatedBuildInputs = with python.pkgs; [
+
# requirements.txt
+
scikit-build
+
# requirements-wheel.txt
+
click
+
flask
+
imutils
+
matplotlib
+
numpy
+
opencv
+
openvino
+
paho-mqtt
+
peewee
+
peewee-migrate
+
psutil
+
pydantic
+
pyyaml
+
requests
+
scipy
+
setproctitle
+
tensorflow
+
tzlocal
+
ws4py
+
zeroconf
+
];
+
+
installPhase = ''
+
runHook preInstall
+
+
mkdir -p $out/${python.sitePackages}/frigate
+
cp -R frigate/* $out/${python.sitePackages}/frigate/
+
+
mkdir -p $out/share/frigate
+
cp -R {migrations,labelmap.txt} $out/share/frigate/
+
+
cp --no-preserve=mode ${openvino_model} $out/share/frigate/coco_91cl_bkgr.txt
+
sed -i 's/truck/car/g' $out/share/frigate/coco_91cl_bkgr.txt
+
+
runHook postInstall
+
'';
+
+
checkInputs = with python.pkgs; [
+
pytestCheckHook
+
];
+
+
passthru = {
+
web = frigate-web;
+
inherit python;
+
pythonPath =(python.pkgs.makePythonPath propagatedBuildInputs) + ":${frigate}/${python.sitePackages}";
+
tests = {
+
inherit (nixosTests) frigate;
+
};
+
};
+
+
meta = with lib; {
+
changelog = "https://github.com/blakeblackshear/frigate/releases/tag/v${version}";
+
description = "NVR with realtime local object detection for IP cameras";
+
longDescription = ''
+
A complete and local NVR designed for Home Assistant with AI
+
object detection. Uses OpenCV and Tensorflow to perform realtime
+
object detection locally for IP cameras.
+
'';
+
homepage = "https://github.com/blakeblackshear/frigate";
+
license = licenses.mit;
+
maintainers = with maintainers; [ hexa ];
+
};
+
}
+26
pkgs/applications/video/frigate/web.nix
···
+
{ buildNpmPackage
+
, src
+
, version
+
}:
+
+
buildNpmPackage {
+
pname = "frigate-web";
+
inherit version src;
+
+
sourceRoot = "source/web";
+
+
postPatch = ''
+
substituteInPlace package.json \
+
--replace "--base=/BASE_PATH/" ""
+
+
substituteInPlace src/routes/Storage.jsx \
+
--replace "/media/frigate" "/var/lib/frigate" \
+
--replace "/tmp/cache" "/var/cache/frigate"
+
'';
+
+
npmDepsHash = "sha256-fvRxpQjSEzd2CnoEOVgQcB6MJJ4dcjN8bOaacHjCdwU=";
+
+
installPhase = ''
+
cp -rv dist/ $out
+
'';
+
}
+6 -5
pkgs/servers/http/nginx/modules.nix
···
};
};
-
secure-token = {
+
secure-token = rec {
name = "secure-token";
+
version = "1.5";
src = fetchFromGitHub {
name = "secure-token";
owner = "kaltura";
repo = "nginx-secure-token-module";
-
rev = "95bdc0d1aca06ea7fe42555f71e65910bd74914d";
-
sha256 = "19wzck1xzq4kz7nyabcwzlank1k7wi7w2wn2c1mwz374c79g8ggp";
+
rev = "refs/tags/${version}";
+
hash = "sha256-qYTjGS9pykRqMFmNls52YKxEdXYhHw+18YC2zzdjEpU=";
};
inputs = [ openssl ];
···
name = "vod";
owner = "kaltura";
repo = "nginx-vod-module";
-
rev = "1.29";
-
sha256 = "1z0ka0cwqbgh3fv2d5yva395sf90626rdzx7lyfrgs89gy4h9nrr";
+
rev = "1.31";
+
hash = "sha256-ZpeO8QWQ+fGkz08u/zFOq7vj4aHcodzSHNrc1SgGUyc=";
};
inputs = [ ffmpeg fdk_aac openssl libxml2 libiconv ];
+2
pkgs/top-level/all-packages.nix
···
inherit (darwin.apple_sdk.frameworks) AppKit CoreFoundation DiskArbitration Foundation IOKit;
};
+
frigate = callPackage ../applications/video/frigate { };
+
frostwire = callPackage ../applications/networking/p2p/frostwire { };
frostwire-bin = callPackage ../applications/networking/p2p/frostwire/frostwire-bin.nix { };