services: Add yggdrasil-service-type.
[jackhill/guix/guix.git] / gnu / services / monitoring.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2018 Sou Bunnbu <iyzsong@member.fsf.org>
3 ;;; Copyright © 2018, 2019 Gábor Boskovits <boskovits@gmail.com>
4 ;;; Copyright © 2018, 2019 Oleg Pykhalov <go.wigust@gmail.com>
5 ;;;
6 ;;; This file is part of GNU Guix.
7 ;;;
8 ;;; GNU Guix is free software; you can redistribute it and/or modify it
9 ;;; under the terms of the GNU General Public License as published by
10 ;;; the Free Software Foundation; either version 3 of the License, or (at
11 ;;; your option) any later version.
12 ;;;
13 ;;; GNU Guix is distributed in the hope that it will be useful, but
14 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;;; GNU General Public License for more details.
17 ;;;
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
21 (define-module (gnu services monitoring)
22 #:use-module (gnu services)
23 #:use-module (gnu services configuration)
24 #:use-module (gnu services shepherd)
25 #:use-module (gnu services web)
26 #:use-module (gnu packages admin)
27 #:use-module (gnu packages monitoring)
28 #:use-module (gnu system shadow)
29 #:use-module (guix gexp)
30 #:use-module (guix packages)
31 #:use-module (guix records)
32 #:use-module (guix utils)
33 #:use-module ((guix ui) #:select (display-hint G_))
34 #:use-module (ice-9 match)
35 #:use-module (ice-9 rdelim)
36 #:use-module (srfi srfi-26)
37 #:use-module (srfi srfi-35)
38 #:export (darkstat-configuration
39 prometheus-node-exporter-configuration
40 darkstat-service-type
41 prometheus-node-exporter-service-type
42
43 zabbix-server-configuration
44 zabbix-server-service-type
45 zabbix-agent-configuration
46 zabbix-agent-service-type
47 zabbix-front-end-configuration
48 zabbix-front-end-service-type
49 %zabbix-front-end-configuration-nginx))
50
51 \f
52 ;;;
53 ;;; darkstat
54 ;;;
55
56 (define-record-type* <darkstat-configuration>
57 darkstat-configuration make-darkstat-configuration darkstat-configuration?
58 (package darkstat-configuration-package
59 (default darkstat))
60 (interface darkstat-configuration-interface)
61 (port darkstat-configuration-port
62 (default "667"))
63 (bind-address darkstat-configuration-bind-address
64 (default "127.0.0.1"))
65 (base darkstat-configuration-base
66 (default "/")))
67
68 (define %darkstat-accounts
69 (list (user-account
70 (name "darkstat")
71 (group "darkstat")
72 (system? #t)
73 (comment "darkstat daemon user")
74 (home-directory "/var/lib/darkstat")
75 (shell (file-append shadow "/sbin/nologin")))
76 (user-group
77 (name "darkstat")
78 (system? #t))))
79
80 (define darkstat-shepherd-service
81 (match-lambda
82 (($ <darkstat-configuration>
83 package interface port bind-address base)
84 (shepherd-service
85 (documentation "Network statistics gatherer.")
86 (provision '(darkstat))
87 (requirement '(networking))
88 (start #~(make-forkexec-constructor
89 (list #$(file-append package "/sbin/darkstat")
90 "-i" #$interface
91 "-p" #$port
92 "-b" #$bind-address
93 "--base" #$base
94 "--syslog" "--no-daemon"
95 "--chroot" "/var/lib/darkstat"
96 "--user" "darkstat"
97 "--import" "darkstat.db"
98 "--export" "darkstat.db")))
99 (stop #~(make-kill-destructor))))))
100
101 (define darkstat-service-type
102 (service-type
103 (name 'darkstat)
104 (description
105 "Run @command{darkstat} to serve network traffic statistics reports over
106 HTTP.")
107 (extensions
108 (list (service-extension account-service-type
109 (const %darkstat-accounts))
110 (service-extension shepherd-root-service-type
111 (compose list darkstat-shepherd-service))))))
112
113 (define-record-type* <prometheus-node-exporter-configuration>
114 prometheus-node-exporter-configuration
115 make-prometheus-node-exporter-configuration
116 prometheus-node-exporter-configuration?
117 (package prometheus-node-exporter-configuration-package
118 (default go-github-com-prometheus-node-exporter))
119 (web-listen-address prometheus-node-exporter-web-listen-address
120 (default ":9100")))
121
122 (define prometheus-node-exporter-shepherd-service
123 (match-lambda
124 (( $ <prometheus-node-exporter-configuration>
125 package web-listen-address)
126 (shepherd-service
127 (documentation "Prometheus node exporter.")
128 (provision '(prometheus-node-exporter))
129 (requirement '(networking))
130 (start #~(make-forkexec-constructor
131 (list #$(file-append package "/bin/node_exporter")
132 "--web.listen-address" #$web-listen-address)))
133 (stop #~(make-kill-destructor))))))
134
135 (define prometheus-node-exporter-service-type
136 (service-type
137 (name 'prometheus-node-exporter)
138 (description
139 "Run @command{node_exporter} to serve hardware and OS metrics to
140 prometheus.")
141 (extensions
142 (list (service-extension
143 shepherd-root-service-type
144 (compose list prometheus-node-exporter-shepherd-service))))
145 (default-value (prometheus-node-exporter-configuration))))
146
147 \f
148 ;;;
149 ;;; Zabbix server
150 ;;;
151
152 (define (uglify-field-name field-name)
153 (apply string-append
154 (map (lambda (str)
155 (if (member (string->symbol str) '(ca db ssl))
156 (string-upcase str)
157 (string-capitalize str)))
158 (string-split (string-delete #\?
159 (symbol->string field-name))
160 #\-))))
161
162 (define (serialize-field field-name val)
163 (format #t "~a=~a~%" (uglify-field-name field-name) val))
164
165 (define (serialize-number field-name val)
166 (serialize-field field-name (number->string val)))
167
168 (define (serialize-list field-name val)
169 (if (null? val) "" (serialize-field field-name (string-join val ","))))
170
171 (define (serialize-string field-name val)
172 (if (and (string? val) (string=? val ""))
173 ""
174 (serialize-field field-name val)))
175
176 (define group? string?)
177
178 (define serialize-group
179 (const ""))
180
181 (define include-files? list?)
182
183 (define (serialize-include-files field-name val)
184 (if (null? val) "" (for-each (cut serialize-field 'include <>) val)))
185
186 (define extra-options? string?)
187
188 (define (serialize-extra-options field-name val)
189 (if (null? val) "" (display val)))
190
191 (define (nginx-server-configuration-list? val)
192 (and (list? val) (and-map nginx-server-configuration? val)))
193
194 (define (serialize-nginx-server-configuration-list field-name val)
195 "")
196
197 (define-configuration zabbix-server-configuration
198 (zabbix-server
199 (package zabbix-server)
200 "The zabbix-server package.")
201 (user
202 (string "zabbix")
203 "User who will run the Zabbix server.")
204 (group ;for zabbix-server-account procedure
205 (group "zabbix")
206 "Group who will run the Zabbix server.")
207 (db-host
208 (string "127.0.0.1")
209 "Database host name.")
210 (db-name
211 (string "zabbix")
212 "Database name.")
213 (db-user
214 (string "zabbix")
215 "Database user.")
216 (db-password
217 (string "")
218 "Database password. Please, use @code{include-files} with
219 @code{DBPassword=SECRET} inside a specified file instead.")
220 (db-port
221 (number 5432)
222 "Database port.")
223 (log-type
224 (string "")
225 "Specifies where log messages are written to:
226 @itemize
227 @item @code{system} - syslog.
228 @item @code{file} - file specified with @code{log-file} parameter.
229 @item @code{console} - standard output.
230 @end itemize\n")
231 (log-file
232 (string "/var/log/zabbix/server.log")
233 "Log file name for @code{log-type} @code{file} parameter.")
234 (pid-file
235 (string "/var/run/zabbix/zabbix_server.pid")
236 "Name of PID file.")
237 (ssl-ca-location
238 (string "/etc/ssl/certs/ca-certificates.crt")
239 "The location of certificate authority (CA) files for SSL server
240 certificate verification.")
241 (ssl-cert-location
242 (string "/etc/ssl/certs")
243 "Location of SSL client certificates.")
244 (extra-options
245 (extra-options "")
246 "Extra options will be appended to Zabbix server configuration file.")
247 (include-files
248 (include-files '())
249 "You may include individual files or all files in a directory in the
250 configuration file."))
251
252 (define (zabbix-server-account config)
253 "Return the user accounts and user groups for CONFIG."
254 (let ((zabbix-user (zabbix-server-configuration-user config))
255 (zabbix-group (zabbix-server-configuration-group config)))
256 (list (user-group (name zabbix-group) (system? #t))
257 (user-account
258 (name zabbix-user)
259 (system? #t)
260 (group zabbix-group)
261 (comment "zabbix privilege separation user")
262 (home-directory (string-append "/var/run/" zabbix-user))
263 (shell (file-append shadow "/sbin/nologin"))))))
264
265 (define (zabbix-server-config-file config)
266 "Return the zabbix-server configuration file corresponding to CONFIG."
267 (computed-file
268 "zabbix_server.conf"
269 #~(begin
270 (call-with-output-file #$output
271 (lambda (port)
272 (display "# Generated by 'zabbix-server-service'.\n" port)
273 (display #$(with-output-to-string
274 (lambda ()
275 (serialize-configuration
276 config zabbix-server-configuration-fields)))
277 port)
278 #t)))))
279
280 (define (zabbix-server-activation config)
281 "Return the activation gexp for CONFIG."
282 (with-imported-modules '((guix build utils))
283 #~(begin
284 (use-modules (guix build utils)
285 (ice-9 rdelim))
286
287 (let ((user (getpw #$(zabbix-server-configuration-user config))))
288 (for-each (lambda (file)
289 (let ((directory (dirname file)))
290 (mkdir-p directory)
291 (chown directory (passwd:uid user) (passwd:gid user))
292 (chmod directory #o755)))
293 (list #$(zabbix-server-configuration-log-file config)
294 #$(zabbix-server-configuration-pid-file config)
295 "/etc/zabbix/maintenance.inc.php"))))))
296
297 (define (zabbix-server-shepherd-service config)
298 "Return a <shepherd-service> for Zabbix server with CONFIG."
299 (list (shepherd-service
300 (provision '(zabbix-server))
301 (documentation "Run Zabbix server daemon.")
302 (start #~(make-forkexec-constructor
303 (list #$(file-append (zabbix-server-configuration-zabbix-server config)
304 "/sbin/zabbix_server")
305 "--config" #$(zabbix-server-config-file config)
306 "--foreground")
307 #:user #$(zabbix-server-configuration-user config)
308 #:group #$(zabbix-server-configuration-group config)
309 #:pid-file #$(zabbix-server-configuration-pid-file config)
310 #:environment-variables
311 (list "SSL_CERT_DIR=/run/current-system/profile\
312 /etc/ssl/certs"
313 "SSL_CERT_FILE=/run/current-system/profile\
314 /etc/ssl/certs/ca-certificates.crt")))
315 (stop #~(make-kill-destructor)))))
316
317 (define zabbix-server-service-type
318 (service-type
319 (name 'zabbix-server)
320 (extensions
321 (list (service-extension shepherd-root-service-type
322 zabbix-server-shepherd-service)
323 (service-extension account-service-type
324 zabbix-server-account)
325 (service-extension activation-service-type
326 zabbix-server-activation)))
327 (default-value (zabbix-server-configuration))))
328
329 (define (generate-zabbix-server-documentation)
330 (generate-documentation
331 `((zabbix-server-configuration
332 ,zabbix-server-configuration-fields))
333 'zabbix-server-configuration))
334
335 (define-configuration zabbix-agent-configuration
336 (zabbix-agent
337 (package zabbix-agentd)
338 "The zabbix-agent package.")
339 (user
340 (string "zabbix")
341 "User who will run the Zabbix agent.")
342 (group
343 (group "zabbix")
344 "Group who will run the Zabbix agent.")
345 (hostname
346 (string "Zabbix server")
347 "Unique, case sensitive hostname which is required for active checks and
348 must match hostname as configured on the server.")
349 (log-type
350 (string "")
351 "Specifies where log messages are written to:
352 @itemize
353 @item @code{system} - syslog.
354 @item @code{file} - file specified with @code{log-file} parameter.
355 @item @code{console} - standard output.
356 @end itemize\n")
357 (log-file
358 (string "/var/log/zabbix/agent.log")
359 "Log file name for @code{log-type} @code{file} parameter.")
360 (pid-file
361 (string "/var/run/zabbix/zabbix_agent.pid")
362 "Name of PID file.")
363 (server
364 (list '("127.0.0.1"))
365 "List of IP addresses, optionally in CIDR notation, or hostnames of Zabbix
366 servers and Zabbix proxies. Incoming connections will be accepted only from
367 the hosts listed here.")
368 (server-active
369 (list '("127.0.0.1"))
370 "List of IP:port (or hostname:port) pairs of Zabbix servers and Zabbix
371 proxies for active checks. If port is not specified, default port is used.
372 If this parameter is not specified, active checks are disabled.")
373 (extra-options
374 (extra-options "")
375 "Extra options will be appended to Zabbix server configuration file.")
376 (include-files
377 (include-files '())
378 "You may include individual files or all files in a directory in the
379 configuration file."))
380
381 (define (zabbix-agent-account config)
382 "Return the user accounts and user groups for CONFIG."
383 (let ((zabbix-user "zabbix")
384 (zabbix-group "zabbix"))
385 (list (user-group (name zabbix-group) (system? #t))
386 (user-account
387 (name zabbix-user)
388 (system? #t)
389 (group zabbix-group)
390 (comment "zabbix privilege separation user")
391 (home-directory (string-append "/var/run/" zabbix-user))
392 (shell (file-append shadow "/sbin/nologin"))))))
393
394 (define (zabbix-agent-activation config)
395 "Return the activation gexp for CONFIG."
396 (with-imported-modules '((guix build utils))
397 #~(begin
398 (use-modules (guix build utils)
399 (ice-9 rdelim))
400 (let ((user
401 (getpw #$(zabbix-agent-configuration-user config))))
402 (for-each (lambda (file)
403 (let ((directory (dirname file)))
404 (mkdir-p directory)
405 (chown directory (passwd:uid user) (passwd:gid user))
406 (chmod directory #o755)))
407 (list #$(zabbix-agent-configuration-log-file config)
408 #$(zabbix-agent-configuration-pid-file config)))))))
409
410 (define (zabbix-agent-config-file config)
411 "Return the zabbix-agent configuration file corresponding to CONFIG."
412 (computed-file
413 "zabbix_agent.conf"
414 #~(begin
415 (call-with-output-file #$output
416 (lambda (port)
417 (display "# Generated by 'zabbix-agent-service'.\n" port)
418 (display #$(with-output-to-string
419 (lambda ()
420 (serialize-configuration
421 config zabbix-agent-configuration-fields)))
422 port)
423 #t)))))
424
425 (define (zabbix-agent-shepherd-service config)
426 "Return a <shepherd-service> for Zabbix agent with CONFIG."
427 (list (shepherd-service
428 (provision '(zabbix-agent))
429 (documentation "Run Zabbix agent daemon.")
430 (start #~(make-forkexec-constructor
431 (list #$(file-append (zabbix-agent-configuration-zabbix-agent config)
432 "/sbin/zabbix_agentd")
433 "--config" #$(zabbix-agent-config-file config)
434 "--foreground")
435 #:user #$(zabbix-agent-configuration-user config)
436 #:group #$(zabbix-agent-configuration-group config)
437 #:pid-file #$(zabbix-agent-configuration-pid-file config)
438 #:environment-variables
439 (list "SSL_CERT_DIR=/run/current-system/profile\
440 /etc/ssl/certs"
441 "SSL_CERT_FILE=/run/current-system/profile\
442 /etc/ssl/certs/ca-certificates.crt")))
443 (stop #~(make-kill-destructor)))))
444
445 (define zabbix-agent-service-type
446 (service-type
447 (name 'zabbix-agent)
448 (extensions
449 (list (service-extension shepherd-root-service-type
450 zabbix-agent-shepherd-service)
451 (service-extension account-service-type
452 zabbix-agent-account)
453 (service-extension activation-service-type
454 zabbix-agent-activation)))
455 (default-value (zabbix-agent-configuration))))
456
457 (define (generate-zabbix-agent-documentation)
458 (generate-documentation
459 `((zabbix-agent-configuration
460 ,zabbix-agent-configuration-fields))
461 'zabbix-agent-configuration))
462
463 (define %zabbix-front-end-configuration-nginx
464 (nginx-server-configuration
465 (root #~(string-append #$zabbix-server:front-end "/share/zabbix/php"))
466 (index '("index.php"))
467 (locations
468 (let ((php-location (nginx-php-location)))
469 (list (nginx-location-configuration
470 (inherit php-location)
471 (body (append (nginx-location-configuration-body php-location)
472 (list "
473 fastcgi_param PHP_VALUE \"post_max_size = 16M
474 max_execution_time = 300\";
475 ")))))))
476 (listen '("80"))))
477
478 (define-configuration zabbix-front-end-configuration
479 ;; TODO: Specify zabbix front-end package.
480 ;; (zabbix-
481 ;; (package zabbix-front-end)
482 ;; "The zabbix-front-end package.")
483 (nginx
484 (nginx-server-configuration-list
485 (list %zabbix-front-end-configuration-nginx))
486 "NGINX configuration.")
487 (db-host
488 (string "localhost")
489 "Database host name.")
490 (db-port
491 (number 5432)
492 "Database port.")
493 (db-name
494 (string "zabbix")
495 "Database name.")
496 (db-user
497 (string "zabbix")
498 "Database user.")
499 (db-password
500 (string "")
501 "Database password. Please, use @code{db-secret-file} instead.")
502 (db-secret-file
503 (string "")
504 "Secret file which will be appended to @file{zabbix.conf.php} file. This
505 file contains credentials for use by Zabbix front-end. You are expected to
506 create it manually.")
507 (zabbix-host
508 (string "localhost")
509 "Zabbix server hostname.")
510 (zabbix-port
511 (number 10051)
512 "Zabbix server port."))
513
514 (define (zabbix-front-end-config config)
515 (match-record config <zabbix-front-end-configuration>
516 (%location db-host db-port db-name db-user db-password db-secret-file
517 zabbix-host zabbix-port)
518 (mixed-text-file "zabbix.conf.php"
519 "\
520 <?php
521 // Zabbix GUI configuration file.
522 global $DB;
523
524 $DB['TYPE'] = 'POSTGRESQL';
525 $DB['SERVER'] = '" db-host "';
526 $DB['PORT'] = '" (number->string db-port) "';
527 $DB['DATABASE'] = '" db-name "';
528 $DB['USER'] = '" db-user "';
529 $DB['PASSWORD'] = '" (let ((file (location-file %location))
530 (line (location-line %location))
531 (column (location-column %location)))
532 (if (string-null? db-password)
533 (if (string-null? db-secret-file)
534 (raise (make-compound-condition
535 (condition
536 (&message
537 (message
538 (format #f "no '~A' or '~A' field in your '~A' record"
539 'db-secret-file 'db-password
540 'zabbix-front-end-configuration))))
541 (condition
542 (&error-location
543 (location %location)))))
544 (string-trim-both
545 (with-input-from-file db-secret-file
546 read-string)))
547 (begin
548 (display-hint (format #f (G_ "~a:~a:~a: ~a:
549 Consider using @code{db-secret-file} instead of @code{db-password} for better
550 security.") file line column 'zabbix-front-end-configuration))
551 db-password))) "';
552
553 // Schema name. Used for IBM DB2 and PostgreSQL.
554 $DB['SCHEMA'] = '';
555
556 $ZBX_SERVER = '" zabbix-host "';
557 $ZBX_SERVER_PORT = '" (number->string zabbix-port) "';
558 $ZBX_SERVER_NAME = '';
559
560 $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
561 ")))
562
563 (define %maintenance.inc.php
564 ;; Empty php file to allow us move zabbix-frontend configs to ‘/etc/zabbix’
565 ;; directory. See ‘install-front-end’ phase in
566 ;; (@ (gnu packages monitoring) zabbix-server) package.
567 "\
568 <?php
569 ")
570
571 (define (zabbix-front-end-activation config)
572 "Return the activation gexp for CONFIG."
573 #~(begin
574 (use-modules (guix build utils))
575 (mkdir-p "/etc/zabbix")
576 (call-with-output-file "/etc/zabbix/maintenance.inc.php"
577 (lambda (port)
578 (display #$%maintenance.inc.php port)))
579 (copy-file #$(zabbix-front-end-config config)
580 "/etc/zabbix/zabbix.conf.php")))
581
582 (define zabbix-front-end-service-type
583 (service-type
584 (name 'zabbix-front-end)
585 (extensions
586 (list (service-extension activation-service-type
587 zabbix-front-end-activation)
588 (service-extension nginx-service-type
589 zabbix-front-end-configuration-nginx)
590 ;; Make sure php-fpm is instantiated.
591 (service-extension php-fpm-service-type
592 (const #t))))
593 (default-value (zabbix-front-end-configuration))
594 (description
595 "Run the zabbix-front-end web interface, which allows users to interact
596 with Zabbix server.")))
597
598 (define (generate-zabbix-front-end-documentation)
599 (generate-documentation
600 `((zabbix-front-end-configuration
601 ,zabbix-front-end-configuration-fields))
602 'zabbix-front-end-configuration))