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