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