249 lines
6.4 KiB
Nix
249 lines
6.4 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
let
|
|
cfg = config.services.frigateContainer;
|
|
|
|
makeEnvFile = envVars:
|
|
let
|
|
envLines =
|
|
mapAttrsToList (var: val: ''${var}="${toString val}"'') envVars;
|
|
in pkgs.writeText "envFile" (concatStringsSep "\n" envLines);
|
|
|
|
hostSecrets = config.fudo.secrets.host-secrets."${config.instance.hostname}";
|
|
|
|
removeNewline = removeSuffix "\n";
|
|
|
|
flatMapAttrs' = f: attrs: listToAttrs (mapConcat f attrs);
|
|
|
|
frigateCfg = let
|
|
content = builtins.toJSON {
|
|
mqtt = {
|
|
enabled = true;
|
|
inherit (cfg.mqtt) host port user;
|
|
password = "{FRIGATE_MQTT_PASSWORD}";
|
|
};
|
|
logger.default = cfg.log-level;
|
|
ffmpeg.hwaccel_args = optional (cfg.hwaccel != null) cfg.hwaccel;
|
|
cameras = mapAttrs' (_: camOpts:
|
|
nameValuePair camOpts.name {
|
|
birdseye.mode = if camOpts.default then "continuous" else "objects";
|
|
ffmpeg.inputs = [
|
|
{
|
|
path = camOpts.streams.high;
|
|
roles = [ "record" ];
|
|
}
|
|
{
|
|
path = camOpts.streams.low;
|
|
roles = [ "detect" ];
|
|
}
|
|
];
|
|
}) cfg.cameras;
|
|
go2rtc.streams = mapAttrs'
|
|
(_: camOpts: nameValuePair camOpts.name [ camOpts.streams.low ])
|
|
cfg.cameras;
|
|
detectors = cfg.detectors;
|
|
record = {
|
|
enabled = true;
|
|
retain = {
|
|
days = cfg.retention.default;
|
|
mode = "motion";
|
|
};
|
|
events.retain = {
|
|
default = cfg.retention.events;
|
|
mode = "active_objects";
|
|
objects = cfg.retention.objects;
|
|
};
|
|
};
|
|
birdseye = {
|
|
enabled = true;
|
|
mode = "continuous";
|
|
};
|
|
};
|
|
in pkgs.writeText "frigate.yaml" content;
|
|
|
|
in {
|
|
options.services.frigateContainer = with types; {
|
|
enable = mkEnableOption "Enable Frigate CCTV in a container.";
|
|
|
|
state-directory = mkOption {
|
|
type = str;
|
|
description = "Path at which to store Frigate recordings & data.";
|
|
};
|
|
|
|
log-level = mkOption {
|
|
type = str;
|
|
description = "Level at which to output Frigate logs.";
|
|
default = "error";
|
|
};
|
|
|
|
images = {
|
|
frigate = mkOption {
|
|
type = str;
|
|
description = "Frigate Docker image to run.";
|
|
};
|
|
};
|
|
|
|
hwaccel = mkOption {
|
|
type = nullOr str;
|
|
description = "Hardware acceleration driver.";
|
|
default = null;
|
|
};
|
|
|
|
retention = {
|
|
default = mkOption {
|
|
type = int;
|
|
description = "Retention time for all motion, in days.";
|
|
default = 7;
|
|
};
|
|
|
|
events = mkOption {
|
|
type = int;
|
|
description = "Retention time for all detected objects, in days.";
|
|
default = 14;
|
|
};
|
|
|
|
objects = mkOption {
|
|
type = attrsOf int;
|
|
description = "Map of object type to retention time in days.";
|
|
default = {
|
|
person = 60;
|
|
dog = 30;
|
|
cat = 30;
|
|
};
|
|
};
|
|
};
|
|
|
|
ports = {
|
|
frigate = mkOption {
|
|
type = port;
|
|
description = "Port on which to listen for Frigate web traffic.";
|
|
default = 5000;
|
|
};
|
|
|
|
rtsp = mkOption {
|
|
type = port;
|
|
description = "Port on which to listen for Frigate RTSP traffic.";
|
|
default = 8554;
|
|
};
|
|
|
|
webrtc = mkOption {
|
|
type = port;
|
|
description = "Port on which to listen for Frigate WebRTC traffic.";
|
|
default = 8555;
|
|
};
|
|
};
|
|
|
|
devices = mkOption {
|
|
type = listOf str;
|
|
description = "List of devices to pass to the container (for hw accel).";
|
|
default = [ ];
|
|
};
|
|
|
|
camera-password-file = mkOption {
|
|
type = str;
|
|
description =
|
|
"Path on build host to file containing the camera password.";
|
|
};
|
|
|
|
detectors = mkOption {
|
|
type = attrsOf (attrsOf str);
|
|
default = { };
|
|
};
|
|
|
|
cameras = let
|
|
cameraOpts = { name, ... }: {
|
|
options = {
|
|
name = mkOption {
|
|
type = str;
|
|
description = "Camera name.";
|
|
default = name;
|
|
};
|
|
|
|
default = mkEnableOption "Make camera the primary birds-eye view.";
|
|
|
|
streams = {
|
|
low = mkOption {
|
|
type = str;
|
|
description = "URL of the low-quality stream.";
|
|
};
|
|
high = mkOption {
|
|
type = str;
|
|
description = "URL of the high-quality stream.";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
in mkOption {
|
|
type = attrsOf (submodule cameraOpts);
|
|
description = "Cameras for Frigate CCTV to use.";
|
|
default = { };
|
|
};
|
|
|
|
mqtt = {
|
|
host = mkOption {
|
|
type = str;
|
|
description = "Hostname of the MQTT server.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = port;
|
|
description = "Port on which to contact the MQTT server.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = str;
|
|
description = "User as which to connect to server.";
|
|
};
|
|
|
|
password-file = mkOption {
|
|
type = str;
|
|
description =
|
|
"File containing password with which to authenticate to MQTT server.";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
fudo.secrets.host-secrets."${config.instance.hostname}" = {
|
|
frigateEnv = {
|
|
source-file = let
|
|
camPasswd = removeNewline (readFile cfg.camera-password-file);
|
|
mqttPasswd = removeNewline (readFile cfg.mqtt.password-file);
|
|
in makeEnvFile {
|
|
FRIGATE_RTSP_PASSWORD = camPasswd;
|
|
FRIGATE_MQTT_PASSWORD = mqttPasswd;
|
|
};
|
|
target-file = "/run/frigate/camera.passwd";
|
|
};
|
|
};
|
|
|
|
virtualisation.arion.projects.frigate.settings = let
|
|
image = { pkgs, ... }: {
|
|
project.name = "frigate-cctv";
|
|
services = {
|
|
frigate.service = {
|
|
image = cfg.images.frigate;
|
|
hostname = "frigate";
|
|
restart = "always";
|
|
volumes = [
|
|
"${frigateCfg}:/config/config.yml"
|
|
"${cfg.state-directory}:/media/frigate"
|
|
];
|
|
# shm_size = cfg.shm-size;
|
|
devices = cfg.devices;
|
|
ports = [
|
|
"${toString cfg.ports.frigate}:5000"
|
|
"${toString cfg.ports.rtsp}:8554"
|
|
"${toString cfg.ports.webrtc}:8555/tcp"
|
|
"${toString cfg.ports.webrtc}:8555/udp"
|
|
];
|
|
env_file = [ hostSecrets.frigateEnv.target-file ];
|
|
};
|
|
# TODO: add metrics exporter
|
|
};
|
|
};
|
|
in { imports = [ image ]; };
|
|
};
|
|
}
|