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