projects/guix·blob bootstrap-team · refs

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