at 23.11-beta 11 kB view raw
1{ config 2, lib 3, pkgs 4, ... 5}: 6 7let 8 inherit (lib) 9 literalExpression 10 mkDefault 11 mdDoc 12 mkEnableOption 13 mkIf 14 mkOption 15 types; 16 17 cfg = config.services.frigate; 18 19 format = pkgs.formats.yaml {}; 20 21 filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! lib.elem v [ null ])) cfg.settings; 22 23 cameraFormat = with types; submodule { 24 freeformType = format.type; 25 options = { 26 ffmpeg = { 27 inputs = mkOption { 28 description = mdDoc '' 29 List of inputs for this camera. 30 ''; 31 type = listOf (submodule { 32 freeformType = format.type; 33 options = { 34 path = mkOption { 35 type = str; 36 example = "rtsp://192.0.2.1:554/rtsp"; 37 description = mdDoc '' 38 Stream URL 39 ''; 40 }; 41 roles = mkOption { 42 type = listOf (enum [ "detect" "record" "rtmp" ]); 43 example = literalExpression '' 44 [ "detect" "rtmp" ] 45 ''; 46 description = mdDoc '' 47 List of roles for this stream 48 ''; 49 }; 50 }; 51 }); 52 }; 53 }; 54 }; 55 }; 56 57in 58 59{ 60 meta.buildDocsInSandbox = false; 61 62 options.services.frigate = with types; { 63 enable = mkEnableOption (mdDoc "Frigate NVR"); 64 65 package = mkOption { 66 type = package; 67 default = pkgs.frigate; 68 description = mdDoc '' 69 The frigate package to use. 70 ''; 71 }; 72 73 hostname = mkOption { 74 type = str; 75 example = "frigate.exampe.com"; 76 description = mdDoc '' 77 Hostname of the nginx vhost to configure. 78 79 Only nginx is supported by upstream for direct reverse proxying. 80 ''; 81 }; 82 83 settings = mkOption { 84 type = submodule { 85 freeformType = format.type; 86 options = { 87 cameras = mkOption { 88 type = attrsOf cameraFormat; 89 description = mdDoc '' 90 Attribute set of cameras configurations. 91 92 https://docs.frigate.video/configuration/cameras 93 ''; 94 }; 95 96 database = { 97 path = mkOption { 98 type = path; 99 default = "/var/lib/frigate/frigate.db"; 100 description = mdDoc '' 101 Path to the SQLite database used 102 ''; 103 }; 104 }; 105 106 mqtt = { 107 enabled = mkEnableOption (mdDoc "MQTT support"); 108 109 host = mkOption { 110 type = nullOr str; 111 default = null; 112 example = "mqtt.example.com"; 113 description = mdDoc '' 114 MQTT server hostname 115 ''; 116 }; 117 }; 118 }; 119 }; 120 default = {}; 121 description = mdDoc '' 122 Frigate configuration as a nix attribute set. 123 124 See the project documentation for how to configure frigate. 125 - [Creating a config file](https://docs.frigate.video/guides/getting_started) 126 - [Configuration reference](https://docs.frigate.video/configuration/index) 127 ''; 128 }; 129 }; 130 131 config = mkIf cfg.enable { 132 services.nginx = { 133 enable =true; 134 additionalModules = with pkgs.nginxModules; [ 135 secure-token 136 rtmp 137 vod 138 ]; 139 recommendedProxySettings = mkDefault true; 140 recommendedGzipSettings = mkDefault true; 141 upstreams = { 142 frigate-api.servers = { 143 "127.0.0.1:5001" = {}; 144 }; 145 frigate-mqtt-ws.servers = { 146 "127.0.0.1:5002" = {}; 147 }; 148 frigate-jsmpeg.servers = { 149 "127.0.0.1:8082" = {}; 150 }; 151 frigate-go2rtc.servers = { 152 "127.0.0.1:1984" = {}; 153 }; 154 }; 155 # Based on https://github.com/blakeblackshear/frigate/blob/v0.12.0/docker/rootfs/usr/local/nginx/conf/nginx.conf 156 virtualHosts."${cfg.hostname}" = { 157 locations = { 158 "/api/" = { 159 proxyPass = "http://frigate-api/"; 160 }; 161 "~* /api/.*\.(jpg|jpeg|png)$" = { 162 proxyPass = "http://frigate-api"; 163 extraConfig = '' 164 add_header 'Access-Control-Allow-Origin' '*'; 165 add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; 166 rewrite ^/api/(.*)$ $1 break; 167 ''; 168 }; 169 "/vod/" = { 170 extraConfig = '' 171 aio threads; 172 vod hls; 173 174 secure_token $args; 175 secure_token_types application/vnd.apple.mpegurl; 176 177 add_header Access-Control-Allow-Headers '*'; 178 add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range'; 179 add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS'; 180 add_header Access-Control-Allow-Origin '*'; 181 add_header Cache-Control "no-store"; 182 expires off; 183 ''; 184 }; 185 "/stream/" = { 186 # TODO 187 }; 188 "/ws" = { 189 proxyPass = "http://frigate-mqtt-ws/"; 190 proxyWebsockets = true; 191 }; 192 "/live/jsmpeg" = { 193 proxyPass = "http://frigate-jsmpeg/"; 194 proxyWebsockets = true; 195 }; 196 "/live/mse/" = { 197 proxyPass = "http://frigate-go2rtc/"; 198 proxyWebsockets = true; 199 }; 200 "/live/webrtc/" = { 201 proxyPass = "http://frigate-go2rtc/"; 202 proxyWebsockets = true; 203 }; 204 "/cache/" = { 205 alias = "/var/cache/frigate/"; 206 }; 207 "/clips/" = { 208 root = "/var/lib/frigate"; 209 extraConfig = '' 210 add_header 'Access-Control-Allow-Origin' "$http_origin" always; 211 add_header 'Access-Control-Allow-Credentials' 'true'; 212 add_header 'Access-Control-Expose-Headers' 'Content-Length'; 213 if ($request_method = 'OPTIONS') { 214 add_header 'Access-Control-Allow-Origin' "$http_origin"; 215 add_header 'Access-Control-Max-Age' 1728000; 216 add_header 'Content-Type' 'text/plain charset=UTF-8'; 217 add_header 'Content-Length' 0; 218 return 204; 219 } 220 221 types { 222 video/mp4 mp4; 223 image/jpeg jpg; 224 } 225 226 autoindex on; 227 ''; 228 }; 229 "/recordings/" = { 230 root = "/var/lib/frigate"; 231 extraConfig = '' 232 add_header 'Access-Control-Allow-Origin' "$http_origin" always; 233 add_header 'Access-Control-Allow-Credentials' 'true'; 234 add_header 'Access-Control-Expose-Headers' 'Content-Length'; 235 if ($request_method = 'OPTIONS') { 236 add_header 'Access-Control-Allow-Origin' "$http_origin"; 237 add_header 'Access-Control-Max-Age' 1728000; 238 add_header 'Content-Type' 'text/plain charset=UTF-8'; 239 add_header 'Content-Length' 0; 240 return 204; 241 } 242 243 types { 244 video/mp4 mp4; 245 } 246 247 autoindex on; 248 autoindex_format json; 249 ''; 250 }; 251 "/assets/" = { 252 root = cfg.package.web; 253 extraConfig = '' 254 access_log off; 255 expires 1y; 256 add_header Cache-Control "public"; 257 ''; 258 }; 259 "/" = { 260 root = cfg.package.web; 261 tryFiles = "$uri $uri/ /index.html"; 262 extraConfig = '' 263 add_header Cache-Control "no-store"; 264 expires off; 265 266 sub_filter 'href="/BASE_PATH/' 'href="$http_x_ingress_path/'; 267 sub_filter 'url(/BASE_PATH/' 'url($http_x_ingress_path/'; 268 sub_filter '"/BASE_PATH/dist/' '"$http_x_ingress_path/dist/'; 269 sub_filter '"/BASE_PATH/js/' '"$http_x_ingress_path/js/'; 270 sub_filter '"/BASE_PATH/assets/' '"$http_x_ingress_path/assets/'; 271 sub_filter '"/BASE_PATH/monacoeditorwork/' '"$http_x_ingress_path/assets/'; 272 sub_filter 'return"/BASE_PATH/"' 'return window.baseUrl'; 273 sub_filter '<body>' '<body><script>window.baseUrl="$http_x_ingress_path/";</script>'; 274 sub_filter_types text/css application/javascript; 275 sub_filter_once off; 276 ''; 277 }; 278 }; 279 extraConfig = '' 280 # vod settings 281 vod_base_url ""; 282 vod_segments_base_url ""; 283 vod_mode mapped; 284 vod_max_mapping_response_size 1m; 285 vod_upstream_location /api; 286 vod_align_segments_to_key_frames on; 287 vod_manifest_segment_durations_mode accurate; 288 vod_ignore_edit_list on; 289 vod_segment_duration 10000; 290 vod_hls_mpegts_align_frames off; 291 vod_hls_mpegts_interleave_frames on; 292 # file handle caching / aio 293 open_file_cache max=1000 inactive=5m; 294 open_file_cache_valid 2m; 295 open_file_cache_min_uses 1; 296 open_file_cache_errors on; 297 aio on; 298 # https://github.com/kaltura/nginx-vod-module#vod_open_file_thread_pool 299 vod_open_file_thread_pool default; 300 # vod caches 301 vod_metadata_cache metadata_cache 512m; 302 vod_mapping_cache mapping_cache 5m 10m; 303 # gzip manifest 304 gzip_types application/vnd.apple.mpegurl; 305 ''; 306 }; 307 appendConfig = '' 308 rtmp { 309 server { 310 listen 1935; 311 chunk_size 4096; 312 allow publish 127.0.0.1; 313 deny publish all; 314 allow play all; 315 application live { 316 live on; 317 record off; 318 meta copy; 319 } 320 } 321 } 322 ''; 323 }; 324 325 systemd.services.nginx.serviceConfig.SupplementaryGroups = [ 326 "frigate" 327 ]; 328 329 users.users.frigate = { 330 isSystemUser = true; 331 group = "frigate"; 332 }; 333 users.groups.frigate = {}; 334 335 systemd.services.frigate = { 336 after = [ 337 "go2rtc.service" 338 "network.target" 339 ]; 340 wantedBy = [ 341 "multi-user.target" 342 ]; 343 environment = { 344 CONFIG_FILE = format.generate "frigate.yml" filteredConfig; 345 HOME = "/var/lib/frigate"; 346 PYTHONPATH = cfg.package.pythonPath; 347 }; 348 path = with pkgs; [ 349 # unfree: 350 # config.boot.kernelPackages.nvidiaPackages.latest.bin 351 ffmpeg_5-headless 352 libva-utils 353 procps 354 radeontop 355 ] ++ lib.optionals (!stdenv.isAarch64) [ 356 # not available on aarch64-linux 357 intel-gpu-tools 358 ]; 359 serviceConfig = { 360 ExecStart = "${cfg.package.python.interpreter} -m frigate"; 361 362 User = "frigate"; 363 Group = "frigate"; 364 365 UMask = "0027"; 366 367 StateDirectory = "frigate"; 368 StateDirectoryMode = "0750"; 369 370 # Caches 371 PrivateTmp = true; 372 CacheDirectory = "frigate"; 373 CacheDirectoryMode = "0750"; 374 375 BindPaths = [ 376 "/migrations:${cfg.package}/share/frigate/migrations:ro" 377 ]; 378 }; 379 }; 380 }; 381}