at 23.11-pre 11 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 cfg = config.services.zoneminder; 5 fpm = config.services.phpfpm.pools.zoneminder; 6 pkg = pkgs.zoneminder; 7 8 dirName = pkg.dirName; 9 10 user = "zoneminder"; 11 group = { 12 nginx = config.services.nginx.group; 13 none = user; 14 }.${cfg.webserver}; 15 16 useNginx = cfg.webserver == "nginx"; 17 18 defaultDir = "/var/lib/${user}"; 19 home = if useCustomDir then cfg.storageDir else defaultDir; 20 21 useCustomDir = cfg.storageDir != null; 22 23 zms = "/cgi-bin/zms"; 24 25 dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList; 26 27 cacheDirs = [ "swap" ]; 28 libDirs = [ "events" "exports" "images" "sounds" ]; 29 30 dirStanzas = baseDir: 31 lib.concatStringsSep "\n" (map (e: 32 "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}" 33 ) libDirs); 34 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} 44 45 # 02-multiserver.conf 46 ZM_SERVER_HOST= 47 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} 54 55 # Web 56 ZM_WEB_USER=${user} 57 ZM_WEB_GROUP=${group} 58 ''; 59 60 configFile = pkgs.writeText "80-nixos.conf" '' 61 # You can override defaults here 62 63 ${cfg.extraConfig} 64 ''; 65 66in { 67 options = { 68 services.zoneminder = with lib; { 69 enable = lib.mkEnableOption (lib.mdDoc '' 70 ZoneMinder 71 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 ''); 79 80 webserver = mkOption { 81 type = types.enum [ "nginx" "none" ]; 82 default = "nginx"; 83 description = lib.mdDoc '' 84 The webserver to configure for the PHP frontend. 85 86 Set it to `none` if you want to configure it yourself. PRs are welcome 87 for support for other web servers. 88 ''; 89 }; 90 91 hostname = mkOption { 92 type = types.str; 93 default = "localhost"; 94 description = lib.mdDoc '' 95 The hostname on which to listen. 96 ''; 97 }; 98 99 port = mkOption { 100 type = types.port; 101 default = 8095; 102 description = lib.mdDoc '' 103 The port on which to listen. 104 ''; 105 }; 106 107 openFirewall = mkOption { 108 type = types.bool; 109 default = false; 110 description = lib.mdDoc '' 111 Open the firewall port(s). 112 ''; 113 }; 114 115 database = { 116 createLocally = mkOption { 117 type = types.bool; 118 default = false; 119 description = lib.mdDoc '' 120 Create the database and database user locally. 121 ''; 122 }; 123 124 host = mkOption { 125 type = types.str; 126 default = "localhost"; 127 description = lib.mdDoc '' 128 Hostname hosting the database. 129 ''; 130 }; 131 132 name = mkOption { 133 type = types.str; 134 default = "zm"; 135 description = lib.mdDoc '' 136 Name of database. 137 ''; 138 }; 139 140 username = mkOption { 141 type = types.str; 142 default = "zmuser"; 143 description = lib.mdDoc '' 144 Username for accessing the database. 145 ''; 146 }; 147 148 password = mkOption { 149 type = types.str; 150 default = "zmpass"; 151 description = lib.mdDoc '' 152 Username for accessing the database. 153 Not used if `createLocally` is set. 154 ''; 155 }; 156 }; 157 158 cameras = mkOption { 159 type = types.int; 160 default = 1; 161 description = lib.mdDoc '' 162 Set this to the number of cameras you expect to support. 163 ''; 164 }; 165 166 storageDir = mkOption { 167 type = types.nullOr types.str; 168 default = null; 169 example = "/storage/tank"; 170 description = lib.mdDoc '' 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 }; 175 176 extraConfig = mkOption { 177 type = types.lines; 178 default = ""; 179 description = lib.mdDoc '' 180 Additional configuration added verbatim to the configuration file. 181 ''; 182 }; 183 }; 184 }; 185 186 config = lib.mkIf cfg.enable { 187 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 ]; 193 194 environment.etc = { 195 "zoneminder/60-defaults.conf".source = defaultsFile; 196 "zoneminder/80-nixos.conf".source = configFile; 197 }; 198 199 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ 200 cfg.port 201 6802 # zmtrigger 202 ]; 203 204 services = { 205 fcgiwrap = lib.mkIf useNginx { 206 enable = true; 207 preforkProcesses = cfg.cameras; 208 inherit user group; 209 }; 210 211 mysql = lib.mkIf cfg.database.createLocally { 212 enable = true; 213 package = lib.mkDefault pkgs.mariadb; 214 ensureDatabases = [ cfg.database.name ]; 215 ensureUsers = [{ 216 name = cfg.database.username; 217 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; 218 }]; 219 }; 220 221 nginx = lib.mkIf useNginx { 222 enable = true; 223 virtualHosts = { 224 ${cfg.hostname} = { 225 default = true; 226 root = "${pkg}/share/zoneminder/www"; 227 listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ]; 228 extraConfig = let 229 fcgi = config.services.fcgiwrap; 230 in '' 231 index index.php; 232 233 location / { 234 try_files $uri $uri/ /index.php?$args =404; 235 236 rewrite ^/skins/.*/css/fonts/(.*)$ /fonts/$1 permanent; 237 238 location ~ /api/(css|img|ico) { 239 rewrite ^/api(.+)$ /api/app/webroot/$1 break; 240 try_files $uri $uri/ =404; 241 } 242 243 location ~ \.(gif|ico|jpg|jpeg|png)$ { 244 access_log off; 245 expires 30d; 246 } 247 248 location /api { 249 rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last; 250 } 251 252 location /cgi-bin { 253 gzip off; 254 255 include ${config.services.nginx.package}/conf/fastcgi_params; 256 fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms}; 257 fastcgi_param HTTP_PROXY ""; 258 fastcgi_intercept_errors on; 259 260 fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress}; 261 } 262 263 location /cache/ { 264 alias /var/cache/${dirName}/; 265 } 266 267 location ~ \.php$ { 268 try_files $uri =404; 269 fastcgi_index index.php; 270 271 include ${config.services.nginx.package}/conf/fastcgi_params; 272 fastcgi_param SCRIPT_FILENAME $request_filename; 273 fastcgi_param HTTP_PROXY ""; 274 275 fastcgi_pass unix:${fpm.socket}; 276 } 277 } 278 ''; 279 }; 280 }; 281 }; 282 283 phpfpm = lib.mkIf useNginx { 284 pools.zoneminder = { 285 inherit user group; 286 phpPackage = pkgs.php.withExtensions ( 287 { enabled, all }: enabled ++ [ all.apcu all.sysvsem ]); 288 phpOptions = '' 289 date.timezone = "${config.time.timeZone}" 290 ''; 291 settings = lib.mapAttrs (name: lib.mkDefault) { 292 "listen.owner" = user; 293 "listen.group" = group; 294 "listen.mode" = "0660"; 295 296 "pm" = "dynamic"; 297 "pm.start_servers" = 1; 298 "pm.min_spare_servers" = 1; 299 "pm.max_spare_servers" = 2; 300 "pm.max_requests" = 500; 301 "pm.max_children" = 5; 302 "pm.status_path" = "/$pool-status"; 303 "ping.path" = "/$pool-ping"; 304 }; 305 }; 306 }; 307 }; 308 309 systemd.services = { 310 zoneminder = with pkgs; { 311 inherit (zoneminder.meta) description; 312 documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ]; 313 path = [ 314 coreutils 315 procps 316 psmisc 317 ]; 318 after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service"; 319 wantedBy = [ "multi-user.target" ]; 320 restartTriggers = [ defaultsFile configFile ]; 321 preStart = lib.optionalString useCustomDir '' 322 install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}} 323 '' + lib.optionalString cfg.database.createLocally '' 324 if ! test -e "/var/lib/${dirName}/db-created"; then 325 ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql 326 touch "/var/lib/${dirName}/db-created" 327 fi 328 329 ${zoneminder}/bin/zmupdate.pl -nointeractive 330 ${zoneminder}/bin/zmupdate.pl --nointeractive -f 331 332 # Update ZM's Nix store path in the configuration table. Do nothing if the config doesn't 333 # contain ZM's Nix store path. 334 ${config.services.mysql.package}/bin/mysql -u zoneminder zm << EOF 335 UPDATE Config 336 SET Value = REGEXP_REPLACE(Value, "^/nix/store/[^-/]+-zoneminder-[^/]+", "${pkgs.zoneminder}") 337 WHERE Name = "ZM_FONT_FILE_LOCATION"; 338 EOF 339 ''; 340 serviceConfig = { 341 User = user; 342 Group = group; 343 SupplementaryGroups = [ "video" ]; 344 ExecStart = "${zoneminder}/bin/zmpkg.pl start"; 345 ExecStop = "${zoneminder}/bin/zmpkg.pl stop"; 346 ExecReload = "${zoneminder}/bin/zmpkg.pl restart"; 347 PIDFile = "/run/${dirName}/zm.pid"; 348 Type = "forking"; 349 Restart = "on-failure"; 350 RestartSec = "10s"; 351 CacheDirectory = dirs cacheDirs; 352 RuntimeDirectory = dirName; 353 ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ]; 354 StateDirectory = dirs (if useCustomDir then [] else libDirs); 355 LogsDirectory = dirName; 356 PrivateTmp = true; 357 ProtectSystem = "strict"; 358 ProtectKernelTunables = true; 359 SystemCallArchitectures = "native"; 360 NoNewPrivileges = true; 361 }; 362 }; 363 }; 364 365 users.groups.${user} = { 366 gid = config.ids.gids.zoneminder; 367 }; 368 369 users.users.${user} = { 370 uid = config.ids.uids.zoneminder; 371 group = user; 372 inherit home; 373 inherit (pkgs.zoneminder.meta) description; 374 }; 375 }; 376 377 meta.maintainers = with lib.maintainers; [ ]; 378}