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