projects/guix·blob librewolf-updates · refs

899 lines · 35.5 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2015 David Thompson <[email protected]>
3;;; Copyright © 2015-2016, 2022-2023, 2025 Ludovic Courtès <[email protected]>
4;;; Copyright © 2016 Leo Famulari <[email protected]>
5;;; Copyright © 2017 Christopher Baines <[email protected]>
6;;; Copyright © 2018 Clément Lassieur <[email protected]>
7;;; Copyright © 2018 Julien Lepiller <[email protected]>
8;;; Copyright © 2019 Robert Vollmert <[email protected]>
9;;; Copyright © 2020, 2022 Marius Bakke <[email protected]>
10;;; Copyright © 2021 David Larsson <[email protected]>
11;;; Copyright © 2021 Aljosha Papsch <[email protected]>
12;;; Copyright © 2025, 2026 Giacomo Leidi <[email protected]>
13;;;
14;;; This file is part of GNU Guix.
15;;;
16;;; GNU Guix is free software; you can redistribute it and/or modify it
17;;; under the terms of the GNU General Public License as published by
18;;; the Free Software Foundation; either version 3 of the License, or (at
19;;; your option) any later version.
20;;;
21;;; GNU Guix is distributed in the hope that it will be useful, but
22;;; WITHOUT ANY WARRANTY; without even the implied warranty of
23;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24;;; GNU General Public License for more details.
25;;;
26;;; You should have received a copy of the GNU General Public License
27;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
28
29(define-module (gnu services databases)
30 #:use-module (gnu services)
31 #:use-module (gnu services base)
32 #:use-module (gnu services configuration)
33 #:use-module (gnu services shepherd)
34 #:use-module (gnu system file-systems)
35 #:use-module (gnu system shadow)
36 #:autoload (gnu system accounts) (default-shell)
37 #:use-module (gnu packages admin)
38 #:use-module (gnu packages base)
39 #:use-module (gnu packages databases)
40 #:use-module (guix build-system trivial)
41 #:use-module (guix build union)
42 #:use-module (guix deprecation)
43 #:use-module (guix modules)
44 #:use-module (guix packages)
45 #:use-module (guix records)
46 #:use-module (guix gexp)
47 #:use-module (srfi srfi-1)
48 #:use-module (ice-9 match)
49 #:export (postgresql-config-file
50 postgresql-config-file?
51 postgresql-config-file-log-destination
52 postgresql-config-file-hba-file
53 postgresql-config-file-ident-file
54 postgresql-config-file-socket-directory
55 postgresql-config-file-extra-config
56
57 postgresql-configuration
58 postgresql-configuration?
59 postgresql-configuration-allow-login?
60 postgresql-configuration-create-account?
61 postgresql-configuration-data-directory
62 postgresql-configuration-extension-packages
63 postgresql-configuration-file
64 postgresql-configuration-gid
65 postgresql-configuration-home-directory
66 postgresql-configuration-locale
67 postgresql-configuration-log-directory
68 postgresql-configuration-port
69 postgresql-configuration-postgresql
70 postgresql-configuration-uid
71
72 postgresql-service-type
73
74 %default-postgresql-role-shepherd-requirement
75
76 postgresql-role
77 postgresql-role?
78 postgresql-role-name
79 postgresql-role-password-file
80 postgresql-role-permissions
81 postgresql-role-create-database?
82 postgresql-role-configuration
83 postgresql-role-configuration?
84 postgresql-role-configuration-host
85 postgresql-role-configuration-shepherd-requirement
86 postgresql-role-configuration-roles
87
88 postgresql-role-service-type
89
90 memcached-service-type
91 memcached-configuration
92 memcached-configuration?
93 memcached-configuration-memecached
94 memcached-configuration-interfaces
95 memcached-configuration-tcp-port
96 memcached-configuration-udp-port
97 memcached-configuration-additional-options
98
99 mysql-service-type
100 mysql-configuration
101 mysql-configuration?
102
103 redis-configuration
104 redis-configuration?
105 redis-service-type))
106
107;;; Commentary:
108;;;
109;;; Database services.
110;;;
111;;; Code:
112
113(define %default-postgres-hba
114 (plain-file "pg_hba.conf"
115 "
116local all all peer
117host all all 127.0.0.1/32 md5
118host all all ::1/128 md5"))
119
120(define %default-postgres-ident
121 (plain-file "pg_ident.conf"
122 "# MAPNAME SYSTEM-USERNAME PG-USERNAME"))
123
124(define-record-type* <postgresql-config-file>
125 postgresql-config-file make-postgresql-config-file
126 postgresql-config-file?
127 (log-destination postgresql-config-file-log-destination
128 (default "syslog"))
129 (hba-file postgresql-config-file-hba-file
130 (default %default-postgres-hba))
131 (ident-file postgresql-config-file-ident-file
132 (default %default-postgres-ident))
133 (socket-directory postgresql-config-file-socket-directory
134 (default "/var/run/postgresql"))
135 (extra-config postgresql-config-file-extra-config
136 (default '())))
137
138(define-gexp-compiler (postgresql-config-file-compiler
139 (file <postgresql-config-file>) system target)
140 (match file
141 (($ <postgresql-config-file> log-destination hba-file
142 ident-file socket-directory
143 extra-config)
144 ;; See: https://www.postgresql.org/docs/current/config-setting.html.
145 (define (format-value value)
146 (cond
147 ((boolean? value)
148 (list (if value "on" "off")))
149 ((number? value)
150 (list (number->string value)))
151 (else
152 (list "'" value "'"))))
153
154 (define contents
155 (append-map
156 (match-lambda
157 ((key) '())
158 ((key . #f) '())
159 ((key values ...)
160 `(,key " = " ,@(append-map format-value values) "\n")))
161
162 `(("log_destination" ,log-destination)
163 ("hba_file" ,hba-file)
164 ("ident_file" ,ident-file)
165 ,@(if socket-directory
166 `(("unix_socket_directories" ,socket-directory))
167 '())
168 ,@extra-config)))
169
170 (gexp->derivation
171 "postgresql.conf"
172 #~(call-with-output-file (ungexp output "out")
173 (lambda (port)
174 (display
175 (string-append #$@contents)
176 port)))
177 #:local-build? #t))))
178
179(define %default-home-directory "/var/empty")
180
181(define-record-type* <postgresql-configuration>
182 postgresql-configuration make-postgresql-configuration
183 postgresql-configuration?
184 ;; Setting no default ensures that the user sets its current postgresql
185 ;; explicitely. Since major upgrades currently require a manual migration
186 ;; of the database, this way the user is responsible for upgrading properly.
187 ;; MAYBE TODO: Write an action to automatically upgrade in Guix.
188 (postgresql postgresql-configuration-postgresql) ;file-like
189 (port postgresql-configuration-port
190 (default 5432))
191 (locale postgresql-configuration-locale
192 (default "en_US.utf8"))
193 (config-file postgresql-configuration-file
194 (default (postgresql-config-file)))
195 (log-directory postgresql-configuration-log-directory
196 (default "/var/log/postgresql"))
197 (data-directory postgresql-configuration-data-directory
198 (default "/var/lib/postgresql/data"))
199 (extension-packages postgresql-configuration-extension-packages
200 (default '()))
201 (create-account? postgresql-configuration-create-account?
202 (default #t))
203 (home-directory postgresql-configuration-home-directory
204 (default %default-home-directory))
205 (allow-login? postgresql-configuration-allow-login?
206 (default #f))
207 (uid postgresql-configuration-uid
208 (default #f))
209 (gid postgresql-configuration-gid
210 (default #f)))
211
212(define (create-postgresql-account config)
213 (match-record config <postgresql-configuration>
214 (create-account? allow-login? home-directory uid gid)
215 (if (not create-account?) '()
216 (list (user-group
217 (name "postgres")
218 (id gid)
219 (system? #t))
220 (user-account
221 (name "postgres")
222 (group "postgres")
223 (system? #t)
224 (uid uid)
225 (comment "PostgreSQL server user")
226 (create-home-directory?
227 (not (string=? home-directory %default-home-directory)))
228 (home-directory home-directory)
229 (shell (if allow-login?
230 (default-shell)
231 (file-append shadow "/sbin/nologin"))))))))
232
233(define (final-postgresql postgresql extension-packages)
234 (if (null? extension-packages)
235 postgresql
236 (package
237 (inherit postgresql)
238 (source #f)
239 (build-system trivial-build-system)
240 (arguments
241 `(#:modules ((guix build utils) (guix build union))
242 #:builder
243 (begin
244 (use-modules (guix build utils) (guix build union) (srfi srfi-26))
245 (union-build (assoc-ref %outputs "out")
246 (map (lambda (input) (cdr input))
247 %build-inputs))
248 #t)))
249 (inputs
250 `(("postgresql" ,postgresql)
251 ,@(map (lambda (extension) (list "extension" extension))
252 extension-packages))))))
253
254(define postgresql-activation
255 (match-lambda
256 (($ <postgresql-configuration> postgresql port locale config-file
257 log-directory data-directory
258 extension-packages)
259 #~(begin
260 (use-modules (guix build utils)
261 (ice-9 match))
262
263 (let ((user (getpwnam "postgres")))
264 ;; Create the socket directory.
265 (let ((socket-directory
266 #$(postgresql-config-file-socket-directory config-file)))
267 (when (string? socket-directory)
268 (mkdir-p socket-directory)
269 (chown socket-directory (passwd:uid user) (passwd:gid user))))
270
271 ;; Create the log directory.
272 (when (string? #$log-directory)
273 (mkdir-p #$log-directory)
274 (chown #$log-directory (passwd:uid user) (passwd:gid user)))
275
276 (unless (file-exists? #$data-directory)
277 (let ((initdb (string-append
278 #$(final-postgresql postgresql
279 extension-packages)
280 "/bin/initdb"))
281 (initdb-args
282 (append
283 (if #$locale
284 (list (string-append "--locale=" #$locale))
285 '()))))
286 ;; Create db state directory.
287 (mkdir-p #$data-directory)
288 (chown #$data-directory (passwd:uid user) (passwd:gid user))
289
290 ;; Drop privileges and init state directory in a new
291 ;; process. Wait for it to finish before proceeding.
292 (match (primitive-fork)
293 (0
294 ;; Exit with a non-zero status code if an exception is
295 ;; thrown.
296 (dynamic-wind
297 (const #t)
298 (lambda ()
299 (setgid (passwd:gid user))
300 (setuid (passwd:uid user))
301 (apply execl initdb
302 initdb "-D" #$data-directory
303 initdb-args))
304 (lambda ()
305 (primitive-exit 1))))
306 (pid (waitpid pid))))))))))
307
308(define postgresql-shepherd-service
309 (match-lambda
310 (($ <postgresql-configuration> postgresql port locale config-file
311 log-directory data-directory
312 extension-packages)
313 (let* ((pg_ctl-wrapper
314 ;; Wrapper script that switches to the 'postgres' user before
315 ;; launching daemon.
316 (program-file
317 "pg_ctl-wrapper"
318 #~(begin
319 (use-modules (ice-9 match)
320 (ice-9 format))
321 (match (command-line)
322 ((_ mode)
323 (let ((user (getpwnam "postgres"))
324 (pg_ctl #$(file-append
325 (final-postgresql postgresql
326 extension-packages)
327 "/bin/pg_ctl"))
328 (options (format #f "--config-file=~a -p ~d"
329 #$config-file #$port)))
330 (setgid (passwd:gid user))
331 (setuid (passwd:uid user))
332 (execl pg_ctl pg_ctl "-D" #$data-directory
333 #$@(if (string? log-directory)
334 (list "-l"
335 (string-append log-directory
336 "/pg_ctl.log"))
337 '())
338 "-o" options
339 mode)))))))
340 (pid-file (in-vicinity data-directory "postmaster.pid"))
341 (action (lambda args
342 #~(lambda _
343 (invoke #$pg_ctl-wrapper #$@args)
344 (match '#$args
345 (("start")
346 (call-with-input-file #$pid-file read))
347 (_ #f))))))
348 (list (shepherd-service
349 (provision '(postgres postgresql))
350 (documentation "Run the PostgreSQL daemon.")
351 (requirement '(user-processes loopback syslogd))
352 (modules `((ice-9 match)
353 ,@%default-modules))
354 (actions (list (shepherd-configuration-action config-file)))
355 (start (action "start"))
356 (stop (action "stop"))))))))
357
358(define postgresql-service-type
359 (service-type
360 (name 'postgresql)
361 (extensions
362 (list (service-extension shepherd-root-service-type
363 postgresql-shepherd-service)
364 (service-extension activation-service-type
365 postgresql-activation)
366 (service-extension account-service-type
367 create-postgresql-account)
368 (service-extension
369 profile-service-type
370 (compose list postgresql-configuration-postgresql))))
371 (description "Run the PostgreSQL database server.")))
372
373(define-record-type* <postgresql-role>
374 postgresql-role make-postgresql-role
375 postgresql-role?
376 (name postgresql-role-name) ;string
377 (password-file postgresql-role-password-file ;string
378 (default #f))
379 (permissions postgresql-role-permissions
380 (default '(createdb login))) ;list
381 (create-database? postgresql-role-create-database? ;boolean
382 (default #f))
383 (encoding postgresql-role-encoding ;string
384 (default "UTF8"))
385 (collation postgresql-role-collation ;string
386 (default "en_US.utf8"))
387 (ctype postgresql-role-ctype ;string
388 (default "en_US.utf8"))
389 (template postgresql-role-template ;string
390 (default "template1")))
391
392(define %default-postgresql-role-shepherd-requirement
393 '(user-processes postgres))
394
395(define %postgresql-role-runtime-dir
396 "/run/postgresql-role")
397
398(define-record-type* <postgresql-role-configuration>
399 postgresql-role-configuration make-postgresql-role-configuration
400 postgresql-role-configuration?
401 (shepherd-requirement
402 postgresql-role-configuration-shepherd-requirement ;list-of-symbols
403 (default %default-postgresql-role-shepherd-requirement))
404 (host postgresql-role-configuration-host ;string
405 (default "/var/run/postgresql"))
406 (log postgresql-role-configuration-log ;string
407 (default "/var/log/postgresql_roles.log"))
408 (roles postgresql-role-configuration-roles
409 (default '()))) ;list
410
411(define (postgresql-create-roles config)
412 ;; See: https://www.postgresql.org/docs/current/sql-createrole.html for the
413 ;; complete permissions list.
414 (define (format-permissions permissions)
415 (let ((dict '(bypassrls createdb createrole login replication superuser)))
416 (string-join (filter-map (lambda (permission)
417 (and (member permission dict)
418 (string-upcase
419 (symbol->string permission))))
420 permissions)
421 " ")))
422
423 (define (role-has-password-file? role)
424 (define password-file
425 (postgresql-role-password-file role))
426 (and (string? password-file)
427 (not (string-null? password-file))))
428
429 (define (roles->queries roles)
430 (apply mixed-text-file "queries"
431 (append-map
432 (lambda (role)
433 (match-record role <postgresql-role>
434 (name permissions create-database? encoding collation ctype
435 template)
436 `("SELECT NOT(EXISTS(SELECT 1 FROM pg_catalog.pg_roles WHERE \
437rolname = '" ,name "')) as not_exists;\n"
438"\\gset\n"
439"\\if :not_exists\n"
440"CREATE ROLE \"" ,name "\""
441" WITH " ,(format-permissions permissions)
442";\n"
443,@(if create-database?
444 `("CREATE DATABASE \"" ,name "\""
445 " OWNER \"" ,name "\"\n"
446 " ENCODING '" ,encoding "'\n"
447 " LC_COLLATE '" ,collation "'\n"
448 " LC_CTYPE '" ,ctype "'\n"
449 " TEMPLATE " ,template ";")
450 '())
451"\\endif\n")))
452 roles)))
453
454 (let ((host (postgresql-role-configuration-host config))
455 (roles (postgresql-role-configuration-roles config)))
456 (program-file "run-queries"
457 (with-imported-modules (source-module-closure
458 '((guix build utils)))
459 #~(begin
460 (use-modules (guix build utils)
461 (ice-9 format)
462 (ice-9 match)
463 (ice-9 textual-ports)
464 (srfi srfi-1))
465 (define role->password-statement
466 (match-lambda
467 (#f "")
468 ((name file)
469 (if (file-exists? file)
470 (format
471 #f "ALTER ROLE ~a WITH ENCRYPTED PASSWORD '~a';"
472 name
473 (call-with-input-file file get-string-all))
474 (begin
475 (format (current-error-port)
476 (string-append
477 "~a does not exist! ~a's "
478 "password will not be set.")
479 file name)
480 "")))))
481
482 (define (create-password-query roles)
483 (define password-query
484 (string-append
485 #$%postgresql-role-runtime-dir "/set-passwords.sql"))
486 (with-output-to-file password-query
487 (lambda _
488 (display
489 (string-join
490 `("BEGIN;"
491 ;; Avoid leaking unencrypted password in the logs.
492 "SET LOCAL log_statement = 'none';"
493 ,@(map role->password-statement roles)
494 "COMMIT;\n")
495 "\n"))))
496 password-query)
497
498 (let* ((psql #$(file-append postgresql "/bin/psql"))
499 (roles
500 (list
501 #$@(map
502 (lambda (role)
503 (and (role-has-password-file? role)
504 #~'(#$(postgresql-role-name role)
505 #$(postgresql-role-password-file role))))
506 roles)))
507 (any-password? (any identity roles)))
508
509 (invoke psql "-a" "-h" #$host "-f" #$(roles->queries roles))
510
511 (when any-password?
512 ;; This way passwords do not leak to the command line.
513 (let ((password-query (create-password-query roles)))
514 (invoke psql "-h" #$host "-f" password-query)
515 (delete-file password-query)))))))))
516
517(define (postgresql-role-shepherd-service config)
518 (match-record config <postgresql-role-configuration>
519 (log shepherd-requirement)
520 (list (shepherd-service
521 (requirement shepherd-requirement)
522 (provision '(postgres-roles))
523 (one-shot? #t)
524 (start
525 #~(lambda args
526 (zero? (spawn-command
527 (list #$(postgresql-create-roles config))
528 #:user "postgres"
529 #:group "postgres"
530 ;; XXX: As of Shepherd 1.0.2, #:log-file is not
531 ;; supported.
532 ;; #:log-file #$log
533 ))))
534 (documentation "Create PostgreSQL roles.")))))
535
536(define %postgresql-role-file-systems
537 (list (file-system
538 (device "none")
539 (mount-point %postgresql-role-runtime-dir)
540 (type "tmpfs"))))
541
542(define postgresql-role-service-type
543 (service-type (name 'postgresql-role)
544 (extensions
545 (list (service-extension shepherd-root-service-type
546 postgresql-role-shepherd-service)
547 (service-extension file-system-service-type
548 (const %postgresql-role-file-systems))))
549 (compose concatenate)
550 (extend (lambda (config extended-roles)
551 (match-record config <postgresql-role-configuration>
552 (host roles)
553 (postgresql-role-configuration
554 (inherit config)
555 (host host)
556 (roles (append roles extended-roles))))))
557 (default-value (postgresql-role-configuration))
558 (description "Ensure the specified PostgreSQL roles are
559created after the PostgreSQL database is started.")))
560
561
562;;;
563;;; Memcached
564;;;
565
566(define-record-type* <memcached-configuration>
567 memcached-configuration make-memcached-configuration
568 memcached-configuration?
569 (memcached memcached-configuration-memcached ;file-like
570 (default memcached))
571 (interfaces memcached-configuration-interfaces
572 (default '("0.0.0.0")))
573 (tcp-port memcached-configuration-tcp-port
574 (default 11211))
575 (udp-port memcached-configuration-udp-port
576 (default 11211))
577 (additional-options memcached-configuration-additional-options
578 (default '())))
579
580(define %memcached-accounts
581 (list (user-group (name "memcached") (system? #t))
582 (user-account
583 (name "memcached")
584 (group "memcached")
585 (system? #t)
586 (comment "Memcached server user")
587 (home-directory "/var/empty")
588 (shell (file-append shadow "/sbin/nologin")))))
589
590(define memcached-activation
591 #~(begin
592 (use-modules (guix build utils))
593 (let ((user (getpwnam "memcached")))
594 (mkdir-p "/var/run/memcached")
595 (chown "/var/run/memcached"
596 (passwd:uid user) (passwd:gid user)))))
597
598(define memcached-shepherd-service
599 (match-lambda
600 (($ <memcached-configuration> memcached interfaces tcp-port udp-port
601 additional-options)
602 (list (shepherd-service
603 (provision '(memcached))
604 (documentation "Run the Memcached daemon.")
605 (requirement '(user-processes loopback))
606 (start #~(make-forkexec-constructor
607 `(#$(file-append memcached "/bin/memcached")
608 "-l" #$(string-join interfaces ",")
609 "-p" #$(number->string tcp-port)
610 "-U" #$(number->string udp-port)
611 "--daemon"
612 ;; Memcached changes to the memcached user prior to
613 ;; writing the pid file, so write it to a directory
614 ;; that memcached owns.
615 "-P" "/var/run/memcached/pid"
616 "-u" "memcached"
617 ,#$@additional-options)
618 #:log-file "/var/log/memcached"
619 #:pid-file "/var/run/memcached/pid"))
620 (stop #~(make-kill-destructor)))))))
621
622(define memcached-service-type
623 (service-type (name 'memcached)
624 (extensions
625 (list (service-extension shepherd-root-service-type
626 memcached-shepherd-service)
627 (service-extension activation-service-type
628 (const memcached-activation))
629 (service-extension account-service-type
630 (const %memcached-accounts))))
631 (default-value (memcached-configuration))
632 (description "Run @command{memcached}, a daemon that provides
633an in-memory caching service, intended for use by dynamic web
634applications.")))
635
636
637;;;
638;;; MySQL.
639;;;
640
641(define-record-type* <mysql-configuration>
642 mysql-configuration make-mysql-configuration
643 mysql-configuration?
644 (mysql mysql-configuration-mysql (default mariadb))
645 (bind-address mysql-configuration-bind-address (default "127.0.0.1"))
646 (port mysql-configuration-port (default 3306))
647 (socket mysql-configuration-socket (default "/run/mysqld/mysqld.sock"))
648 (datadir mysql-configuration-datadir (default "/var/lib/mysql"))
649 (extra-content mysql-configuration-extra-content (default ""))
650 (extra-environment mysql-configuration-extra-environment (default #~'()))
651 (auto-upgrade? mysql-configuration-auto-upgrade? (default #t)))
652
653(define %mysql-accounts
654 (list (user-group
655 (name "mysql")
656 (system? #t))
657 (user-account
658 (name "mysql")
659 (group "mysql")
660 (system? #t)
661 (home-directory "/var/empty")
662 (shell (file-append shadow "/sbin/nologin")))))
663
664(define mysql-configuration-file
665 (match-lambda
666 (($ <mysql-configuration> mysql bind-address port socket datadir extra-content)
667 (mixed-text-file "my.cnf" "[mysqld]
668datadir=" datadir "
669socket=" socket "
670bind-address=" bind-address "
671port=" (number->string port) "
672" extra-content "
673"))))
674
675(define (mysqld-wrapper config)
676 "Start mysqld, and initialize the system tables if necessary."
677 (program-file
678 "mysqld-wrapper"
679 (with-imported-modules (source-module-closure
680 '((guix build utils)))
681 (let ((mysql (mysql-configuration-mysql config))
682 (datadir (mysql-configuration-datadir config))
683 (my.cnf (mysql-configuration-file config)))
684 #~(begin
685 (use-modules (guix build utils))
686 (let* ((mysqld (string-append #$mysql "/bin/mysqld"))
687 (user (getpwnam "mysql"))
688 (uid (passwd:uid user))
689 (gid (passwd:gid user))
690 (rundir "/run/mysqld"))
691 (mkdir-p #$datadir)
692 (chown #$datadir uid gid)
693 (mkdir-p rundir)
694 (chown rundir uid gid)
695 (unless (file-exists? (string-append #$datadir "/mysql"))
696 (let ((init (system* #$(mysql-install config))))
697 (unless (= 0 (status:exit-val init))
698 (throw 'system-error "MySQL initialization failed."))))
699 ;; Drop privileges and start the server.
700 (setgid gid) (setuid uid)
701 (execl mysqld mysqld
702 (string-append "--defaults-file=" #$my.cnf))))))))
703
704(define (mysql-shepherd-service config)
705 (list (shepherd-service
706 (provision '(mysql))
707 (requirement '(user-processes))
708 (documentation "Run the MySQL server.")
709 (actions (list (shepherd-configuration-action
710 (mysql-configuration-file config))))
711 (start (let ((mysql (mysql-configuration-mysql config))
712 (extra-env (mysql-configuration-extra-environment config))
713 (my.cnf (mysql-configuration-file config)))
714 #~(make-forkexec-constructor
715 (list #$(mysqld-wrapper config))
716 #:log-file "/var/log/mysqld.log"
717 #:environment-variables #$extra-env)))
718 (stop #~(make-kill-destructor)))))
719
720(define (mysql-install config)
721 "Install MySQL system database and secure the installation."
722 (let ((mysql (mysql-configuration-mysql config))
723 (my.cnf (mysql-configuration-file config)))
724 (program-file
725 "mysql-install"
726 (with-imported-modules (source-module-closure
727 '((guix build utils)))
728 #~(begin
729 (use-modules (guix build utils))
730 ;; Make sed, mkdir, uname, etc available for mariadb-install-db.
731 (set-path-environment-variable "PATH" '("bin")
732 (list #$sed #$coreutils))
733 (if (string=? "mariadb" #$(package-name mysql))
734 ;; For MariaDB.
735 (system* #$(file-append mysql "/bin/mariadb-install-db")
736 (string-append "--defaults-file=" #$my.cnf)
737 "--skip-test-db"
738 "--user=mysql")
739 ;; For MySQL.
740 (system* #$(file-append mysql "/bin/mysqld")
741 (string-append "--defaults-file=" #$my.cnf)
742 "--initialize"
743 "--user=mysql")))))))
744
745(define (mysql-upgrade-wrapper config)
746 ;; The MySQL socket and PID file may appear before the server is ready to
747 ;; accept connections. Ensure the socket is responsive before attempting
748 ;; to run the upgrade script.
749 (let ((mysql (mysql-configuration-mysql config))
750 (socket-file (mysql-configuration-socket config))
751 (config-file (mysql-configuration-file config)))
752 (program-file
753 "mysql-upgrade-wrapper"
754 #~(begin
755 (let ((mysql-upgrade #$(file-append mysql "/bin/mysql_upgrade"))
756 (timeout 20))
757 (begin
758 (let loop ((i 0))
759 (catch 'system-error
760 (lambda ()
761 (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
762 (connect sock AF_UNIX #$socket-file)
763 (close-port sock)
764 ;; The socket is ready!
765 (execl mysql-upgrade mysql-upgrade
766 (string-append "--defaults-file=" #$config-file)
767 "--user=mysql")))
768 (lambda args
769 (if (< i timeout)
770 (begin
771 (sleep 1)
772 (loop (+ 1 i)))
773 ;; No luck, give up.
774 (throw 'timeout-error
775 "MySQL server did not appear in time!")))))))))))
776
777(define (mysql-upgrade-shepherd-service config)
778 (list (shepherd-service
779 (provision '(mysql-upgrade))
780 (requirement '(mysql))
781 (one-shot? #t)
782 (documentation "Upgrade MySQL database schemas.")
783 (start #~(make-forkexec-constructor
784 (list #$(mysql-upgrade-wrapper config))
785 #:user "mysql" #:group "mysql"
786 #:log-file "/var/log/mysql_upgrade.log")))))
787
788(define (mysql-shepherd-services config)
789 (let ((mysql-services (mysql-shepherd-service config)))
790 (if (mysql-configuration-auto-upgrade? config)
791 (append mysql-services
792 (mysql-upgrade-shepherd-service config))
793 mysql-services)))
794
795(define mysql-service-type
796 (service-type
797 (name 'mysql)
798 (extensions
799 (list (service-extension account-service-type
800 (const %mysql-accounts))
801 (service-extension shepherd-root-service-type
802 mysql-shepherd-services)))
803 (default-value (mysql-configuration))
804 (description "Run the MySQL or MariaDB database server,
805@command{mysqld}.")))
806
807
808;;;
809;;; Redis
810;;;
811
812(define-maybe string)
813
814(define (uglify-field-name field-name)
815 (string-delete #\? (symbol->string field-name)))
816
817(define (serialize-field field-name val)
818 #~(format #f "~a=~a\n" #$(uglify-field-name field-name) #$val))
819
820(define serialize-string serialize-field)
821(define serialize-number serialize-field)
822
823(define-configuration redis-configuration
824 (redis (package redis)
825 "The Redis package to use.")
826 (bind (string "127.0.0.1")
827 "Network interface on which to listen.")
828 (port (number 6379)
829 "Port on which to accept connections on,
830a value of 0 will disable listening on a TCP socket.")
831 (working-directory (string "/var/lib/redis")
832 "Directory in which to store the
833database and related files.")
834 (config-file maybe-string "Default location for config file."))
835
836(define (default-redis.conf bind port working-directory)
837 (mixed-text-file "redis.conf"
838 "bind " bind "\n"
839 "port " (number->string port) "\n"
840 "dir " working-directory "\n"
841 "daemonize no\n"))
842
843(define %redis-accounts
844 (list (user-group (name "redis") (system? #t))
845 (user-account
846 (name "redis")
847 (group "redis")
848 (system? #t)
849 (comment "Redis server user")
850 (home-directory "/var/empty")
851 (shell (file-append shadow "/sbin/nologin")))))
852
853(define redis-activation
854 (match-lambda
855 (($ <redis-configuration> redis bind port working-directory config-file)
856 #~(begin
857 (use-modules (guix build utils)
858 (ice-9 match))
859
860 (let ((user (getpwnam "redis")))
861 (mkdir-p #$working-directory)
862 (chown #$working-directory (passwd:uid user) (passwd:gid user)))))))
863
864(define redis-shepherd-service
865 (match-lambda
866 (($ <redis-configuration> redis bind port working-directory config-file)
867 (let ((config-file
868 (if (maybe-value-set? config-file)
869 config-file
870 (default-redis.conf bind port working-directory))))
871 (list (shepherd-service
872 (provision '(redis))
873 (documentation "Run the Redis daemon.")
874 (requirement '(user-processes syslogd))
875 (actions (list (shepherd-configuration-action config-file)))
876 (start #~(make-forkexec-constructor
877 '(#$(file-append redis "/bin/redis-server")
878 #$config-file)
879 #:user "redis"
880 #:group "redis"))
881 (stop #~(make-kill-destructor))))))))
882
883(define redis-service-type
884 (service-type (name 'redis)
885 (extensions
886 (list (service-extension shepherd-root-service-type
887 redis-shepherd-service)
888 (service-extension activation-service-type
889 redis-activation)
890 (service-extension account-service-type
891 (const %redis-accounts))))
892 (default-value (redis-configuration))
893 (description "Run Redis, a caching key/value store.")))
894
895;;;
896;;; Generate documentation.
897;;;
898(define (redis-generate-doc)
899 (configuration->documentation 'redis-configuration))