projects/guix·blob master · refs

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