1 { config, lib, options, pkgs, ... }:
4 cfg = config.services.zabbixProxy;
5 opt = options.services.zabbixProxy;
6 pgsql = config.services.postgresql;
7 mysql = config.services.mysql;
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;
15 runtimeDir = "/run/zabbix";
16 stateDir = "/var/lib/zabbix";
17 passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
19 moduleEnv = pkgs.symlinkJoin {
20 name = "zabbix-proxy-module-env";
21 paths = attrValues cfg.modules;
24 configFile = pkgs.writeText "zabbix_proxy.conf" (toKeyValue { listsAsDuplicateKeys = true; } cfg.settings);
26 mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
27 pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
33 (lib.mkRemovedOptionModule [ "services" "zabbixProxy" "extraConfig" ] "Use services.zabbixProxy.settings instead.")
40 services.zabbixProxy = {
41 enable = mkEnableOption "the Zabbix Proxy";
46 The IP address or hostname of the Zabbix server to connect to.
53 if cfg.database.type == "mysql" then pkgs.zabbix.proxy-mysql
54 else if cfg.database.type == "pgsql" then pkgs.zabbix.proxy-pgsql
55 else pkgs.zabbix.proxy-sqlite;
56 defaultText = literalExpression "pkgs.zabbix.proxy-pgsql";
57 description = "The Zabbix package to use.";
60 extraPackages = mkOption {
61 type = types.listOf types.package;
62 default = with pkgs; [ nettools nmap traceroute ];
63 defaultText = literalExpression "[ nettools nmap traceroute ]";
65 Packages to be added to the Zabbix {env}`PATH`.
66 Typically used to add executables for scripts, but can be anything.
71 type = types.attrsOf types.package;
72 description = "A set of modules to load.";
74 example = literalExpression ''
76 "dummy.so" = pkgs.stdenv.mkDerivation {
77 name = "zabbix-dummy-module-''${cfg.package.version}";
78 src = cfg.package.src;
79 buildInputs = [ cfg.package ];
80 sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
92 type = types.enum [ "mysql" "pgsql" "sqlite" ];
95 description = "Database engine to use.";
100 default = "localhost";
101 description = "Database host address.";
106 default = if cfg.database.type == "mysql" then mysql.port else pgsql.settings.port;
107 defaultText = literalExpression ''
108 if config.${opt.database.type} == "mysql"
109 then config.${options.services.mysql.port}
110 else config.services.postgresql.settings.port
112 description = "Database host port.";
117 default = if cfg.database.type == "sqlite" then "${stateDir}/zabbix.db" else "zabbix";
118 defaultText = literalExpression "zabbix";
119 description = "Database name.";
125 description = "Database user.";
128 passwordFile = mkOption {
129 type = types.nullOr types.path;
131 example = "/run/keys/zabbix-dbpassword";
133 A file containing the password corresponding to
134 {option}`database.user`.
139 type = types.nullOr types.path;
141 example = "/run/postgresql";
142 description = "Path to the unix socket file to use for authentication.";
145 createLocally = mkOption {
148 description = "Whether to create a local database automatically.";
157 List of comma delimited IP addresses that the trapper should listen on.
158 Trapper will listen on all network interfaces if this parameter is missing.
166 Listen port for trapper.
171 openFirewall = mkOption {
175 Open ports in the firewall for the Zabbix Proxy.
179 settings = mkOption {
180 type = with types; attrsOf (oneOf [ int str (listOf str) ]);
183 Zabbix Proxy configuration. Refer to
184 <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy>
185 for details on supported values.
189 SSHKeyLocation = "/var/lib/zabbix/.ssh";
200 config = mkIf cfg.enable {
203 { assertion = !config.services.zabbixServer.enable;
204 message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
206 { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
207 message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
209 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
210 message = "a password cannot be specified if services.zabbixProxy.database.createLocally is set to true";
214 services.zabbixProxy.settings = mkMerge [
217 ListenIP = cfg.listen.ip;
218 ListenPort = cfg.listen.port;
220 # TODO: set to cfg.database.socket if database type is pgsql?
221 DBHost = optionalString (cfg.database.createLocally != true) cfg.database.host;
222 DBName = cfg.database.name;
223 DBUser = cfg.database.user;
224 SocketDir = runtimeDir;
225 FpingLocation = "/run/wrappers/bin/fping";
226 LoadModule = builtins.attrNames cfg.modules;
228 (mkIf (cfg.database.createLocally != true) { DBPort = cfg.database.port; })
229 (mkIf (cfg.database.passwordFile != null) { Include = [ "${passwordFile}" ]; })
230 (mkIf (mysqlLocal && cfg.database.socket != null) { DBSocket = cfg.database.socket; })
231 (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; })
234 networking.firewall = mkIf cfg.openFirewall {
235 allowedTCPPorts = [ cfg.listen.port ];
238 services.mysql = optionalAttrs mysqlLocal {
240 package = mkDefault pkgs.mariadb;
243 systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal ''
244 ( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;"
245 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"};"
246 echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';"
247 ) | ${config.services.mysql.package}/bin/mysql -N
250 services.postgresql = optionalAttrs pgsqlLocal {
252 ensureDatabases = [ cfg.database.name ];
254 { name = cfg.database.user;
255 ensureDBOwnership = true;
260 users.users.${user} = {
261 description = "Zabbix daemon user";
262 uid = config.ids.uids.zabbix;
266 users.groups.${group} = {
267 gid = config.ids.gids.zabbix;
270 security.wrappers = {
275 source = "${pkgs.fping}/bin/fping";
279 systemd.services.zabbix-proxy = {
280 description = "Zabbix Proxy";
282 wantedBy = [ "multi-user.target" ];
283 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
285 path = [ "/run/wrappers" ] ++ cfg.extraPackages;
286 preStart = optionalString pgsqlLocal ''
287 if ! test -e "${stateDir}/db-created"; then
288 cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
289 touch "${stateDir}/db-created"
291 '' + optionalString mysqlLocal ''
292 if ! test -e "${stateDir}/db-created"; then
293 cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
294 touch "${stateDir}/db-created"
296 '' + optionalString (cfg.database.type == "sqlite") ''
297 if ! test -e "${cfg.database.name}"; then
298 ${pkgs.sqlite}/bin/sqlite3 "${cfg.database.name}" < ${cfg.package}/share/zabbix/database/sqlite3/schema.sql
300 '' + optionalString (cfg.database.passwordFile != null) ''
301 # create a copy of the supplied password file in a format zabbix can consume
302 install -m 0600 <(echo "DBPassword = $(cat ${cfg.database.passwordFile})") ${passwordFile}
306 ExecStart = "@${cfg.package}/sbin/zabbix_proxy zabbix_proxy -f --config ${configFile}";
312 RuntimeDirectory = "zabbix";
313 StateDirectory = "zabbix";