vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / video / frigate.nix
blobb3f38eba664297c6e452f5cd76b97cf0e9ab359e
1 { config
2 , lib
3 , pkgs
4 , ...
5 }:
7 let
8   inherit (lib)
9     literalExpression
10     mkDefault
11     mkEnableOption
12     mkPackageOption
13     mkIf
14     mkOption
15     types;
17   cfg = config.services.frigate;
19   format = pkgs.formats.yaml { };
21   filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! lib.elem v [ null ])) cfg.settings;
23   cameraFormat = with types; submodule {
24     freeformType = format.type;
25     options = {
26       ffmpeg = {
27         inputs = mkOption {
28           description = ''
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 = ''
38                   Stream URL
39                 '';
40               };
41               roles = mkOption {
42                 type = listOf (enum [ "audio" "detect" "record" ]);
43                 example = [ "detect" "record" ];
44                 description = ''
45                   List of roles for this stream
46                 '';
47               };
48             };
49           });
50         };
51       };
52     };
53   };
55   # auth_request.conf
56   nginxAuthRequest = ''
57     # Send a subrequest to verify if the user is authenticated and has permission to access the resource.
58     auth_request /auth;
60     # Save the upstream metadata response headers from Authelia to variables.
61     auth_request_set $user $upstream_http_remote_user;
62     auth_request_set $groups $upstream_http_remote_groups;
63     auth_request_set $name $upstream_http_remote_name;
64     auth_request_set $email $upstream_http_remote_email;
66     # Inject the metadata response headers from the variables into the request made to the backend.
67     proxy_set_header Remote-User $user;
68     proxy_set_header Remote-Groups $groups;
69     proxy_set_header Remote-Email $email;
70     proxy_set_header Remote-Name $name;
72     # Refresh the cookie as needed
73     auth_request_set $auth_cookie $upstream_http_set_cookie;
74     add_header Set-Cookie $auth_cookie;
76     # Pass the location header back up if it exists
77     auth_request_set $redirection_url $upstream_http_location;
78     add_header Location $redirection_url;
79   '';
81   nginxProxySettings = ''
82     # Basic Proxy Configuration
83     client_body_buffer_size 128k;
84     proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; ## Timeout if the real server is dead.
85     proxy_redirect  http://  $scheme://;
86     proxy_cache_bypass $cookie_session;
87     proxy_no_cache $cookie_session;
88     proxy_buffers 64 256k;
90     # Advanced Proxy Configuration
91     send_timeout 5m;
92     proxy_read_timeout 360;
93     proxy_send_timeout 360;
94     proxy_connect_timeout 360;
95   '';
100   meta.buildDocsInSandbox = false;
102   options.services.frigate = with types; {
103     enable = mkEnableOption "Frigate NVR";
105     package = mkPackageOption pkgs "frigate" { };
107     hostname = mkOption {
108       type = str;
109       example = "frigate.exampe.com";
110       description = ''
111         Hostname of the nginx vhost to configure.
113         Only nginx is supported by upstream for direct reverse proxying.
114       '';
115     };
117     settings = mkOption {
118       type = submodule {
119         freeformType = format.type;
120         options = {
121           cameras = mkOption {
122             type = attrsOf cameraFormat;
123             description = ''
124               Attribute set of cameras configurations.
126               https://docs.frigate.video/configuration/cameras
127             '';
128           };
130           database = {
131             path = mkOption {
132               type = path;
133               default = "/var/lib/frigate/frigate.db";
134               description = ''
135                 Path to the SQLite database used
136               '';
137             };
138           };
140           mqtt = {
141             enabled = mkEnableOption "MQTT support";
143             host = mkOption {
144               type = nullOr str;
145               default = null;
146               example = "mqtt.example.com";
147               description = ''
148                 MQTT server hostname
149               '';
150             };
151           };
152         };
153       };
154       default = { };
155       description = ''
156         Frigate configuration as a nix attribute set.
158         See the project documentation for how to configure frigate.
159         - [Creating a config file](https://docs.frigate.video/guides/getting_started)
160         - [Configuration reference](https://docs.frigate.video/configuration/index)
161       '';
162     };
163   };
165   config = mkIf cfg.enable {
166     services.nginx = {
167       enable = true;
168       additionalModules = with pkgs.nginxModules; [
169         develkit
170         secure-token
171         set-misc
172         vod
173       ];
174       recommendedProxySettings = mkDefault true;
175       recommendedGzipSettings = mkDefault true;
176       mapHashBucketSize = mkDefault 128;
177       upstreams = {
178         frigate-api.servers = {
179           "127.0.0.1:5001" = { };
180         };
181         frigate-mqtt-ws.servers = {
182           "127.0.0.1:5002" = { };
183         };
184         frigate-jsmpeg.servers = {
185           "127.0.0.1:8082" = { };
186         };
187         frigate-go2rtc.servers = {
188           "127.0.0.1:1984" = { };
189         };
190       };
191       proxyCachePath."frigate" = {
192         enable = true;
193         keysZoneSize = "10m";
194         keysZoneName = "frigate_api_cache";
195         maxSize = "10m";
196         inactive = "1m";
197         levels = "1:2";
198       };
199       # Based on https://github.com/blakeblackshear/frigate/blob/v0.13.1/docker/main/rootfs/usr/local/nginx/conf/nginx.conf
200       virtualHosts."${cfg.hostname}" = {
201         locations = {
202           # auth_location.conf
203           "/auth" = {
204             proxyPass = "http://frigate-api/auth";
205             extraConfig = ''
206               internal;
208               # Strip all request headers
209               proxy_pass_request_headers off;
211               # Pass info about the request
212               proxy_set_header X-Original-Method $request_method;
213               proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
214               proxy_set_header X-Server-Port $server_port;
215               proxy_set_header Content-Length "";
217               # Pass along auth related info
218               proxy_set_header Authorization $http_authorization;
219               proxy_set_header Cookie $http_cookie;
220               proxy_set_header X-CSRF-TOKEN "1";
222               # Pass headers for common auth proxies
223               proxy_set_header Remote-User $http_remote_user;
224               proxy_set_header Remote-Groups $http_remote_groups;
225               proxy_set_header Remote-Email $http_remote_email;
226               proxy_set_header Remote-Name $http_remote_name;
227               proxy_set_header X-Forwarded-User $http_x_forwarded_user;
228               proxy_set_header X-Forwarded-Groups $http_x_forwarded_groups;
229               proxy_set_header X-Forwarded-Email $http_x_forwarded_email;
230               proxy_set_header X-Forwarded-Preferred-Username $http_x_forwarded_preferred_username;
231               proxy_set_header X-authentik-username $http_x_authentik_username;
232               proxy_set_header X-authentik-groups $http_x_authentik_groups;
233               proxy_set_header X-authentik-email $http_x_authentik_email;
234               proxy_set_header X-authentik-name $http_x_authentik_name;
235               proxy_set_header X-authentik-uid $http_x_authentik_uid;
237               ${nginxProxySettings}
238             '';
239           };
240           "/vod/" = {
241             extraConfig = nginxAuthRequest + ''
242               aio threads;
243               vod hls;
245               secure_token $args;
246               secure_token_types application/vnd.apple.mpegurl;
248               add_header Cache-Control "no-store";
249               expires off;
250             '';
251           };
252           "/stream/" = {
253             alias = "/var/cache/frigate/stream/";
254             extraConfig = nginxAuthRequest + ''
255               add_header Cache-Control "no-store";
256               expires off;
258               types {
259                   application/dash+xml mpd;
260                   application/vnd.apple.mpegurl m3u8;
261                   video/mp2t ts;
262                   image/jpeg jpg;
263               }
264             '';
265           };
266           "/clips/" = {
267             root = "/var/lib/frigate";
268             extraConfig = nginxAuthRequest + ''
269               types {
270                   video/mp4 mp4;
271                   image/jpeg jpg;
272               }
274               expires 7d;
275               add_header Cache-Control "public";
276               autoindex on;
277             '';
278           };
279           "/cache/" = {
280             alias = "/var/cache/frigate/";
281             extraConfig = ''
282               internal;
283             '';
284           };
285           "/recordings/" = {
286             root = "/var/lib/frigate";
287             extraConfig = nginxAuthRequest + ''
288               types {
289                   video/mp4 mp4;
290               }
292               autoindex on;
293               autoindex_format json;
294             '';
295           };
296           "/exports/" = {
297             root = "/var/lib/frigate";
298             extraConfig = nginxAuthRequest + ''
299               types {
300                 video/mp4 mp4;
301               }
303               autoindex on;
304               autoindex_format json;
305             '';
306           };
307           "/ws" = {
308             proxyPass = "http://frigate-mqtt-ws/";
309             proxyWebsockets = true;
310             extraConfig = nginxAuthRequest + nginxProxySettings;
311           };
312           "/live/jsmpeg" = {
313             proxyPass = "http://frigate-jsmpeg/";
314             proxyWebsockets = true;
315             extraConfig = nginxAuthRequest + nginxProxySettings;
316           };
317           # frigate lovelace card uses this path
318           "/live/mse/api/ws" = {
319             proxyPass = "http://frigate-go2rtc/api/ws";
320             proxyWebsockets = true;
321             extraConfig = nginxAuthRequest + nginxProxySettings + ''
322               limit_except GET {
323                   deny  all;
324               }
325             '';
326           };
327           "/live/webrtc/api/ws" = {
328             proxyPass = "http://frigate-go2rtc/api/ws";
329             proxyWebsockets = true;
330             extraConfig = nginxAuthRequest + nginxProxySettings + ''
331               limit_except GET {
332                   deny  all;
333               }
334             '';
335           };
336           # pass through go2rtc player
337           "/live/webrtc/webrtc.html" = {
338             proxyPass = "http://frigate-go2rtc/webrtc.html";
339             extraConfig = nginxAuthRequest + nginxProxySettings + ''
340               limit_except GET {
341                   deny  all;
342               }
343             '';
344           };
345           # frontend uses this to fetch the version
346           "/api/go2rtc/api" = {
347             proxyPass = "http://frigate-go2rtc/api";
348             extraConfig = nginxAuthRequest + nginxProxySettings + ''
349               limit_except GET {
350                   deny  all;
351               }
352             '';
353           };
354           # integrationn uses this to add webrtc candidate
355           "/api/go2rtc/webrtc" = {
356             proxyPass = "http://frigate-go2rtc/api/webrtc";
357             proxyWebsockets = true;
358             extraConfig = nginxAuthRequest + nginxProxySettings + ''
359               limit_except GET {
360                   deny  all;
361               }
362             '';
363           };
364           "~* /api/.*\.(jpg|jpeg|png|webp|gif)$" = {
365             proxyPass = "http://frigate-api";
366             extraConfig = nginxAuthRequest + nginxProxySettings + ''
367               rewrite ^/api/(.*)$ $1 break;
368             '';
369           };
370           "/api/" = {
371             proxyPass = "http://frigate-api/";
372             extraConfig = nginxAuthRequest + nginxProxySettings + ''
373               add_header Cache-Control "no-store";
374               expires off;
376               proxy_cache frigate_api_cache;
377               proxy_cache_lock on;
378               proxy_cache_use_stale updating;
379               proxy_cache_valid 200 5s;
380               proxy_cache_bypass $http_x_cache_bypass;
381               proxy_no_cache $should_not_cache;
382               add_header X-Cache-Status $upstream_cache_status;
384               location /api/vod/ {
385                   ${nginxAuthRequest}
386                   proxy_pass http://frigate-api/vod/;
387                   proxy_cache off;
388                   add_header Cache-Control "no-store";
389                   ${nginxProxySettings}
390               }
392               location /api/login {
393                   auth_request off;
394                   rewrite ^/api(/.*)$ $1 break;
395                   proxy_pass http://frigate-api;
396                   ${nginxProxySettings}
397               }
399               location /api/stats {
400                   ${nginxAuthRequest}
401                   access_log off;
402                   rewrite ^/api/(.*)$ $1 break;
403                   add_header Cache-Control "no-store";
404                   proxy_pass http://frigate-api;
405                   ${nginxProxySettings}
406               }
408               location /api/version {
409                   ${nginxAuthRequest}
410                   access_log off;
411                   rewrite ^/api/(.*)$ $1 break;
412                   add_header Cache-Control "no-store";
413                   proxy_pass http://frigate-api;
414                   ${nginxProxySettings}
415               }
416             '';
417           };
418           "/assets/" = {
419             root = cfg.package.web;
420             extraConfig = ''
421               access_log off;
422               expires 1y;
423               add_header Cache-Control "public";
424             '';
425           };
426           "/" = {
427             root = cfg.package.web;
428             tryFiles = "$uri $uri.html $uri/ /index.html";
429             extraConfig = ''
430               add_header Cache-Control "no-store";
431               expires off;
432             '';
433           };
434         };
435         extraConfig = ''
436           # vod settings
437           vod_base_url "";
438           vod_segments_base_url "";
439           vod_mode mapped;
440           vod_max_mapping_response_size 1m;
441           vod_upstream_location /api;
442           vod_align_segments_to_key_frames on;
443           vod_manifest_segment_durations_mode accurate;
444           vod_ignore_edit_list on;
445           vod_segment_duration 10000;
446           vod_hls_mpegts_align_frames off;
447           vod_hls_mpegts_interleave_frames on;
449           # file handle caching / aio
450           open_file_cache max=1000 inactive=5m;
451           open_file_cache_valid 2m;
452           open_file_cache_min_uses 1;
453           open_file_cache_errors on;
454           aio on;
456           # https://github.com/kaltura/nginx-vod-module#vod_open_file_thread_pool
457           vod_open_file_thread_pool default;
459           # vod caches
460           vod_metadata_cache metadata_cache 512m;
461           vod_mapping_cache mapping_cache 5m 10m;
463           # gzip manifest
464           gzip_types application/vnd.apple.mpegurl;
465         '';
466       };
467       appendConfig = ''
468         rtmp {
469             server {
470                 listen 1935;
471                 chunk_size 4096;
472                 allow publish 127.0.0.1;
473                 deny publish all;
474                 allow play all;
475                 application live {
476                     live on;
477                     record off;
478                     meta copy;
479                 }
480             }
481         }
482       '';
483       appendHttpConfig = ''
484         map $sent_http_content_type $should_not_cache {
485           'application/json' 0;
486           default 1;
487         }
488       '';
489     };
491     systemd.services.nginx.serviceConfig.SupplementaryGroups = [
492       "frigate"
493     ];
495     users.users.frigate = {
496       isSystemUser = true;
497       group = "frigate";
498     };
499     users.groups.frigate = { };
501     systemd.services.frigate = {
502       after = [
503         "go2rtc.service"
504         "network.target"
505       ];
506       wantedBy = [
507         "multi-user.target"
508       ];
509       environment = {
510         CONFIG_FILE = format.generate "frigate.yml" filteredConfig;
511         HOME = "/var/lib/frigate";
512         PYTHONPATH = cfg.package.pythonPath;
513       };
514       path = with pkgs; [
515         # unfree:
516         # config.boot.kernelPackages.nvidiaPackages.latest.bin
517         ffmpeg-headless
518         libva-utils
519         procps
520         radeontop
521       ] ++ lib.optionals (!stdenv.hostPlatform.isAarch64) [
522         # not available on aarch64-linux
523         intel-gpu-tools
524       ];
525       serviceConfig = {
526         ExecStart = "${cfg.package.python.interpreter} -m frigate";
527         Restart = "on-failure";
529         User = "frigate";
530         Group = "frigate";
532         UMask = "0027";
534         StateDirectory = "frigate";
535         StateDirectoryMode = "0750";
537         # Caches
538         PrivateTmp = true;
539         CacheDirectory = "frigate";
540         CacheDirectoryMode = "0750";
542         # Sockets/IPC
543         RuntimeDirectory = "frigate";
544       };
545     };
546   };