grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / monitoring / zabbix-proxy.nix
blobdec403df85ea8b5ed33310da157d9053816cd31f
1 { config, lib, options, pkgs, ... }:
3 let
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;
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-proxy-module-env";
21     paths = attrValues cfg.modules;
22   };
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";
32   imports = [
33     (lib.mkRemovedOptionModule [ "services" "zabbixProxy" "extraConfig" ] "Use services.zabbixProxy.settings instead.")
34   ];
36   # interface
38   options = {
40     services.zabbixProxy = {
41       enable = mkEnableOption "the Zabbix Proxy";
43       server = mkOption {
44         type = types.str;
45         description = ''
46           The IP address or hostname of the Zabbix server to connect to.
47           '';
48         };
50       package = mkOption {
51         type = types.package;
52         default =
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.";
58       };
60       extraPackages = mkOption {
61         type = types.listOf types.package;
62         default = with pkgs; [ nettools nmap traceroute ];
63         defaultText = literalExpression "[ nettools nmap traceroute ]";
64         description = ''
65           Packages to be added to the Zabbix {env}`PATH`.
66           Typically used to add executables for scripts, but can be anything.
67         '';
68       };
70       modules = mkOption {
71         type = types.attrsOf types.package;
72         description = "A set of modules to load.";
73         default = {};
74         example = literalExpression ''
75           {
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";
81               installPhase = '''
82                 mkdir -p $out/lib
83                 cp dummy.so $out/lib/
84               ''';
85             };
86           }
87         '';
88       };
90       database = {
91         type = mkOption {
92           type = types.enum [ "mysql" "pgsql" "sqlite" ];
93           example = "mysql";
94           default = "pgsql";
95           description = "Database engine to use.";
96         };
98         host = mkOption {
99           type = types.str;
100           default = "localhost";
101           description = "Database host address.";
102         };
104         port = mkOption {
105           type = types.port;
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
111           '';
112           description = "Database host port.";
113         };
115         name = mkOption {
116           type = types.str;
117           default = if cfg.database.type == "sqlite" then "${stateDir}/zabbix.db" else "zabbix";
118           defaultText = literalExpression "zabbix";
119           description = "Database name.";
120         };
122         user = mkOption {
123           type = types.str;
124           default = "zabbix";
125           description = "Database user.";
126         };
128         passwordFile = mkOption {
129           type = types.nullOr types.path;
130           default = null;
131           example = "/run/keys/zabbix-dbpassword";
132           description = ''
133             A file containing the password corresponding to
134             {option}`database.user`.
135           '';
136         };
138         socket = mkOption {
139           type = types.nullOr types.path;
140           default = null;
141           example = "/run/postgresql";
142           description = "Path to the unix socket file to use for authentication.";
143         };
145         createLocally = mkOption {
146           type = types.bool;
147           default = true;
148           description = "Whether to create a local database automatically.";
149         };
150       };
152       listen = {
153         ip = mkOption {
154           type = types.str;
155           default = "0.0.0.0";
156           description = ''
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.
159           '';
160         };
162         port = mkOption {
163           type = types.port;
164           default = 10051;
165           description = ''
166             Listen port for trapper.
167           '';
168         };
169       };
171       openFirewall = mkOption {
172         type = types.bool;
173         default = false;
174         description = ''
175           Open ports in the firewall for the Zabbix Proxy.
176         '';
177       };
179       settings = mkOption {
180         type = with types; attrsOf (oneOf [ int str (listOf str) ]);
181         default = {};
182         description = ''
183           Zabbix Proxy configuration. Refer to
184           <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy>
185           for details on supported values.
186         '';
187         example = {
188           CacheSize = "1G";
189           SSHKeyLocation = "/var/lib/zabbix/.ssh";
190           StartPingers = 32;
191         };
192       };
194     };
196   };
198   # implementation
200   config = mkIf cfg.enable {
202     assertions = [
203       { assertion = !config.services.zabbixServer.enable;
204         message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
205       }
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";
208       }
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";
211       }
212     ];
214     services.zabbixProxy.settings = mkMerge [
215       {
216         LogType = "console";
217         ListenIP = cfg.listen.ip;
218         ListenPort = cfg.listen.port;
219         Server = cfg.server;
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;
227       }
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"; })
232     ];
234     networking.firewall = mkIf cfg.openFirewall {
235       allowedTCPPorts = [ cfg.listen.port ];
236     };
238     services.mysql = optionalAttrs mysqlLocal {
239       enable = true;
240       package = mkDefault pkgs.mariadb;
241     };
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
248     '');
250     services.postgresql = optionalAttrs pgsqlLocal {
251       enable = true;
252       ensureDatabases = [ cfg.database.name ];
253       ensureUsers = [
254         { name = cfg.database.user;
255           ensureDBOwnership = true;
256         }
257       ];
258     };
260     users.users.${user} = {
261       description = "Zabbix daemon user";
262       uid = config.ids.uids.zabbix;
263       inherit group;
264     };
266     users.groups.${group} = {
267       gid = config.ids.gids.zabbix;
268     };
270     security.wrappers = {
271       fping =
272         { setuid = true;
273           owner = "root";
274           group = "root";
275           source = "${pkgs.fping}/bin/fping";
276         };
277     };
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"
290         fi
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"
295         fi
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
299         fi
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}
303       '';
305       serviceConfig = {
306         ExecStart = "@${cfg.package}/sbin/zabbix_proxy zabbix_proxy -f --config ${configFile}";
307         Restart = "always";
308         RestartSec = 2;
310         User = user;
311         Group = group;
312         RuntimeDirectory = "zabbix";
313         StateDirectory = "zabbix";
314         PrivateTmp = true;
315       };
316     };
318   };