1{
2 config,
3 lib,
4 options,
5 pkgs,
6 ...
7}:
8
9let
10
11 inherit (lib)
12 mkDefault
13 mkEnableOption
14 mkPackageOption
15 mkRenamedOptionModule
16 mkForce
17 mkIf
18 mkMerge
19 mkOption
20 types
21 ;
22 inherit (lib)
23 literalExpression
24 mapAttrs
25 optionalString
26 optionals
27 versionAtLeast
28 ;
29
30 cfg = config.services.zabbixWeb;
31 opt = options.services.zabbixWeb;
32 fpm = config.services.phpfpm.pools.zabbix;
33
34 user = "zabbix";
35 group = "zabbix";
36 stateDir = "/var/lib/zabbix";
37
38 zabbixConfig = pkgs.writeText "zabbix.conf.php" ''
39 <?php
40 // Zabbix GUI configuration file.
41 global $DB;
42 $DB['TYPE'] = '${
43 {
44 mysql = "MYSQL";
45 pgsql = "POSTGRESQL";
46 oracle = "ORACLE";
47 }
48 .${cfg.database.type}
49 }';
50 $DB['SERVER'] = '${cfg.database.host}';
51 $DB['PORT'] = '${toString cfg.database.port}';
52 $DB['DATABASE'] = '${cfg.database.name}';
53 $DB['USER'] = '${cfg.database.user}';
54 # NOTE: file_get_contents adds newline at the end of returned string
55 $DB['PASSWORD'] = ${
56 if cfg.database.passwordFile != null then
57 "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")"
58 else
59 "''"
60 };
61 // Schema name. Used for IBM DB2 and PostgreSQL.
62 $DB['SCHEMA'] = ''';
63 $ZBX_SERVER = '${cfg.server.address}';
64 $ZBX_SERVER_PORT = '${toString cfg.server.port}';
65 $ZBX_SERVER_NAME = ''';
66 $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
67
68 ${cfg.extraConfig}
69 '';
70in
71{
72 imports = [
73 (mkRenamedOptionModule
74 [
75 "services"
76 "zabbixWeb"
77 "virtualHost"
78 ]
79 [
80 "services"
81 "zabbixWeb"
82 "httpd"
83 "virtualHost"
84 ]
85 )
86 ];
87 # interface
88
89 options.services = {
90 zabbixWeb = {
91 enable = mkEnableOption "the Zabbix web interface";
92
93 package = mkPackageOption pkgs [
94 "zabbix"
95 "web"
96 ] { };
97
98 server = {
99 port = mkOption {
100 type = types.port;
101 description = "The port of the Zabbix server to connect to.";
102 default = 10051;
103 };
104
105 address = mkOption {
106 type = types.str;
107 description = "The IP address or hostname of the Zabbix server to connect to.";
108 default = "localhost";
109 };
110 };
111
112 database = {
113 type = mkOption {
114 type = types.enum [
115 "mysql"
116 "pgsql"
117 "oracle"
118 ];
119 example = "mysql";
120 default = "pgsql";
121 description = "Database engine to use.";
122 };
123
124 host = mkOption {
125 type = types.str;
126 default = "";
127 description = "Database host address.";
128 };
129
130 port = mkOption {
131 type = types.port;
132 default =
133 if cfg.database.type == "mysql" then
134 config.services.mysql.port
135 else if cfg.database.type == "pgsql" then
136 config.services.postgresql.settings.port
137 else
138 1521;
139 defaultText = literalExpression ''
140 if config.${opt.database.type} == "mysql" then config.${options.services.mysql.port}
141 else if config.${opt.database.type} == "pgsql" then config.services.postgresql.settings.port
142 else 1521
143 '';
144 description = "Database host port.";
145 };
146
147 name = mkOption {
148 type = types.str;
149 default = "zabbix";
150 description = "Database name.";
151 };
152
153 user = mkOption {
154 type = types.str;
155 default = "zabbix";
156 description = "Database user.";
157 };
158
159 passwordFile = mkOption {
160 type = types.nullOr types.path;
161 default = null;
162 example = "/run/keys/zabbix-dbpassword";
163 description = ''
164 A file containing the password corresponding to
165 {option}`database.user`.
166 '';
167 };
168
169 socket = mkOption {
170 type = types.nullOr types.path;
171 default = null;
172 example = "/run/postgresql";
173 description = "Path to the unix socket file to use for authentication.";
174 };
175 };
176
177 frontend = mkOption {
178 type = types.enum [
179 "nginx"
180 "httpd"
181 ];
182 example = "nginx";
183 default = "httpd";
184 description = "Frontend server to use.";
185 };
186
187 httpd.virtualHost = mkOption {
188 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
189 example = literalExpression ''
190 {
191 hostName = "zabbix.example.org";
192 adminAddr = "webmaster@example.org";
193 forceSSL = true;
194 enableACME = true;
195 }
196 '';
197 default = { };
198 description = ''
199 Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
200 See [](#opt-services.httpd.virtualHosts) for further information.
201 '';
202 };
203
204 hostname = mkOption {
205 type = types.str;
206 default = "zabbix.local";
207 description = "Hostname for either nginx or httpd.";
208 };
209
210 nginx.virtualHost = mkOption {
211 type = types.submodule (import ../web-servers/nginx/vhost-options.nix);
212 example = literalExpression ''
213 {
214 forceSSL = true;
215 sslCertificateKey = "/etc/ssl/zabbix.key";
216 sslCertificate = "/etc/ssl/zabbix.crt";
217 }
218 '';
219 default = { };
220 description = ''
221 Nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
222 See [](#opt-services.nginx.virtualHosts) for further information.
223 '';
224 };
225
226 poolConfig = mkOption {
227 type =
228 with types;
229 attrsOf (oneOf [
230 str
231 int
232 bool
233 ]);
234 default = {
235 "pm" = "dynamic";
236 "pm.max_children" = 32;
237 "pm.start_servers" = 2;
238 "pm.min_spare_servers" = 2;
239 "pm.max_spare_servers" = 4;
240 "pm.max_requests" = 500;
241 };
242 description = ''
243 Options for the Zabbix PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
244 '';
245 };
246
247 extraConfig = mkOption {
248 type = types.lines;
249 default = "";
250 description = ''
251 Additional configuration to be copied verbatim into {file}`zabbix.conf.php`.
252 '';
253 };
254 };
255 };
256
257 # implementation
258
259 config = mkIf cfg.enable {
260
261 services.zabbixWeb.extraConfig =
262 optionalString
263 (
264 (versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")
265 )
266 ''
267 $DB['DOUBLE_IEEE754'] = 'true';
268 '';
269
270 systemd.tmpfiles.rules =
271 [ "d '${stateDir}' 0750 ${user} ${group} - -" ]
272 ++ optionals (cfg.frontend == "httpd") [
273 "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
274 ]
275 ++ optionals (cfg.frontend == "nginx") [
276 "d '${stateDir}/session' 0750 ${user} ${config.services.nginx.group} - -"
277 ];
278
279 services.phpfpm.pools.zabbix = {
280 inherit user;
281 group = config.services.${cfg.frontend}.group;
282 phpOptions =
283 ''
284 # https://www.zabbix.com/documentation/current/manual/installation/install
285 memory_limit = 128M
286 post_max_size = 16M
287 upload_max_filesize = 2M
288 max_execution_time = 300
289 max_input_time = 300
290 session.auto_start = 0
291 mbstring.func_overload = 0
292 always_populate_raw_post_data = -1
293 # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
294 session.save_path = ${stateDir}/session
295 ''
296 + optionalString (config.time.timeZone != null) ''
297 date.timezone = "${config.time.timeZone}"
298 ''
299 + optionalString (cfg.database.type == "oracle") ''
300 extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
301 '';
302 phpEnv.ZABBIX_CONFIG = "${zabbixConfig}";
303 settings = {
304 "listen.owner" =
305 if cfg.frontend == "httpd" then config.services.httpd.user else config.services.nginx.user;
306 "listen.group" =
307 if cfg.frontend == "httpd" then config.services.httpd.group else config.services.nginx.group;
308 } // cfg.poolConfig;
309 };
310
311 services.httpd = mkIf (cfg.frontend == "httpd") {
312 enable = true;
313 adminAddr = mkDefault cfg.httpd.virtualHost.adminAddr;
314 extraModules = [ "proxy_fcgi" ];
315 virtualHosts.${cfg.hostname} = mkMerge [
316 cfg.httpd.virtualHost
317 {
318 documentRoot = mkForce "${cfg.package}/share/zabbix";
319 extraConfig = ''
320 <Directory "${cfg.package}/share/zabbix">
321 <FilesMatch "\.php$">
322 <If "-f %{REQUEST_FILENAME}">
323 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
324 </If>
325 </FilesMatch>
326 AllowOverride all
327 Options -Indexes
328 DirectoryIndex index.php
329 </Directory>
330 '';
331 }
332 ];
333 };
334
335 services.nginx = mkIf (cfg.frontend == "nginx") {
336 enable = true;
337 virtualHosts.${cfg.hostname} = mkMerge [
338 cfg.nginx.virtualHost
339 {
340 root = mkForce "${cfg.package}/share/zabbix";
341 locations."/" = {
342 index = "index.html index.htm index.php";
343 tryFiles = "$uri $uri/ =404";
344 };
345 locations."~ \\.php$".extraConfig = ''
346 fastcgi_pass unix:${fpm.socket};
347 fastcgi_index index.php;
348 '';
349 }
350 ];
351 };
352
353 users.users.${user} = mapAttrs (name: mkDefault) {
354 description = "Zabbix daemon user";
355 uid = config.ids.uids.zabbix;
356 inherit group;
357 };
358
359 users.groups.${group} = mapAttrs (name: mkDefault) { gid = config.ids.gids.zabbix; };
360 };
361}