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