at 22.05-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 literalExpression 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 = literalExpression "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 = literalExpression "[ 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 = literalExpression '' 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 = 254 { setuid = true; 255 owner = "root"; 256 group = "root"; 257 source = "${pkgs.fping}/bin/fping"; 258 }; 259 }; 260 261 systemd.services.zabbix-server = { 262 description = "Zabbix Server"; 263 264 wantedBy = [ "multi-user.target" ]; 265 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 266 267 path = [ "/run/wrappers" ] ++ cfg.extraPackages; 268 preStart = '' 269 # pre 19.09 compatibility 270 if test -e "${runtimeDir}/db-created"; then 271 mv "${runtimeDir}/db-created" "${stateDir}/" 272 fi 273 '' + optionalString pgsqlLocal '' 274 if ! test -e "${stateDir}/db-created"; then 275 cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name} 276 cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name} 277 cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name} 278 touch "${stateDir}/db-created" 279 fi 280 '' + optionalString mysqlLocal '' 281 if ! test -e "${stateDir}/db-created"; then 282 cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name} 283 cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name} 284 cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name} 285 touch "${stateDir}/db-created" 286 fi 287 '' + optionalString (cfg.database.passwordFile != null) '' 288 # create a copy of the supplied password file in a format zabbix can consume 289 touch ${passwordFile} 290 chmod 0600 ${passwordFile} 291 echo -n "DBPassword = " > ${passwordFile} 292 cat ${cfg.database.passwordFile} >> ${passwordFile} 293 ''; 294 295 serviceConfig = { 296 ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}"; 297 Restart = "always"; 298 RestartSec = 2; 299 300 User = user; 301 Group = group; 302 RuntimeDirectory = "zabbix"; 303 StateDirectory = "zabbix"; 304 PrivateTmp = true; 305 }; 306 }; 307 308 systemd.services.httpd.after = 309 optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++ 310 optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service"; 311 312 }; 313 314}