grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / monitoring / zabbix-server.nix
blob3c6f60b9d722e62405e20f67e132a2ea225182e7
1 { config, lib, options, pkgs, ... }:
3 let
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;
13   user = "zabbix";
14   group = "zabbix";
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;
22   };
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";
32   imports = [
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.")
36   ];
38   # interface
40   options = {
42     services.zabbixServer = {
43       enable = mkEnableOption "the Zabbix Server";
45       package = mkOption {
46         type = types.package;
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.";
50       };
52       extraPackages = mkOption {
53         type = types.listOf types.package;
54         default = with pkgs; [ nettools nmap traceroute ];
55         defaultText = literalExpression "[ nettools nmap traceroute ]";
56         description = ''
57           Packages to be added to the Zabbix {env}`PATH`.
58           Typically used to add executables for scripts, but can be anything.
59         '';
60       };
62       modules = mkOption {
63         type = types.attrsOf types.package;
64         description = "A set of modules to load.";
65         default = {};
66         example = literalExpression ''
67           {
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";
73               installPhase = '''
74                 mkdir -p $out/lib
75                 cp dummy.so $out/lib/
76               ''';
77             };
78           }
79         '';
80       };
82       database = {
83         type = mkOption {
84           type = types.enum [ "mysql" "pgsql" ];
85           example = "mysql";
86           default = "pgsql";
87           description = "Database engine to use.";
88         };
90         host = mkOption {
91           type = types.str;
92           default = "localhost";
93           description = "Database host address.";
94         };
96         port = mkOption {
97           type = types.port;
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
103           '';
104           description = "Database host port.";
105         };
107         name = mkOption {
108           type = types.str;
109           default = "zabbix";
110           description = "Database name.";
111         };
113         user = mkOption {
114           type = types.str;
115           default = "zabbix";
116           description = "Database user.";
117         };
119         passwordFile = mkOption {
120           type = types.nullOr types.path;
121           default = null;
122           example = "/run/keys/zabbix-dbpassword";
123           description = ''
124             A file containing the password corresponding to
125             {option}`database.user`.
126           '';
127         };
129         socket = mkOption {
130           type = types.nullOr types.path;
131           default = null;
132           example = "/run/postgresql";
133           description = "Path to the unix socket file to use for authentication.";
134         };
136         createLocally = mkOption {
137           type = types.bool;
138           default = true;
139           description = "Whether to create a local database automatically.";
140         };
141       };
143       listen = {
144         ip = mkOption {
145           type = types.str;
146           default = "0.0.0.0";
147           description = ''
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.
150           '';
151         };
153         port = mkOption {
154           type = types.port;
155           default = 10051;
156           description = ''
157             Listen port for trapper.
158           '';
159         };
160       };
162       openFirewall = mkOption {
163         type = types.bool;
164         default = false;
165         description = ''
166           Open ports in the firewall for the Zabbix Server.
167         '';
168       };
170       settings = mkOption {
171         type = with types; attrsOf (oneOf [ int str (listOf str) ]);
172         default = {};
173         description = ''
174           Zabbix Server configuration. Refer to
175           <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server>
176           for details on supported values.
177         '';
178         example = {
179           CacheSize = "1G";
180           SSHKeyLocation = "/var/lib/zabbix/.ssh";
181           StartPingers = 32;
182         };
183       };
185     };
187   };
189   # implementation
191   config = mkIf cfg.enable {
193     assertions = [
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";
196       }
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";
199       }
200     ];
202     services.zabbixServer.settings = mkMerge [
203       {
204         LogType = "console";
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;
215       }
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"; })
220     ];
222     networking.firewall = mkIf cfg.openFirewall {
223       allowedTCPPorts = [ cfg.listen.port ];
224     };
226     services.mysql = optionalAttrs mysqlLocal {
227       enable = true;
228       package = mkDefault pkgs.mariadb;
229     };
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
236     '');
238     services.postgresql = optionalAttrs pgsqlLocal {
239       enable = true;
240       ensureDatabases = [ cfg.database.name ];
241       ensureUsers = [
242         { name = cfg.database.user;
243           ensureDBOwnership = true;
244         }
245       ];
246     };
248     users.users.${user} = {
249       description = "Zabbix daemon user";
250       uid = config.ids.uids.zabbix;
251       inherit group;
252     };
254     users.groups.${group} = {
255       gid = config.ids.gids.zabbix;
256     };
258     security.wrappers = {
259       fping =
260         { setuid = true;
261           owner = "root";
262           group = "root";
263           source = "${pkgs.fping}/bin/fping";
264         };
265     };
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;
274       preStart = ''
275         # pre 19.09 compatibility
276         if test -e "${runtimeDir}/db-created"; then
277           mv "${runtimeDir}/db-created" "${stateDir}/"
278         fi
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"
285         fi
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"
292         fi
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}
296       '';
298       serviceConfig = {
299         ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}";
300         Restart = "always";
301         RestartSec = 2;
303         User = user;
304         Group = group;
305         RuntimeDirectory = "zabbix";
306         StateDirectory = "zabbix";
307         PrivateTmp = true;
308       };
309     };
311     systemd.services.httpd.after =
312       optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++
313       optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service";
315   };