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