grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / misc / zoneminder.nix
blob5b0b1448f685622117a3f177c07a11eba11af22d
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.services.zoneminder;
5   fpm = config.services.phpfpm.pools.zoneminder;
6   pkg = pkgs.zoneminder;
8   dirName = pkg.dirName;
10   user = "zoneminder";
11   group = {
12     nginx = config.services.nginx.group;
13     none  = user;
14   }.${cfg.webserver};
16   useNginx = cfg.webserver == "nginx";
18   defaultDir = "/var/lib/${user}";
19   home = if useCustomDir then cfg.storageDir else defaultDir;
21   useCustomDir = cfg.storageDir != null;
23   zms = "/cgi-bin/zms";
25   dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList;
27   cacheDirs = [ "swap" ];
28   libDirs   = [ "events" "exports" "images" "sounds" ];
30   dirStanzas = baseDir:
31     lib.concatStringsSep "\n" (map (e:
32       "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}"
33       ) libDirs);
35   defaultsFile = pkgs.writeText "60-defaults.conf" ''
36     # 01-system-paths.conf
37     ${dirStanzas home}
38     ZM_PATH_ARP=${lib.getBin pkgs.nettools}/bin/arp
39     ZM_PATH_LOGS=/var/log/${dirName}
40     ZM_PATH_MAP=/dev/shm
41     ZM_PATH_SOCKS=/run/${dirName}
42     ZM_PATH_SWAP=/var/cache/${dirName}/swap
43     ZM_PATH_ZMS=${zms}
45     # 02-multiserver.conf
46     ZM_SERVER_HOST=
48     # Database
49     ZM_DB_TYPE=mysql
50     ZM_DB_HOST=${cfg.database.host}
51     ZM_DB_NAME=${cfg.database.name}
52     ZM_DB_USER=${cfg.database.username}
53     ZM_DB_PASS=${cfg.database.password}
55     # Web
56     ZM_WEB_USER=${user}
57     ZM_WEB_GROUP=${group}
58   '';
60   configFile = pkgs.writeText "80-nixos.conf" ''
61     # You can override defaults here
63     ${cfg.extraConfig}
64   '';
66 in {
67   options = {
68     services.zoneminder = with lib; {
69       enable = lib.mkEnableOption ''
70         ZoneMinder.
72         If you intend to run the database locally, you should set
73         `config.services.zoneminder.database.createLocally` to true. Otherwise,
74         when set to `false` (the default), you will have to create the database
75         and database user as well as populate the database yourself.
76         Additionally, you will need to run `zmupdate.pl` yourself when
77         upgrading to a newer version
78       '';
80       webserver = mkOption {
81         type = types.enum [ "nginx" "none" ];
82         default = "nginx";
83         description = ''
84           The webserver to configure for the PHP frontend.
86           Set it to `none` if you want to configure it yourself. PRs are welcome
87           for support for other web servers.
88         '';
89       };
91       hostname = mkOption {
92         type = types.str;
93         default = "localhost";
94         description = ''
95           The hostname on which to listen.
96         '';
97       };
99       port = mkOption {
100         type = types.port;
101         default = 8095;
102         description = ''
103           The port on which to listen.
104         '';
105       };
107       openFirewall = mkOption {
108         type = types.bool;
109         default = false;
110         description = ''
111           Open the firewall port(s).
112         '';
113       };
115       database = {
116         createLocally = mkOption {
117           type = types.bool;
118           default = false;
119           description = ''
120             Create the database and database user locally.
121           '';
122         };
124         host = mkOption {
125           type = types.str;
126           default = "localhost";
127           description = ''
128             Hostname hosting the database.
129           '';
130         };
132         name = mkOption {
133           type = types.str;
134           default = "zm";
135           description = ''
136             Name of database.
137           '';
138         };
140         username = mkOption {
141           type = types.str;
142           default = "zmuser";
143           description = ''
144             Username for accessing the database.
145           '';
146         };
148         password = mkOption {
149           type = types.str;
150           default = "zmpass";
151           description = ''
152             Username for accessing the database.
153             Not used if `createLocally` is set.
154           '';
155         };
156       };
158       cameras = mkOption {
159         type = types.int;
160         default = 1;
161         description = ''
162           Set this to the number of cameras you expect to support.
163         '';
164       };
166       storageDir = mkOption {
167         type = types.nullOr types.str;
168         default = null;
169         example = "/storage/tank";
170         description = ''
171           ZoneMinder can generate quite a lot of data, so in case you don't want
172           to use the default ${defaultDir}, you can override the path here.
173         '';
174       };
176       extraConfig = mkOption {
177         type = types.lines;
178         default = "";
179         description = ''
180           Additional configuration added verbatim to the configuration file.
181         '';
182       };
183     };
184   };
186   config = lib.mkIf cfg.enable {
188     assertions = [
189       { assertion = cfg.database.createLocally -> cfg.database.username == user;
190         message = "services.zoneminder.database.username must be set to ${user} if services.zoneminder.database.createLocally is set true";
191       }
192     ];
194     environment.etc = {
195       "zoneminder/60-defaults.conf".source = defaultsFile;
196       "zoneminder/80-nixos.conf".source    = configFile;
197     };
199     networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
200       cfg.port
201       6802 # zmtrigger
202     ];
204     services = {
205       fcgiwrap.instances.zoneminder = lib.mkIf useNginx {
206         process.prefork = cfg.cameras;
207         process.user = user;
208         process.group = group;
209         socket = { inherit (config.services.nginx) user group; };
210       };
212       mysql = lib.mkIf cfg.database.createLocally {
213         enable = true;
214         package = lib.mkDefault pkgs.mariadb;
215         ensureDatabases = [ cfg.database.name ];
216         ensureUsers = [{
217           name = cfg.database.username;
218           ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
219         }];
220       };
222       nginx = lib.mkIf useNginx {
223         enable = true;
224         virtualHosts = {
225           ${cfg.hostname} = {
226             default = true;
227             root = "${pkg}/share/zoneminder/www";
228             listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
229             extraConfig = ''
230               index index.php;
232               location / {
233                 try_files $uri $uri/ /index.php?$args =404;
235                 rewrite ^/skins/.*/css/fonts/(.*)$ /fonts/$1 permanent;
237                 location ~ /api/(css|img|ico) {
238                   rewrite ^/api(.+)$ /api/app/webroot/$1 break;
239                   try_files $uri $uri/ =404;
240                 }
242                 location ~ \.(gif|ico|jpg|jpeg|png)$ {
243                   access_log off;
244                   expires 30d;
245                 }
247                 location /api {
248                   rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last;
249                 }
251                 location /cgi-bin {
252                   gzip off;
254                   include ${config.services.nginx.package}/conf/fastcgi_params;
255                   fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms};
256                   fastcgi_param HTTP_PROXY "";
257                   fastcgi_intercept_errors on;
259                   fastcgi_pass unix:${config.services.fcgiwrap.instances.zoneminder.socket.address};
260                 }
262                 location /cache/ {
263                   alias /var/cache/${dirName}/;
264                 }
266                 location ~ \.php$ {
267                   try_files $uri =404;
268                   fastcgi_index index.php;
270                   include ${config.services.nginx.package}/conf/fastcgi_params;
271                   fastcgi_param SCRIPT_FILENAME $request_filename;
272                   fastcgi_param HTTP_PROXY "";
274                   fastcgi_pass unix:${fpm.socket};
275                 }
276               }
277             '';
278           };
279         };
280       };
282       phpfpm = lib.mkIf useNginx {
283         pools.zoneminder = {
284           inherit user group;
285           phpPackage = pkgs.php.withExtensions (
286             { enabled, all }: enabled ++ [ all.apcu all.sysvsem ]);
287           phpOptions = ''
288             date.timezone = "${config.time.timeZone}"
289           '';
290           settings = lib.mapAttrs (name: lib.mkDefault) {
291             "listen.owner" = user;
292             "listen.group" = group;
293             "listen.mode" = "0660";
295             "pm" = "dynamic";
296             "pm.start_servers" = 1;
297             "pm.min_spare_servers" = 1;
298             "pm.max_spare_servers" = 2;
299             "pm.max_requests" = 500;
300             "pm.max_children" = 5;
301             "pm.status_path" = "/$pool-status";
302             "ping.path" = "/$pool-ping";
303           };
304         };
305       };
306     };
308     systemd.services = {
309       zoneminder = with pkgs; {
310         inherit (zoneminder.meta) description;
311         documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ];
312         path = [
313           coreutils
314           procps
315           psmisc
316         ];
317         after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service";
318         wantedBy = [ "multi-user.target" ];
319         restartTriggers = [ defaultsFile configFile ];
320         preStart = lib.optionalString useCustomDir ''
321           install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}}
322         '' + lib.optionalString cfg.database.createLocally ''
323           if ! test -e "/var/lib/${dirName}/db-created"; then
324             ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql
325             touch "/var/lib/${dirName}/db-created"
326           fi
328           ${zoneminder}/bin/zmupdate.pl -nointeractive
329           ${zoneminder}/bin/zmupdate.pl --nointeractive -f
331           # Update ZM's Nix store path in the configuration table. Do nothing if the config doesn't
332           # contain ZM's Nix store path.
333           ${config.services.mysql.package}/bin/mysql -u zoneminder zm << EOF
334             UPDATE Config
335               SET Value = REGEXP_REPLACE(Value, "^/nix/store/[^-/]+-zoneminder-[^/]+", "${pkgs.zoneminder}")
336               WHERE Name = "ZM_FONT_FILE_LOCATION";
337           EOF
338         '';
339         serviceConfig = {
340           User = user;
341           Group = group;
342           SupplementaryGroups = [ "video" ];
343           ExecStart  = "${zoneminder}/bin/zmpkg.pl start";
344           ExecStop   = "${zoneminder}/bin/zmpkg.pl stop";
345           ExecReload = "${zoneminder}/bin/zmpkg.pl restart";
346           PIDFile = "/run/${dirName}/zm.pid";
347           Type = "forking";
348           Restart = "on-failure";
349           RestartSec = "10s";
350           CacheDirectory = dirs cacheDirs;
351           RuntimeDirectory = dirName;
352           ReadWritePaths = lib.mkIf useCustomDir [ cfg.storageDir ];
353           StateDirectory = dirs (lib.optionals (!useCustomDir) libDirs);
354           LogsDirectory = dirName;
355           PrivateTmp = true;
356           ProtectSystem = "strict";
357           ProtectKernelTunables = true;
358           SystemCallArchitectures = "native";
359           NoNewPrivileges = true;
360         };
361       };
362     };
364     users.groups.${user} = {
365       gid = config.ids.gids.zoneminder;
366     };
368     users.users.${user} = {
369       uid = config.ids.uids.zoneminder;
370       group = user;
371       inherit home;
372       inherit (pkgs.zoneminder.meta) description;
373     };
374   };
376   meta.maintainers = [ ];