summary refs log tree commit diff
diff options
context:
space:
mode:
author0x4A6F <0x4A6F@users.noreply.github.com>2023-05-22 18:29:37 +0200
committerGitHub <noreply@github.com>2023-05-22 18:29:37 +0200
commitafad0c152b877e384c69e42b50c67480ca237f66 (patch)
tree744bb884cbad2012ce719e56b6ee2ddcbc289045
parent6972da146e29485ef7c9c1072854fbf2333717ad (diff)
parent9d0bbc2c12c5b38c059bae15d17e2a601fc9a567 (diff)
Merge pull request #214428 from mweinelt/frigate-init
frigate: init at 0.12.0
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/video/frigate.nix368
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/frigate.nix60
-rw-r--r--pkgs/applications/video/frigate/default.nix179
-rw-r--r--pkgs/applications/video/frigate/web.nix26
-rw-r--r--pkgs/servers/http/nginx/modules.nix11
-rw-r--r--pkgs/top-level/all-packages.nix2
9 files changed, 645 insertions, 5 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index a9d4f99b798bd..9d7387a75af6e 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -54,6 +54,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [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).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index b83e30a3b5a66..ff06a72ff9dd5 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1151,6 +1151,7 @@
   ./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
diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix
new file mode 100644
index 0000000000000..217637cbebcf9
--- /dev/null
+++ b/nixos/modules/services/video/frigate.nix
@@ -0,0 +1,368 @@
+{ 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"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index ec012522f0a30..af541737e12f4 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -273,6 +273,7 @@ in {
   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; };
diff --git a/nixos/tests/frigate.nix b/nixos/tests/frigate.nix
new file mode 100644
index 0000000000000..836fe0d063f87
--- /dev/null
+++ b/nixos/tests/frigate.nix
@@ -0,0 +1,60 @@
+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")
+  '';
+})
diff --git a/pkgs/applications/video/frigate/default.nix b/pkgs/applications/video/frigate/default.nix
new file mode 100644
index 0000000000000..05f4c16a63adc
--- /dev/null
+++ b/pkgs/applications/video/frigate/default.nix
@@ -0,0 +1,179 @@
+{ 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 ];
+  };
+}
diff --git a/pkgs/applications/video/frigate/web.nix b/pkgs/applications/video/frigate/web.nix
new file mode 100644
index 0000000000000..08b9cef19ee76
--- /dev/null
+++ b/pkgs/applications/video/frigate/web.nix
@@ -0,0 +1,26 @@
+{ 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
+  '';
+}
diff --git a/pkgs/servers/http/nginx/modules.nix b/pkgs/servers/http/nginx/modules.nix
index aa070df188c4c..14e9e97deb0f7 100644
--- a/pkgs/servers/http/nginx/modules.nix
+++ b/pkgs/servers/http/nginx/modules.nix
@@ -662,14 +662,15 @@ let self = {
     };
   };
 
-  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 ];
@@ -980,8 +981,8 @@ let self = {
       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 ];
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 1f38640fd8f61..d97eb416919ce 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -7720,6 +7720,8 @@ with pkgs;
     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 { };