projects/guix·blob java-team · refs

2914 lines · 116.3 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2015 David Thompson <[email protected]>
3;;; Copyright © 2015-2023, 2025-2026 Ludovic Courtès <[email protected]>
4;;; Copyright © 2016 Nikita <[email protected]>
5;;; Copyright © 2016, 2017, 2018 Julien Lepiller <[email protected]>
6;;; Copyright © 2017, 2018, 2019 Christopher Baines <[email protected]>
7;;; Copyright © 2017 nee <[email protected]>
8;;; Copyright © 2017, 2018 Clément Lassieur <[email protected]>
9;;; Copyright © 2018 Pierre-Antoine Rouby <[email protected]>
10;;; Copyright © 2018 Marius Bakke <[email protected]>
11;;; Copyright © 2019, 2020 Florian Pelz <[email protected]>
12;;; Copyright © 2020, 2022 Ricardo Wurmus <[email protected]>
13;;; Copyright © 2020 Tobias Geerinckx-Rice <[email protected]>
14;;; Copyright © 2020, 2025, 2026 Arun Isaac <[email protected]>
15;;; Copyright © 2020 Oleg Pykhalov <[email protected]>
16;;; Copyright © 2020, 2021 Alexandru-Sergiu Marton <[email protected]>
17;;; Copyright © 2022 Simen Endsjø <[email protected]>
18;;; Copyright © 2023 Bruno Victal <[email protected]>
19;;; Copyright © 2023 Miguel Ángel Moreno <[email protected]>
20;;; Copyright © 2024 Leo Nikkilä <[email protected]>
21;;; Copyright © 2025-2026 Maxim Cournoyer <[email protected]>
22;;; Copyright © 2025 Rodion Goritskov <[email protected]>
23;;; Copyright © 2026 Fabio Natali <[email protected]>
24;;;
25;;; This file is part of GNU Guix.
26;;;
27;;; GNU Guix is free software; you can redistribute it and/or modify it
28;;; under the terms of the GNU General Public License as published by
29;;; the Free Software Foundation; either version 3 of the License, or (at
30;;; your option) any later version.
31;;;
32;;; GNU Guix is distributed in the hope that it will be useful, but
33;;; WITHOUT ANY WARRANTY; without even the implied warranty of
34;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35;;; GNU General Public License for more details.
36;;;
37;;; You should have received a copy of the GNU General Public License
38;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
39
40(define-module (gnu services web)
41 #:use-module (gnu services)
42 #:use-module (gnu services shepherd)
43 #:use-module (gnu services admin)
44 #:use-module (gnu services configuration)
45 #:use-module (gnu services databases)
46 #:use-module (gnu services getmail)
47 #:use-module (gnu services mail)
48 #:use-module (gnu system file-systems)
49 #:use-module (gnu system pam)
50 #:use-module (gnu system shadow)
51 #:use-module (gnu packages admin)
52 #:use-module (gnu packages base)
53 #:use-module (gnu packages databases)
54 #:use-module (gnu packages web)
55 #:use-module (gnu packages patchutils)
56 #:use-module (gnu packages php)
57 #:use-module (gnu packages python)
58 #:use-module (gnu packages python-web)
59 #:use-module (gnu packages rsync)
60 #:use-module (gnu packages gnupg)
61 #:use-module (gnu packages golang-xyz)
62 #:use-module (gnu packages guile)
63 #:use-module (gnu packages logging)
64 #:use-module (gnu packages mail)
65 #:use-module (gnu packages rust-apps)
66 #:autoload (guix i18n) (G_)
67 #:autoload (gnu build linux-container) (%namespaces)
68 #:use-module (guix diagnostics)
69 #:use-module (guix gexp)
70 #:use-module (guix least-authority)
71 #:use-module (guix modules)
72 #:use-module (guix packages)
73 #:use-module (guix profiles)
74 #:use-module (guix records)
75 #:use-module (guix search-paths)
76 #:use-module (guix utils)
77 #:use-module ((guix store) #:select (text-file))
78 #:use-module ((guix utils) #:select (version-major))
79 #:use-module ((guix packages) #:select (package-version))
80 #:use-module (srfi srfi-1)
81 #:use-module (srfi srfi-9)
82 #:use-module (srfi srfi-26)
83 #:use-module (srfi srfi-34)
84 #:use-module (ice-9 match)
85 #:use-module (ice-9 format)
86 #:use-module (ice-9 regex)
87 #:use-module (web uri)
88 #:export (httpd-configuration
89 httpd-configuration?
90 httpd-configuration-package
91 httpd-configuration-pid-file
92 httpd-configuration-config
93
94 httpd-virtualhost
95 httpd-virtualhost?
96 httpd-virtualhost-addresses-and-ports
97 httpd-virtualhost-contents
98
99 httpd-config-file
100 httpd-config-file?
101 httpd-config-file-modules
102 httpd-config-file-server-root
103 httpd-config-file-server-name
104 httpd-config-file-listen
105 httpd-config-file-pid-file
106 httpd-config-file-error-log
107 httpd-config-file-user
108 httpd-config-file-group
109
110 httpd-module
111 httpd-module?
112 %default-httpd-modules
113
114 httpd-service-type
115
116 nginx-configuration
117 nginx-configuration?
118 nginx-configuration-nginx
119 nginx-configuration-shepherd-requirement
120 nginx-configuration-log-directory
121 nginx-configuration-log-format
122 nginx-configuration-log-formats
123 nginx-configuration-log-level
124 nginx-configuration-run-directory
125 nginx-configuration-server-blocks
126 nginx-configuration-upstream-blocks
127 nginx-configuration-server-names-hash-bucket-size
128 nginx-configuration-server-names-hash-bucket-max-size
129 nginx-configuration-stream
130 nginx-configuration-modules
131 nginx-configuration-global-directives
132 nginx-configuration-extra-content
133 nginx-configuration-file
134
135 nginx-log-format-configuration
136 nginx-log-format-configuration?
137 nginx-log-format-configuration-name
138 nginx-log-format-configuration-escape
139 nginx-log-format-configuration-format
140
141 nginx-server-configuration
142 nginx-server-configuration?
143 nginx-server-configuration-listen
144 nginx-server-configuration-server-name
145 nginx-server-configuration-root
146 nginx-server-configuration-locations
147 nginx-server-configuration-index
148 nginx-server-configuration-try-files
149 nginx-server-configuration-ssl-certificate
150 nginx-server-configuration-ssl-certificate-key
151 nginx-server-configuration-server-tokens?
152 nginx-server-configuration-raw-content
153
154 nginx-upstream-configuration
155 nginx-upstream-configuration?
156 nginx-upstream-configuration-name
157 nginx-upstream-configuration-servers
158 nginx-upstream-configuration-extra-content
159
160 nginx-location-configuration
161 nginx-location-configuration?
162 nginx-location-configuration-uri
163 nginx-location-configuration-body
164
165 nginx-named-location-configuration
166 nginx-named-location-configuration?
167 nginx-named-location-configuration-name
168 nginx-named-location-configuration-body
169
170 nginx-stream-configuration
171 nginx-stream-configuration-server-blocks
172 nginx-stream-configuraiton-upstream-blocks
173 nginx-stream-configuration-extra-content
174
175 nginx-service
176 nginx-service-type
177
178 gunicorn-app
179 gunicorn-app-environment-variables
180 gunicorn-app-extra-cli-arguments
181 gunicorn-app-group
182 gunicorn-app-mappings
183 gunicorn-app-name
184 gunicorn-app-package
185 gunicorn-app-control-socket
186 gunicorn-app-sockets
187 gunicorn-app-timeout
188 gunicorn-app-user
189 gunicorn-app-workers
190 gunicorn-app-wsgi-app-module
191 gunicorn-app?
192 gunicorn-configuration
193 gunicorn-configuration-apps
194 gunicorn-configuration-package
195 gunicorn-configuration?
196 gunicorn-service-type
197
198 fcgiwrap-configuration
199 fcgiwrap-configuration?
200 fcgiwrap-service-type
201
202 php-fpm-configuration
203 make-php-fpm-configuration
204 php-fpm-configuration?
205 php-fpm-configuration-php
206 php-fpm-configuration-socket
207 php-fpm-configuration-user
208 php-fpm-configuration-group
209 php-fpm-configuration-socket-user
210 php-fpm-configuration-socket-group
211 php-fpm-configuration-pid-file
212 php-fpm-configuration-log-file
213 php-fpm-configuration-process-manager
214 php-fpm-configuration-display-errors
215 php-fpm-configuration-timezone
216 php-fpm-configuration-workers-log-file
217 php-fpm-configuration-file
218 php-fpm-configuration-php-ini-file
219
220 php-fpm-dynamic-process-manager-configuration
221 make-php-fpm-dynamic-process-manager-configuration
222 php-fpm-dynamic-process-manager-configuration?
223 php-fpm-dynamic-process-manager-configuration-max-children
224 php-fpm-dynamic-process-manager-configuration-start-servers
225 php-fpm-dynamic-process-manager-configuration-min-spare-servers
226 php-fpm-dynamic-process-manager-configuration-max-spare-servers
227
228 php-fpm-static-process-manager-configuration
229 make-php-fpm-static-process-manager-configuration
230 php-fpm-static-process-manager-configuration?
231 php-fpm-static-process-manager-configuration-max-children
232
233 php-fpm-on-demand-process-manager-configuration
234 make-php-fpm-on-demand-process-manager-configuration
235 php-fpm-on-demand-process-manager-configuration?
236 php-fpm-on-demand-process-manager-configuration-max-children
237 php-fpm-on-demand-process-manager-configuration-process-idle-timeout
238
239 php-fpm-service-type
240 nginx-php-location
241
242 cat-avatar-generator-service
243
244 hpcguix-web-configuration
245 hpcguix-web-configuration?
246 hpcguix-web-service-type
247
248 anonip-configuration
249 anonip-configuration?
250 anonip-configuration-anonip
251 anonip-configuration-input
252 anonip-configuration-output
253 anonip-configuration-debug?
254 anonip-configuration-skip-private?
255 anonip-configuration-column
256 anonip-configuration-replacement
257 anonip-configuration-ipv4mask
258 anonip-configuration-ipv6mask
259 anonip-configuration-increment
260 anonip-configuration-delimiter
261 anonip-configuration-regex
262 anonip-service-type
263
264 varnish-configuration
265 varnish-configuration?
266 varnish-configuration-package
267 varnish-configuration-name
268 varnish-configuration-backend
269 varnish-configuration-vcl
270 varnish-configuration-listen
271 varnish-configuration-storage
272 varnish-configuration-parameters
273 varnish-configuration-extra-options
274
275 varnish-service-type
276
277 whoogle-service-type
278 whoogle-configuration
279 whoogle-configuration-package
280 whoogle-configuration-host
281 whoogle-configuration-port
282 whoogle-configuration-environment-variables
283
284 go-webdav-service-type
285
286 patchwork-database-configuration
287 patchwork-database-configuration?
288 patchwork-database-configuration-engine
289 patchwork-database-configuration-name
290 patchwork-database-configuration-user
291 patchwork-database-configuration-password
292 patchwork-database-configuration-host
293 patchwork-database-configuration-port
294
295 patchwork-settings-module
296 patchwork-settings-module?
297 patchwork-settings-module-database-configuration
298 patchwork-settings-module-secret-key
299 patchwork-settings-module-allowed-hosts
300 patchwork-settings-module-default-from-email
301 patchwork-settings-module-static-url
302 patchwork-settings-module-admins
303 patchwork-settings-module-debug?
304 patchwork-settings-module-enable-rest-api?
305 patchwork-settings-module-enable-xmlrpc?
306 patchwork-settings-module-force-https-links?
307 patchwork-settings-module-extra-settings
308
309 patchwork-configuration
310 patchwork-configuration?
311 patchwork-configuration-patchwork
312 patchwork-configuration-settings-module
313 patchwork-configuration-domain
314
315 patchwork-virtualhost
316 patchwork-service-type
317
318 sogogi-service-type
319 sogogi-configuration
320 sogogi-config-file
321 sogogi-configuration?
322 sogogi-user
323 sogogi-user?
324 sogogi-location
325 sogogi-location?
326
327 mumi-configuration
328 mumi-configuration?
329 mumi-configuration-mumi
330 mumi-configuration-data-directory
331 mumi-configuration-rsync-remote
332 mumi-configuration-rsync-flags
333 mumi-configuration-rsync-and-index-event
334 mumi-configuration-mailer?
335 mumi-configuration-sender
336 mumi-configuration-smtp
337 mumi-configuration-packages
338
339 mumi-package-configuration
340 mumi-package-configuration?
341 mumi-package-configuration-name
342 mumi-package-configuration-display-name
343 mumi-package-configuration-debbugs-packages
344 mumi-package-configuration-bug-email-address
345 mumi-package-configuration-patch-email-address
346 mumi-package-configuration-file-tags
347
348 mumi-service-type
349
350 gmnisrv-configuration
351 gmnisrv-configuration?
352 gmnisrv-configuration-package
353 gmnisrv-configuration-config-file
354
355 gmnisrv-service-type
356
357 agate-configuration
358 agate-configuration?
359 agate-configuration-package
360 agate-configuration-content
361 agate-configuration-certs
362 agate-configuration-addr
363 agate-configuration-hostname
364 agate-configuration-lang
365 agate-configuration-only-tls13
366 agate-configuration-serve-secret
367 agate-configuration-central-conf
368 agate-configuration-ed25519
369 agate-configuration-skip-port-check
370 agate-configuration-log-ip
371 agate-configuration-user
372 agate-configuration-group
373 agate-configuration-log-file
374
375 agate-service-type
376
377 miniflux-configuration
378 miniflux-configuration?
379 miniflux-configuration-listen-address
380 miniflux-configuration-base-url
381 miniflux-configuration-create-administrator-account?
382 miniflux-configuration-administrator-account-name
383 miniflux-configuration-administrator-account-password
384 miniflux-configuration-run-migrations?
385 miniflux-configuration-database-url
386 miniflux-configuration-user
387 miniflux-configuration-group
388 miniflux-configuration-log-file
389 miniflux-configuration-extra-settings
390
391 miniflux-service-type))
392
393;;; Commentary:
394;;;
395;;; Web services.
396;;;
397;;; Code:
398
399(define-record-type* <httpd-module>
400 httpd-module make-httpd-module
401 httpd-module?
402 (name httpd-load-module-name)
403 (file httpd-load-module-file))
404
405;; Default modules for the httpd-service-type, taken from etc/httpd/httpd.conf
406;; file in the httpd package.
407(define %default-httpd-modules
408 (map (match-lambda
409 ((name file)
410 (httpd-module
411 (name name)
412 (file file))))
413 '(("authn_file_module" "modules/mod_authn_file.so")
414 ("authn_core_module" "modules/mod_authn_core.so")
415 ("authz_host_module" "modules/mod_authz_host.so")
416 ("authz_groupfile_module" "modules/mod_authz_groupfile.so")
417 ("authz_user_module" "modules/mod_authz_user.so")
418 ("authz_core_module" "modules/mod_authz_core.so")
419 ("access_compat_module" "modules/mod_access_compat.so")
420 ("auth_basic_module" "modules/mod_auth_basic.so")
421 ("reqtimeout_module" "modules/mod_reqtimeout.so")
422 ("filter_module" "modules/mod_filter.so")
423 ("mime_module" "modules/mod_mime.so")
424 ("log_config_module" "modules/mod_log_config.so")
425 ("env_module" "modules/mod_env.so")
426 ("headers_module" "modules/mod_headers.so")
427 ("setenvif_module" "modules/mod_setenvif.so")
428 ("version_module" "modules/mod_version.so")
429 ("unixd_module" "modules/mod_unixd.so")
430 ("status_module" "modules/mod_status.so")
431 ("autoindex_module" "modules/mod_autoindex.so")
432 ("dir_module" "modules/mod_dir.so")
433 ("alias_module" "modules/mod_alias.so"))))
434
435(define-record-type* <httpd-config-file>
436 httpd-config-file make-httpd-config-file
437 httpd-config-file?
438 (modules httpd-config-file-modules
439 (default %default-httpd-modules))
440 (server-root httpd-config-file-server-root
441 (default httpd))
442 (server-name httpd-config-file-server-name
443 (default #f))
444 (document-root httpd-config-file-document-root
445 (default "/srv/http"))
446 (listen httpd-config-file-listen
447 (default '("80")))
448 (pid-file httpd-config-file-pid-file
449 (default "/var/run/httpd"))
450 (error-log httpd-config-file-error-log
451 (default "/var/log/httpd/error_log"))
452 (user httpd-config-file-user
453 (default "httpd"))
454 (group httpd-config-file-group
455 (default "httpd"))
456 (extra-config httpd-config-file-extra-config
457 (default
458 (list "TypesConfig etc/httpd/mime.types"))))
459
460(define-gexp-compiler (httpd-config-file-compiler
461 (file <httpd-config-file>) system target)
462 (match file
463 (($ <httpd-config-file> load-modules server-root server-name
464 document-root listen pid-file error-log
465 user group extra-config)
466 (gexp->derivation
467 "httpd.conf"
468 #~(call-with-output-file (ungexp output "out")
469 (lambda (port)
470 (display
471 (string-append
472 (ungexp-splicing
473 `(,@(append-map
474 (match-lambda
475 (($ <httpd-module> name module)
476 `("LoadModule " ,name " " ,module "\n")))
477 load-modules)
478 ,@`("ServerRoot " ,server-root "\n")
479 ,@(if server-name
480 `("ServerName " ,server-name "\n")
481 '())
482 ,@`("DocumentRoot " ,document-root "\n")
483 ,@(append-map
484 (lambda (listen-value)
485 `("Listen " ,listen-value "\n"))
486 listen)
487 ,@(if pid-file
488 `("Pidfile " ,pid-file "\n")
489 '())
490 ,@(if error-log
491 `("ErrorLog " ,error-log "\n")
492 '())
493 ,@(if user
494 `("User " ,user "\n")
495 '())
496 ,@(if group
497 `("Group " ,group "\n")
498 '())
499 "\n\n"
500 ,@extra-config)))
501 port)))
502 #:local-build? #t))))
503
504(define-record-type <httpd-virtualhost>
505 (httpd-virtualhost addresses-and-ports contents)
506 httpd-virtualhost?
507 (addresses-and-ports httpd-virtualhost-addresses-and-ports)
508 (contents httpd-virtualhost-contents))
509
510(define-record-type* <httpd-configuration>
511 httpd-configuration make-httpd-configuration
512 httpd-configuration?
513 (package httpd-configuration-package
514 (default httpd))
515 (pid-file httpd-configuration-pid-file
516 (default "/var/run/httpd"))
517 (config httpd-configuration-config
518 (default (httpd-config-file))))
519
520(define %httpd-accounts
521 (list (user-group (name "httpd") (system? #t))
522 (user-account
523 (name "httpd")
524 (group "httpd")
525 (system? #t)
526 (comment "Apache HTTPD server user")
527 (home-directory "/var/empty")
528 (shell (file-append shadow "/sbin/nologin")))))
529
530(define httpd-shepherd-services
531 (match-lambda
532 (($ <httpd-configuration> package pid-file config)
533 (list (shepherd-service
534 (provision '(httpd))
535 (documentation "The Apache HTTP Server")
536 (requirement '(user-processes networking))
537 (start #~(make-forkexec-constructor
538 `(#$(file-append package "/bin/httpd")
539 #$@(if config
540 (list "-f" config)
541 '()))
542 #:pid-file #$pid-file))
543 (stop #~(make-kill-destructor)))))))
544
545(define httpd-activation
546 (match-lambda
547 (($ <httpd-configuration> package pid-file config)
548 (match-record
549 config
550 <httpd-config-file>
551 (error-log document-root)
552 #~(begin
553 (use-modules (guix build utils))
554
555 (mkdir-p #$(dirname error-log))
556 (mkdir-p #$document-root))))))
557
558(define (httpd-process-extensions original-config extension-configs)
559 (let ((config (httpd-configuration-config
560 original-config)))
561 (if (httpd-config-file? config)
562 (httpd-configuration
563 (inherit original-config)
564 (config
565 (httpd-config-file
566 (inherit config)
567 (extra-config
568 (append (httpd-config-file-extra-config config)
569 (append-map
570 (match-lambda
571 (($ <httpd-virtualhost>
572 addresses-and-ports
573 contents)
574 `(,(string-append
575 "\n<VirtualHost " addresses-and-ports ">\n")
576 ,@contents
577 "\n</VirtualHost>\n"))
578 ((? string? x)
579 `("\n" ,x "\n"))
580 ((? list? x)
581 `("\n" ,@x "\n")))
582 extension-configs)))))))))
583
584(define httpd-service-type
585 (service-type (name 'httpd)
586 (extensions
587 (list (service-extension shepherd-root-service-type
588 httpd-shepherd-services)
589 (service-extension activation-service-type
590 httpd-activation)
591 (service-extension account-service-type
592 (const %httpd-accounts))))
593 (compose concatenate)
594 (extend httpd-process-extensions)
595 (default-value
596 (httpd-configuration))
597 (description "Run the Apache httpd Web server.")))
598
599(define-record-type* <nginx-log-format-configuration>
600 nginx-log-format-configuration make-nginx-log-format-configuration
601 nginx-log-format-configuration?
602 (name nginx-log-format-configuration-name)
603 (escape nginx-log-format-configuration-escape
604 (sanitize assert-valid-log-format-escape)
605 (default 'default))
606 (format nginx-log-format-configuration-format))
607
608(define (assert-valid-log-format-escape escape)
609 "Ensure @var{escape} is one of @code{'default}, @code{'json}, or
610@code{'none}."
611 (unless (memq escape '(default json none))
612 (raise
613 (formatted-message (G_ "unknown log format escape '~a'~%") escape)))
614 escape)
615
616(define-record-type* <nginx-server-configuration>
617 nginx-server-configuration make-nginx-server-configuration
618 nginx-server-configuration?
619 (listen nginx-server-configuration-listen
620 (default '("80" "443 ssl")))
621 (server-name nginx-server-configuration-server-name
622 (default (list 'default)))
623 (root nginx-server-configuration-root
624 (default "/srv/http"))
625 (locations nginx-server-configuration-locations
626 (default '()))
627 (index nginx-server-configuration-index
628 (default (list "index.html")))
629 (try-files nginx-server-configuration-try-files
630 (default '()))
631 (ssl-certificate nginx-server-configuration-ssl-certificate
632 (default #f))
633 (ssl-certificate-key nginx-server-configuration-ssl-certificate-key
634 (default #f))
635 (server-tokens? nginx-server-configuration-server-tokens?
636 (default #f))
637 (raw-content nginx-server-configuration-raw-content
638 (default '())))
639
640(define-record-type* <nginx-upstream-configuration>
641 nginx-upstream-configuration make-nginx-upstream-configuration
642 nginx-upstream-configuration?
643 (name nginx-upstream-configuration-name)
644 (servers nginx-upstream-configuration-servers)
645 (extra-content nginx-upstream-configuration-extra-content
646 (default '())))
647
648(define-record-type* <nginx-location-configuration>
649 nginx-location-configuration make-nginx-location-configuration
650 nginx-location-configuration?
651 (uri nginx-location-configuration-uri
652 (default #f))
653 (body nginx-location-configuration-body))
654
655(define-record-type* <nginx-named-location-configuration>
656 nginx-named-location-configuration make-nginx-named-location-configuration
657 nginx-named-location-configuration?
658 (name nginx-named-location-configuration-name
659 (default #f))
660 (body nginx-named-location-configuration-body))
661
662(define-record-type* <nginx-stream-configuration>
663 nginx-stream-configuration make-nginx-stream-configuration
664 nginx-stream-configuration?
665 (upstream-blocks nginx-stream-configuration-upstream-blocks
666 (default '())) ;list of <nginx-upstream-configuration>
667 (server-blocks nginx-stream-configuration-server-blocks
668 (default '())) ;list of <nginx-server-configuration>
669 (extra-content nginx-stream-configuration-extra-content
670 (default '())))
671
672(define-record-type* <nginx-configuration>
673 nginx-configuration make-nginx-configuration
674 nginx-configuration?
675 (nginx nginx-configuration-nginx ;file-like
676 (default nginx))
677 (shepherd-requirement nginx-configuration-shepherd-requirement
678 (default '())) ;list of symbols
679 (log-directory nginx-configuration-log-directory ;string
680 (default "/var/log/nginx"))
681 (log-level nginx-configuration-log-level
682 (sanitize assert-valid-log-level)
683 (default 'error))
684 (log-format nginx-configuration-log-format ;symbol
685 (default 'combined))
686 (log-formats nginx-configuration-log-formats ;list of <nginx-log-format-configuration>
687 (default '()))
688 (run-directory nginx-configuration-run-directory ;string
689 (default "/var/run/nginx"))
690 (server-blocks nginx-configuration-server-blocks
691 (default '())) ;list of <nginx-server-configuration>
692 (upstream-blocks nginx-configuration-upstream-blocks
693 (default '())) ;list of <nginx-upstream-configuration>
694 (server-names-hash-bucket-size nginx-configuration-server-names-hash-bucket-size
695 (default #f))
696 (server-names-hash-bucket-max-size nginx-configuration-server-names-hash-bucket-max-size
697 (default #f))
698 (stream nginx-configuration-stream
699 (default #f)) ;#f | <nginx-stream-configuration>
700 (modules nginx-configuration-modules (default '()))
701 (global-directives nginx-configuration-global-directives
702 (default '((events . ()))))
703 (lua-package-path nginx-lua-package-path ;list of file-like
704 (default #f))
705 (lua-package-cpath nginx-lua-package-cpath ;list of file-like
706 (default #f))
707 (extra-content nginx-configuration-extra-content
708 (default ""))
709 (file nginx-configuration-file ;#f | string | file-like
710 (default #f)))
711
712(define (assert-valid-log-level level)
713 "Ensure @var{level} is one of @code{'debug}, @code{'info}, @code{'notice},
714@code{'warn}, @code{'error}, @code{'crit}, @code{'alert}, or @code{'emerg}."
715 (unless (memq level '(debug info notice warn error crit alert emerg))
716 (raise
717 (formatted-message (G_ "unknown log level '~a'~%") level)))
718 level)
719
720(define (config-domain-strings names)
721 "Return a string denoting the nginx config representation of NAMES, a list
722of domain names."
723 (map (match-lambda
724 ('default "_ ")
725 ((? string? str) (list str " ")))
726 names))
727
728(define (config-index-strings names)
729 "Return a string denoting the nginx config representation of NAMES, a list
730of index files."
731 (map (match-lambda
732 ((? string? str) (list str " ")))
733 names))
734
735(define (emit-load-module module)
736 (list "load_module " module ";\n"))
737
738(define emit-global-directive
739 (match-lambda
740 ((key . (? list? alist))
741 (format #f "~a { ~{~a~}}~%" key (map emit-global-directive alist)))
742 ((key . value)
743 (format #f "~a ~a;~%" key value))))
744
745(define emit-nginx-log-format-config
746 (match-lambda
747 (($ <nginx-log-format-configuration> name escape format)
748 (list " log_format " (symbol->string name) " escape="
749 (symbol->string escape) " " format ";\n"))))
750
751(define emit-nginx-location-config
752 (match-lambda
753 (($ <nginx-location-configuration> uri body)
754 (list
755 " location " uri " {\n"
756 (map (lambda (x) (list " " x "\n")) body)
757 " }\n"))
758 (($ <nginx-named-location-configuration> name body)
759 (list
760 " location @" name " {\n"
761 (map (lambda (x) (list " " x "\n")) body)
762 " }\n"))))
763
764(define* (emit-nginx-server-config server #:optional (context 'http))
765 (let ((listen (nginx-server-configuration-listen server))
766 (server-name (nginx-server-configuration-server-name server))
767 (ssl-certificate (nginx-server-configuration-ssl-certificate server))
768 (ssl-certificate-key
769 (nginx-server-configuration-ssl-certificate-key server))
770 (root (nginx-server-configuration-root server))
771 (index (nginx-server-configuration-index server))
772 (try-files (nginx-server-configuration-try-files server))
773 (server-tokens? (nginx-server-configuration-server-tokens? server))
774 (locations (nginx-server-configuration-locations server))
775 (raw-content (nginx-server-configuration-raw-content server)))
776 (define-syntax-parameter <> (syntax-rules ()))
777 (define-syntax-rule (and/l x tail ...)
778 (let ((x* x))
779 (if x*
780 (syntax-parameterize ((<> (identifier-syntax x*)))
781 (list tail ...))
782 '())))
783 (list
784 " server {\n"
785 (map (lambda (directive) (list " listen " directive ";\n")) listen)
786 " server_name " (config-domain-strings server-name) ";\n"
787 (and/l ssl-certificate " ssl_certificate " <> ";\n")
788 (and/l ssl-certificate-key " ssl_certificate_key " <> ";\n")
789 (if (and (eq? context 'http)
790 (not (equal? "" root)))
791 (list " root " root ";\n")
792 "")
793 (if (and (eq? context 'http)
794 (not (null? index)))
795 (list " index " (config-index-strings index) ";\n")
796 "")
797 (if (not (nil? try-files))
798 (and/l (config-index-strings try-files) " try_files " <> ";\n")
799 "")
800 (if (eq? context 'http)
801 (list " server_tokens " (if server-tokens? "on" "off") ";\n")
802 "")
803 "\n"
804 (map emit-nginx-location-config locations)
805 "\n"
806 (map (lambda (x) (list " " x "\n")) raw-content)
807 " }\n")))
808
809(define (emit-nginx-upstream-config upstream)
810 (list
811 " upstream " (nginx-upstream-configuration-name upstream) " {\n"
812 (map (lambda (server)
813 (simple-format #f " server ~A;\n" server))
814 (nginx-upstream-configuration-servers upstream))
815 (let ((extra-content
816 (nginx-upstream-configuration-extra-content upstream)))
817 (if (and extra-content (not (null? extra-content)))
818 (cons
819 "\n"
820 (map (lambda (line)
821 `(" " ,line "\n"))
822 (if (list? extra-content)
823 extra-content
824 (list extra-content))))
825 '()))
826 " }\n"))
827
828(define (flatten . lst)
829 "Return a list that recursively concatenates all sub-lists of LST."
830 (define (flatten1 head out)
831 (if (list? head)
832 (fold-right flatten1 out head)
833 (cons head out)))
834 (fold-right flatten1 '() lst))
835
836(define (nginx-access-log-file config)
837 (string-append (nginx-configuration-log-directory config)
838 "/access.log"))
839
840(define (nginx-error-log-file config)
841 (string-append (nginx-configuration-log-directory config)
842 "/error.log"))
843
844(define (default-nginx-config config)
845 (match-record config
846 <nginx-configuration>
847 (nginx log-directory run-directory
848 log-level log-format log-formats
849 server-blocks upstream-blocks
850 server-names-hash-bucket-size
851 server-names-hash-bucket-max-size
852 stream
853 modules
854 global-directives
855 lua-package-path
856 lua-package-cpath
857 extra-content)
858 (apply mixed-text-file "nginx.conf"
859 (flatten
860 "user nginx nginx;\n"
861 "pid " run-directory "/pid;\n"
862 "error_log " (nginx-error-log-file config) " " (symbol->string log-level) ";\n"
863 (map emit-load-module modules)
864 (map emit-global-directive global-directives)
865 (match stream
866 (#f "")
867 (_
868 (list "stream {\n"
869 (map emit-nginx-upstream-config
870 (nginx-stream-configuration-upstream-blocks stream))
871 (map (cut emit-nginx-server-config <> 'stream)
872 (nginx-stream-configuration-server-blocks stream))
873 (let ((extra-content (nginx-stream-configuration-extra-content stream)))
874 (if (list? extra-content)
875 (map (cut list " " <> "\n")
876 extra-content)
877 extra-content))
878 "}\n")))
879 "http {\n"
880 " client_body_temp_path " run-directory "/client_body_temp;\n"
881 " proxy_temp_path " run-directory "/proxy_temp;\n"
882 " fastcgi_temp_path " run-directory "/fastcgi_temp;\n"
883 " uwsgi_temp_path " run-directory "/uwsgi_temp;\n"
884 " scgi_temp_path " run-directory "/scgi_temp;\n"
885 (map emit-nginx-log-format-config log-formats)
886 " access_log " (nginx-access-log-file config) " " (symbol->string log-format) ";\n"
887 " include " nginx "/share/nginx/conf/mime.types;\n"
888 (if lua-package-path
889 #~(format #f " lua_package_path ~s;~%"
890 (string-join (map (lambda (path)
891 (string-append path "/lib/?.lua"))
892 '#$lua-package-path)
893 ";"))
894 "")
895 (if lua-package-cpath
896 #~(format #f " lua_package_cpath ~s;~%"
897 (string-join (map (lambda (cpath)
898 (string-append cpath "/lib/lua/?.lua"))
899 '#$lua-package-cpath)
900 ";"))
901 "")
902 (if server-names-hash-bucket-size
903 (string-append
904 " server_names_hash_bucket_size "
905 (number->string server-names-hash-bucket-size)
906 ";\n")
907 "")
908 (if server-names-hash-bucket-max-size
909 (string-append
910 " server_names_hash_bucket_max_size "
911 (number->string server-names-hash-bucket-max-size)
912 ";\n")
913 "")
914 (match extra-content
915 ((? list? extra-content)
916 (map (lambda (line)
917 `(" " ,line "\n"))
918 extra-content))
919 ;; XXX: For compatibility strings and gexp's are inserted
920 ;; directly.
921 (_ extra-content))
922 "\n"
923 (map emit-nginx-upstream-config upstream-blocks)
924 (map emit-nginx-server-config server-blocks)
925 "}\n"))))
926
927(define %nginx-accounts
928 (list (user-group (name "nginx") (system? #t))
929 (user-account
930 (name "nginx")
931 (group "nginx")
932 (system? #t)
933 (comment "nginx server user")
934 (home-directory "/var/empty")
935 (shell (file-append shadow "/sbin/nologin")))))
936
937(define %nginx-configuration-file
938 ;; A fixed location is used for the configuration so that it can be reloaded
939 ;; in place. The supporting nginx default configuration files are also
940 ;; added to the /etc/nginx directory so that they can be included normally,
941 ;; e.g. via 'include fastcgi_params;'.
942 "/etc/nginx/nginx.conf")
943
944(define (nginx-activation config)
945 (match-record config
946 <nginx-configuration>
947 (nginx log-directory run-directory file)
948 #~(begin
949 (use-modules (guix build utils))
950
951 (format #t "creating nginx log directory '~a'~%" #$log-directory)
952 (mkdir-p #$log-directory)
953 (format #t "creating nginx run directory '~a'~%" #$run-directory)
954 (mkdir-p #$run-directory)
955 (format #t "creating nginx temp directories '~a/{client_body,proxy,fastcgi,uwsgi,scgi}_temp'~%" #$run-directory)
956 (mkdir-p (string-append #$run-directory "/client_body_temp"))
957 (mkdir-p (string-append #$run-directory "/proxy_temp"))
958 (mkdir-p (string-append #$run-directory "/fastcgi_temp"))
959 (mkdir-p (string-append #$run-directory "/uwsgi_temp"))
960 (mkdir-p (string-append #$run-directory "/scgi_temp"))
961 ;; Start-up logs. Once configuration is loaded, nginx switches to
962 ;; log-directory.
963 (mkdir-p (string-append #$run-directory "/logs")))))
964
965(define (nginx-shepherd-service config)
966 (match-record config <nginx-configuration>
967 (nginx file run-directory shepherd-requirement)
968 (let* ((nginx-binary (file-append nginx "/sbin/nginx"))
969 (pid-file (in-vicinity run-directory "pid"))
970 (nginx-action
971 (lambda args
972 #~(lambda _
973 (invoke #$nginx-binary "-c" #$%nginx-configuration-file
974 #$@args)
975 (match '#$args
976 (("-s" "stop") #f)
977 (("-s" . _) #t)
978 (_
979 ;; When FILE is true, we cannot be sure that PID-FILE will
980 ;; be created, so assume it won't show up. When FILE is
981 ;; false, read PID-FILE.
982 #$(if file
983 #~#t
984 #~(read-pid-file #$pid-file))))))))
985
986 (list (shepherd-service
987 (documentation "Run nginx pre-start actions.")
988 (requirement '(user-processes loopback))
989 (provision '(nginx-init))
990 (one-shot? #t)
991 (start
992 #~(lambda _
993 ;; Check configuration file syntax.
994 (invoke #$nginx-binary
995 "-c" #$%nginx-configuration-file
996 "-p" #$run-directory
997 "-t"))))
998 (shepherd-service
999 (provision '(nginx))
1000 (documentation "Run the nginx daemon.")
1001 (requirement `( user-processes loopback nginx-init
1002 ,@shepherd-requirement))
1003 (modules `((ice-9 match)
1004 ,@%default-modules))
1005 (start (nginx-action "-p" run-directory))
1006 (stop #~(lambda (value)
1007 ;; When the PID is known, use 'terminate-process', which
1008 ;; waits for the main process to actually terminate.
1009 ;; When FILE is true, there's potentially no PID file
1010 ;; and thus the PID is not known; in that case, invoke
1011 ;; "nginx -s stop".
1012 (if (process? value)
1013 (terminate-process (process-id value) SIGTERM)
1014 (#$(nginx-action "-s" "stop")))))
1015
1016 (actions
1017 (list
1018 (shepherd-configuration-action %nginx-configuration-file)
1019 (shepherd-action
1020 (name 'reload)
1021 (documentation "Reload the nginx configuration file and
1022restart worker processes. This has the effect of killing old worker processes
1023and starting new ones, using the same configuration file. This is useful when
1024either the configuration files itself, or any files it refers to or includes
1025changed (for example, TLS certificates).")
1026 (procedure (nginx-action "-p" run-directory "-s" "reload")))
1027 (shepherd-action
1028 (name 'reopen)
1029 (documentation "Re-open log files.")
1030 (procedure (nginx-action "-p" run-directory "-s" "reopen"))))))))))
1031
1032(define (nginx-log-files config)
1033 (list (nginx-access-log-file config)
1034 (nginx-error-log-file config)))
1035
1036(define (nginx-conf-files config)
1037 ;; Nginx's conf directory files except nginx.conf and .default files.
1038 (match-record config <nginx-configuration>
1039 (nginx file)
1040 (computed-file
1041 "nginx-conf-files"
1042 #~(begin
1043 (use-modules (ice-9 ftw)
1044 (srfi srfi-26))
1045 (mkdir #$output)
1046 (define nginx-conf #$(file-append nginx "/share/nginx/conf"))
1047 (for-each
1048 (lambda (x)
1049 (symlink x (string-append #$output "/" (basename x))))
1050 (map (cut string-append nginx-conf "/" <>)
1051 (scandir nginx-conf
1052 (negate (lambda (name)
1053 (or (string-suffix? ".default" name)
1054 (member name '("." ".."
1055 "nginx.conf"))))))))
1056 (symlink #$(or file (default-nginx-config config))
1057 (string-append #$output "/nginx.conf"))))))
1058
1059(define (nginx-etc-directory config)
1060 `(("nginx" ,(nginx-conf-files config))))
1061
1062(define nginx-service-type
1063 (service-type
1064 (name 'nginx)
1065 (extensions
1066 (list (service-extension shepherd-root-service-type
1067 nginx-shepherd-service)
1068 (service-extension log-rotation-service-type
1069 nginx-log-files)
1070 (service-extension activation-service-type
1071 nginx-activation)
1072 (service-extension account-service-type
1073 (const %nginx-accounts))
1074 (service-extension etc-service-type
1075 nginx-etc-directory)
1076 ;; Make the nginx manual page available.
1077 (service-extension profile-service-type
1078 (compose list nginx-configuration-nginx))))
1079 (compose concatenate)
1080 (extend (lambda (config servers)
1081 (nginx-configuration
1082 (inherit config)
1083 (server-blocks
1084 (append (nginx-configuration-server-blocks config)
1085 servers)))))
1086 (default-value (nginx-configuration))
1087 (description "Run the nginx Web server.")))
1088
1089(define-record-type* <gunicorn-configuration>
1090 gunicorn-configuration make-gunicorn-configuration
1091 gunicorn-configuration?
1092 (package gunicorn-configuration-package
1093 (default gunicorn))
1094 (apps gunicorn-configuration-apps
1095 (default '())))
1096
1097(define (sanitize-gunicorn-app-sockets value)
1098 (unless (and (list? value)
1099 (not (null? value)))
1100 (leave (G_ "gunicorn: '~a' is invalid; must be a non-empty list~%") value))
1101 value)
1102
1103(define-record-type* <gunicorn-app>
1104 gunicorn-app make-gunicorn-app
1105 gunicorn-app?
1106 this-gunicorn-app
1107 (name gunicorn-app-name)
1108 (package gunicorn-app-package)
1109 (wsgi-app-module gunicorn-app-wsgi-app-module)
1110 (user gunicorn-app-user)
1111 (group gunicorn-app-group)
1112 (socket-user gunicorn-app-socket-user
1113 (default (gunicorn-app-user this-gunicorn-app))
1114 (thunked))
1115 (socket-group gunicorn-app-socket-group
1116 (default (gunicorn-app-group this-gunicorn-app))
1117 (thunked))
1118 (socket-mode gunicorn-app-socket-mode
1119 (default #o750))
1120 (control-socket gunicorn-app-control-socket
1121 (default #f))
1122 (sockets gunicorn-app-sockets
1123 (default (list (string-append "unix:/var/run/gunicorn/"
1124 (gunicorn-app-name this-gunicorn-app)
1125 "/socket")))
1126 (sanitize sanitize-gunicorn-app-sockets)
1127 (thunked))
1128 (workers gunicorn-app-workers
1129 (default 1))
1130 (extra-cli-arguments gunicorn-app-extra-cli-arguments
1131 (default '()))
1132 (environment-variables gunicorn-app-environment-variables
1133 (default '()))
1134 (timeout gunicorn-app-timeout
1135 (default 30))
1136 (mappings gunicorn-app-mappings
1137 (default '())))
1138
1139(define (unix-socket? string)
1140 "Whether STRING indicates a Unix socket."
1141 (let ((uri (string->uri string)))
1142 (and uri (eq? 'unix (uri-scheme uri)))))
1143
1144(define (socket-dir string)
1145 "Return the socket directory."
1146 (dirname (uri-path (string->uri string))))
1147
1148(define (gunicorn-activation config)
1149 (with-imported-modules '((guix build utils))
1150 #~(begin
1151 (use-modules (guix build utils)
1152 (ice-9 match))
1153
1154 ;; Create socket directories and set ownership.
1155 (for-each (match-lambda
1156 ((user group mode socket-directories ...)
1157 (for-each (lambda (socket-directory)
1158 (mkdir-p socket-directory)
1159 (chmod socket-directory mode)
1160 (chown socket-directory
1161 (passwd:uid (getpw user))
1162 (group:gid (getgrnam group))))
1163 socket-directories)))
1164 '#$(map (lambda (app)
1165 (cons* (gunicorn-app-socket-user app)
1166 (gunicorn-app-socket-group app)
1167 (gunicorn-app-socket-mode app)
1168 (filter-map (lambda (socket)
1169 (and
1170 (unix-socket? socket)
1171 (socket-dir socket)))
1172 (gunicorn-app-sockets app))))
1173 (gunicorn-configuration-apps config))))))
1174
1175(define (gunicorn-shepherd-services config)
1176 (map (lambda (app)
1177 (let ((name (string-append "gunicorn-" (gunicorn-app-name app)))
1178 (user (gunicorn-app-user app))
1179 (group (gunicorn-app-group app)))
1180 (shepherd-service
1181 (documentation (string-append "Run gunicorn for app "
1182 (gunicorn-app-name app)
1183 "."))
1184 (provision (list (string->symbol name)))
1185 (requirement '(networking))
1186 (modules '((guix search-paths)
1187 (ice-9 match)))
1188 (start
1189 (let* ((app-manifest (packages->manifest
1190 ;; Using python-minimal in the
1191 ;; manifest creates collisions with
1192 ;; the python in the app package.
1193 (list python
1194 (gunicorn-app-package app))))
1195 (app-profile (profile
1196 (content app-manifest)
1197 (allow-collisions? #t))))
1198 (with-imported-modules
1199 (source-module-closure '((guix search-paths)))
1200 #~(make-forkexec-constructor
1201 (cons*
1202 #$(least-authority-wrapper
1203 (file-append (gunicorn-configuration-package config)
1204 "/bin/gunicorn")
1205 #:name (string-append name "-pola-wrapper")
1206 #:mappings
1207 (cons (file-system-mapping
1208 ;; Mapping the app package
1209 (source app-profile)
1210 (target source))
1211 (append
1212 ;; Mappings for Unix socket directories
1213 (filter-map
1214 (lambda (socket)
1215 (and (unix-socket? socket)
1216 (file-system-mapping
1217 (source (socket-dir socket))
1218 (target source)
1219 (writable? #t))))
1220 (gunicorn-app-sockets app))
1221 ;; Additional mappings
1222 (gunicorn-app-mappings app)))
1223 #:preserved-environment-variables
1224 (map search-path-specification-variable
1225 (manifest-search-paths app-manifest))
1226 #:namespaces (delq 'net %namespaces))
1227 "--workers" #$(number->string (gunicorn-app-workers app))
1228 "--timeout" #$(number->string (gunicorn-app-timeout app))
1229 #$(let ((ctrl-socket (gunicorn-app-control-socket app)))
1230 (if ctrl-socket
1231 (string-append "--control-socket=" ctrl-socket)
1232 "--no-control-socket"))
1233 (list
1234 #$@(append
1235 (append-map (lambda (socket)
1236 (list "--bind" socket))
1237 (gunicorn-app-sockets app))
1238 (append-map
1239 (lambda (pair)
1240 (list "--env"
1241 #~(string-append
1242 #$(car pair) "=" #$(cdr pair))))
1243 (gunicorn-app-environment-variables app))
1244 (gunicorn-app-extra-cli-arguments app)
1245 (list (gunicorn-app-wsgi-app-module app)))))
1246 #:user #$user
1247 #:group #$group
1248 #:environment-variables
1249 (map (match-lambda
1250 ((spec . value)
1251 (string-append
1252 (search-path-specification-variable spec)
1253 "="
1254 value)))
1255 (evaluate-search-paths
1256 (map sexp->search-path-specification
1257 '#$(map search-path-specification->sexp
1258 (manifest-search-paths app-manifest)))
1259 (list #$app-profile)))
1260 #:log-file #$(string-append "/var/log/" name ".log")))))
1261 (stop #~(make-kill-destructor)))))
1262 (gunicorn-configuration-apps config)))
1263
1264(define gunicorn-service-type
1265 (service-type
1266 (name 'gunicorn)
1267 (description "Run gunicorn.")
1268 (extensions (list (service-extension activation-service-type
1269 gunicorn-activation)
1270 (service-extension shepherd-root-service-type
1271 gunicorn-shepherd-services)))
1272 (compose concatenate)
1273 (extend (lambda (config apps)
1274 (gunicorn-configuration
1275 (inherit config)
1276 (apps (append (gunicorn-configuration-apps config)
1277 apps)))))
1278 (default-value (gunicorn-configuration))))
1279
1280(define-record-type* <fcgiwrap-configuration> fcgiwrap-configuration
1281 make-fcgiwrap-configuration
1282 fcgiwrap-configuration?
1283 (package fcgiwrap-configuration-package ;file-like
1284 (default fcgiwrap))
1285 (socket fcgiwrap-configuration-socket
1286 (default "tcp:127.0.0.1:9000"))
1287 (user fcgiwrap-configuration-user
1288 (default "fcgiwrap"))
1289 (group fcgiwrap-configuration-group
1290 (default "fcgiwrap")))
1291
1292(define fcgiwrap-accounts
1293 (match-lambda
1294 (($ <fcgiwrap-configuration> package socket user group)
1295 (filter identity
1296 (list
1297 (and (equal? group "fcgiwrap")
1298 (user-group
1299 (name "fcgiwrap")
1300 (system? #t)))
1301 (and (equal? user "fcgiwrap")
1302 (user-account
1303 (name "fcgiwrap")
1304 (group group)
1305 (system? #t)
1306 (comment "Fcgiwrap Daemon")
1307 (home-directory "/var/empty")
1308 (shell (file-append shadow "/sbin/nologin")))))))))
1309
1310(define fcgiwrap-shepherd-service
1311 (match-lambda
1312 (($ <fcgiwrap-configuration> package socket user group)
1313 (list (shepherd-service
1314 (provision '(fcgiwrap))
1315 (documentation "Run the fcgiwrap daemon.")
1316 (requirement '(user-processes networking))
1317 (start #~(make-forkexec-constructor
1318 '(#$(file-append package "/sbin/fcgiwrap")
1319 "-s" #$socket)
1320 #:user #$user #:group #$group
1321 #:log-file "/var/log/fcgiwrap.log"))
1322 (stop #~(make-kill-destructor)))))))
1323
1324(define fcgiwrap-activation
1325 (match-lambda
1326 (($ <fcgiwrap-configuration> package socket user group)
1327 #~(begin
1328 ;; When listening on a unix socket, create a parent directory for the
1329 ;; socket with the correct permissions.
1330 (when (string-prefix? "unix:" #$socket)
1331 (let ((run-directory
1332 (dirname (substring #$socket (string-length "unix:")))))
1333 (mkdir-p run-directory)
1334 (chown run-directory
1335 (passwd:uid (getpw #$user))
1336 (group:gid (getgr #$group)))))))))
1337
1338(define fcgiwrap-service-type
1339 (service-type (name 'fcgiwrap)
1340 (extensions
1341 (list (service-extension shepherd-root-service-type
1342 fcgiwrap-shepherd-service)
1343 (service-extension account-service-type
1344 fcgiwrap-accounts)
1345 (service-extension activation-service-type
1346 fcgiwrap-activation)))
1347 (default-value (fcgiwrap-configuration))
1348 (description "Run FastCGI, an interface between the front-end
1349and the back-end of a Web service.")))
1350
1351(define-record-type* <php-fpm-configuration> php-fpm-configuration
1352 make-php-fpm-configuration
1353 php-fpm-configuration?
1354 (php php-fpm-configuration-php ;file-like
1355 (default php))
1356 (socket php-fpm-configuration-socket
1357 (default (string-append "/var/run/php"
1358 (version-major (package-version php))
1359 "-fpm.sock")))
1360 (user php-fpm-configuration-user
1361 (default "php-fpm"))
1362 (group php-fpm-configuration-group
1363 (default "php-fpm"))
1364 (socket-user php-fpm-configuration-socket-user
1365 (default "php-fpm"))
1366 (socket-group php-fpm-configuration-socket-group
1367 (default "nginx"))
1368 (pid-file php-fpm-configuration-pid-file
1369 (default (string-append "/var/run/php"
1370 (version-major (package-version php))
1371 "-fpm.pid")))
1372 (log-file php-fpm-configuration-log-file
1373 (default (string-append "/var/log/php"
1374 (version-major (package-version php))
1375 "-fpm.log")))
1376 (process-manager php-fpm-configuration-process-manager
1377 (default (php-fpm-dynamic-process-manager-configuration)))
1378 (display-errors php-fpm-configuration-display-errors
1379 (default #f))
1380 (timezone php-fpm-configuration-timezone
1381 (default #f))
1382 (workers-log-file php-fpm-configuration-workers-log-file
1383 (default (string-append "/var/log/php"
1384 (version-major (package-version php))
1385 "-fpm.www.log")))
1386 (file php-fpm-configuration-file ;#f | file-like
1387 (default #f))
1388 (php-ini-file php-fpm-configuration-php-ini-file ;#f | file-like
1389 (default #f)))
1390
1391(define-record-type* <php-fpm-dynamic-process-manager-configuration>
1392 php-fpm-dynamic-process-manager-configuration
1393 make-php-fpm-dynamic-process-manager-configuration
1394 php-fpm-dynamic-process-manager-configuration?
1395 (max-children php-fpm-dynamic-process-manager-configuration-max-children
1396 (default 5))
1397 (start-servers php-fpm-dynamic-process-manager-configuration-start-servers
1398 (default 2))
1399 (min-spare-servers php-fpm-dynamic-process-manager-configuration-min-spare-servers
1400 (default 1))
1401 (max-spare-servers php-fpm-dynamic-process-manager-configuration-max-spare-servers
1402 (default 3)))
1403
1404(define-record-type* <php-fpm-static-process-manager-configuration>
1405 php-fpm-static-process-manager-configuration
1406 make-php-fpm-static-process-manager-configuration
1407 php-fpm-static-process-manager-configuration?
1408 (max-children php-fpm-static-process-manager-configuration-max-children
1409 (default 5)))
1410
1411(define-record-type* <php-fpm-on-demand-process-manager-configuration>
1412 php-fpm-on-demand-process-manager-configuration
1413 make-php-fpm-on-demand-process-manager-configuration
1414 php-fpm-on-demand-process-manager-configuration?
1415 (max-children php-fpm-on-demand-process-manager-configuration-max-children
1416 (default 5))
1417 (process-idle-timeout php-fpm-on-demand-process-manager-configuration-process-idle-timeout
1418 (default 10)))
1419
1420(define php-fpm-accounts
1421 (match-lambda
1422 (($ <php-fpm-configuration> php socket user group socket-user socket-group)
1423 `(,@(if (equal? group "php-fpm")
1424 '()
1425 (list (user-group (name "php-fpm") (system? #t))))
1426 ,(user-group
1427 (name group)
1428 (system? #t))
1429 ,(user-account
1430 (name user)
1431 (group group)
1432 (supplementary-groups '("php-fpm"))
1433 (system? #t)
1434 (comment "php-fpm daemon user")
1435 (home-directory "/var/empty")
1436 (shell (file-append shadow "/sbin/nologin")))))))
1437
1438(define (default-php-fpm-config socket user group socket-user socket-group
1439 pid-file log-file pm display-errors timezone workers-log-file)
1440 (apply mixed-text-file "php-fpm.conf"
1441 (flatten
1442 "[global]\n"
1443 "pid =" pid-file "\n"
1444 "error_log =" log-file "\n"
1445 "[www]\n"
1446 "user =" user "\n"
1447 "group =" group "\n"
1448 "listen =" socket "\n"
1449 "listen.owner =" socket-user "\n"
1450 "listen.group =" socket-group "\n"
1451
1452 (if timezone
1453 (string-append "php_admin_value[date.timezone] = \"" timezone "\"\n")
1454 "")
1455
1456 (match pm
1457 (($ <php-fpm-dynamic-process-manager-configuration>
1458 pm.max-children
1459 pm.start-servers
1460 pm.min-spare-servers
1461 pm.max-spare-servers)
1462 (list
1463 "pm = dynamic\n"
1464 "pm.max_children =" (number->string pm.max-children) "\n"
1465 "pm.start_servers =" (number->string pm.start-servers) "\n"
1466 "pm.min_spare_servers =" (number->string pm.min-spare-servers) "\n"
1467 "pm.max_spare_servers =" (number->string pm.max-spare-servers) "\n"))
1468
1469 (($ <php-fpm-static-process-manager-configuration>
1470 pm.max-children)
1471 (list
1472 "pm = static\n"
1473 "pm.max_children =" (number->string pm.max-children) "\n"))
1474
1475 (($ <php-fpm-on-demand-process-manager-configuration>
1476 pm.max-children
1477 pm.process-idle-timeout)
1478 (list
1479 "pm = ondemand\n"
1480 "pm.max_children =" (number->string pm.max-children) "\n"
1481 "pm.process_idle_timeout =" (number->string pm.process-idle-timeout) "s\n")))
1482
1483
1484 "php_flag[display_errors] = " (if display-errors "on" "off") "\n"
1485
1486 (if workers-log-file
1487 (list "catch_workers_output = yes\n"
1488 "php_admin_value[error_log] =" workers-log-file "\n"
1489 "php_admin_flag[log_errors] = on\n")
1490 (list "catch_workers_output = no\n")))))
1491
1492(define php-fpm-shepherd-service
1493 (match-lambda
1494 (($ <php-fpm-configuration> php socket user group socket-user socket-group
1495 pid-file log-file pm display-errors
1496 timezone workers-log-file file php-ini-file)
1497 (list (shepherd-service
1498 (provision '(php-fpm))
1499 (documentation "Run the php-fpm daemon.")
1500 (requirement '(user-processes networking))
1501 (start #~(make-forkexec-constructor
1502 '(#$(file-append php "/sbin/php-fpm")
1503 "--fpm-config"
1504 #$(or file
1505 (default-php-fpm-config socket user group
1506 socket-user socket-group pid-file log-file
1507 pm display-errors timezone workers-log-file))
1508 #$@(if php-ini-file
1509 `("-c" ,php-ini-file)
1510 '()))
1511 #:pid-file #$pid-file))
1512 (stop #~(make-kill-destructor)))))))
1513
1514(define (php-fpm-activation config)
1515 #~(begin
1516 (use-modules (guix build utils))
1517 (let* ((user (getpwnam #$(php-fpm-configuration-user config)))
1518 (touch (lambda (file-name)
1519 (call-with-output-file file-name (const #t))))
1520 (workers-log-file
1521 #$(php-fpm-configuration-workers-log-file config))
1522 (init-log-file
1523 (lambda (file-name)
1524 (when workers-log-file
1525 (when (not (file-exists? file-name))
1526 (touch file-name))
1527 (chown file-name (passwd:uid user) (passwd:gid user))
1528 (chmod file-name #o660)))))
1529 (init-log-file #$(php-fpm-configuration-log-file config))
1530 (init-log-file workers-log-file))))
1531
1532
1533(define php-fpm-service-type
1534 (service-type
1535 (name 'php-fpm)
1536 (description
1537 "Run @command{php-fpm} to provide a fastcgi socket for calling php through
1538a webserver.")
1539 (extensions
1540 (list (service-extension shepherd-root-service-type
1541 php-fpm-shepherd-service)
1542 (service-extension activation-service-type
1543 php-fpm-activation)
1544 (service-extension account-service-type
1545 php-fpm-accounts)))
1546 (default-value (php-fpm-configuration))))
1547
1548(define* (nginx-php-location
1549 #:key
1550 (nginx-package nginx)
1551 (socket (string-append "/var/run/php"
1552 (version-major (package-version php))
1553 "-fpm.sock")))
1554 "Return a nginx-location-configuration that makes nginx run .php files."
1555 (nginx-location-configuration
1556 (uri "~ \\.php$")
1557 (body (list
1558 "fastcgi_split_path_info ^(.+\\.php)(/.+)$;"
1559
1560 ;; Include some upstream recommendations from
1561 ;; https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi
1562 ;; Mitigate https://httpoxy.org/ vulnerabilities
1563 "fastcgi_param HTTP_PROXY \"\";"
1564 ;; Only pass existing php files to the backend.
1565 "if (!-f $document_root$fastcgi_script_name) { return 404; }"
1566
1567 (string-append "fastcgi_pass unix:" socket ";")
1568 "fastcgi_index index.php;"
1569 (list "include " nginx-package "/share/nginx/conf/fastcgi.conf;")))))
1570
1571(define* (cat-avatar-generator-service
1572 #:key
1573 (cache-dir "/var/cache/cat-avatar-generator/")
1574 (package cat-avatar-generator)
1575 (configuration (nginx-server-configuration)))
1576 (simple-service
1577 'cat-http-server nginx-service-type
1578 (list (nginx-server-configuration
1579 (inherit configuration)
1580 (locations
1581 (cons
1582 (let ((base (nginx-php-location)))
1583 (nginx-location-configuration
1584 (inherit base)
1585 (body (list (string-append "fastcgi_param CACHE_DIR \""
1586 cache-dir "\";")
1587 (nginx-location-configuration-body base)))))
1588 (nginx-server-configuration-locations configuration)))
1589 (root #~(string-append #$package
1590 "/share/web/cat-avatar-generator"))))))
1591
1592
1593(define-record-type* <hpcguix-web-configuration>
1594 hpcguix-web-configuration make-hpcguix-web-configuration
1595 hpcguix-web-configuration?
1596
1597 (package hpcguix-web-package (default hpcguix-web)) ;file-like
1598
1599 (specs hpcguix-web-configuration-specs (default #f)) ;#f | gexp
1600 (address hpcguix-web-configuration-address (default "127.0.0.1"))
1601 (port hpcguix-web-configuration-port (default 5000)))
1602
1603(define %hpcguix-web-accounts
1604 (list (user-group
1605 (name "hpcguix-web")
1606 (system? #t))
1607 (user-account
1608 (name "hpcguix-web")
1609 (group "hpcguix-web")
1610 (system? #t)
1611 (comment "hpcguix-web")
1612 (home-directory "/var/empty")
1613 (shell (file-append shadow "/sbin/nologin")))))
1614
1615(define %hpcguix-web-activation
1616 (with-imported-modules '((guix build utils))
1617 #~(begin
1618 (use-modules (guix build utils)
1619 (ice-9 ftw))
1620
1621 (let ((home-dir "/var/cache/guix/web")
1622 (user (getpwnam "hpcguix-web")))
1623 (mkdir-p home-dir)
1624 (chown home-dir (passwd:uid user) (passwd:gid user))
1625 (chmod home-dir #o755)
1626
1627 ;; Remove stale 'packages.json.lock' file (and other lock files, if
1628 ;; any) since that would prevent 'packages.json' from being updated.
1629 (for-each (lambda (lock)
1630 (delete-file (string-append home-dir "/" lock)))
1631 (scandir home-dir
1632 (lambda (file)
1633 (string-suffix? ".lock" file))))))))
1634
1635(define %hpcguix-web-log-file
1636 "/var/log/hpcguix-web.log")
1637
1638(define (hpcguix-web-shepherd-service config)
1639 (let* ((specs (hpcguix-web-configuration-specs config))
1640 (config-file (and specs (scheme-file "hpcguix-web.scm" specs)))
1641 (hpcguix-web (hpcguix-web-package config)))
1642 (shepherd-service
1643 (documentation "hpcguix-web daemon")
1644 (provision '(hpcguix-web))
1645 (requirement '(user-processes networking))
1646 (start #~(make-forkexec-constructor
1647 (list #$(file-append hpcguix-web "/bin/hpcguix-web")
1648 (string-append "--listen="
1649 #$(hpcguix-web-configuration-address
1650 config))
1651 "-p"
1652 #$(number->string
1653 (hpcguix-web-configuration-port config))
1654 #$@(if specs
1655 #~((string-append "--config=" #$config-file))
1656 #~()))
1657 #:user "hpcguix-web"
1658 #:group "hpcguix-web"
1659 #:environment-variables
1660 (list "XDG_CACHE_HOME=/var/cache/guix/web"
1661 "SSL_CERT_DIR=/etc/ssl/certs")
1662 #:log-file #$%hpcguix-web-log-file))
1663 (stop #~(make-kill-destructor))
1664 (actions (list (shepherd-configuration-action config-file))))))
1665
1666(define hpcguix-web-service-type
1667 (service-type
1668 (name 'hpcguix-web)
1669 (description "Run the hpcguix-web server.")
1670 (extensions
1671 (list (service-extension account-service-type
1672 (const %hpcguix-web-accounts))
1673 (service-extension activation-service-type
1674 (const %hpcguix-web-activation))
1675 (service-extension shepherd-root-service-type
1676 (compose list hpcguix-web-shepherd-service))))
1677 (default-value (hpcguix-web-configuration))))
1678
1679
1680;;;
1681;;; Log anonymization
1682;;;
1683
1684(define-record-type* <anonip-configuration>
1685 anonip-configuration make-anonip-configuration
1686 anonip-configuration?
1687 (anonip anonip-configuration-anonip ;file-like
1688 (default anonip))
1689 (input anonip-configuration-input) ;string
1690 (output anonip-configuration-output) ;string
1691 (debug? anonip-configuration-debug? ;boolean
1692 (default #f))
1693 (skip-private? anonip-configuration-skip-private? ;boolean
1694 (default #f))
1695 (column anonip-configuration-column ;number
1696 (default #f))
1697 (replacement anonip-configuration-replacement ;string
1698 (default #f))
1699 (ipv4mask anonip-configuration-ipv4mask ;number
1700 (default #f))
1701 (ipv6mask anonip-configuration-ipv6mask ;number
1702 (default #f))
1703 (increment anonip-configuration-increment ;number
1704 (default #f))
1705 (delimiter anonip-configuration-delimiter ;string
1706 (default #f))
1707 (regex anonip-configuration-regex ;string
1708 (default #f)))
1709
1710(define (anonip-activation config)
1711 (with-imported-modules '((guix build utils))
1712 #~(begin
1713 (use-modules (guix build utils))
1714 (for-each
1715 (lambda (directory)
1716 (mkdir-p directory)
1717 (chmod directory #o755))
1718 (list (dirname #$(anonip-configuration-input config))
1719 (dirname #$(anonip-configuration-output config)))))))
1720
1721(define (anonip-shepherd-service config)
1722 (let ((input (anonip-configuration-input config))
1723 (output (anonip-configuration-output config))
1724 (optional
1725 (lambda (accessor option)
1726 (or (and=> (accessor config)
1727 (lambda (value)
1728 (list
1729 (format #false "~a=~a"
1730 option value))))
1731 (list)))))
1732 (list
1733 (shepherd-service
1734 (provision
1735 (list (symbol-append 'anonip- (string->symbol output))))
1736 (requirement '(user-processes))
1737 (documentation
1738 "Anonimyze the given log file location with anonip.")
1739 (start
1740 #~(lambda ()
1741 ;; Always attempt to recreate the named pipe, as activation scripts
1742 ;; such as that of nginx may have created plain files in its place
1743 ;; (see: https://issues.guix.gnu.org/59181).
1744 (false-if-exception (delete-file #$input))
1745 (mknod #$input 'fifo #o600 0)
1746
1747 (fork+exec-command
1748 (append
1749 (list #$(file-append (anonip-configuration-anonip config)
1750 "/bin/anonip")
1751 (string-append "--input=" #$input)
1752 (string-append "--output=" #$output))
1753 (if #$(anonip-configuration-debug? config)
1754 '("--debug") (list))
1755 (if #$(anonip-configuration-skip-private? config)
1756 '("--skip-private") (list))
1757 '#$(optional anonip-configuration-column "--column")
1758 '#$(optional anonip-configuration-ipv4mask "--ipv4mask")
1759 '#$(optional anonip-configuration-ipv6mask "--ipv6mask")
1760 '#$(optional anonip-configuration-increment "--increment")
1761 '#$(optional anonip-configuration-replacement "--replacement")
1762 '#$(optional anonip-configuration-delimiter "--delimiter")
1763 '#$(optional anonip-configuration-regex "--regex"))
1764 ;; Run in a UTF-8 locale
1765 #:environment-variables
1766 (list (string-append "GUIX_LOCPATH="
1767 #$(libc-utf8-locales-for-target)
1768 "/lib/locale")
1769 "LC_ALL=en_US.utf8"))))
1770 (stop #~(make-kill-destructor))))))
1771
1772(define (anonip-log-files config)
1773 "Return the list of log files produced by @command{anonip}."
1774 (list (anonip-configuration-output config)))
1775
1776(define anonip-service-type
1777 (service-type
1778 (name 'anonip)
1779 (extensions
1780 (list (service-extension shepherd-root-service-type
1781 anonip-shepherd-service)
1782 (service-extension log-rotation-service-type
1783 anonip-log-files)
1784 (service-extension activation-service-type
1785 anonip-activation)))
1786 (description
1787 "Provide web server log anonymization with @command{anonip}.")))
1788
1789
1790;;;
1791;;; Varnish
1792;;;
1793
1794(define-record-type* <varnish-configuration>
1795 varnish-configuration make-varnish-configuration
1796 varnish-configuration?
1797 (package varnish-configuration-package ;file-like
1798 (default varnish))
1799 (name varnish-configuration-name ;string
1800 (default "default"))
1801 (backend varnish-configuration-backend ;string
1802 (default "localhost:8080"))
1803 (vcl varnish-configuration-vcl ;#f | <file-like>
1804 (default #f))
1805 (listen varnish-configuration-listen ;list of strings
1806 (default '("localhost:80")))
1807 (storage varnish-configuration-storage ;list of strings
1808 (default '("malloc,128m")))
1809 (parameters varnish-configuration-parameters ;list of string pairs
1810 (default '()))
1811 (extra-options varnish-configuration-extra-options ;list of strings
1812 (default '())))
1813
1814(define %varnish-accounts
1815 (list (user-group
1816 (name "varnish")
1817 (system? #t))
1818 (user-account
1819 (name "varnish")
1820 (group "varnish")
1821 (system? #t)
1822 (comment "Varnish Cache User")
1823 (home-directory "/var/varnish")
1824 (shell (file-append shadow "/sbin/nologin")))))
1825
1826(define varnish-shepherd-service
1827 (match-lambda
1828 (($ <varnish-configuration> package name backend vcl listen storage
1829 parameters extra-options)
1830 (list (shepherd-service
1831 (provision (list (symbol-append 'varnish- (string->symbol name))))
1832 (documentation (string-append "The Varnish Web Accelerator"
1833 " (" name ")"))
1834 (requirement '(user-processes networking))
1835 (start #~(make-forkexec-constructor
1836 (list #$(file-append package "/sbin/varnishd")
1837 "-n" #$name
1838 #$@(if vcl
1839 #~("-f" #$vcl)
1840 #~("-b" #$backend))
1841 #$@(append-map (lambda (a) (list "-a" a)) listen)
1842 #$@(append-map (lambda (s) (list "-s" s)) storage)
1843 #$@(append-map (lambda (p)
1844 (list "-p" (format #f "~a=~a"
1845 (car p) (cdr p))))
1846 parameters)
1847 #$@extra-options)
1848 ;; Varnish will drop privileges to the "varnish" user when
1849 ;; it exists. Not passing #:user here allows the service
1850 ;; to bind to ports < 1024.
1851 #:pid-file (if (string-prefix? "/" #$name)
1852 (string-append #$name "/_.pid")
1853 (string-append "/var/varnish/" #$name "/_.pid"))))
1854 (stop #~(make-kill-destructor)))))))
1855
1856(define varnish-service-type
1857 (service-type
1858 (name 'varnish)
1859 (description "Run the Varnish cache server.")
1860 (extensions
1861 (list (service-extension account-service-type
1862 (const %varnish-accounts))
1863 (service-extension shepherd-root-service-type
1864 varnish-shepherd-service)))
1865 (default-value
1866 (varnish-configuration))))
1867
1868
1869;;;
1870;;; Whoogle
1871;;;
1872
1873(define-configuration/no-serialization whoogle-configuration
1874 (package
1875 (package whoogle-search)
1876 "The @code{whoogle-search} package to use.")
1877 (host
1878 (string "127.0.0.1")
1879 "The host address to run Whoogle on.")
1880 (port
1881 (integer 5000)
1882 "The port to run Whoogle on.")
1883 (environment-variables
1884 (list-of-strings '())
1885 "A list of strings specifying environment variables used to configure
1886Whoogle."))
1887
1888(define (whoogle-shepherd-service config)
1889 (match-record config <whoogle-configuration>
1890 (package host port environment-variables)
1891 (list
1892 (shepherd-service
1893 (provision '(whoogle-search))
1894 (requirement '(user-processes))
1895 (start #~(make-forkexec-constructor
1896 (list (string-append #$package "/bin/whoogle-search")
1897 "--host" #$host "--port" #$(number->string port))
1898 #:environment-variables
1899 (append (list "CONFIG_VOLUME=/var/cache/whoogle-search")
1900 '#$environment-variables)))
1901 (stop #~(make-kill-destructor))
1902 (documentation "Run a @code{whoogle-search} instance.")))))
1903
1904(define whoogle-service-type
1905 (service-type
1906 (name 'whoogle-search)
1907 (extensions
1908 (list (service-extension shepherd-root-service-type
1909 whoogle-shepherd-service)
1910 (service-extension profile-service-type
1911 (compose list whoogle-configuration-package))))
1912 (default-value (whoogle-configuration))
1913 (description "Set up the @code{whoogle-search} metasearch engine.")))
1914
1915
1916;;;
1917;;; go-webdav
1918;;;
1919
1920(define (go-webdav-shepherd-service args)
1921 (list (shepherd-service
1922 (documentation "go-webdav daemon.")
1923 (provision '(go-webdav))
1924 ;; go-webdav may be bound to a particular IP address, hence
1925 ;; only start it after the networking service has started.
1926 (requirement '(user-processes networking))
1927 (start #~(make-forkexec-constructor
1928 (list (string-append #$go-webdav "/bin/webdav-server")
1929 #$@args)
1930 #:user "go-webdav" #:group "go-webdav"))
1931 (stop #~(make-kill-destructor)))))
1932
1933(define go-webdav-account-service
1934 (list (user-group (name "go-webdav") (system? #t))
1935 (user-account
1936 (name "go-webdav")
1937 (group "go-webdav")
1938 (system? #t)
1939 (comment "go-webdav daemon user")
1940 (home-directory "/var/empty")
1941 (shell (file-append shadow "/sbin/nologin")))))
1942
1943(define-public go-webdav-service-type
1944 (service-type (name 'go-webdav)
1945 (description "Run the go-webdav WebDAV server.")
1946 (extensions
1947 (list (service-extension account-service-type
1948 (const go-webdav-account-service))
1949 (service-extension shepherd-root-service-type
1950 go-webdav-shepherd-service)))
1951 (compose concatenate)
1952 (default-value '("-addr" "127.0.0.1:8080"))))
1953
1954
1955;;;
1956;;; Patchwork
1957;;;
1958
1959(define-record-type* <patchwork-database-configuration>
1960 patchwork-database-configuration make-patchwork-database-configuration
1961 patchwork-database-configuration?
1962 (engine patchwork-database-configuration-engine
1963 (default "django.db.backends.postgresql_psycopg2"))
1964 (name patchwork-database-configuration-name
1965 (default "patchwork"))
1966 (user patchwork-database-configuration-user
1967 (default "httpd"))
1968 (password patchwork-database-configuration-password
1969 (default ""))
1970 (host patchwork-database-configuration-host
1971 (default ""))
1972 (port patchwork-database-configuration-port
1973 (default "")))
1974
1975(define-record-type* <patchwork-settings-module>
1976 patchwork-settings-module make-patchwork-settings-module
1977 patchwork-settings-module?
1978 (database-configuration patchwork-settings-module-database-configuration
1979 (default (patchwork-database-configuration)))
1980 (secret-key-file patchwork-settings-module-secret-key-file
1981 (default "/etc/patchwork/django-secret-key"))
1982 (allowed-hosts patchwork-settings-module-allowed-hosts)
1983 (default-from-email patchwork-settings-module-default-from-email)
1984 (static-url patchwork-settings-module-static-url
1985 (default "/static/"))
1986 (admins patchwork-settings-module-admins
1987 (default '()))
1988 (debug? patchwork-settings-module-debug?
1989 (default #f))
1990 (enable-rest-api? patchwork-settings-module-enable-rest-api?
1991 (default #t))
1992 (enable-xmlrpc? patchwork-settings-module-enable-xmlrpc?
1993 (default #t))
1994 (force-https-links? patchwork-settings-module-force-https-links?
1995 (default #t))
1996 (extra-settings patchwork-settings-module-extra-settings
1997 (default "")))
1998
1999(define-record-type* <patchwork-configuration>
2000 patchwork-configuration make-patchwork-configuration
2001 patchwork-configuration?
2002 (patchwork patchwork-configuration-patchwork
2003 (default patchwork))
2004 (domain patchwork-configuration-domain)
2005 (settings-module patchwork-configuration-settings-module)
2006 (static-path patchwork-configuration-static-url
2007 (default "/static/"))
2008 (getmail-retriever-config getmail-retriever-config))
2009
2010;; Django uses a Python module for configuration, so this compiler generates a
2011;; Python module from the configuration record.
2012(define-gexp-compiler (patchwork-settings-module-compiler
2013 (file <patchwork-settings-module>) system target)
2014 (match file
2015 (($ <patchwork-settings-module> database-configuration secret-key-file
2016 allowed-hosts default-from-email
2017 static-url admins debug? enable-rest-api?
2018 enable-xmlrpc? force-https-links?
2019 extra-configuration)
2020 (gexp->derivation
2021 "patchwork-settings"
2022 (with-imported-modules '((guix build utils))
2023 #~(let ((output #$output))
2024 (define (create-__init__.py filename)
2025 (call-with-output-file filename
2026 (lambda (port) (display "" port))))
2027
2028 (use-modules (guix build utils)
2029 (srfi srfi-1))
2030
2031 (mkdir-p (string-append output "/guix/patchwork"))
2032 (create-__init__.py
2033 (string-append output "/guix/__init__.py"))
2034 (create-__init__.py
2035 (string-append output "/guix/patchwork/__init__.py"))
2036
2037 (call-with-output-file
2038 (string-append output "/guix/patchwork/settings.py")
2039 (lambda (port)
2040 (display
2041 (string-append "from patchwork.settings.base import *
2042
2043# Configuration from Guix
2044with open('" #$secret-key-file "') as f:
2045 SECRET_KEY = f.read().strip()
2046
2047ALLOWED_HOSTS = [
2048" #$(string-concatenate
2049 (map (lambda (allowed-host)
2050 (string-append " '" allowed-host "'\n"))
2051 allowed-hosts))
2052"]
2053
2054DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
2055
2056DEFAULT_FROM_EMAIL = '" #$default-from-email "'
2057SERVER_EMAIL = DEFAULT_FROM_EMAIL
2058NOTIFICATION_FROM_EMAIL = DEFAULT_FROM_EMAIL
2059
2060ADMINS = [
2061" #$(string-concatenate
2062 (map (match-lambda
2063 ((name email-address)
2064 (string-append
2065 "('" name "','" email-address "'),")))
2066 admins))
2067"]
2068
2069DEBUG = " #$(if debug? "True" "False") "
2070
2071ENABLE_REST_API = " #$(if enable-rest-api? "True" "False") "
2072ENABLE_XMLRPC = " #$(if enable-xmlrpc? "True" "False") "
2073
2074FORCE_HTTPS_LINKS = " #$(if force-https-links? "True" "False") "
2075
2076DATABASES = {
2077 'default': {
2078" #$(match database-configuration
2079 (($ <patchwork-database-configuration>
2080 engine name user password host port)
2081 (string-append
2082 " 'ENGINE': '" engine "',\n"
2083 " 'NAME': '" name "',\n"
2084 " 'USER': '" user "',\n"
2085 " 'PASSWORD': '" password "',\n"
2086 " 'HOST': '" host "',\n"
2087 " 'PORT': '" port "',\n"))) "
2088 },
2089}
2090
2091" #$(if static-url
2092 #~(string-append "STATIC_URL = '" #$static-url "'")
2093 "") "
2094
2095STATICFILES_STORAGE = (
2096 'django.contrib.staticfiles.storage.StaticFilesStorage'
2097)
2098
2099# Guix Extra Configuration
2100" #$extra-configuration "
2101") port)))
2102 #t))
2103 #:local-build? #t))))
2104
2105(define patchwork-virtualhost
2106 (match-lambda
2107 (($ <patchwork-configuration> patchwork domain
2108 settings-module static-path
2109 getmail-retriever-config)
2110 (define wsgi.py
2111 (file-append patchwork
2112 (string-append
2113 "/lib/python"
2114 (version-major+minor
2115 (package-version python))
2116 "/site-packages/patchwork/wsgi.py")))
2117
2118 (httpd-virtualhost
2119 "*:8080"
2120 `("ServerAdmin [email protected]`
2121ServerName " ,domain "
2122
2123LogFormat \"%v %h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\"\" customformat
2124LogLevel info
2125CustomLog \"/var/log/httpd/" ,domain "-access_log\" customformat
2126
2127ErrorLog /var/log/httpd/error.log
2128
2129WSGIScriptAlias / " ,wsgi.py "
2130WSGIDaemonProcess " ,(package-name patchwork) " user=httpd group=httpd processes=1 threads=2 display-name=%{GROUP} lang='en_US.UTF-8' locale='en_US.UTF-8' python-path=" ,settings-module "
2131WSGIProcessGroup " ,(package-name patchwork) "
2132WSGIPassAuthorization On
2133
2134<Files " ,wsgi.py ">
2135 Require all granted
2136</Files>
2137
2138" ,@(if static-path
2139 `("Alias " ,static-path " " ,patchwork "/share/patchwork/htdocs/")
2140 '())
2141"
2142<Directory \"/srv/http/" ,domain "/\">
2143 AllowOverride None
2144 Options MultiViews Indexes SymlinksIfOwnerMatch IncludesNoExec
2145 Require method GET POST OPTIONS
2146</Directory>")))))
2147
2148(define (patchwork-httpd-configuration patchwork-configuration)
2149 (list "WSGISocketPrefix /var/run/mod_wsgi"
2150 (list "LoadModule wsgi_module "
2151 (file-append mod-wsgi "/modules/mod_wsgi.so"))
2152 (patchwork-virtualhost patchwork-configuration)))
2153
2154(define (patchwork-django-admin-gexp patchwork settings-module)
2155 #~(lambda command
2156 (zero? (spawn-command
2157 `(#$(file-append patchwork "/bin/patchwork-admin")
2158 ,@command)
2159 #:user "httpd"
2160 #:group "httpd"
2161 #:environment-variables
2162 `("DJANGO_SETTINGS_MODULE=guix.patchwork.settings"
2163 ,(string-append "PYTHONPATH=" #$settings-module))))))
2164
2165(define (patchwork-django-admin-action patchwork settings-module)
2166 (shepherd-action
2167 (name 'django-admin)
2168 (documentation
2169 "Run a django admin command for patchwork")
2170 (procedure (patchwork-django-admin-gexp patchwork settings-module))))
2171
2172(define patchwork-shepherd-services
2173 (match-lambda
2174 (($ <patchwork-configuration> patchwork domain
2175 settings-module static-path
2176 getmail-retriever-config)
2177 (define secret-key-file-creation-gexp
2178 (if (patchwork-settings-module? settings-module)
2179 (with-extensions (list guile-gcrypt)
2180 #~(let ((secret-key-file
2181 #$(patchwork-settings-module-secret-key-file
2182 settings-module))
2183 (random-token
2184 (@ (gcrypt random) random-token)))
2185 (unless (file-exists? secret-key-file)
2186 (mkdir-p (dirname secret-key-file))
2187 (call-with-output-file secret-key-file
2188 (lambda (port)
2189 (display (random-token 30 'very-strong) port)))
2190 (let* ((pw (getpwnam "httpd"))
2191 (uid (passwd:uid pw))
2192 (gid (passwd:gid pw)))
2193 (chown secret-key-file uid gid)
2194 (chmod secret-key-file #o400)))))
2195 #~()))
2196
2197 (list (shepherd-service
2198 (requirement '(postgres))
2199 (provision (list (string->symbol
2200 (string-append (package-name patchwork)
2201 "-setup"))))
2202 (start
2203 #~(lambda ()
2204 (define run-django-admin-command
2205 #$(patchwork-django-admin-gexp patchwork
2206 settings-module))
2207
2208 #$secret-key-file-creation-gexp
2209
2210 (run-django-admin-command "migrate")))
2211 (stop #~(const #f))
2212 (actions
2213 (list (patchwork-django-admin-action patchwork
2214 settings-module)))
2215 (respawn? #f)
2216 (documentation "Setup Patchwork."))))))
2217
2218(define patchwork-getmail-configs
2219 (match-lambda
2220 (($ <patchwork-configuration> patchwork domain
2221 settings-module static-path
2222 getmail-retriever-config)
2223 (list
2224 (getmail-configuration
2225 (name (string->symbol (package-name patchwork)))
2226 (user "httpd")
2227 (directory (string-append
2228 "/var/lib/getmail/" (package-name patchwork)))
2229 (rcfile
2230 (getmail-configuration-file
2231 (retriever getmail-retriever-config)
2232 (destination
2233 (getmail-destination-configuration
2234 (type "MDA_external")
2235 (path (file-append patchwork "/bin/patchwork-admin"))
2236 (extra-parameters
2237 '((arguments . ("parsemail"))))))
2238 (options
2239 (getmail-options-configuration
2240 (read-all #f)
2241 (delivered-to #f)
2242 (received #f)))))
2243 (idle (assq-ref
2244 (getmail-retriever-configuration-extra-parameters
2245 getmail-retriever-config)
2246 'mailboxes))
2247 (environment-variables
2248 (list "DJANGO_SETTINGS_MODULE=guix.patchwork.settings"
2249 #~(string-append "PYTHONPATH=" #$settings-module))))))))
2250
2251(define patchwork-service-type
2252 (service-type
2253 (name 'patchwork-setup)
2254 (extensions
2255 (list (service-extension httpd-service-type
2256 patchwork-httpd-configuration)
2257 (service-extension shepherd-root-service-type
2258 patchwork-shepherd-services)
2259 (service-extension getmail-service-type
2260 patchwork-getmail-configs)))
2261 (description
2262 "Patchwork patch tracking system.")))
2263
2264
2265;;;
2266;;; sogogi.
2267;;;
2268
2269(define (sogogi-serialize-section section-name value fields)
2270 (let ((first-field (car fields)))
2271 #~(format #f "~a ~a {~%~a}~%"
2272 #$(object->string section-name)
2273 #$((configuration-field-getter first-field) value)
2274 #$(serialize-configuration value (cdr fields)))))
2275
2276(define (sogogi-serialize-field field-name value)
2277 (let ((field (object->string field-name)))
2278 #~(format #f "~a ~a~%" #$field #$value)))
2279
2280(define sogogi-serialize-string sogogi-serialize-field)
2281(define (sogogi-serialize-list-of-strings field-name value)
2282 #~(string-append
2283 #$@(map (cut sogogi-serialize-string field-name <>)
2284 value)))
2285
2286(define-maybe string (prefix sogogi-))
2287(define-maybe list-of-strings (prefix sogogi-))
2288
2289(define-configuration sogogi-user
2290 (name
2291 maybe-string
2292 "Name of the user.")
2293
2294 (password
2295 maybe-string
2296 "Password of the user.")
2297
2298 (prefix sogogi-))
2299
2300(define (sogogi-serialize-sogogi-user field-name value)
2301 (sogogi-serialize-section field-name value sogogi-user-fields))
2302
2303(define-configuration sogogi-location
2304 (path
2305 string
2306 "HTTP path at which the directory will be exposed.")
2307
2308 (dir
2309 string
2310 "Path to local directory to serve.")
2311
2312 (grant
2313 maybe-list-of-strings
2314 "Grant remote users access to the directory.")
2315
2316 (prefix sogogi-))
2317
2318(define (sogogi-serialize-sogogi-location field-name value)
2319 (sogogi-serialize-section field-name value sogogi-location-fields))
2320
2321(define (sogogi-serialize-list-of-sogogi-location field-name value)
2322 #~(string-append #$@(map (cut sogogi-serialize-sogogi-location field-name <>) value)))
2323
2324(define (sogogi-serialize-list-of-sogogi-user field-name value)
2325 #~(string-append #$@(map (cut sogogi-serialize-sogogi-user field-name <>) value)))
2326
2327(define list-of-sogogi-user? (list-of sogogi-user?))
2328(define list-of-sogogi-location? (list-of sogogi-location?))
2329
2330(define-configuration sogogi-configuration
2331 (listen
2332 (string "localhost:8080")
2333 "Listening address.")
2334
2335 (location
2336 (list-of-sogogi-location '())
2337 "Local directories to expose via a HTTP path.")
2338
2339 (user
2340 (list-of-sogogi-user '())
2341 "Users with access to the location.")
2342
2343 (prefix sogogi-))
2344
2345(define (sogogi-config-file config)
2346 (mixed-text-file "sogogi.conf"
2347 (serialize-configuration
2348 config
2349 sogogi-configuration-fields)))
2350
2351(define (sogogi-shepherd-service config)
2352 (let ((config-file (sogogi-config-file config)))
2353 (list (shepherd-service
2354 (documentation "Sogogi daemon.")
2355 (provision '(sogogi))
2356 ;; sogogi may be bound to a particular IP address, hence
2357 ;; only start it after the networking service has started.
2358 (requirement '(user-processes networking))
2359 (actions (list (shepherd-configuration-action config-file)))
2360 (start #~(make-forkexec-constructor
2361 (list (string-append #$sogogi "/bin/sogogi")
2362 "-config" #$config-file)
2363 #:user "sogogi" #:group "sogogi"))
2364 (stop #~(make-kill-destructor))))))
2365
2366(define sogogi-account-service
2367 (list (user-group (name "sogogi") (system? #t))
2368 (user-account
2369 (name "sogogi")
2370 (group "sogogi")
2371 (system? #t)
2372 (comment "Sogogi daemon user")
2373 (home-directory "/var/empty")
2374 (shell (file-append shadow "/sbin/nologin")))))
2375
2376(define sogogi-service-type
2377 (service-type (name 'sogogi)
2378 (description "Run the sogogi WebDAV server.")
2379 (extensions
2380 (list (service-extension account-service-type
2381 (const sogogi-account-service))
2382 (service-extension shepherd-root-service-type
2383 sogogi-shepherd-service)))
2384 (compose concatenate)
2385 (default-value (sogogi-configuration))))
2386
2387
2388;;;
2389;;; Mumi.
2390;;;
2391
2392(define-record-type* <mumi-configuration>
2393 mumi-configuration make-mumi-configuration
2394 mumi-configuration?
2395 (mumi mumi-configuration-mumi (default mumi))
2396 (data-directory mumi-configuration-data-directory
2397 (default "/var/mumi/data"))
2398 (rsync-remote mumi-configuration-rsync-remote)
2399 (rsync-flags mumi-configuration-rsync-flags
2400 (default '()))
2401 (rsync-and-index-event mumi-configuration-rsync-and-index-event
2402 ;; Run every 5 minutes by default.
2403 (default #~(calendar-event #:minutes '#$(iota 12 0 5))))
2404 (mailer? mumi-configuration-mailer? (default #t))
2405 (sender mumi-configuration-sender (default #f))
2406 (smtp mumi-configuration-smtp (default #f))
2407 (packages mumi-configuration-packages (default '())))
2408
2409(define-record-type* <mumi-package-configuration>
2410 mumi-package-configuration make-mumi-package-configuration
2411 mumi-package-configuration?
2412 (name mumi-package-configuration-name)
2413 (display-name mumi-package-configuration-display-name
2414 (default #f))
2415 (debbugs-packages mumi-package-configuration-debbugs-packages)
2416 (bug-email-address mumi-package-configuration-bug-email-address
2417 (default #f))
2418 (patch-email-address mumi-package-configuration-patch-email-address
2419 (default #f))
2420 (file-tags mumi-package-configuration-file-tags
2421 (default '())))
2422
2423(define %mumi-activation
2424 (with-imported-modules '((guix build utils))
2425 #~(begin
2426 (use-modules (guix build utils))
2427
2428 (mkdir-p "/var/mumi/db")
2429 (mkdir-p "/var/mumi/mails")
2430 (let* ((pw (getpwnam "mumi"))
2431 (uid (passwd:uid pw))
2432 (gid (passwd:gid pw)))
2433 (chown "/var/mumi" uid gid)
2434 (chown "/var/mumi/mails" uid gid)
2435 (chown "/var/mumi/db" uid gid)))))
2436
2437(define %mumi-accounts
2438 (list (user-group (name "mumi") (system? #t))
2439 (user-account
2440 (name "mumi")
2441 (group "mumi")
2442 (system? #t)
2443 (comment "Mumi web server")
2444 (home-directory "/var/empty")
2445 (shell (file-append shadow "/sbin/nologin")))))
2446
2447(define %mumi-log "/var/log/mumi.log")
2448
2449(define %mumi-mailer-log "/var/log/mumi.mailer.log")
2450
2451(define %mumi-rsync-and-index-log "/var/log/mumi.rsync-and-index.log")
2452
2453(define mumi-package-configuration->alist
2454 (match-record-lambda <mumi-package-configuration>
2455 (name display-name debbugs-packages bug-email-address
2456 patch-email-address file-tags)
2457 `(,name
2458 ,@(if display-name
2459 `((display-name . ,display-name))
2460 '())
2461 ,@(if debbugs-packages
2462 `((debbugs-packages . ,debbugs-packages))
2463 '())
2464 ,@(if bug-email-address
2465 `((bug-email-address . ,bug-email-address))
2466 '())
2467 ,@(if patch-email-address
2468 `((patch-email-address . ,patch-email-address))
2469 '())
2470 ,@(match file-tags
2471 (() '())
2472 (_ `((file-tags . ,file-tags)))))))
2473
2474(define mumi-config-file
2475 (match-record-lambda <mumi-configuration>
2476 (mailer? packages)
2477 (computed-file "mumi.conf"
2478 #~(begin
2479 (use-modules (srfi srfi-26))
2480
2481 (call-with-output-file #$output
2482 (cut write
2483 '((mailer-enabled? . #$mailer?)
2484 (packages
2485 . #$(map mumi-package-configuration->alist
2486 packages)))
2487 <>))))))
2488
2489(define (mumi-rsync-and-index config)
2490 (match-record config <mumi-configuration>
2491 (data-directory rsync-remote rsync-flags)
2492 (program-file "mumi-rsync-and-index"
2493 (with-imported-modules '((guix build utils))
2494 #~(begin
2495 (use-modules (guix build utils))
2496
2497 (invoke #$(file-append rsync "/bin/rsync")
2498 "--delete" "--archive" "--verbose"
2499 #$@rsync-flags
2500 #$rsync-remote
2501 #$data-directory)
2502 (invoke #$(file-append mumi "/bin/mumi") "fetch"
2503 (string-append "--config="
2504 #$(mumi-config-file config))))))))
2505
2506(define (mumi-shepherd-services config)
2507 (define environment
2508 #~(list "LC_ALL=en_US.utf8"
2509 (string-append "GUIX_LOCPATH="
2510 #$(libc-utf8-locales-for-target)
2511 "/lib/locale")))
2512
2513 (match-record config <mumi-configuration>
2514 (mumi rsync-and-index-event mailer? sender smtp)
2515 (list (shepherd-service
2516 (provision '(mumi))
2517 (documentation "Mumi bug-tracking web interface.")
2518 (requirement '(user-processes networking))
2519 (start #~(make-forkexec-constructor
2520 `(#$(file-append mumi "/bin/mumi") "web"
2521 ,(string-append "--config="
2522 #$(mumi-config-file config)))
2523 #:environment-variables #$environment
2524 #:user "mumi" #:group "mumi"
2525 #:log-file #$%mumi-log))
2526 (stop #~(make-kill-destructor)))
2527 (shepherd-service
2528 (provision '(mumi-rsync-and-index))
2529 (modules '((shepherd service timer)))
2530 (start #~(make-timer-constructor
2531 #$rsync-and-index-event
2532 (command (list #$(mumi-rsync-and-index config)))
2533 #:log-file #$%mumi-rsync-and-index-log
2534 #:max-duration (* 2 60 60)
2535 #:wait-for-termination? #t))
2536 (stop #~(make-timer-destructor))
2537 (actions (list shepherd-trigger-action))
2538 (documentation "Rsync and index the GNU Debbugs data"))
2539 (shepherd-service
2540 (provision '(mumi-mailer))
2541 (documentation "Mumi bug-tracking web interface mailer.")
2542 (requirement '(user-processes networking))
2543 (start #~(make-forkexec-constructor
2544 `(#$(file-append mumi "/bin/mumi") "mailer"
2545 ,(string-append "--config="
2546 #$(mumi-config-file config))
2547 ,@(if #$sender
2548 (list (string-append "--sender=" #$sender))
2549 '())
2550 ,@(if #$smtp
2551 (list (string-append "--smtp=" #$smtp))
2552 '()))
2553 #:environment-variables #$environment
2554 #:user "mumi" #:group "mumi"
2555 #:log-file #$%mumi-mailer-log))
2556 (stop #~(make-kill-destructor))))))
2557
2558(define mumi-service-type
2559 (service-type
2560 (name 'mumi)
2561 (extensions
2562 (list (service-extension activation-service-type
2563 (const %mumi-activation))
2564 (service-extension account-service-type
2565 (const %mumi-accounts))
2566 (service-extension shepherd-root-service-type
2567 mumi-shepherd-services)))
2568 (description
2569 "Run Mumi, a Web interface to the Debbugs bug-tracking server.")))
2570
2571(define %default-gmnisrv-config-file
2572 (plain-file "gmnisrv.ini" "
2573listen=0.0.0.0:1965 [::]:1965
2574
2575[:tls]
2576store=/var/lib/gemini/certs
2577
2578organization=gmnisrv on Guix user
2579
2580[localhost]
2581root=/srv/gemini
2582"))
2583
2584(define-record-type* <gmnisrv-configuration>
2585 gmnisrv-configuration make-gmnisrv-configuration
2586 gmnisrv-configuration?
2587 (package gmnisrv-configuration-package
2588 (default gmnisrv))
2589 (config-file gmnisrv-configuration-config-file
2590 (default %default-gmnisrv-config-file)))
2591
2592(define gmnisrv-shepherd-service
2593 (match-lambda
2594 (($ <gmnisrv-configuration> package config-file)
2595 (list (shepherd-service
2596 (provision '(gmnisrv))
2597 (requirement '(user-processes networking))
2598 (documentation "Run the gmnisrv Gemini server.")
2599 (start (let ((gmnisrv (file-append package "/bin/gmnisrv")))
2600 #~(make-forkexec-constructor
2601 (list #$gmnisrv "-C" #$config-file)
2602 #:user "gmnisrv" #:group "gmnisrv"
2603 #:log-file "/var/log/gmnisrv.log")))
2604 (stop #~(make-kill-destructor)))))))
2605
2606(define %gmnisrv-accounts
2607 (list (user-group (name "gmnisrv") (system? #t))
2608 (user-account
2609 (name "gmnisrv")
2610 (group "gmnisrv")
2611 (system? #t)
2612 (comment "gmnisrv Gemini server")
2613 (home-directory "/var/empty")
2614 (shell (file-append shadow "/sbin/nologin")))))
2615
2616(define %gmnisrv-activation
2617 (with-imported-modules '((guix build utils))
2618 #~(begin
2619 (use-modules (guix build utils))
2620
2621 (mkdir-p "/var/lib/gemini/certs")
2622 (let* ((pw (getpwnam "gmnisrv"))
2623 (uid (passwd:uid pw))
2624 (gid (passwd:gid pw)))
2625 (chown "/var/lib/gemini" uid gid)
2626 (chown "/var/lib/gemini/certs" uid gid)))))
2627
2628(define gmnisrv-service-type
2629 (service-type
2630 (name 'gmnisrv)
2631 (extensions
2632 (list (service-extension activation-service-type
2633 (const %gmnisrv-activation))
2634 (service-extension account-service-type
2635 (const %gmnisrv-accounts))
2636 (service-extension shepherd-root-service-type
2637 gmnisrv-shepherd-service)))
2638 (description
2639 "Run the gmnisrv Gemini server.")
2640 (default-value
2641 (gmnisrv-configuration))))
2642
2643(define-record-type* <agate-configuration>
2644 agate-configuration make-agate-configuration
2645 agate-configuration?
2646 (package agate-configuration-package
2647 (default agate))
2648 (content agate-configuration-content
2649 (default "/srv/gemini"))
2650 (certificates agate-configuration-certificates
2651 (default "/srv/gemini-certs"))
2652 (addresses agate-configuration-addresses
2653 (default '("[::]:1965" "0.0.0.0:1965")))
2654 (hostnames agate-configuration-hostnames
2655 (default '()))
2656 (languages agate-configuration-languages
2657 (default #f))
2658 (only-tls13? agate-configuration-only-tls13
2659 (default #f))
2660 (serve-secret? agate-configuration-serve-secret
2661 (default #f))
2662 (central-configuration? agate-configuration-central-configuration
2663 (default #f))
2664 (ed25519? agate-configuration-ed25519
2665 (default #f))
2666 (skip-port-check? agate-configuration-skip-port-check
2667 (default #f))
2668 (log-ip? agate-configuration-log-ip
2669 (default #t))
2670 (user agate-configuration-user
2671 (default "agate"))
2672 (group agate-configuration-group
2673 (default "agate"))
2674 (log-file agate-configuration-log
2675 (default "/var/log/agate.log")))
2676
2677(define agate-shepherd-service
2678 (match-lambda
2679 (($ <agate-configuration> package content certificates addresses
2680 hostnames languages only-tls13?
2681 serve-secret? central-configuration?
2682 ed25519? skip-port-check?
2683 log-ip? user group log-file)
2684 (list (shepherd-service
2685 (provision '(agate))
2686 (requirement '(user-processes networking))
2687 (documentation "Run the agate Gemini server.")
2688 (start (let ((agate (file-append package "/bin/agate")))
2689 #~(make-forkexec-constructor
2690 (list #$agate
2691 "--content" #$content
2692 "--certs" #$certificates
2693 #$@(append-map
2694 (lambda x (append '("--addr") x))
2695 addresses)
2696 #$@(append-map
2697 (lambda x (append '("--hostname") x))
2698 hostnames)
2699 #$@(if languages
2700 (list "--lang" languages)
2701 '())
2702 #$@(if serve-secret? '("--serve-secret") '())
2703 #$@(if only-tls13? '("--only-tls13") '())
2704 #$@(if central-configuration? '("--central-conf") '())
2705 #$@(if ed25519? '("--ed25519") '())
2706 #$@(if skip-port-check? '("--skip-port-check") '())
2707 #$@(if log-ip? '("--log-ip") '()))
2708 #:user #$user #:group #$group
2709 #:log-file #$log-file)))
2710 (stop #~(make-kill-destructor)))))))
2711
2712(define agate-accounts
2713 (lambda (config)
2714 (let ((group (agate-configuration-group config))
2715 (user (agate-configuration-user config)))
2716 `(,@(if (equal? group "agate")
2717 '()
2718 (list (user-group (name "agate") (system? #t))))
2719 ,(user-group
2720 (name group)
2721 (system? #t))
2722 ,(user-account
2723 (name user)
2724 (group group)
2725 (supplementary-groups '("agate"))
2726 (system? #t)
2727 (comment "agate server user")
2728 (home-directory "/var/empty")
2729 (shell (file-append shadow "/sbin/nologin")))))))
2730
2731(define agate-service-type
2732 (service-type
2733 (name 'agate)
2734 (extensions
2735 (list (service-extension account-service-type
2736 agate-accounts)
2737 (service-extension shepherd-root-service-type
2738 agate-shepherd-service)))
2739 (default-value (agate-configuration))
2740 (description "Run Agate, a simple Gemini protocol server written in
2741Rust.")))
2742
2743(define (serialize-string field-name val)
2744 (format #f "~a=~a\n" field-name val))
2745
2746(define (string-or-file-path? val)
2747 (string? val))
2748(define (serialize-string-or-file-path field-name val)
2749 (serialize-string (if (absolute-file-name? val)
2750 (format #f "~a_FILE" field-name) field-name) val))
2751(define-maybe string-or-file-path)
2752
2753(define (serialize-list field-name val)
2754 (string-append (string-join val "\n") "\n"))
2755(define-maybe list)
2756
2757(define (serialize-boolean field-name val)
2758 (if val (serialize-string field-name "1") (serialize-string field-name "0")))
2759
2760(define-configuration/no-serialization miniflux-configuration
2761 (listen-address
2762 (string "127.0.0.1:8080")
2763 "Address to listen on.
2764Use absolute path like @code{\"/var/run/miniflux/miniflux.sock\"} for a Unix socket.")
2765 (base-url
2766 (string "http://127.0.0.1/")
2767 "Base URL to generate HTML links and base path for cookies.")
2768 (create-administrator-account?
2769 (boolean #f)
2770 "Create an initial administrator account.")
2771 (administrator-account-name
2772 maybe-string-or-file-path
2773 "Initial administrator account name as a string or an absolute path to a file with a account name inside.")
2774 (administrator-account-password
2775 maybe-string-or-file-path
2776 "Initial administrator account password as a string or an absolute path to a file with a password inside.")
2777 (run-migrations?
2778 (boolean #t)
2779 "Run database migrations during application startup.")
2780 (database-url
2781 (string "host=/var/run/postgresql")
2782 "PostgreSQL connection string.")
2783 (user
2784 (string "miniflux")
2785 "User name for Postgresql and system account.")
2786 (group
2787 (string "miniflux")
2788 "Group for the system account.")
2789 (log-file
2790 (string "/var/log/miniflux.log")
2791 "Path to the log file.")
2792 (extra-settings
2793 maybe-list
2794 "Extra configuration parameters as a list of strings."))
2795
2796(define (miniflux-serialize-configuration config)
2797 (match-record config <miniflux-configuration>
2798 (listen-address base-url create-administrator-account?
2799 administrator-account-name administrator-account-password
2800 run-migrations? database-url extra-settings)
2801 (string-append (serialize-string "LISTEN_ADDR" listen-address)
2802 (serialize-string "BASE_URL" base-url)
2803 (serialize-boolean "CREATE_ADMIN" create-administrator-account?)
2804 (serialize-maybe-string-or-file-path "ADMIN_USERNAME" administrator-account-name)
2805 (serialize-maybe-string-or-file-path "ADMIN_PASSWORD" administrator-account-password)
2806 (serialize-boolean "RUN_MIGRATIONS" run-migrations?)
2807 (serialize-string "DATABASE_URL" database-url)
2808 (serialize-maybe-list #f extra-settings))))
2809
2810(define (miniflux-configuration-file config)
2811 (mixed-text-file "miniflux.conf" (miniflux-serialize-configuration config)))
2812
2813(define (pair->file-system-mapping pair previous)
2814 (if (pair? pair)
2815 (let ((path (car pair))
2816 (writable (cdr pair)))
2817 (if (or (and (string? path)
2818 (absolute-file-name? path))
2819 (computed-file? path))
2820 (append previous (list (file-system-mapping
2821 (source path)
2822 (target source)
2823 (writable? writable))))
2824 previous))
2825 previous))
2826
2827(define (miniflux-shepherd-service config)
2828 (match-record config <miniflux-configuration>
2829 (user group log-file database-url listen-address
2830 administrator-account-name administrator-account-password)
2831 (let ((config-file (miniflux-configuration-file config)))
2832 (list (shepherd-service
2833 (documentation "Run Miniflux server")
2834 (provision '(miniflux))
2835 (requirement '(postgres networking))
2836 (start #~(make-forkexec-constructor
2837 (list #$(least-authority-wrapper
2838 (file-append miniflux "/bin/miniflux")
2839 #:name "miniflux"
2840 #:user user
2841 #:group group
2842 #:preserved-environment-variables
2843 (append %default-preserved-environment-variables
2844 '("SSL_CERT_FILE"))
2845 #:mappings
2846 (fold pair->file-system-mapping
2847 '()
2848 `((,log-file . #t)
2849 (,config-file . #f)
2850 ("/etc/ssl/certs/ca-certificates.crt" . #f)
2851 (,administrator-account-name . #f)
2852 (,administrator-account-password . #f)
2853 (,(dirname listen-address) . #t)
2854 ,(let* ((db-socket-match (string-match ".*host=(/[^ ]*).*" database-url))
2855 (db-socket (if db-socket-match (match:substring db-socket-match 1) #f)))
2856 (if db-socket
2857 `(,db-socket . #t)))))
2858 #:namespaces
2859 (fold delq %namespaces '(net user)))
2860 "-config-file"
2861 #$config-file)
2862 #:log-file #$log-file))
2863 (stop #~(make-kill-destructor))
2864 (actions
2865 (list
2866 (shepherd-configuration-action config-file))))))))
2867
2868(define (miniflux-accounts config)
2869 (match-record config <miniflux-configuration>
2870 (user group)
2871 `(,(user-group
2872 (name group)
2873 (system? #t))
2874 ,(user-account
2875 (name user)
2876 (group group)
2877 (system? #t)
2878 (comment "miniflux server user")
2879 (home-directory "/var/empty")
2880 (shell (file-append shadow "/sbin/nologin"))))))
2881
2882(define (miniflux-postgresql-role config)
2883 (list (postgresql-role
2884 (name (miniflux-configuration-user config))
2885 (create-database? #t))))
2886
2887(define (miniflux-log-files config)
2888 (list (miniflux-configuration-log-file config)))
2889
2890(define (miniflux-activation-service-type config)
2891 (match-record config <miniflux-configuration>
2892 (user listen-address)
2893 #~(begin
2894 (use-modules (gnu build activation))
2895 (let ((user (getpwnam #$user)))
2896 (if (absolute-file-name? #$listen-address)
2897 (mkdir-p/perms (dirname #$listen-address) user #o755))))))
2898
2899(define miniflux-service-type
2900 (service-type
2901 (name 'miniflux)
2902 (default-value (miniflux-configuration))
2903 (extensions
2904 (list (service-extension account-service-type
2905 miniflux-accounts)
2906 (service-extension postgresql-role-service-type
2907 miniflux-postgresql-role)
2908 (service-extension shepherd-root-service-type
2909 miniflux-shepherd-service)
2910 (service-extension log-rotation-service-type
2911 miniflux-log-files)
2912 (service-extension activation-service-type
2913 miniflux-activation-service-type)))
2914 (description "Run Miniflux, minimalist feed reader")))