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