at 21.11-pre 11 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 cfg = config.services.zabbixServer; 5 pgsql = config.services.postgresql; 6 mysql = config.services.mysql; 7 8 inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption; 9 inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types; 10 inherit (lib.generators) toKeyValue; 11 12 user = "zabbix"; 13 group = "zabbix"; 14 runtimeDir = "/run/zabbix"; 15 stateDir = "/var/lib/zabbix"; 16 passwordFile = "${runtimeDir}/zabbix-dbpassword.conf"; 17 18 moduleEnv = pkgs.symlinkJoin { 19 name = "zabbix-server-module-env"; 20 paths = attrValues cfg.modules; 21 }; 22 23 configFile = pkgs.writeText "zabbix_server.conf" (toKeyValue { listsAsDuplicateKeys = true; } cfg.settings); 24 25 mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; 26 pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; 27 28in 29 30{ 31 imports = [ 32 (lib.mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ]) 33 (lib.mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.") 34 (lib.mkRemovedOptionModule [ "services" "zabbixServer" "extraConfig" ] "Use services.zabbixServer.settings instead.") 35 ]; 36 37 # interface 38 39 options = { 40 41 services.zabbixServer = { 42 enable = mkEnableOption "the Zabbix Server"; 43 44 package = mkOption { 45 type = types.package; 46 default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql; 47 defaultText = "pkgs.zabbix.server-pgsql"; 48 description = "The Zabbix package to use."; 49 }; 50 51 extraPackages = mkOption { 52 type = types.listOf types.package; 53 default = with pkgs; [ nettools nmap traceroute ]; 54 defaultText = "[ nettools nmap traceroute ]"; 55 description = '' 56 Packages to be added to the Zabbix <envar>PATH</envar>. 57 Typically used to add executables for scripts, but can be anything. 58 ''; 59 }; 60 61 modules = mkOption { 62 type = types.attrsOf types.package; 63 description = "A set of modules to load."; 64 default = {}; 65 example = literalExample '' 66 { 67 "dummy.so" = pkgs.stdenv.mkDerivation { 68 name = "zabbix-dummy-module-''${cfg.package.version}"; 69 src = cfg.package.src; 70 buildInputs = [ cfg.package ]; 71 sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy"; 72 installPhase = ''' 73 mkdir -p $out/lib 74 cp dummy.so $out/lib/ 75 '''; 76 }; 77 } 78 ''; 79 }; 80 81 database = { 82 type = mkOption { 83 type = types.enum [ "mysql" "pgsql" ]; 84 example = "mysql"; 85 default = "pgsql"; 86 description = "Database engine to use."; 87 }; 88 89 host = mkOption { 90 type = types.str; 91 default = "localhost"; 92 description = "Database host address."; 93 }; 94 95 port = mkOption { 96 type = types.int; 97 default = if cfg.database.type == "mysql" then mysql.port else pgsql.port; 98 description = "Database host port."; 99 }; 100 101 name = mkOption { 102 type = types.str; 103 default = "zabbix"; 104 description = "Database name."; 105 }; 106 107 user = mkOption { 108 type = types.str; 109 default = "zabbix"; 110 description = "Database user."; 111 }; 112 113 passwordFile = mkOption { 114 type = types.nullOr types.path; 115 default = null; 116 example = "/run/keys/zabbix-dbpassword"; 117 description = '' 118 A file containing the password corresponding to 119 <option>database.user</option>. 120 ''; 121 }; 122 123 socket = mkOption { 124 type = types.nullOr types.path; 125 default = null; 126 example = "/run/postgresql"; 127 description = "Path to the unix socket file to use for authentication."; 128 }; 129 130 createLocally = mkOption { 131 type = types.bool; 132 default = true; 133 description = "Whether to create a local database automatically."; 134 }; 135 }; 136 137 listen = { 138 ip = mkOption { 139 type = types.str; 140 default = "0.0.0.0"; 141 description = '' 142 List of comma delimited IP addresses that the trapper should listen on. 143 Trapper will listen on all network interfaces if this parameter is missing. 144 ''; 145 }; 146 147 port = mkOption { 148 type = types.port; 149 default = 10051; 150 description = '' 151 Listen port for trapper. 152 ''; 153 }; 154 }; 155 156 openFirewall = mkOption { 157 type = types.bool; 158 default = false; 159 description = '' 160 Open ports in the firewall for the Zabbix Server. 161 ''; 162 }; 163 164 settings = mkOption { 165 type = with types; attrsOf (oneOf [ int str (listOf str) ]); 166 default = {}; 167 description = '' 168 Zabbix Server configuration. Refer to 169 <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server"/> 170 for details on supported values. 171 ''; 172 example = { 173 CacheSize = "1G"; 174 SSHKeyLocation = "/var/lib/zabbix/.ssh"; 175 StartPingers = 32; 176 }; 177 }; 178 179 }; 180 181 }; 182 183 # implementation 184 185 config = mkIf cfg.enable { 186 187 assertions = [ 188 { assertion = cfg.database.createLocally -> cfg.database.user == user; 189 message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true"; 190 } 191 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; 192 message = "a password cannot be specified if services.zabbixServer.database.createLocally is set to true"; 193 } 194 ]; 195 196 services.zabbixServer.settings = mkMerge [ 197 { 198 LogType = "console"; 199 ListenIP = cfg.listen.ip; 200 ListenPort = cfg.listen.port; 201 # TODO: set to cfg.database.socket if database type is pgsql? 202 DBHost = optionalString (cfg.database.createLocally != true) cfg.database.host; 203 DBName = cfg.database.name; 204 DBUser = cfg.database.user; 205 PidFile = "${runtimeDir}/zabbix_server.pid"; 206 SocketDir = runtimeDir; 207 FpingLocation = "/run/wrappers/bin/fping"; 208 LoadModule = builtins.attrNames cfg.modules; 209 } 210 (mkIf (cfg.database.createLocally != true) { DBPort = cfg.database.port; }) 211 (mkIf (cfg.database.passwordFile != null) { Include = [ "${passwordFile}" ]; }) 212 (mkIf (mysqlLocal && cfg.database.socket != null) { DBSocket = cfg.database.socket; }) 213 (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; }) 214 ]; 215 216 networking.firewall = mkIf cfg.openFirewall { 217 allowedTCPPorts = [ cfg.listen.port ]; 218 }; 219 220 services.mysql = optionalAttrs mysqlLocal { 221 enable = true; 222 package = mkDefault pkgs.mariadb; 223 }; 224 225 systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal '' 226 ( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;" 227 echo "CREATE USER IF NOT EXISTS '${cfg.database.user}'@'localhost' IDENTIFIED WITH ${if (getName config.services.mysql.package == getName pkgs.mariadb) then "unix_socket" else "auth_socket"};" 228 echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';" 229 ) | ${config.services.mysql.package}/bin/mysql -N 230 ''); 231 232 services.postgresql = optionalAttrs pgsqlLocal { 233 enable = true; 234 ensureDatabases = [ cfg.database.name ]; 235 ensureUsers = [ 236 { name = cfg.database.user; 237 ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; 238 } 239 ]; 240 }; 241 242 users.users.${user} = { 243 description = "Zabbix daemon user"; 244 uid = config.ids.uids.zabbix; 245 inherit group; 246 }; 247 248 users.groups.${group} = { 249 gid = config.ids.gids.zabbix; 250 }; 251 252 security.wrappers = { 253 fping.source = "${pkgs.fping}/bin/fping"; 254 }; 255 256 systemd.services.zabbix-server = { 257 description = "Zabbix Server"; 258 259 wantedBy = [ "multi-user.target" ]; 260 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 261 262 path = [ "/run/wrappers" ] ++ cfg.extraPackages; 263 preStart = '' 264 # pre 19.09 compatibility 265 if test -e "${runtimeDir}/db-created"; then 266 mv "${runtimeDir}/db-created" "${stateDir}/" 267 fi 268 '' + optionalString pgsqlLocal '' 269 if ! test -e "${stateDir}/db-created"; then 270 cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name} 271 cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name} 272 cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name} 273 touch "${stateDir}/db-created" 274 fi 275 '' + optionalString mysqlLocal '' 276 if ! test -e "${stateDir}/db-created"; then 277 cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name} 278 cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name} 279 cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name} 280 touch "${stateDir}/db-created" 281 fi 282 '' + optionalString (cfg.database.passwordFile != null) '' 283 # create a copy of the supplied password file in a format zabbix can consume 284 touch ${passwordFile} 285 chmod 0600 ${passwordFile} 286 echo -n "DBPassword = " > ${passwordFile} 287 cat ${cfg.database.passwordFile} >> ${passwordFile} 288 ''; 289 290 serviceConfig = { 291 ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}"; 292 Restart = "always"; 293 RestartSec = 2; 294 295 User = user; 296 Group = group; 297 RuntimeDirectory = "zabbix"; 298 StateDirectory = "zabbix"; 299 PrivateTmp = true; 300 }; 301 }; 302 303 systemd.services.httpd.after = 304 optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++ 305 optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service"; 306 307 }; 308 309}