From 2cfe47cb3dc1d04e6317ed6b1a909d58870c2554 Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Sun, 19 Apr 2026 13:23:07 +0200 Subject: [PATCH] feat: streamline camera config --- flake.nix | 1 + hosts/kameramann/nvr.nix | 249 +++++++++++++++++---------------------- 2 files changed, 107 insertions(+), 143 deletions(-) diff --git a/flake.nix b/flake.nix index 03f9c0a..196403f 100644 --- a/flake.nix +++ b/flake.nix @@ -43,6 +43,7 @@ default = pkgs.mkShell { packages = [ pkgs.colmena + pkgs.nixfmt agenix.packages.${system}.default ]; }; diff --git a/hosts/kameramann/nvr.nix b/hosts/kameramann/nvr.nix index 23a0c82..417d136 100644 --- a/hosts/kameramann/nvr.nix +++ b/hosts/kameramann/nvr.nix @@ -1,88 +1,8 @@ -{ config, ... }: { - age.secrets."camera-ulfried-url".file = ../../secrets/camera-ulfried-url.age; - age.secrets."camera-ulfried-sub-url".file = ../../secrets/camera-ulfried-sub-url.age; - age.secrets."camera-gnisbert-url".file = ../../secrets/camera-gnisbert-url.age; - age.secrets."camera-gnisbert-sub-url".file = ../../secrets/camera-gnisbert-sub-url.age; - age.secrets."camera-taubis-url".file = ../../secrets/camera-taubis-url.age; - age.secrets."camera-taubis-sub-url".file = ../../secrets/camera-taubis-sub-url.age; - age.secrets."camera-foeff-url".file = ../../secrets/camera-foeff-url.age; - age.secrets."camera-foeff-sub-url".file = ../../secrets/camera-foeff-sub-url.age; - - systemd.services.go2rtc.serviceConfig.LoadCredential = [ - "ULFRIED_URL:${config.age.secrets."camera-ulfried-url".path}" - "ULFRIED_SUB_URL:${config.age.secrets."camera-ulfried-sub-url".path}" - "GNISBERT_URL:${config.age.secrets."camera-gnisbert-url".path}" - "GNISBERT_SUB_URL:${config.age.secrets."camera-gnisbert-sub-url".path}" - "TAUBIS_URL:${config.age.secrets."camera-taubis-url".path}" - "TAUBIS_SUB_URL:${config.age.secrets."camera-taubis-sub-url".path}" - "FOEFF_URL:${config.age.secrets."camera-foeff-url".path}" - "FOEFF_SUB_URL:${config.age.secrets."camera-foeff-sub-url".path}" - ]; - - services.go2rtc.enable = true; - services.go2rtc.settings = { - rtsp.listen = ":8554"; - webrtc.listen = ":8555"; - }; - services.go2rtc.settings.streams = { - "ulfried" = [ - "\${ULFRIED_URL}" - "ffmpeg:ulfried#audio=opus#audio=aac" - ]; - "ulfried_sub" = [ - "\${ULFRIED_SUB_URL}" - "ffmpeg:ulfried_sub#audio=opus#audio=aac" - ]; - "gnisbert" = [ - "\${GNISBERT_URL}" - "ffmpeg:gnisbert#audio=opus#audio=aac" - ]; - "gnisbert_sub" = [ - "\${GNISBERT_SUB_URL}" - "ffmpeg:gnisbert_sub#audio=opus#audio=aac" - ]; - "taubis" = [ - "\${TAUBIS_URL}" - "ffmpeg:taubis#audio=opus#audio=aac" - ]; - "taubis_sub" = [ - "\${TAUBIS_SUB_URL}" - "ffmpeg:taubis_sub#audio=opus#audio=aac" - ]; - "foeff" = [ - "\${FOEFF_URL}" - "ffmpeg:foeff#audio=opus#audio=aac#video=copy" - ]; - "foeff_sub" = [ - "\${FOEFF_SUB_URL}" - "ffmpeg:foeff_sub#audio=opus#audio=aac#video=copy" - ]; - }; - - services.frigate.enable = true; - services.frigate.hostname = "kameramann.lan.baubs.net"; - services.frigate.settings.go2rtc = { streams = { - "ulfried" = []; - "ulfried_sub" = []; - "taubis" = []; - "taubis_sub" = []; - "foeff" = []; - "foeff_sub" = []; - "gnisbert" = []; - "gnisbert_sub" = []; - };}; - services.frigate.settings.detect = { - enabled = true; - }; - services.frigate.settings.objects = { - track = ["person" "bird" "car"]; - }; - services.frigate.settings.classification.bird.enabled = true; - - - services.frigate.settings.cameras = { +{ config, lib, ... }: +let + cameras = { "ulfried" = { - record = { + frigate.record = { enabled = true; continuous.days = 0; alerts.retain = { @@ -90,24 +10,21 @@ mode = "motion"; }; }; - ffmpeg.inputs = [{ - path = "rtsp://127.0.0.1:8554/ulfried?timeout=30"; - input_args = "preset-rtsp-generic"; - roles = [ - "record" - ]; - } - { - path = "rtsp://127.0.0.1:8554/ulfried_sub?timeout=30"; - input_args = "preset-rtsp-generic"; - roles = [ - "detect" - "audio" - ]; - }]; + }; + "gnisbert" = { }; + "taubis" = { + frigate = { + detect.enabled = true; + record = { + enabled = true; + continuous.days = 1; + detections.retain.days = 7; + }; + }; }; "foeff" = { - record = { + videoPassthrough = true; + frigate.record = { enabled = true; continuous.days = 0; alerts.retain = { @@ -115,51 +32,97 @@ mode = "motion"; }; }; - ffmpeg.inputs = [ - { - path = "rtsp://127.0.0.1:8554/foeff?timeout=30"; - input_args = "preset-rtsp-generic"; - roles = [ - "record" - ]; - } - { - path = "rtsp://127.0.0.1:8554/foeff_sub?timeout=30"; - input_args = "preset-rtsp-generic"; - roles = [ - "detect" - "audio" - ]; - } - ]; - }; - "taubis" = { - detect.enabled = true; - record = { - enabled = true; - continuous.days = 1; - detections.retain.days = 7; - }; - ffmpeg.inputs = [ - { - path = "rtsp://127.0.0.1:8554/taubis?timeout=30"; - input_args = "preset-rtsp-generic"; - roles = [ - "record" - ]; - } - { - path = "rtsp://127.0.0.1:8554/taubis_sub?timeout=30"; - input_args = "preset-rtsp-generic"; - roles = [ - "detect" - "audio" - ]; - } - ]; }; }; - networking.firewall.allowedTCPPorts = [ 5000 1984 80 8555 ]; + # Uppercase camera name for use as systemd credential / env var name + varName = name: lib.toUpper (lib.replaceStrings [ "-" ] [ "_" ] name); + + go2rtcCameraStreams = + name: cam: + let + video = lib.optionalString (cam.videoPassthrough or false) "#video=copy"; + in + { + "${name}" = [ + "\${${varName name}_URL}" + "ffmpeg:${name}#audio=opus#audio=aac${video}" + ]; + "${name}_sub" = [ + "\${${varName name}_SUB_URL}" + "ffmpeg:${name}_sub#audio=opus#audio=aac${video}" + ]; + }; + + frigateInputs = name: [ + { + path = "rtsp://127.0.0.1:8554/${name}?timeout=30"; + input_args = "preset-rtsp-generic"; + roles = [ "record" ]; + } + { + path = "rtsp://127.0.0.1:8554/${name}_sub?timeout=30"; + input_args = "preset-rtsp-generic"; + roles = [ + "detect" + "audio" + ]; + } + ]; + + # Only cameras with a frigate key get a frigate camera entry + frigCameras = lib.filterAttrs (_: cam: cam ? frigate) cameras; +in +{ + age.secrets = lib.mkMerge ( + lib.mapAttrsToList (name: _: { + "camera-${name}-url".file = ../../secrets/camera-${name}-url.age; + "camera-${name}-sub-url".file = ../../secrets/camera-${name}-sub-url.age; + }) cameras + ); + + systemd.services.go2rtc.serviceConfig.LoadCredential = lib.concatMap (name: [ + "${varName name}_URL:${config.age.secrets."camera-${name}-url".path}" + "${varName name}_SUB_URL:${config.age.secrets."camera-${name}-sub-url".path}" + ]) (lib.attrNames cameras); + + services.go2rtc = { + enable = true; + settings = { + rtsp.listen = ":8554"; + webrtc.listen = ":8555"; + streams = lib.foldl' lib.mergeAttrs { } (lib.mapAttrsToList go2rtcCameraStreams cameras); + }; + }; + + services.frigate = { + enable = true; + hostname = "kameramann.lan.baubs.net"; + settings = { + go2rtc.streams = lib.foldl' lib.mergeAttrs { } ( + lib.mapAttrsToList (name: _: { + "${name}" = [ ]; + "${name}_sub" = [ ]; + }) cameras + ); + detect.enabled = true; + objects.track = [ + "person" + "bird" + "car" + ]; + classification.bird.enabled = true; + cameras = lib.mapAttrs ( + name: cam: cam.frigate // { ffmpeg.inputs = frigateInputs name; } + ) frigCameras; + }; + }; + + networking.firewall.allowedTCPPorts = [ + 5000 + 1984 + 80 + 8555 + ]; networking.firewall.allowedUDPPorts = [ 8555 ]; }