1 { config, lib, options, pkgs, ... }:
4 cfg = config.services.zabbixServer;
5 opt = options.services.zabbixServer;
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-server-module-env";
21 paths = attrValues cfg.modules;
24 configFile = pkgs.writeText "zabbix_server.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.mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
34 (lib.mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
35 (lib.mkRemovedOptionModule [ "services" "zabbixServer" "extraConfig" ] "Use services.zabbixServer.settings instead.")
42 services.zabbixServer = {
43 enable = mkEnableOption "the Zabbix Server";
47 default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql;
48 defaultText = literalExpression "pkgs.zabbix.server-pgsql";
49 description = "The Zabbix package to use.";
52 extraPackages = mkOption {
53 type = types.listOf types.package;
54 default = with pkgs; [ nettools nmap traceroute ];
55 defaultText = literalExpression "[ nettools nmap traceroute ]";
57 Packages to be added to the Zabbix {env}`PATH`.
58 Typically used to add executables for scripts, but can be anything.
63 type = types.attrsOf types.package;
64 description = "A set of modules to load.";
66 example = literalExpression ''
68 "dummy.so" = pkgs.stdenv.mkDerivation {
69 name = "zabbix-dummy-module-''${cfg.package.version}";
70 src = cfg.package.src;
71 buildInputs = [ cfg.package ];
72 sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
84 type = types.enum [ "mysql" "pgsql" ];
87 description = "Database engine to use.";
92 default = "localhost";
93 description = "Database host address.";
98 default = if cfg.database.type == "mysql" then mysql.port else pgsql.settings.port;
99 defaultText = literalExpression ''
100 if config.${opt.database.type} == "mysql"
101 then config.${options.services.mysql.port}
102 else config.services.postgresql.settings.port
104 description = "Database host port.";
110 description = "Database name.";
116 description = "Database user.";
119 passwordFile = mkOption {
120 type = types.nullOr types.path;
122 example = "/run/keys/zabbix-dbpassword";
124 A file containing the password corresponding to
125 {option}`database.user`.
130 type = types.nullOr types.path;
132 example = "/run/postgresql";
133 description = "Path to the unix socket file to use for authentication.";
136 createLocally = mkOption {
139 description = "Whether to create a local database automatically.";
148 List of comma delimited IP addresses that the trapper should listen on.
149 Trapper will listen on all network interfaces if this parameter is missing.
157 Listen port for trapper.
162 openFirewall = mkOption {
166 Open ports in the firewall for the Zabbix Server.
170 settings = mkOption {
171 type = with types; attrsOf (oneOf [ int str (listOf str) ]);
174 Zabbix Server configuration. Refer to
175 <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server>
176 for details on supported values.
180 SSHKeyLocation = "/var/lib/zabbix/.ssh";
191 config = mkIf cfg.enable {
194 { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
195 message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
197 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
198 message = "a password cannot be specified if services.zabbixServer.database.createLocally is set to true";
202 services.zabbixServer.settings = mkMerge [
205 ListenIP = cfg.listen.ip;
206 ListenPort = cfg.listen.port;
207 # TODO: set to cfg.database.socket if database type is pgsql?
208 DBHost = optionalString (cfg.database.createLocally != true) cfg.database.host;
209 DBName = cfg.database.name;
210 DBUser = cfg.database.user;
211 PidFile = "${runtimeDir}/zabbix_server.pid";
212 SocketDir = runtimeDir;
213 FpingLocation = "/run/wrappers/bin/fping";
214 LoadModule = builtins.attrNames cfg.modules;
216 (mkIf (cfg.database.createLocally != true) { DBPort = cfg.database.port; })
217 (mkIf (cfg.database.passwordFile != null) { Include = [ "${passwordFile}" ]; })
218 (mkIf (mysqlLocal && cfg.database.socket != null) { DBSocket = cfg.database.socket; })
219 (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; })
222 networking.firewall = mkIf cfg.openFirewall {
223 allowedTCPPorts = [ cfg.listen.port ];
226 services.mysql = optionalAttrs mysqlLocal {
228 package = mkDefault pkgs.mariadb;
231 systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal ''
232 ( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;"
233 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"};"
234 echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';"
235 ) | ${config.services.mysql.package}/bin/mysql -N
238 services.postgresql = optionalAttrs pgsqlLocal {
240 ensureDatabases = [ cfg.database.name ];
242 { name = cfg.database.user;
243 ensureDBOwnership = true;
248 users.users.${user} = {
249 description = "Zabbix daemon user";
250 uid = config.ids.uids.zabbix;
254 users.groups.${group} = {
255 gid = config.ids.gids.zabbix;
258 security.wrappers = {
263 source = "${pkgs.fping}/bin/fping";
267 systemd.services.zabbix-server = {
268 description = "Zabbix Server";
270 wantedBy = [ "multi-user.target" ];
271 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
273 path = [ "/run/wrappers" ] ++ cfg.extraPackages;
275 # pre 19.09 compatibility
276 if test -e "${runtimeDir}/db-created"; then
277 mv "${runtimeDir}/db-created" "${stateDir}/"
279 '' + optionalString pgsqlLocal ''
280 if ! test -e "${stateDir}/db-created"; then
281 cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
282 cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
283 cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
284 touch "${stateDir}/db-created"
286 '' + optionalString mysqlLocal ''
287 if ! test -e "${stateDir}/db-created"; then
288 cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
289 cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
290 cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
291 touch "${stateDir}/db-created"
293 '' + optionalString (cfg.database.passwordFile != null) ''
294 # create a copy of the supplied password file in a format zabbix can consume
295 install -m 0600 <(echo "DBPassword = $(cat ${cfg.database.passwordFile})") ${passwordFile}
299 ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}";
305 RuntimeDirectory = "zabbix";
306 StateDirectory = "zabbix";
311 systemd.services.httpd.after =
312 optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++
313 optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service";