diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix index 5fa67b5ca264..5c259b7fa14d 100644 --- a/nixos/modules/services/video/frigate.nix +++ b/nixos/modules/services/video/frigate.nix @@ -39,10 +39,8 @@ let ''; }; roles = mkOption { - type = listOf (enum [ "detect" "record" "rtmp" ]); - example = literalExpression '' - [ "detect" "rtmp" ] - ''; + type = listOf (enum [ "audio" "detect" "record" ]); + example = [ "detect" "record" ]; description = '' List of roles for this stream ''; @@ -54,6 +52,48 @@ let }; }; + # auth_request.conf + nginxAuthRequest = '' + # Send a subrequest to verify if the user is authenticated and has permission to access the resource. + auth_request /auth; + + # Save the upstream metadata response headers from Authelia to variables. + auth_request_set $user $upstream_http_remote_user; + auth_request_set $groups $upstream_http_remote_groups; + auth_request_set $name $upstream_http_remote_name; + auth_request_set $email $upstream_http_remote_email; + + # Inject the metadata response headers from the variables into the request made to the backend. + proxy_set_header Remote-User $user; + proxy_set_header Remote-Groups $groups; + proxy_set_header Remote-Email $email; + proxy_set_header Remote-Name $name; + + # Refresh the cookie as needed + auth_request_set $auth_cookie $upstream_http_set_cookie; + add_header Set-Cookie $auth_cookie; + + # Pass the location header back up if it exists + auth_request_set $redirection_url $upstream_http_location; + add_header Location $redirection_url; + ''; + + nginxProxySettings = '' + # Basic Proxy Configuration + client_body_buffer_size 128k; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; ## Timeout if the real server is dead. + proxy_redirect http:// $scheme://; + proxy_cache_bypass $cookie_session; + proxy_no_cache $cookie_session; + proxy_buffers 64 256k; + + # Advanced Proxy Configuration + send_timeout 5m; + proxy_read_timeout 360; + proxy_send_timeout 360; + proxy_connect_timeout 360; + ''; + in { @@ -126,8 +166,9 @@ in services.nginx = { enable = true; additionalModules = with pkgs.nginxModules; [ + develkit secure-token - rtmp + set-misc vod ]; recommendedProxySettings = mkDefault true; @@ -158,43 +199,46 @@ in # Based on https://github.com/blakeblackshear/frigate/blob/v0.13.1/docker/main/rootfs/usr/local/nginx/conf/nginx.conf virtualHosts."${cfg.hostname}" = { locations = { - "/api/" = { - proxyPass = "http://frigate-api/"; + # auth_location.conf + "/auth" = { + proxyPass = "http://frigate-api/auth"; extraConfig = '' - proxy_cache frigate_api_cache; - proxy_cache_lock on; - proxy_cache_use_stale updating; - proxy_cache_valid 200 5s; - proxy_cache_bypass $http_x_cache_bypass; - proxy_no_cache $should_not_cache; - add_header X-Cache-Status $upstream_cache_status; + internal; - location /api/vod/ { - proxy_pass http://frigate-api/vod/; - proxy_cache off; - } + # Strip all request headers + proxy_pass_request_headers off; - location /api/stats { - access_log off; - rewrite ^/api/(.*)$ $1 break; - proxy_pass http://frigate-api; - } + # Pass info about the request + proxy_set_header X-Original-Method $request_method; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_set_header X-Server-Port $server_port; + proxy_set_header Content-Length ""; - location /api/version { - access_log off; - rewrite ^/api/(.*)$ $1 break; - proxy_pass http://frigate-api; - } - ''; - }; - "~* /api/.*\.(jpg|jpeg|png)$" = { - proxyPass = "http://frigate-api"; - extraConfig = '' - rewrite ^/api/(.*)$ $1 break; + # Pass along auth related info + proxy_set_header Authorization $http_authorization; + proxy_set_header Cookie $http_cookie; + proxy_set_header X-CSRF-TOKEN "1"; + + # Pass headers for common auth proxies + proxy_set_header Remote-User $http_remote_user; + proxy_set_header Remote-Groups $http_remote_groups; + proxy_set_header Remote-Email $http_remote_email; + proxy_set_header Remote-Name $http_remote_name; + proxy_set_header X-Forwarded-User $http_x_forwarded_user; + proxy_set_header X-Forwarded-Groups $http_x_forwarded_groups; + proxy_set_header X-Forwarded-Email $http_x_forwarded_email; + proxy_set_header X-Forwarded-Preferred-Username $http_x_forwarded_preferred_username; + proxy_set_header X-authentik-username $http_x_authentik_username; + proxy_set_header X-authentik-groups $http_x_authentik_groups; + proxy_set_header X-authentik-email $http_x_authentik_email; + proxy_set_header X-authentik-name $http_x_authentik_name; + proxy_set_header X-authentik-uid $http_x_authentik_uid; + + ${nginxProxySettings} ''; }; "/vod/" = { - extraConfig = '' + extraConfig = nginxAuthRequest + '' aio threads; vod hls; @@ -206,38 +250,84 @@ in ''; }; "/stream/" = { - # TODO + alias = "/var/cache/frigate/stream/"; + extraConfig = nginxAuthRequest + '' + add_header Cache-Control "no-store"; + expires off; + + types { + application/dash+xml mpd; + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + image/jpeg jpg; + } + ''; + }; + "/clips/" = { + root = "/var/lib/frigate"; + extraConfig = nginxAuthRequest + '' + types { + video/mp4 mp4; + image/jpeg jpg; + } + + expires 7d; + add_header Cache-Control "public"; + autoindex on; + ''; + }; + "/cache/" = { + alias = "/var/cache/frigate/"; + extraConfig = '' + internal; + ''; + }; + "/recordings/" = { + root = "/var/lib/frigate"; + extraConfig = nginxAuthRequest + '' + types { + video/mp4 mp4; + } + + autoindex on; + autoindex_format json; + ''; + }; + "/exports/" = { + root = "/var/lib/frigate"; + extraConfig = nginxAuthRequest + '' + types { + video/mp4 mp4; + } + + autoindex on; + autoindex_format json; + ''; }; "/ws" = { proxyPass = "http://frigate-mqtt-ws/"; proxyWebsockets = true; + extraConfig = nginxAuthRequest + nginxProxySettings; }; "/live/jsmpeg" = { proxyPass = "http://frigate-jsmpeg/"; proxyWebsockets = true; - }; - "/live/mse/" = { - proxyPass = "http://frigate-go2rtc/"; - proxyWebsockets = true; + extraConfig = nginxAuthRequest + nginxProxySettings; }; # frigate lovelace card uses this path "/live/mse/api/ws" = { proxyPass = "http://frigate-go2rtc/api/ws"; proxyWebsockets = true; - extraConfig = '' + extraConfig = nginxAuthRequest + nginxProxySettings + '' limit_except GET { deny all; } ''; }; - "/live/webrtc/" = { - proxyPass = "http://frigate-go2rtc/"; - proxyWebsockets = true; - }; "/live/webrtc/api/ws" = { proxyPass = "http://frigate-go2rtc/api/ws"; proxyWebsockets = true; - extraConfig = '' + extraConfig = nginxAuthRequest + nginxProxySettings + '' limit_except GET { deny all; } @@ -246,17 +336,16 @@ in # pass through go2rtc player "/live/webrtc/webrtc.html" = { proxyPass = "http://frigate-go2rtc/webrtc.html"; - proxyWebsockets = true; - extraConfig = '' + extraConfig = nginxAuthRequest + nginxProxySettings + '' limit_except GET { deny all; } ''; }; + # frontend uses this to fetch the version "/api/go2rtc/api" = { proxyPass = "http://frigate-go2rtc/api"; - proxyWebsockets = true; - extraConfig = '' + extraConfig = nginxAuthRequest + nginxProxySettings + '' limit_except GET { deny all; } @@ -266,35 +355,64 @@ in "/api/go2rtc/webrtc" = { proxyPass = "http://frigate-go2rtc/api/webrtc"; proxyWebsockets = true; - extraConfig = '' + extraConfig = nginxAuthRequest + nginxProxySettings + '' limit_except GET { deny all; } ''; }; - "/cache/" = { - alias = "/var/cache/frigate/"; - }; - "/clips/" = { - root = "/var/lib/frigate"; - extraConfig = '' - types { - video/mp4 mp4; - image/jpeg jpg; - } - - autoindex on; + "~* /api/.*\.(jpg|jpeg|png|webp|gif)$" = { + proxyPass = "http://frigate-api"; + extraConfig = nginxAuthRequest + nginxProxySettings + '' + rewrite ^/api/(.*)$ $1 break; ''; }; - "/recordings/" = { - root = "/var/lib/frigate"; - extraConfig = '' - types { - video/mp4 mp4; + "/api/" = { + proxyPass = "http://frigate-api/"; + extraConfig = nginxAuthRequest + nginxProxySettings + '' + add_header Cache-Control "no-store"; + expires off; + + proxy_cache frigate_api_cache; + proxy_cache_lock on; + proxy_cache_use_stale updating; + proxy_cache_valid 200 5s; + proxy_cache_bypass $http_x_cache_bypass; + proxy_no_cache $should_not_cache; + add_header X-Cache-Status $upstream_cache_status; + + location /api/vod/ { + ${nginxAuthRequest} + proxy_pass http://frigate-api/vod/; + proxy_cache off; + add_header Cache-Control "no-store"; + ${nginxProxySettings} } - autoindex on; - autoindex_format json; + location /api/login { + auth_request off; + rewrite ^/api(/.*)$ $1 break; + proxy_pass http://frigate-api; + ${nginxProxySettings} + } + + location /api/stats { + ${nginxAuthRequest} + access_log off; + rewrite ^/api/(.*)$ $1 break; + add_header Cache-Control "no-store"; + proxy_pass http://frigate-api; + ${nginxProxySettings} + } + + location /api/version { + ${nginxAuthRequest} + access_log off; + rewrite ^/api/(.*)$ $1 break; + add_header Cache-Control "no-store"; + proxy_pass http://frigate-api; + ${nginxProxySettings} + } ''; }; "/assets/" = { @@ -307,21 +425,10 @@ in }; "/" = { root = cfg.package.web; - tryFiles = "$uri $uri/ /index.html"; + tryFiles = "$uri $uri.html $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 '' ''; - sub_filter_types text/css application/javascript; - sub_filter_once off; ''; }; }; @@ -338,17 +445,21 @@ in 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; ''; @@ -427,6 +538,9 @@ in PrivateTmp = true; CacheDirectory = "frigate"; CacheDirectoryMode = "0750"; + + # Sockets/IPC + RuntimeDirectory = "frigate"; }; }; }; diff --git a/nixos/tests/frigate.nix b/nixos/tests/frigate.nix index 03bd2b89611d..e3679e3f07a3 100644 --- a/nixos/tests/frigate.nix +++ b/nixos/tests/frigate.nix @@ -40,7 +40,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : ]; 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"; + ExecStart = "${lib.getExe pkgs.ffmpeg-headless} -re -f lavfi -i smptebars=size=1280x720:rate=5 -f mpegts -listen 1 http://0.0.0.0:8080"; Restart = "always"; }; }; @@ -54,11 +54,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : # Frigate startup machine.wait_for_open_port(5001) + machine.wait_until_succeeds("journalctl -u frigate.service -o cat | grep -q 'Created a default user'") - # nginx startup - machine.wait_for_open_port(80) - - machine.succeed("curl http://localhost") + #machine.log(machine.succeed("curl -vvv --fail http://localhost/api/version")[1]) machine.wait_for_file("/var/cache/frigate/test@*.mp4") ''; diff --git a/pkgs/applications/video/frigate/default.nix b/pkgs/applications/video/frigate/default.nix index 311e585b2d47..7ee4f6eba606 100644 --- a/pkgs/applications/video/frigate/default.nix +++ b/pkgs/applications/video/frigate/default.nix @@ -1,37 +1,31 @@ { lib , callPackage -, python311 +, python312 , fetchFromGitHub , fetchurl -, fetchpatch2 , frigate , nixosTests }: let - version = "0.13.2"; + version = "0.14.1"; src = fetchFromGitHub { - #name = "frigate-${version}-source"; + name = "frigate-${version}-source"; owner = "blakeblackshear"; repo = "frigate"; rev = "refs/tags/v${version}"; - hash = "sha256-NVT7yaJkVA7b7GL0S0fHjNneBzhjCru56qY1Q4sTVcE="; + hash = "sha256-PfUlo9ua4SVcQJTfmSVoEXHH1MUJ8A/w3kJHFpEzll8="; }; frigate-web = callPackage ./web.nix { inherit version src; }; - python = python311.override { + python = python312.override { self = python; packageOverrides = self: super: { - pydantic = super.pydantic_1; - - versioningit = super.versioningit.overridePythonAttrs { - # checkPhase requires pydantic>=2 - doCheck = false; - }; + paho-mqtt = super.paho-mqtt_2; }; }; @@ -60,56 +54,35 @@ python.pkgs.buildPythonApplication rec { inherit src; - patches = [ - (fetchpatch2 { - name = "frigate-flask3.0-compat.patch"; - url = "https://github.com/blakeblackshear/frigate/commit/56bdacc1c661eff8a323e033520e75e2ba0a3842.patch"; - hash = "sha256-s/goUJxIbjq/woCEOEZECdcZoJDoWc1eM63sd60cxeY="; - }) - (fetchpatch2 { - # https://github.com/blakeblackshear/frigate/pull/10967 - name = "frigate-wsdl-path.patch"; - url = "https://github.com/blakeblackshear/frigate/commit/b65656fa8733c1c2f3d944f716d2e9493ae7c99f.patch"; - hash = "sha256-taPWFV4PldBGUKAwFMKag4W/3TLMSGdKLYG8bj1Y5mU="; - }) - (fetchpatch2 { - # https://github.com/blakeblackshear/frigate/pull/10097 - name = "frigate-secrets-permissionerror.patch"; - url = "https://github.com/blakeblackshear/frigate/commit/a1424bad6c0163e790129ade7a9784514d0bf89d.patch"; - hash = "sha256-/kIy4aW9o5AKHJQfCDVY46si+DKaUb+CsZsCGIbXvUQ="; - }) - # https://github.com/blakeblackshear/frigate/pull/12324 - ./mpl-3.9.0.patch - ]; - postPatch = '' echo 'VERSION = "${version}"' > frigate/version.py substituteInPlace frigate/app.py \ - --replace "Router(migrate_db)" 'Router(migrate_db, "${placeholder "out"}/share/frigate/migrations")' + --replace-fail "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" \ - --replace "/config" "/var/lib/frigate" \ - --replace "{CONFIG_DIR}/model_cache" "/var/cache/frigate/model_cache" + --replace-fail "/media/frigate" "/var/lib/frigate" \ + --replace-fail "/tmp/cache" "/var/cache/frigate" \ + --replace-fail "/config" "/var/lib/frigate" \ + --replace-fail "{CONFIG_DIR}/model_cache" "/var/cache/frigate/model_cache" - substituteInPlace frigate/http.py \ - --replace "/opt/frigate" "${placeholder "out"}/${python.sitePackages}" - - substituteInPlace frigate/output.py \ - --replace "/opt/frigate" "${placeholder "out"}/${python.sitePackages}" + substituteInPlace frigate/comms/{config,detections,events}_updater.py frigate/comms/inter_process.py \ + --replace-fail "ipc:///tmp/cache" "ipc:///run/frigate" substituteInPlace frigate/detectors/detector_config.py \ - --replace "/labelmap.txt" "${placeholder "out"}/share/frigate/labelmap.txt" + --replace-fail "/labelmap.txt" "${placeholder "out"}/share/frigate/labelmap.txt" + + # work around onvif-zeep idiosyncrasy + substituteInPlace frigate/ptz/onvif.py \ + --replace-fail dist-packages site-packages substituteInPlace frigate/config.py \ - --replace "/cpu_model.tflite" "${tflite_cpu_model}" \ - --replace "/edgetpu_model.tflite" "${tflite_edgetpu_model}" + --replace-fail "/cpu_model.tflite" "${tflite_cpu_model}" \ + --replace-fail "/edgetpu_model.tflite" "${tflite_edgetpu_model}" substituteInPlace frigate/test/test_config.py \ - --replace "(MODEL_CACHE_DIR" "('/build/model_cache'" \ - --replace "/config/model_cache" "/build/model_cache" + --replace-fail "(MODEL_CACHE_DIR" "('/build/model_cache'" \ + --replace-fail "/config/model_cache" "/build/model_cache" ''; dontBuild = true; @@ -119,8 +92,11 @@ python.pkgs.buildPythonApplication rec { scikit-build # docker/main/requirements-wheel.txt click + distutils flask + flask-limiter imutils + joserfc markupsafe matplotlib norfair @@ -128,6 +104,7 @@ python.pkgs.buildPythonApplication rec { onvif-zeep opencv4 openvino + pandas paho-mqtt peewee peewee-migrate @@ -136,11 +113,12 @@ python.pkgs.buildPythonApplication rec { pydantic pytz pyyaml + pyzmq requests ruamel-yaml scipy setproctitle - tensorflow + tensorflow-bin tzlocal unidecode ws4py diff --git a/pkgs/applications/video/frigate/mpl-3.9.0.patch b/pkgs/applications/video/frigate/mpl-3.9.0.patch deleted file mode 100644 index 84fe7867438d..000000000000 --- a/pkgs/applications/video/frigate/mpl-3.9.0.patch +++ /dev/null @@ -1,42 +0,0 @@ -From fba8cff13186bd80ceaa06806392957598139deb Mon Sep 17 00:00:00 2001 -From: Martin Weinelt -Date: Sun, 7 Jul 2024 14:23:29 +0200 -Subject: [PATCH] Fix colormap usage with matplotlib 3.9.0 - -The mpl.cm toplevel registration has been removed. - -https://matplotlib.org/stable/api/prev_api_changes/api_changes_3.9.0.html#top-level-cmap-registration-and-access-functions-in-mpl-cm ---- - frigate/config.py | 2 +- - frigate/detectors/detector_config.py | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/frigate/config.py b/frigate/config.py -index 2e8b2570..af4f3263 100644 ---- a/frigate/config.py -+++ b/frigate/config.py -@@ -807,7 +807,7 @@ class CameraConfig(FrigateBaseModel): - def __init__(self, **config): - # Set zone colors - if "zones" in config: -- colors = plt.cm.get_cmap("tab10", len(config["zones"])) -+ colors = plt.colormaps["tab10"].resampled(len(config["zones"])) - config["zones"] = { - name: {**z, "color": tuple(round(255 * c) for c in colors(idx)[:3])} - for idx, (name, z) in enumerate(config["zones"].items()) -diff --git a/frigate/detectors/detector_config.py b/frigate/detectors/detector_config.py -index 7fc958a3..b65631eb 100644 ---- a/frigate/detectors/detector_config.py -+++ b/frigate/detectors/detector_config.py -@@ -125,7 +125,7 @@ class ModelConfig(BaseModel): - - def create_colormap(self, enabled_labels: set[str]) -> None: - """Get a list of colors for enabled labels.""" -- cmap = plt.cm.get_cmap("tab10", len(enabled_labels)) -+ cmap = plt.colormaps["tab10"].resampled(len(enabled_labels)) - - for key, val in enumerate(enabled_labels): - self._colormap[val] = tuple(int(round(255 * c)) for c in cmap(key)[:3]) --- -2.45.1 - diff --git a/pkgs/applications/video/frigate/web.nix b/pkgs/applications/video/frigate/web.nix index be530c5c3637..cea410849ec8 100644 --- a/pkgs/applications/video/frigate/web.nix +++ b/pkgs/applications/video/frigate/web.nix @@ -13,12 +13,19 @@ buildNpmPackage { substituteInPlace package.json \ --replace-fail "--base=/BASE_PATH/" "" - substituteInPlace src/routes/Storage.jsx \ + substituteInPlace \ + src/views/system/StorageMetrics.tsx \ + src/components/card/{AnimatedEvent,Export,Review}Card.tsx \ + src/components/timeline/EventSegment.tsx \ + src/pages/Exports.tsx \ + src/components/player/PreviewThumbnailPlayer.tsx \ --replace-fail "/media/frigate" "/var/lib/frigate" \ + + substituteInPlace src/views/system/StorageMetrics.tsx \ --replace-fail "/tmp/cache" "/var/cache/frigate" ''; - npmDepsHash = "sha256-+36quezGArqIM9dM+UihwcIgmE3EVmJQThuicLgDW4A="; + npmDepsHash = "sha256-PLs3oCWQjK38eHgdQt2Qkj7YqkfanC8JnLMpzMjNfxU="; installPhase = '' cp -rv dist/ $out