diff options
author | Peter Hoeg <peter@hoeg.com> | 2019-01-18 15:49:28 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-18 15:49:28 +0800 |
commit | eaa665e2435ab3dfc356d9799ba5938aabcd75d8 (patch) | |
tree | ea94ec10d117346835e6b299df97d9861a7d2baf | |
parent | 3ba707d228eb0646c8f367ca7e6282c6900162e2 (diff) | |
parent | 982354284d36a906400beec79b6894ba5d4ee4f2 (diff) | |
download | nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.tar nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.tar.gz nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.tar.bz2 nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.tar.lz nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.tar.xz nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.tar.zst nixlib-eaa665e2435ab3dfc356d9799ba5938aabcd75d8.zip |
Merge pull request #53495 from peterhoeg/p/zm
zoneminder: init at 1.32.3 and add NixOS module
-rw-r--r-- | nixos/modules/misc/ids.nix | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/misc/zoneminder.nix | 362 | ||||
-rw-r--r-- | pkgs/servers/zoneminder/default-to-http-1dot1.patch | 13 | ||||
-rw-r--r-- | pkgs/servers/zoneminder/default.nix | 192 | ||||
-rw-r--r-- | pkgs/top-level/all-packages.nix | 2 | ||||
-rw-r--r-- | pkgs/top-level/perl-packages.nix | 21 |
7 files changed, 593 insertions, 0 deletions
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index d9ba2efa0c8a..49f30dc85a0c 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -338,6 +338,7 @@ minetest = 311; rss2email = 312; cockroachdb = 313; + zoneminder = 314; # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! @@ -636,6 +637,7 @@ minetest = 311; rss2email = 312; cockroachdb = 313; + zoneminder = 314; # When adding a gid, make sure it doesn't match an existing # uid. Users and groups with the same name should have equal diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 1ae96f427ad8..a597485120c4 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 000000000000..a40e9e846137 --- /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 + </para><para> + 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. + </para> + <para> + + 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 ]; +} diff --git a/pkgs/servers/zoneminder/default-to-http-1dot1.patch b/pkgs/servers/zoneminder/default-to-http-1dot1.patch new file mode 100644 index 000000000000..abd7ffccbb5f --- /dev/null +++ b/pkgs/servers/zoneminder/default-to-http-1dot1.patch @@ -0,0 +1,13 @@ +diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +index fa7b86079..c9d3c6f6c 100644 +--- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in ++++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +@@ -877,7 +877,7 @@ our @options = ( + }, + { + name => 'ZM_HTTP_VERSION', +- default => '1.0', ++ default => '1.1', + description => 'The version of HTTP that ZoneMinder will use to connect', + help => q` + ZoneMinder can communicate with network cameras using either of diff --git a/pkgs/servers/zoneminder/default.nix b/pkgs/servers/zoneminder/default.nix new file mode 100644 index 000000000000..d9fd7d27ee5a --- /dev/null +++ b/pkgs/servers/zoneminder/default.nix @@ -0,0 +1,192 @@ +{ stdenv, lib, fetchFromGitHub, fetchurl, cmake, makeWrapper, pkgconfig +, curl, ffmpeg, glib, libjpeg, libselinux, libsepol, mp4v2, mysql, nettools, pcre, perl, perlPackages +, polkit, utillinuxMinimal, x264, zlib +, avahi, dbus, gettext, git, gnutar, gzip, bzip2, libiconv, openssl, python +, coreutils, procps, psmisc }: + +# NOTES: +# +# 1. ZM_CONFIG_DIR is set to $out/etc/zoneminder as the .conf file distributed +# by upstream contains defaults and is not supposed to be edited so it is fine +# to keep it read-only. +# +# 2. ZM_CONFIG_SUBDIR is where we place our configuration from the NixOS module +# but as the installer will try to put files there, we patch Config.pm after the +# install. +# +# 3. ZoneMinder is run with -T passed to the perl interpreter which makes perl +# ignore PERL5LIB. We therefore have to do the substitution into -I parameters +# ourselves which results in ugly wrappers. +# +# 4. The makefile for the perl modules needs patching to put things into the +# right place. That also means we have to not run "make install" for them. +# +# 5. In principal the various ZM_xx variables should be overridable from the +# config file but some of them are baked into the perl scripts, so we *have* to +# set them here instead of in the configuration in the NixOS module. +# +# 6. I am no PolicyKit expert but the .policy file looks fishy: +# a. The user needs to be known at build-time so we should probably throw +# upstream's policy file away and generate it from the NixOS module +# b. I *think* we may have to substitute the store paths with +# /run/current-system/sw/bin paths for it to work. +# +# 7. we manually fix up the perl paths in the scripts as fixupPhase will only +# handle pkexec and not perl if both are present. +# +# 8. There are several perl modules needed at runtime which are not checked when +# building so if a new version stops working, check if there is a missing +# dependency by running the failing component manually. +# +# 9. Parts of the web UI has a hardcoded /zm path so we create a symlink to work +# around it. + +let + modules = [ + { + path = "web/api/app/Plugin/Crud"; + src = fetchFromGitHub { + owner = "ZoneMinder"; + repo = "crud"; + rev = "3.1.0-zm"; + sha256 = "061avzyml7mla4hlx057fm8a9yjh6m6qslgyzn74cv5p2y7f463l"; + }; + } + { + path = "web/api/app/Plugin/CakePHP-Enum-Behavior"; + src = fetchFromGitHub { + owner = "ZoneMinder"; + repo = "CakePHP-Enum-Behavior"; + rev = "1.0-zm"; + sha256 = "0zsi6s8xymb183kx3szspbrwfjqcgga7786zqvydy6hc8c909cgx"; + }; + } + ]; + + addons = [ + { + path = "scripts/ZoneMinder/lib/ZoneMinder/Control/Xiaomi.pm"; + src = fetchurl { + url = "https://gist.githubusercontent.com/joshstrange/73a2f24dfaf5cd5b470024096ce2680f/raw/e964270c5cdbf95e5b7f214f7f0fc6113791530e/Xiaomi.pm"; + sha256 = "04n1ap8fx66xfl9q9rypj48pzbgzikq0gisfsfm8wdsmflarz43v"; + }; + } + ]; + + user = "zoneminder"; + dirName = "zoneminder"; + perlBin = "${perl}/bin/perl"; + +in stdenv.mkDerivation rec { + name = "zoneminder-${version}"; + version = "1.32.3"; + + src = fetchFromGitHub { + owner = "ZoneMinder"; + repo = "zoneminder"; + rev = version; + sha256 = "1sx2fn99861zh0gp8g53ynr1q6yfmymxamn82y54jqj6nv475njz"; + }; + + patches = [ + ./default-to-http-1dot1.patch + ]; + + postPatch = '' + ${lib.concatStringsSep "\n" (map (e: '' + rm -rf ${e.path}/* + cp -r ${e.src}/* ${e.path}/ + '') modules)} + + rm -rf web/api/lib/Cake/Test + + ${lib.concatStringsSep "\n" (map (e: '' + cp ${e.src} ${e.path} + '') addons)} + + for d in scripts/ZoneMinder onvif/{modules,proxy} ; do + substituteInPlace $d/CMakeLists.txt \ + --replace 'DESTDIR="''${CMAKE_CURRENT_BINARY_DIR}/output"' "PREFIX=$out INSTALLDIRS=site" + sed -i '/^install/d' $d/CMakeLists.txt + done + + substituteInPlace misc/CMakeLists.txt \ + --replace '"''${PC_POLKIT_PREFIX}/''${CMAKE_INSTALL_DATAROOTDIR}' "\"$out/share" + + for f in misc/*.policy.in \ + scripts/*.pl* \ + scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in ; do + substituteInPlace $f \ + --replace '/usr/bin/perl' '${perlBin}' \ + --replace '/bin:/usr/bin' "$out/bin:${lib.makeBinPath [ coreutils procps psmisc ]}" + done + + substituteInPlace scripts/zmdbbackup.in \ + --replace /usr/bin/mysqldump ${mysql}/bin/mysqldump + + for f in scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in \ + scripts/zmupdate.pl.in \ + src/zm_config.h.in \ + web/api/app/Config/bootstrap.php.in \ + web/includes/config.php.in ; do + substituteInPlace $f --replace @ZM_CONFIG_SUBDIR@ /etc/zoneminder + done + + for f in includes/Event.php views/image.php skins/classic/views/image-ffmpeg.php ; do + substituteInPlace web/$f \ + --replace "'ffmpeg " "'${ffmpeg}/bin/ffmpeg " + done + ''; + + buildInputs = [ + curl ffmpeg glib libjpeg libselinux libsepol mp4v2 mysql pcre perl polkit x264 zlib + utillinuxMinimal # for libmount + ] ++ (with perlPackages; [ + DateManip DBI DBDmysql LWP SysMmap + # runtime dependencies not checked at build-time + JSONMaybeXS LWPProtocolHttps NumberBytesHuman SysCPU SysMemInfo TimeDate + ]); + + nativeBuildInputs = [ cmake makeWrapper pkgconfig ]; + + enableParallelBuilding = true; + + cmakeFlags = [ + "-DWITH_SYSTEMD=ON" + "-DZM_LOGDIR=/var/log/${dirName}" + "-DZM_RUNDIR=/run/${dirName}" + "-DZM_SOCKDIR=/run/${dirName}" + "-DZM_TMPDIR=/tmp/${dirName}" + "-DZM_CONFIG_DIR=${placeholder "out"}/etc/zoneminder" + "-DZM_WEB_USER=${user}" + "-DZM_WEB_GROUP=${user}" + ]; + + passthru = { inherit dirName; }; + + postInstall = '' + PERL5LIB="$PERL5LIB''${PERL5LIB:+:}$out/${perl.libPrefix}" + + perlFlags="-wT" + for i in $(IFS=$'\n'; echo $PERL5LIB | tr ':' "\n" | sort -u); do + perlFlags="$perlFlags -I$i" + done + + for f in $out/bin/*.pl ; do + mv $f $out/libexec/ + makeWrapper ${perlBin} $f \ + --prefix PATH : $out/bin \ + --add-flags "$perlFlags $out/libexec/$(basename $f)" + done + + ln -s $out/share/zoneminder/www $out/share/zoneminder/www/zm + ''; + + meta = with stdenv.lib; { + description = "Video surveillance software system"; + homepage = https://zoneminder.com; + license = licenses.gpl3; + maintainers = with maintainers; [ peterhoeg ]; + platforms = platforms.unix; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 6c18c3158665..25c0457c247d 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -23100,6 +23100,8 @@ in callPackage ../applications/networking/znc/modules.nix { } ); + zoneminder = callPackage ../servers/zoneminder { }; + zsnes = pkgsi686Linux.callPackage ../misc/emulators/zsnes { }; xcpc = callPackage ../misc/emulators/xcpc { }; diff --git a/pkgs/top-level/perl-packages.nix b/pkgs/top-level/perl-packages.nix index 4885b2439003..fd0cc917954b 100644 --- a/pkgs/top-level/perl-packages.nix +++ b/pkgs/top-level/perl-packages.nix @@ -11497,6 +11497,14 @@ let }; }; + NumberBytesHuman = buildPerlPackage rec { + name = "Number-Bytes-Human-0.11"; + src = fetchurl { + url = "mirror://cpan/authors/id/F/FE/FERREIRA/${name}.tar.gz"; + sha256 = "0b3gprpbcrdwc2gqalpys5m2ngilh5injhww8y0gf3dln14rrisz"; + }; + }; + NumberCompare = buildPerlPackage rec { name = "Number-Compare-0.03"; src = fetchurl { @@ -14318,6 +14326,19 @@ let }; }; + SysMmap = buildPerlPackage rec { + name = "Sys-Mmap-0.19"; + src = fetchurl { + url = "mirror://cpan/authors/id/S/SW/SWALTERS/${name}.tar.gz"; + sha256 = "1yh0170xfw3z7n3lynffcb6axv7wi6zb46cx03crj1cvrhjmwa89"; + }; + meta = with stdenv.lib; { + description = "Use mmap to map in a file as a Perl variable"; + maintainers = with maintainers; [ peterhoeg ]; + license = with licenses; [ gpl2Plus ]; + }; + }; + SysMemInfo = buildPerlPackage rec { name = "Sys-MemInfo-0.99"; src = fetchurl { |