From 982354284d36a906400beec79b6894ba5d4ee4f2 Mon Sep 17 00:00:00 2001 From: Peter Hoeg Date: Thu, 3 Jan 2019 22:16:44 +0800 Subject: [PATCH] zoneminder (nixos): add basic module --- nixos/modules/module-list.nix | 1 + nixos/modules/services/misc/zoneminder.nix | 362 +++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 nixos/modules/services/misc/zoneminder.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4a392b6f5c9..8ae3c7dca00 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -432,6 +432,7 @@ ./services/misc/uhub.nix ./services/misc/weechat.nix ./services/misc/xmr-stak.nix + ./services/misc/zoneminder.nix ./services/misc/zookeeper.nix ./services/monitoring/alerta.nix ./services/monitoring/apcupsd.nix diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix new file mode 100644 index 00000000000..a40e9e84613 --- /dev/null +++ b/nixos/modules/services/misc/zoneminder.nix @@ -0,0 +1,362 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.zoneminder; + pkg = pkgs.zoneminder; + + dirName = pkg.dirName; + + user = "zoneminder"; + group = { + nginx = config.services.nginx.group; + none = user; + }."${cfg.webserver}"; + + useNginx = cfg.webserver == "nginx"; + + defaultDir = "/var/lib/${user}"; + home = if useCustomDir then cfg.storageDir else defaultDir; + + useCustomDir = !(builtins.isNull cfg.storageDir); + + socket = "/run/phpfpm/${dirName}.sock"; + + zms = "/cgi-bin/zms"; + + dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList; + + cacheDirs = [ "swap" ]; + libDirs = [ "events" "exports" "images" "sounds" ]; + + dirStanzas = baseDir: + lib.concatStringsSep "\n" (map (e: + "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}" + ) libDirs); + + defaultsFile = pkgs.writeText "60-defaults.conf" '' + # 01-system-paths.conf + ${dirStanzas home} + ZM_PATH_ARP=${lib.getBin pkgs.nettools}/bin/arp + ZM_PATH_LOGS=/var/log/${dirName} + ZM_PATH_MAP=/dev/shm + ZM_PATH_SOCKS=/run/${dirName} + ZM_PATH_SWAP=/var/cache/${dirName}/swap + ZM_PATH_ZMS=${zms} + + # 02-multiserver.conf + ZM_SERVER_HOST= + + # Database + ZM_DB_TYPE=mysql + ZM_DB_HOST=${cfg.database.host} + ZM_DB_NAME=${cfg.database.name} + ZM_DB_USER=${cfg.database.username} + ZM_DB_PASS=${cfg.database.password} + + # Web + ZM_WEB_USER=${user} + ZM_WEB_GROUP=${group} + ''; + + configFile = pkgs.writeText "80-nixos.conf" '' + # You can override defaults here + + ${cfg.extraConfig} + ''; + + phpExtensions = with pkgs.phpPackages; [ + { pkg = apcu; name = "apcu"; } + ]; + +in { + options = { + services.zoneminder = with lib; { + enable = lib.mkEnableOption '' + ZoneMinder + + If you intend to run the database locally, you should set + `config.services.zoneminder.database.createLocally` to true. Otherwise, + when set to `false` (the default), you will have to create the database + and database user as well as populate the database yourself. + ''; + + webserver = mkOption { + type = types.enum [ "nginx" "none" ]; + default = "nginx"; + description = '' + The webserver to configure for the PHP frontend. + + + + Set it to `none` if you want to configure it yourself. PRs are welcome + for support for other web servers. + ''; + }; + + hostname = mkOption { + type = types.str; + default = "localhost"; + description = '' + The hostname on which to listen. + ''; + }; + + port = mkOption { + type = types.int; + default = 8095; + description = '' + The port on which to listen. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open the firewall port(s). + ''; + }; + + database = { + createLocally = mkOption { + type = types.bool; + default = false; + description = '' + Create the database and database user locally. + ''; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + Hostname hosting the database. + ''; + }; + + name = mkOption { + type = types.str; + default = "zm"; + description = '' + Name of database. + ''; + }; + + username = mkOption { + type = types.str; + default = "zmuser"; + description = '' + Username for accessing the database. + ''; + }; + + password = mkOption { + type = types.str; + default = "zmpass"; + description = '' + Username for accessing the database. + ''; + }; + }; + + cameras = mkOption { + type = types.int; + default = 1; + description = '' + Set this to the number of cameras you expect to support. + ''; + }; + + storageDir = mkOption { + type = types.nullOr types.str; + default = null; + example = "/storage/tank"; + description = '' + ZoneMinder can generate quite a lot of data, so in case you don't want + to use the default ${home}, you can override the path here. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional configuration added verbatim to the configuration file. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + + environment.etc = { + "zoneminder/60-defaults.conf".source = defaultsFile; + "zoneminder/80-nixos.conf".source = configFile; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; + + services = { + fcgiwrap = lib.mkIf useNginx { + enable = true; + preforkProcesses = cfg.cameras; + inherit user group; + }; + + mysql = lib.mkIf cfg.database.createLocally { + ensureDatabases = [ cfg.database.name ]; + ensureUsers = { + name = cfg.database.username; + ensurePermissions = [ + { "${cfg.database.name}.*" = "ALL PRIVILEGES"; } + ]; + initialDatabases = [ + { inherit (cfg.database) name; schema = "${pkg}/share/zoneminder/db/zm_create.sql"; } + ]; + }; + }; + + nginx = lib.mkIf useNginx { + enable = true; + virtualHosts = { + "${cfg.hostname}" = { + default = true; + root = "${pkg}/share/zoneminder/www"; + listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ]; + extraConfig = let + fcgi = config.services.fcgiwrap; + in '' + index index.php; + + location / { + try_files $uri $uri/ /index.php?$args =404; + + location ~ /api/(css|img|ico) { + rewrite ^/api(.+)$ /api/app/webroot/$1 break; + try_files $uri $uri/ =404; + } + + location ~ \.(gif|ico|jpg|jpeg|png)$ { + access_log off; + expires 30d; + } + + location /api { + rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last; + } + + location /cgi-bin { + gzip off; + + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms}; + fastcgi_param HTTP_PROXY ""; + fastcgi_intercept_errors on; + + fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress}; + } + + location /cache { + alias /var/cache/${dirName}; + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_index index.php; + + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_param HTTP_PROXY ""; + + fastcgi_pass unix:${socket}; + } + } + ''; + }; + }; + }; + + phpfpm = lib.mkIf useNginx { + phpOptions = '' + date.timezone = "${config.time.timeZone}" + + ${lib.concatStringsSep "\n" (map (e: + "extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)} + ''; + pools.zoneminder = { + listen = socket; + extraConfig = '' + user = ${user} + group = ${group} + + listen.owner = ${user} + listen.group = ${group} + listen.mode = 0660 + + pm = dynamic + pm.start_servers = 1 + pm.min_spare_servers = 1 + pm.max_spare_servers = 2 + pm.max_requests = 500 + pm.max_children = 5 + pm.status_path = /$pool-status + ping.path = /$pool-ping + ''; + }; + }; + }; + + systemd.services = { + zoneminder = with pkgs; rec { + inherit (zoneminder.meta) description; + documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ]; + path = [ + coreutils + procps + psmisc + ]; + after = [ "mysql.service" "nginx.service" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ defaultsFile configFile ]; + preStart = lib.mkIf useCustomDir '' + install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}} + ''; + serviceConfig = { + User = user; + Group = group; + SupplementaryGroups = [ "video" ]; + ExecStart = "${zoneminder}/bin/zmpkg.pl start"; + ExecStop = "${zoneminder}/bin/zmpkg.pl stop"; + ExecReload = "${zoneminder}/bin/zmpkg.pl restart"; + PIDFile = "/run/${dirName}/zm.pid"; + Type = "forking"; + Restart = "on-failure"; + RestartSec = "10s"; + CacheDirectory = dirs cacheDirs; + RuntimeDirectory = dirName; + ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ]; + StateDirectory = dirs (if useCustomDir then [] else libDirs); + LogsDirectory = dirName; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectKernelTunables = true; + SystemCallArchitectures = "native"; + NoNewPrivileges = true; + }; + }; + }; + + users.groups."${user}" = { + gid = config.ids.gids.zoneminder; + }; + + users.users."${user}" = { + uid = config.ids.uids.zoneminder; + group = user; + inherit home; + inherit (pkgs.zoneminder.meta) description; + }; + }; + + meta.maintainers = with lib.maintainers; [ peterhoeg ]; +}