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