summary refs log tree commit diff
path: root/gnu/services
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/services')
-rw-r--r--gnu/services/admin.scm20
-rw-r--r--gnu/services/avahi.scm5
-rw-r--r--gnu/services/base.scm1145
-rw-r--r--gnu/services/certbot.scm4
-rw-r--r--gnu/services/configuration.scm21
-rw-r--r--gnu/services/cuirass.scm28
-rw-r--r--gnu/services/databases.scm199
-rw-r--r--gnu/services/dbus.scm2
-rw-r--r--gnu/services/desktop.scm25
-rw-r--r--gnu/services/dict.scm3
-rw-r--r--gnu/services/games.scm36
-rw-r--r--gnu/services/ganeti.scm101
-rw-r--r--gnu/services/getmail.scm68
-rw-r--r--gnu/services/guix.scm243
-rw-r--r--gnu/services/ldap.scm317
-rw-r--r--gnu/services/lightdm.scm22
-rw-r--r--gnu/services/mail.scm234
-rw-r--r--gnu/services/mcron.scm47
-rw-r--r--gnu/services/monitoring.scm43
-rw-r--r--gnu/services/networking.scm821
-rw-r--r--gnu/services/nix.scm11
-rw-r--r--gnu/services/samba.scm12
-rw-r--r--gnu/services/security.scm66
-rw-r--r--gnu/services/shepherd.scm25
-rw-r--r--gnu/services/sound.scm18
-rw-r--r--gnu/services/ssh.scm6
-rw-r--r--gnu/services/version-control.scm16
-rw-r--r--gnu/services/virtualization.scm10
-rw-r--r--gnu/services/vnc.scm247
-rw-r--r--gnu/services/vpn.scm80
-rw-r--r--gnu/services/web.scm132
-rw-r--r--gnu/services/xorg.scm213
32 files changed, 2709 insertions, 1511 deletions
diff --git a/gnu/services/admin.scm b/gnu/services/admin.scm
index 252bedb0bd..1c10cfb1f6 100644
--- a/gnu/services/admin.scm
+++ b/gnu/services/admin.scm
@@ -1,7 +1,8 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Jan Nieuwenhuizen <janneke@gnu.org>
-;;; Copyright © 2016-2022 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016-2023 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2020 Brice Waegeneire <brice@waegenei.re>
+;;; Copyright © 2023 Giacomo Leidi <goodoldpaul@autistici.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -58,6 +59,7 @@
             unattended-upgrade-configuration
             unattended-upgrade-configuration?
             unattended-upgrade-configuration-operating-system-file
+            unattended-upgrade-configuration-operating-system-expression
             unattended-upgrade-configuration-channels
             unattended-upgrade-configuration-schedule
             unattended-upgrade-configuration-services-to-restart
@@ -263,6 +265,8 @@ Old log files are removed or compressed according to the configuration.")
   unattended-upgrade-configuration?
   (operating-system-file unattended-upgrade-operating-system-file
                          (default "/run/current-system/configuration.scm"))
+  (operating-system-expression unattended-upgrade-operating-system-expression
+                               (default #f))
   (schedule             unattended-upgrade-configuration-schedule
                         (default "30 01 * * 0"))
   (channels             unattended-upgrade-configuration-channels
@@ -296,6 +300,14 @@ Old log files are removed or compressed according to the configuration.")
   (define config-file
     (unattended-upgrade-operating-system-file config))
 
+  (define expression
+    (unattended-upgrade-operating-system-expression config))
+
+  (define arguments
+    (if expression
+        #~(list "-e" (object->string '#$expression))
+        #~(list #$config-file)))
+
   (define code
     (with-imported-modules (source-module-closure '((guix build utils)
                                                     (gnu services herd)))
@@ -333,9 +345,9 @@ Old log files are removed or compressed according to the configuration.")
           (format #t "~a starting upgrade...~%" (timestamp))
           (guard (c ((invoke-error? c)
                      (report-invoke-error c)))
-            (invoke #$(file-append guix "/bin/guix")
-                    "time-machine" "-C" #$channels
-                    "--" "system" "reconfigure" #$config-file)
+            (apply invoke #$(file-append guix "/bin/guix")
+                   "time-machine" "-C" #$channels
+                   "--" "system" "reconfigure" #$arguments)
 
             ;; 'guix system delete-generations' fails when there's no
             ;; matching generation.  Thus, catch 'invoke-error?'.
diff --git a/gnu/services/avahi.scm b/gnu/services/avahi.scm
index 3b8d0512c7..1c4220e490 100644
--- a/gnu/services/avahi.scm
+++ b/gnu/services/avahi.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2014-2020, 2022 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -137,7 +137,8 @@
                            #$@(if debug? #~("--debug") #~())
                            "-f" #$config)
                      #:pid-file "/run/avahi-daemon/pid"))
-           (stop #~(make-kill-destructor))))))
+           (stop #~(make-kill-destructor))
+           (actions (list (shepherd-configuration-action config)))))))
 
 (define avahi-service-type
   (let ((avahi-package (compose list avahi-configuration-avahi)))
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 616bc42e69..9e799445d2 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -19,6 +19,7 @@
 ;;; Copyright © 2021 muradm <mail@muradm.net>
 ;;; Copyright © 2022 Guillaume Le Vaillant <glv@posteo.net>
 ;;; Copyright © 2022 Justin Veilleux <terramorpha@cock.li>
+;;; Copyright © 2022 ( <paren@disroot.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -55,17 +56,25 @@
                 #:select (file-system-packages))
   #:use-module (gnu packages admin)
   #:use-module ((gnu packages linux)
-                #:select (alsa-utils crda eudev e2fsprogs fuse gpm kbd lvm2 rng-tools))
+                #:select (alsa-utils btrfs-progs crda eudev
+                          e2fsprogs f2fs-tools fuse gpm kbd lvm2 rng-tools
+                          util-linux xfsprogs))
   #:use-module (gnu packages bash)
   #:use-module ((gnu packages base)
-                #:select (coreutils glibc glibc-utf8-locales tar))
+                #:select (coreutils glibc glibc-utf8-locales tar
+                          canonical-package))
   #:use-module ((gnu packages compression) #:select (gzip))
   #:autoload   (gnu packages guile-xyz) (guile-netlink)
   #:autoload   (gnu packages hurd) (hurd)
   #:use-module (gnu packages package-management)
   #:use-module ((gnu packages gnupg) #:select (guile-gcrypt))
-  #:use-module (gnu packages linux)
+  #:use-module ((gnu packages disk)
+                #:select (dosfstools))
+  #:use-module ((gnu packages file-systems)
+                #:select (bcachefs-tools exfat-utils jfsutils zfs))
+  #:use-module (gnu packages fonts)
   #:use-module (gnu packages terminals)
+  #:use-module ((gnu packages wm) #:select (sway))
   #:use-module ((gnu build file-systems)
                 #:select (mount-flags->bit-mask
                           swap-space->flags-bit-mask))
@@ -86,6 +95,7 @@
   #:export (fstab-service-type
             root-file-system-service
             file-system-service-type
+            file-system-utilities
             swap-service
             host-name-service
             %default-console-font
@@ -188,6 +198,7 @@
             guix-configuration-generate-substitute-key?
             guix-configuration-extra-options
             guix-configuration-log-file
+            guix-configuration-environment
 
             guix-extension
             guix-extension?
@@ -231,6 +242,8 @@
             greetd-configuration
             greetd-terminal-configuration
             greetd-agreety-session
+            greetd-wlgreet-session
+            greetd-wlgreet-sway-session
 
             %base-services))
 
@@ -488,6 +501,31 @@ upon boot."
                 (memq 'bind-mount (file-system-flags file-system))))
           file-systems))
 
+(define (file-system-type->utilities type)
+  "Return the package providing the utilities for file system TYPE, #f
+otherwise."
+  (assoc-ref
+   `(("bcachefs" . ,bcachefs-tools)
+     ("btrfs" . ,btrfs-progs)
+     ("exfat" . ,exfat-utils)
+     ("ext2" . ,e2fsprogs)
+     ("ext3" . ,e2fsprogs)
+     ("ext4" . ,e2fsprogs)
+     ("fat" . ,dosfstools)
+     ("f2fs" . ,f2fs-tools)
+     ("jfs" . ,jfsutils)
+     ("vfat" . ,dosfstools)
+     ("xfs" . ,xfsprogs)
+     ("zfs" . ,zfs))
+   type))
+
+(define (file-system-utilities file-systems)
+  "Return a list of packages containing file system utilities for
+FILE-SYSTEMS."
+  (filter-map (lambda (file-system)
+                (file-system-type->utilities (file-system-type file-system)))
+              file-systems))
+
 (define file-system-service-type
   (service-type (name 'file-systems)
                 (extensions
@@ -495,6 +533,8 @@ upon boot."
                                           file-system-shepherd-services)
                        (service-extension fstab-service-type
                                           file-system-fstab-entries)
+                       (service-extension profile-service-type
+                                          file-system-utilities)
 
                        ;; Have 'user-processes' depend on 'file-systems'.
                        (service-extension user-processes-service-type
@@ -940,148 +980,148 @@ to use as the tty.  This is primarily useful for headless systems."
                ((device-name _ ...)
                 device-name))))))))
 
-(define agetty-shepherd-service
-  (match-lambda
-    (($ <agetty-configuration> agetty tty term baud-rate auto-login
-        login-program login-pause? eight-bits? no-reset? remote? flow-control?
-        host no-issue? init-string no-clear? local-line extract-baud?
-        skip-login? no-newline? login-options chroot hangup? keep-baud? timeout
-        detect-case? wait-cr? no-hints? no-hostname? long-hostname?
-        erase-characters kill-characters chdir delay nice extra-options
-        shepherd-requirement)
-     (list
-       (shepherd-service
-         (documentation "Run agetty on a tty.")
-         (provision (list (symbol-append 'term- (string->symbol (or tty "console")))))
-
-         ;; Since the login prompt shows the host name, wait for the 'host-name'
-         ;; service to be done.  Also wait for udev essentially so that the tty
-         ;; text is not lost in the middle of kernel messages (see also
-         ;; mingetty-shepherd-service).
-         (requirement (cons* 'user-processes 'host-name 'udev
-                             shepherd-requirement))
-
-         (modules '((ice-9 match) (gnu build linux-boot)))
-         (start
-          (with-imported-modules  (source-module-closure
-                                   '((gnu build linux-boot)))
-            #~(lambda args
-                (let ((defaulted-tty #$(or tty (default-serial-port))))
-                  (apply
-                   (if defaulted-tty
-                       (make-forkexec-constructor
-                        (list #$(file-append util-linux "/sbin/agetty")
-                              #$@extra-options
-                              #$@(if eight-bits?
-                                     #~("--8bits")
-                                     #~())
-                              #$@(if no-reset?
-                                     #~("--noreset")
-                                     #~())
-                              #$@(if remote?
-                                     #~("--remote")
-                                     #~())
-                              #$@(if flow-control?
-                                     #~("--flow-control")
-                                     #~())
-                              #$@(if host
-                                     #~("--host" #$host)
-                                     #~())
-                              #$@(if no-issue?
-                                     #~("--noissue")
-                                     #~())
-                              #$@(if init-string
-                                     #~("--init-string" #$init-string)
-                                     #~())
-                              #$@(if no-clear?
-                                     #~("--noclear")
-                                     #~())
+(define (agetty-shepherd-service config)
+  (match-record config <agetty-configuration>
+    (agetty tty term baud-rate auto-login
+            login-program login-pause? eight-bits? no-reset? remote? flow-control?
+            host no-issue? init-string no-clear? local-line extract-baud?
+            skip-login? no-newline? login-options chroot hangup? keep-baud? timeout
+            detect-case? wait-cr? no-hints? no-hostname? long-hostname?
+            erase-characters kill-characters chdir delay nice extra-options
+            shepherd-requirement)
+    (list
+     (shepherd-service
+      (documentation "Run agetty on a tty.")
+      (provision (list (symbol-append 'term- (string->symbol (or tty "console")))))
+
+      ;; Since the login prompt shows the host name, wait for the 'host-name'
+      ;; service to be done.  Also wait for udev essentially so that the tty
+      ;; text is not lost in the middle of kernel messages (see also
+      ;; mingetty-shepherd-service).
+      (requirement (cons* 'user-processes 'host-name 'udev
+                          shepherd-requirement))
+
+      (modules '((ice-9 match) (gnu build linux-boot)))
+      (start
+       (with-imported-modules  (source-module-closure
+                                '((gnu build linux-boot)))
+         #~(lambda args
+             (let ((defaulted-tty #$(or tty (default-serial-port))))
+               (apply
+                (if defaulted-tty
+                    (make-forkexec-constructor
+                     (list #$(file-append util-linux "/sbin/agetty")
+                           #$@extra-options
+                           #$@(if eight-bits?
+                                  #~("--8bits")
+                                  #~())
+                           #$@(if no-reset?
+                                  #~("--noreset")
+                                  #~())
+                           #$@(if remote?
+                                  #~("--remote")
+                                  #~())
+                           #$@(if flow-control?
+                                  #~("--flow-control")
+                                  #~())
+                           #$@(if host
+                                  #~("--host" #$host)
+                                  #~())
+                           #$@(if no-issue?
+                                  #~("--noissue")
+                                  #~())
+                           #$@(if init-string
+                                  #~("--init-string" #$init-string)
+                                  #~())
+                           #$@(if no-clear?
+                                  #~("--noclear")
+                                  #~())
 ;;; FIXME This doesn't work as expected. According to agetty(8), if this option
 ;;; is not passed, then the default is 'auto'. However, in my tests, when that
 ;;; option is selected, agetty never presents the login prompt, and the
 ;;; term-ttyS0 service respawns every few seconds.
-                              #$@(if local-line
-                                     #~(#$(match local-line
-                                            ('auto "--local-line=auto")
-                                            ('always "--local-line=always")
-                                            ('never "-local-line=never")))
-                                     #~())
-                              #$@(if tty
-                                     #~()
-                                     #~("--keep-baud"))
-                              #$@(if extract-baud?
-                                     #~("--extract-baud")
-                                     #~())
-                              #$@(if skip-login?
-                                     #~("--skip-login")
-                                     #~())
-                              #$@(if no-newline?
-                                     #~("--nonewline")
-                                     #~())
-                              #$@(if login-options
-                                     #~("--login-options" #$login-options)
-                                     #~())
-                              #$@(if chroot
-                                     #~("--chroot" #$chroot)
-                                     #~())
-                              #$@(if hangup?
-                                     #~("--hangup")
-                                     #~())
-                              #$@(if keep-baud?
-                                     #~("--keep-baud")
-                                     #~())
-                              #$@(if timeout
-                                     #~("--timeout" #$(number->string timeout))
-                                     #~())
-                              #$@(if detect-case?
-                                     #~("--detect-case")
-                                     #~())
-                              #$@(if wait-cr?
-                                     #~("--wait-cr")
-                                     #~())
-                              #$@(if no-hints?
-                                     #~("--nohints?")
-                                     #~())
-                              #$@(if no-hostname?
-                                     #~("--nohostname")
-                                     #~())
-                              #$@(if long-hostname?
-                                     #~("--long-hostname")
-                                     #~())
-                              #$@(if erase-characters
-                                     #~("--erase-chars" #$erase-characters)
-                                     #~())
-                              #$@(if kill-characters
-                                     #~("--kill-chars" #$kill-characters)
-                                     #~())
-                              #$@(if chdir
-                                     #~("--chdir" #$chdir)
-                                     #~())
-                              #$@(if delay
-                                     #~("--delay" #$(number->string delay))
-                                     #~())
-                              #$@(if nice
-                                     #~("--nice" #$(number->string nice))
-                                     #~())
-                              #$@(if auto-login
-                                     (list "--autologin" auto-login)
-                                     '())
-                              #$@(if login-program
-                                     #~("--login-program" #$login-program)
-                                     #~())
-                              #$@(if login-pause?
-                                     #~("--login-pause")
-                                     #~())
-                              defaulted-tty
-                              #$@(if baud-rate
-                                     #~(#$baud-rate)
-                                     #~())
-                              #$@(if term
-                                     #~(#$term)
-                                     #~())))
-                       (const #f))                 ; never start.
-                   args)))))
-         (stop #~(make-kill-destructor)))))))
+                           #$@(if local-line
+                                  #~(#$(match local-line
+                                         ('auto "--local-line=auto")
+                                         ('always "--local-line=always")
+                                         ('never "-local-line=never")))
+                                  #~())
+                           #$@(if tty
+                                  #~()
+                                  #~("--keep-baud"))
+                           #$@(if extract-baud?
+                                  #~("--extract-baud")
+                                  #~())
+                           #$@(if skip-login?
+                                  #~("--skip-login")
+                                  #~())
+                           #$@(if no-newline?
+                                  #~("--nonewline")
+                                  #~())
+                           #$@(if login-options
+                                  #~("--login-options" #$login-options)
+                                  #~())
+                           #$@(if chroot
+                                  #~("--chroot" #$chroot)
+                                  #~())
+                           #$@(if hangup?
+                                  #~("--hangup")
+                                  #~())
+                           #$@(if keep-baud?
+                                  #~("--keep-baud")
+                                  #~())
+                           #$@(if timeout
+                                  #~("--timeout" #$(number->string timeout))
+                                  #~())
+                           #$@(if detect-case?
+                                  #~("--detect-case")
+                                  #~())
+                           #$@(if wait-cr?
+                                  #~("--wait-cr")
+                                  #~())
+                           #$@(if no-hints?
+                                  #~("--nohints?")
+                                  #~())
+                           #$@(if no-hostname?
+                                  #~("--nohostname")
+                                  #~())
+                           #$@(if long-hostname?
+                                  #~("--long-hostname")
+                                  #~())
+                           #$@(if erase-characters
+                                  #~("--erase-chars" #$erase-characters)
+                                  #~())
+                           #$@(if kill-characters
+                                  #~("--kill-chars" #$kill-characters)
+                                  #~())
+                           #$@(if chdir
+                                  #~("--chdir" #$chdir)
+                                  #~())
+                           #$@(if delay
+                                  #~("--delay" #$(number->string delay))
+                                  #~())
+                           #$@(if nice
+                                  #~("--nice" #$(number->string nice))
+                                  #~())
+                           #$@(if auto-login
+                                  (list "--autologin" auto-login)
+                                  '())
+                           #$@(if login-program
+                                  #~("--login-program" #$login-program)
+                                  #~())
+                           #$@(if login-pause?
+                                  #~("--login-pause")
+                                  #~())
+                           defaulted-tty
+                           #$@(if baud-rate
+                                  #~(#$baud-rate)
+                                  #~())
+                           #$@(if term
+                                  #~(#$term)
+                                  #~())))
+                    (const #f))                   ; never start.
+                args)))))
+      (stop #~(make-kill-destructor))))))
 
 (define agetty-service-type
   (service-type (name 'agetty)
@@ -1111,42 +1151,42 @@ the tty to run, among other things."
   (clear-on-logout? mingetty-clear-on-logout?       ;Boolean
                     (default #t)))
 
-(define mingetty-shepherd-service
-  (match-lambda
-    (($ <mingetty-configuration> mingetty tty auto-login login-program
-                                 login-pause? clear-on-logout?)
-     (list
-      (shepherd-service
-       (documentation "Run mingetty on an tty.")
-       (provision (list (symbol-append 'term- (string->symbol tty))))
-
-       ;; Since the login prompt shows the host name, wait for the 'host-name'
-       ;; service to be done.  Also wait for udev essentially so that the tty
-       ;; text is not lost in the middle of kernel messages (XXX).
-       (requirement '(user-processes host-name udev virtual-terminal))
-
-       (start  #~(make-forkexec-constructor
-                  (list #$(file-append mingetty "/sbin/mingetty")
-
-                        ;; Avoiding 'vhangup' allows us to avoid 'setfont'
-                        ;; errors down the path where various ioctls get
-                        ;; EIO--see 'hung_up_tty_ioctl' in driver/tty/tty_io.c
-                        ;; in Linux.
-                        "--nohangup" #$tty
-
-                        #$@(if clear-on-logout?
-                               #~()
-                               #~("--noclear"))
-                        #$@(if auto-login
-                               #~("--autologin" #$auto-login)
-                               #~())
-                        #$@(if login-program
-                               #~("--loginprog" #$login-program)
-                               #~())
-                        #$@(if login-pause?
-                               #~("--loginpause")
-                               #~()))))
-       (stop   #~(make-kill-destructor)))))))
+(define (mingetty-shepherd-service config)
+  (match-record config <mingetty-configuration>
+    (mingetty tty auto-login login-program
+              login-pause? clear-on-logout?)
+    (list
+     (shepherd-service
+      (documentation "Run mingetty on an tty.")
+      (provision (list (symbol-append 'term- (string->symbol tty))))
+
+      ;; Since the login prompt shows the host name, wait for the 'host-name'
+      ;; service to be done.  Also wait for udev essentially so that the tty
+      ;; text is not lost in the middle of kernel messages (XXX).
+      (requirement '(user-processes host-name udev virtual-terminal))
+
+      (start  #~(make-forkexec-constructor
+                 (list #$(file-append mingetty "/sbin/mingetty")
+
+                       ;; Avoiding 'vhangup' allows us to avoid 'setfont'
+                       ;; errors down the path where various ioctls get
+                       ;; EIO--see 'hung_up_tty_ioctl' in driver/tty/tty_io.c
+                       ;; in Linux.
+                       "--nohangup" #$tty
+
+                       #$@(if clear-on-logout?
+                              #~()
+                              #~("--noclear"))
+                       #$@(if auto-login
+                              #~("--autologin" #$auto-login)
+                              #~())
+                       #$@(if login-program
+                              #~("--loginprog" #$login-program)
+                              #~())
+                       #$@(if login-pause?
+                              #~("--loginpause")
+                              #~()))))
+      (stop   #~(make-kill-destructor))))))
 
 (define mingetty-service-type
   (service-type (name 'mingetty)
@@ -1174,7 +1214,13 @@ the tty to run, among other things."
   (name-services nscd-configuration-name-services ;list of file-like
                  (default '()))
   (glibc      nscd-configuration-glibc            ;file-like
-              (default glibc)))
+              (default (let-system (system target)
+                         ;; Unless we're cross-compiling, arrange to use nscd
+                         ;; from 'glibc-final' instead of pulling in a second
+                         ;; glibc copy.
+                         (if target
+                             glibc
+                             (canonical-package glibc))))))
 
 (define-record-type* <nscd-cache> nscd-cache make-nscd-cache
   nscd-cache?
@@ -1223,46 +1269,47 @@ the tty to run, among other things."
 (define (nscd.conf-file config)
   "Return the @file{nscd.conf} configuration file for @var{config}, an
 @code{<nscd-configuration>} object."
-  (define cache->config
-    (match-lambda
-      (($ <nscd-cache> (= symbol->string database)
-                       positive-ttl negative-ttl size check-files?
-                       persistent? shared? max-size propagate?)
-       (string-append "\nenable-cache\t" database "\tyes\n"
-
-                      "positive-time-to-live\t" database "\t"
-                      (number->string positive-ttl) "\n"
-                      "negative-time-to-live\t" database "\t"
-                      (number->string negative-ttl) "\n"
-                      "suggested-size\t" database "\t"
-                      (number->string size) "\n"
-                      "check-files\t" database "\t"
-                      (if check-files? "yes\n" "no\n")
-                      "persistent\t" database "\t"
-                      (if persistent? "yes\n" "no\n")
-                      "shared\t" database "\t"
-                      (if shared? "yes\n" "no\n")
-                      "max-db-size\t" database "\t"
-                      (number->string max-size) "\n"
-                      "auto-propagate\t" database "\t"
-                      (if propagate? "yes\n" "no\n")))))
-
-  (match config
-    (($ <nscd-configuration> log-file debug-level caches)
-     (plain-file "nscd.conf"
-                 (string-append "\
+  (define (cache->config cache)
+    (match-record cache <nscd-cache>
+      (database positive-time-to-live negative-time-to-live
+                suggested-size check-files?
+                persistent? shared? max-database-size auto-propagate?)
+      (let ((database (symbol->string database)))
+        (string-append "\nenable-cache\t" database "\tyes\n"
+
+                       "positive-time-to-live\t" database "\t"
+                       (number->string positive-time-to-live) "\n"
+                       "negative-time-to-live\t" database "\t"
+                       (number->string negative-time-to-live) "\n"
+                       "suggested-size\t" database "\t"
+                       (number->string suggested-size) "\n"
+                       "check-files\t" database "\t"
+                       (if check-files? "yes\n" "no\n")
+                       "persistent\t" database "\t"
+                       (if persistent? "yes\n" "no\n")
+                       "shared\t" database "\t"
+                       (if shared? "yes\n" "no\n")
+                       "max-db-size\t" database "\t"
+                       (number->string max-database-size) "\n"
+                       "auto-propagate\t" database "\t"
+                       (if auto-propagate? "yes\n" "no\n")))))
+
+  (match-record config <nscd-configuration>
+    (log-file debug-level caches)
+    (plain-file "nscd.conf"
+                (string-append "\
 # Configuration of libc's name service cache daemon (nscd).\n\n"
-                                (if log-file
-                                    (string-append "logfile\t" log-file)
-                                    "")
-                                "\n"
-                                (if debug-level
-                                    (string-append "debug-level\t"
-                                                   (number->string debug-level))
-                                    "")
-                                "\n"
-                                (string-concatenate
-                                 (map cache->config caches)))))))
+                               (if log-file
+                                   (string-append "logfile\t" log-file)
+                                   "")
+                               "\n"
+                               (if debug-level
+                                   (string-append "debug-level\t"
+                                                  (number->string debug-level))
+                                   "")
+                               "\n"
+                               (string-concatenate
+                                (map cache->config caches))))))
 
 (define (nscd-action-procedure nscd config option)
   ;; XXX: This is duplicated from mcron; factorize.
@@ -1290,10 +1337,11 @@ the tty to run, among other things."
              (loop)))))))
 
 (define (nscd-actions nscd config)
-  "Return Shepherd actions for NSCD."
+  "Return Shepherd actions for NSCD using CONFIG its config file."
   ;; Make this functionality available as actions because that's a simple way
   ;; to run the right 'nscd' binary with the right config file.
-  (list (shepherd-action
+  (list (shepherd-configuration-action config)
+        (shepherd-action
          (name 'statistics)
          (documentation "Display statistics about nscd usage.")
          (procedure (nscd-action-procedure nscd config "--statistics")))
@@ -1607,7 +1655,9 @@ archive' public keys, with GUIX."
   (http-proxy       guix-http-proxy               ;string | #f
                     (default #f))
   (tmpdir           guix-tmpdir                   ;string | #f
-                    (default #f)))
+                    (default #f))
+  (environment      guix-configuration-environment  ;list of strings
+                    (default '())))
 
 (define %default-guix-configuration
   (guix-configuration))
@@ -1663,7 +1713,7 @@ proxy of 'guix-daemon'...~%")
     (guix build-group build-accounts authorize-key? authorized-keys
           use-substitutes? substitute-urls max-silent-time timeout
           log-compression discover? extra-options log-file
-          http-proxy tmpdir chroot-directories)
+          http-proxy tmpdir chroot-directories environment)
     (list (shepherd-service
            (documentation "Run the Guix daemon.")
            (provision '(guix-daemon))
@@ -1752,24 +1802,23 @@ proxy of 'guix-daemon'...~%")
                            (if proxy
                                (list (string-append "http_proxy=" proxy)
                                      (string-append "https_proxy=" proxy))
-                               '()))
+                               '())
+                           '#$environment)
 
                    #:log-file #$log-file))))
            (stop #~(make-kill-destructor))))))
 
 (define (guix-accounts config)
   "Return the user accounts and user groups for CONFIG."
-  (match config
-    (($ <guix-configuration> _ build-group build-accounts)
-     (cons (user-group
-            (name build-group)
-            (system? #t)
-
-            ;; Use a fixed GID so that we can create the store with the right
-            ;; owner.
-            (id 30000))
-           (guix-build-accounts build-accounts
-                                #:group build-group)))))
+  (cons (user-group
+         (name (guix-configuration-build-group config))
+         (system? #t)
+
+         ;; Use a fixed GID so that we can create the store with the right
+         ;; owner.
+         (id 30000))
+        (guix-build-accounts (guix-configuration-build-accounts config)
+                             #:group (guix-configuration-build-group config))))
 
 (define (guix-activation config)
   "Return the activation gexp for CONFIG."
@@ -1979,7 +2028,9 @@ raise a deprecation warning if the 'compression-level' field was used."
 
 (define %guix-publish-log-rotations
   (list (log-rotation
-         (files (list "/var/log/guix-publish.log")))))
+         (files (list "/var/log/guix-publish.log"))
+         (options `("rotate 4"                    ;don't keep too many of them
+                    ,@%default-log-rotation-options)))))
 
 (define (guix-publish-activation config)
   (let ((cache (guix-publish-configuration-cache config)))
@@ -2092,95 +2143,94 @@ item of @var{packages}."
   (udev-rule "90-kvm.rules"
              "KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0660\"\n"))
 
-(define udev-shepherd-service
+(define (udev-shepherd-service config)
   ;; Return a <shepherd-service> for UDEV with RULES.
-  (match-lambda
-    (($ <udev-configuration> udev)
-     (list
-      (shepherd-service
-       (provision '(udev))
-
-       ;; Udev needs /dev to be a 'devtmpfs' mount so that new device nodes can
-       ;; be added: see
-       ;; <http://www.linuxfromscratch.org/lfs/view/development/chapter07/udev.html>.
-       (requirement '(root-file-system))
-
-       (documentation "Populate the /dev directory, dynamically.")
-       (start
-        (with-imported-modules (source-module-closure
-                                '((gnu build linux-boot)))
-          #~(lambda ()
-              (define udevd
-                ;; 'udevd' from eudev.
-                #$(file-append udev "/sbin/udevd"))
-
-              (define (wait-for-udevd)
-                ;; Wait until someone's listening on udevd's control
-                ;; socket.
-                (let ((sock (socket AF_UNIX SOCK_SEQPACKET 0)))
-                  (let try ()
-                    (catch 'system-error
-                      (lambda ()
-                        (connect sock PF_UNIX "/run/udev/control")
-                        (close-port sock))
-                      (lambda args
-                        (format #t "waiting for udevd...~%")
-                        (usleep 500000)
-                        (try))))))
-
-              ;; Allow udev to find the modules.
-              (setenv "LINUX_MODULE_DIRECTORY"
-                      "/run/booted-system/kernel/lib/modules")
-
-              (let* ((kernel-release
-                      (utsname:release (uname)))
-                     (linux-module-directory
-                      (getenv "LINUX_MODULE_DIRECTORY"))
-                     (directory
-                      (string-append linux-module-directory "/"
-                                     kernel-release))
-                     (old-umask (umask #o022)))
-                ;; If we're in a container, DIRECTORY might not exist,
-                ;; for instance because the host runs a different
-                ;; kernel.  In that case, skip it; we'll just miss a few
-                ;; nodes like /dev/fuse.
-                (when (file-exists? directory)
-                  (make-static-device-nodes directory))
-                (umask old-umask))
-
-              (let ((pid (fork+exec-command
-                          (list udevd)
-                          #:environment-variables
-                          (cons*
-                           ;; The first one is for udev, the second one for
-                           ;; eudev.
-                           "UDEV_CONFIG_FILE=/etc/udev/udev.conf"
-                           "EUDEV_RULES_DIRECTORY=/etc/udev/rules.d"
-                           (string-append "LINUX_MODULE_DIRECTORY="
-                                          (getenv "LINUX_MODULE_DIRECTORY"))
-                           (default-environment-variables)))))
-                ;; Wait until udevd is up and running.  This appears to
-                ;; be needed so that the events triggered below are
-                ;; actually handled.
-                (wait-for-udevd)
-
-                ;; Trigger device node creation.
-                (system* #$(file-append udev "/bin/udevadm")
-                         "trigger" "--action=add")
-
-                ;; Wait for things to settle down.
-                (system* #$(file-append udev "/bin/udevadm")
-                         "settle")
-                pid))))
-       (stop #~(make-kill-destructor))
-
-       ;; When halting the system, 'udev' is actually killed by
-       ;; 'user-processes', i.e., before its own 'stop' method was called.
-       ;; Thus, make sure it is not respawned.
-       (respawn? #f)
-       ;; We need additional modules.
-       (modules `((gnu build linux-boot)        ;'make-static-device-nodes'
-                  ,@%default-modules)))))))
+  (let ((udev (udev-configuration-udev config)))
+    (list
+     (shepherd-service
+      (provision '(udev))
+
+      ;; Udev needs /dev to be a 'devtmpfs' mount so that new device nodes can
+      ;; be added: see
+      ;; <http://www.linuxfromscratch.org/lfs/view/development/chapter07/udev.html>.
+      (requirement '(root-file-system))
+
+      (documentation "Populate the /dev directory, dynamically.")
+      (start
+       (with-imported-modules (source-module-closure
+                               '((gnu build linux-boot)))
+         #~(lambda ()
+             (define udevd
+               ;; 'udevd' from eudev.
+               #$(file-append udev "/sbin/udevd"))
+
+             (define (wait-for-udevd)
+               ;; Wait until someone's listening on udevd's control
+               ;; socket.
+               (let ((sock (socket AF_UNIX SOCK_SEQPACKET 0)))
+                 (let try ()
+                   (catch 'system-error
+                     (lambda ()
+                       (connect sock PF_UNIX "/run/udev/control")
+                       (close-port sock))
+                     (lambda args
+                       (format #t "waiting for udevd...~%")
+                       (usleep 500000)
+                       (try))))))
+
+             ;; Allow udev to find the modules.
+             (setenv "LINUX_MODULE_DIRECTORY"
+                     "/run/booted-system/kernel/lib/modules")
+
+             (let* ((kernel-release
+                     (utsname:release (uname)))
+                    (linux-module-directory
+                     (getenv "LINUX_MODULE_DIRECTORY"))
+                    (directory
+                     (string-append linux-module-directory "/"
+                                    kernel-release))
+                    (old-umask (umask #o022)))
+               ;; If we're in a container, DIRECTORY might not exist,
+               ;; for instance because the host runs a different
+               ;; kernel.  In that case, skip it; we'll just miss a few
+               ;; nodes like /dev/fuse.
+               (when (file-exists? directory)
+                 (make-static-device-nodes directory))
+               (umask old-umask))
+
+             (let ((pid (fork+exec-command
+                         (list udevd)
+                         #:environment-variables
+                         (cons*
+                          ;; The first one is for udev, the second one for
+                          ;; eudev.
+                          "UDEV_CONFIG_FILE=/etc/udev/udev.conf"
+                          "EUDEV_RULES_DIRECTORY=/etc/udev/rules.d"
+                          (string-append "LINUX_MODULE_DIRECTORY="
+                                         (getenv "LINUX_MODULE_DIRECTORY"))
+                          (default-environment-variables)))))
+               ;; Wait until udevd is up and running.  This appears to
+               ;; be needed so that the events triggered below are
+               ;; actually handled.
+               (wait-for-udevd)
+
+               ;; Trigger device node creation.
+               (system* #$(file-append udev "/bin/udevadm")
+                        "trigger" "--action=add")
+
+               ;; Wait for things to settle down.
+               (system* #$(file-append udev "/bin/udevadm")
+                        "settle")
+               pid))))
+      (stop #~(make-kill-destructor))
+
+      ;; When halting the system, 'udev' is actually killed by
+      ;; 'user-processes', i.e., before its own 'stop' method was called.
+      ;; Thus, make sure it is not respawned.
+      (respawn? #f)
+      ;; We need additional modules.
+      (modules `((gnu build linux-boot)           ;'make-static-device-nodes'
+                 ,@%default-modules))))))
 
 (define udev.conf
   (computed-file "udev.conf"
@@ -2188,14 +2238,15 @@ item of @var{packages}."
                      (lambda (port)
                        (format port "udev_rules=\"/etc/udev/rules.d\"~%")))))
 
-(define udev-etc
-  (match-lambda
-    (($ <udev-configuration> udev rules)
-     `(("udev"
-        ,(file-union
-          "udev" `(("udev.conf" ,udev.conf)
-                   ("rules.d" ,(udev-rules-union (cons* udev kvm-udev-rule
-                                                        rules))))))))))
+(define (udev-etc config)
+  (match-record config <udev-configuration>
+    (udev rules)
+    `(("udev"
+       ,(file-union "udev"
+                    `(("udev.conf" ,udev.conf)
+                      ("rules.d"
+                       ,(udev-rules-union (cons* udev kvm-udev-rule
+                                                 rules)))))))))
 
 (define udev-service-type
   (service-type (name 'udev)
@@ -2205,11 +2256,11 @@ item of @var{packages}."
                        (service-extension etc-service-type udev-etc)))
                 (compose concatenate)           ;concatenate the list of rules
                 (extend (lambda (config rules)
-                          (match config
-                            (($ <udev-configuration> udev initial-rules)
-                             (udev-configuration
-                              (udev udev)
-                              (rules (append initial-rules rules)))))))
+                          (let ((initial-rules
+                                 (udev-configuration-rules config)))
+                            (udev-configuration
+                             (inherit config)
+                             (rules (append initial-rules rules))))))
                 (default-value (udev-configuration))
                 (description
                  "Run @command{udev}, which populates the @file{/dev}
@@ -2347,23 +2398,23 @@ instance."
   (options  gpm-configuration-options             ;list of strings
             (default %default-gpm-options)))
 
-(define gpm-shepherd-service
-  (match-lambda
-    (($ <gpm-configuration> gpm options)
-     (list (shepherd-service
-            (requirement '(udev))
-            (provision '(gpm))
-            ;; 'gpm' runs in the background and sets a PID file.
-            ;; Note that it requires running as "root".
-            (start #~(make-forkexec-constructor
-                      (list #$(file-append gpm "/sbin/gpm")
-                            #$@options)
-                      #:pid-file "/var/run/gpm.pid"
-                      #:pid-file-timeout 3))
-            (stop #~(lambda (_)
-                      ;; Return #f if successfully stopped.
-                      (not (zero? (system* #$(file-append gpm "/sbin/gpm")
-                                           "-k"))))))))))
+(define (gpm-shepherd-service config)
+  (match-record config <gpm-configuration>
+    (gpm options)
+    (list (shepherd-service
+           (requirement '(udev))
+           (provision '(gpm))
+           ;; 'gpm' runs in the background and sets a PID file.
+           ;; Note that it requires running as "root".
+           (start #~(make-forkexec-constructor
+                     (list #$(file-append gpm "/sbin/gpm")
+                           #$@options)
+                     #:pid-file "/var/run/gpm.pid"
+                     #:pid-file-timeout 3))
+           (stop #~(lambda (_)
+                     ;; Return #f if successfully stopped.
+                     (not (zero? (system* #$(file-append gpm "/sbin/gpm")
+                                          "-k")))))))))
 
 (define gpm-service-type
   (service-type (name 'gpm)
@@ -2443,7 +2494,15 @@ notably to select, copy, and paste text.  The default options use the
         (documentation "kmscon virtual terminal")
         (requirement '(user-processes udev dbus-system))
         (provision (list (symbol-append 'term- (string->symbol virtual-terminal))))
-        (start #~(make-forkexec-constructor #$kmscon-command))
+        (start #~(make-forkexec-constructor
+                  #$kmscon-command
+
+                  ;; The installer needs to be able to display glyphs from
+                  ;; various scripts, so give it access to unifont.
+                  ;; TODO: Make this configurable.
+                  #:environment-variables
+                  (list (string-append "XDG_DATA_DIRS="
+                                       #$font-gnu-unifont "/share"))))
         (stop #~(make-kill-destructor)))))
    (description "Start the @command{kmscon} virtual terminal emulator for the
 Linux @dfn{kernel mode setting} (KMS).")))
@@ -2616,32 +2675,64 @@ to CONFIG."
                              "/servers/socket/2")
                      #f))))
 
-(define network-set-up/linux
-  (match-lambda
-    (($ <static-networking> addresses links routes)
-     (scheme-file "set-up-network"
-                  (with-extensions (list guile-netlink)
-                    #~(begin
-                        (use-modules (ip addr) (ip link) (ip route))
-
-                        #$@(map (lambda (address)
-                                  #~(begin
-                                      (addr-add #$(network-address-device address)
-                                                #$(network-address-value address)
-                                                #:ipv6?
-                                                #$(network-address-ipv6? address))
-                                      ;; FIXME: loopback?
-                                      (link-set #$(network-address-device address)
-                                                #:multicast-on #t
-                                                #:up #t)))
-                                addresses)
-                        #$@(map (match-lambda
-                                  (($ <network-link> name type arguments)
-                                   #~(link-add #$name #$type
-                                               #:type-args '#$arguments)))
-                                links)
-                        #$@(map (lambda (route)
-                                  #~(route-add #$(network-route-destination route)
+(define (network-set-up/linux config)
+  (match-record config <static-networking>
+    (addresses links routes)
+    (scheme-file "set-up-network"
+                 (with-extensions (list guile-netlink)
+                   #~(begin
+                       (use-modules (ip addr) (ip link) (ip route))
+
+                       #$@(map (lambda (address)
+                                 #~(begin
+                                     (addr-add #$(network-address-device address)
+                                               #$(network-address-value address)
+                                               #:ipv6?
+                                               #$(network-address-ipv6? address))
+                                     ;; FIXME: loopback?
+                                     (link-set #$(network-address-device address)
+                                               #:multicast-on #t
+                                               #:up #t)))
+                               addresses)
+                       #$@(map (match-lambda
+                                 (($ <network-link> name type arguments)
+                                  #~(link-add #$name #$type
+                                              #:type-args '#$arguments)))
+                               links)
+                       #$@(map (lambda (route)
+                                 #~(route-add #$(network-route-destination route)
+                                              #:device
+                                              #$(network-route-device route)
+                                              #:ipv6?
+                                              #$(network-route-ipv6? route)
+                                              #:via
+                                              #$(network-route-gateway route)
+                                              #:src
+                                              #$(network-route-source route)))
+                               routes)
+                       #t)))))
+
+(define (network-tear-down/linux config)
+  (match-record config <static-networking>
+    (addresses links routes)
+    (scheme-file "tear-down-network"
+                 (with-extensions (list guile-netlink)
+                   #~(begin
+                       (use-modules (ip addr) (ip link) (ip route)
+                                    (netlink error)
+                                    (srfi srfi-34))
+
+                       (define-syntax-rule (false-if-netlink-error exp)
+                         (guard (c ((netlink-error? c) #f))
+                           exp))
+
+                       ;; Wrap calls in 'false-if-netlink-error' so this
+                       ;; script goes as far as possible undoing the effects
+                       ;; of "set-up-network".
+
+                       #$@(map (lambda (route)
+                                 #~(false-if-netlink-error
+                                    (route-del #$(network-route-destination route)
                                                #:device
                                                #$(network-route-device route)
                                                #:ipv6?
@@ -2649,80 +2740,47 @@ to CONFIG."
                                                #:via
                                                #$(network-route-gateway route)
                                                #:src
-                                               #$(network-route-source route)))
-                                routes)
-                        #t))))))
-
-(define network-tear-down/linux
-  (match-lambda
-    (($ <static-networking> addresses links routes)
-     (scheme-file "tear-down-network"
-                  (with-extensions (list guile-netlink)
-                    #~(begin
-                        (use-modules (ip addr) (ip link) (ip route)
-                                     (netlink error)
-                                     (srfi srfi-34))
-
-                        (define-syntax-rule (false-if-netlink-error exp)
-                          (guard (c ((netlink-error? c) #f))
-                            exp))
-
-                        ;; Wrap calls in 'false-if-netlink-error' so this
-                        ;; script goes as far as possible undoing the effects
-                        ;; of "set-up-network".
-
-                        #$@(map (lambda (route)
-                                  #~(false-if-netlink-error
-                                     (route-del #$(network-route-destination route)
-                                                #:device
-                                                #$(network-route-device route)
-                                                #:ipv6?
-                                                #$(network-route-ipv6? route)
-                                                #:via
-                                                #$(network-route-gateway route)
-                                                #:src
-                                                #$(network-route-source route))))
-                                routes)
-                        #$@(map (match-lambda
-                                  (($ <network-link> name type arguments)
-                                   #~(false-if-netlink-error
-                                      (link-del #$name))))
-                                links)
-                        #$@(map (lambda (address)
+                                               #$(network-route-source route))))
+                               routes)
+                       #$@(map (match-lambda
+                                 (($ <network-link> name type arguments)
                                   #~(false-if-netlink-error
-                                     (addr-del #$(network-address-device
-                                                  address)
-                                               #$(network-address-value address)
-                                               #:ipv6?
-                                               #$(network-address-ipv6? address))))
-                                addresses)
-                        #f))))))
+                                     (link-del #$name))))
+                               links)
+                       #$@(map (lambda (address)
+                                 #~(false-if-netlink-error
+                                    (addr-del #$(network-address-device
+                                                 address)
+                                              #$(network-address-value address)
+                                              #:ipv6?
+                                              #$(network-address-ipv6? address))))
+                               addresses)
+                       #f)))))
 
 (define (static-networking-shepherd-service config)
-  (match config
-    (($ <static-networking> addresses links routes
-                            provision requirement name-servers)
-     (let ((loopback? (and provision (memq 'loopback provision))))
-       (shepherd-service
+  (match-record config <static-networking>
+    (addresses links routes provision requirement name-servers)
+    (let ((loopback? (and provision (memq 'loopback provision))))
+      (shepherd-service
 
-        (documentation
-         "Bring up the networking interface using a static IP address.")
-        (requirement requirement)
-        (provision provision)
+       (documentation
+        "Bring up the networking interface using a static IP address.")
+       (requirement requirement)
+       (provision provision)
 
-        (start #~(lambda _
-                   ;; Return #t if successfully started.
-                   (load #$(let-system (system target)
-                             (if (string-contains (or target system) "-linux")
-                                 (network-set-up/linux config)
-                                 (network-set-up/hurd config))))))
-        (stop #~(lambda _
-                  ;; Return #f is successfully stopped.
+       (start #~(lambda _
+                  ;; Return #t if successfully started.
                   (load #$(let-system (system target)
                             (if (string-contains (or target system) "-linux")
-                                (network-tear-down/linux config)
-                                (network-tear-down/hurd config))))))
-        (respawn? #f))))))
+                                (network-set-up/linux config)
+                                (network-set-up/hurd config))))))
+       (stop #~(lambda _
+                 ;; Return #f is successfully stopped.
+                 (load #$(let-system (system target)
+                           (if (string-contains (or target system) "-linux")
+                               (network-tear-down/linux config)
+                               (network-tear-down/hurd config))))))
+       (respawn? #f)))))
 
 (define (static-networking-shepherd-services networks)
   (map static-networking-shepherd-service networks))
@@ -2835,51 +2893,152 @@ to handle."
   (extra-env greetd-agreety-extra-env (default '()))
   (xdg-env? greetd-agreety-xdg-env? (default #t)))
 
-(define greetd-agreety-tty-session-command
-  (match-lambda
-    (($ <greetd-agreety-session> _ command args extra-env)
-     (program-file
-      "agreety-tty-session-command"
-      #~(begin
-          (use-modules (ice-9 match))
-          (for-each (match-lambda ((var . val) (setenv var val)))
-                    (quote (#$@extra-env)))
-          (apply execl #$command #$command (list #$@args)))))))
-
-(define greetd-agreety-tty-xdg-session-command
-  (match-lambda
-    (($ <greetd-agreety-session> _ command args extra-env)
-     (program-file
-      "agreety-tty-xdg-session-command"
-      #~(begin
-          (use-modules (ice-9 match))
-          (let*
-              ((username (getenv "USER"))
-               (useruid (passwd:uid (getpwuid username)))
-               (useruid (number->string useruid)))
-            (setenv "XDG_SESSION_TYPE" "tty")
-            (setenv "XDG_RUNTIME_DIR" (string-append "/run/user/" useruid)))
-          (for-each (match-lambda ((var . val) (setenv var val)))
-                    (quote (#$@extra-env)))
-          (apply execl #$command #$command (list #$@args)))))))
-
-(define (make-greetd-agreety-session-command config command)
-  (let ((agreety (file-append (greetd-agreety config) "/bin/agreety")))
+(define (greetd-agreety-tty-session-command config)
+  (match-record config <greetd-agreety-session>
+    (command command-args extra-env)
+    (program-file
+     "agreety-tty-session-command"
+     #~(begin
+         (use-modules (ice-9 match))
+         (for-each (match-lambda ((var . val) (setenv var val)))
+                   (quote (#$@extra-env)))
+         (apply execl #$command #$command (list #$@command-args))))))
+
+(define (greetd-agreety-tty-xdg-session-command config)
+  (match-record config <greetd-agreety-session>
+    (command command-args extra-env)
     (program-file
-     "agreety-command"
-     #~(execl #$agreety #$agreety "-c" #$command))))
-
-(define (make-greetd-default-session-command config-or-command)
-  (cond ((greetd-agreety-session? config-or-command)
-         (cond ((greetd-agreety-xdg-env? config-or-command)
-                (make-greetd-agreety-session-command
-                 config-or-command
-                 (greetd-agreety-tty-xdg-session-command config-or-command)))
-               (#t
-                (make-greetd-agreety-session-command
-                 config-or-command
-                 (greetd-agreety-tty-session-command config-or-command)))))
-        (#t config-or-command)))
+     "agreety-tty-xdg-session-command"
+     #~(begin
+         (use-modules (ice-9 match))
+         (let*
+             ((username (getenv "USER"))
+              (useruid (passwd:uid (getpwuid username)))
+              (useruid (number->string useruid)))
+           (setenv "XDG_SESSION_TYPE" "tty")
+           (setenv "XDG_RUNTIME_DIR" (string-append "/run/user/" useruid)))
+         (for-each (match-lambda ((var . val) (setenv var val)))
+                   (quote (#$@extra-env)))
+         (apply execl #$command #$command (list #$@command-args))))))
+
+(define-gexp-compiler (greetd-agreety-session-compiler
+                       (session <greetd-agreety-session>)
+                       system target)
+  (let ((agreety (file-append (greetd-agreety session)
+                              "/bin/agreety"))
+        (command ((if (greetd-agreety-xdg-env? session)
+                      greetd-agreety-tty-xdg-session-command
+                      greetd-agreety-tty-session-command)
+                  session)))
+    (lower-object
+     (program-file "agreety-command"
+       #~(execl #$agreety #$agreety "-c" #$command)))))
+
+(define-record-type* <greetd-wlgreet-session>
+  greetd-wlgreet-session make-greetd-wlgreet-session
+  greetd-wlgreet-session?
+  (wlgreet greetd-wlgreet (default wlgreet))
+  (command greetd-wlgreet-command
+           (default (file-append sway "/bin/sway")))
+  (command-args greetd-wlgreet-command-args (default '()))
+  (output-mode greetd-wlgreet-output-mode (default "all"))
+  (scale greetd-wlgreet-scale (default 1))
+  (background greetd-wlgreet-background (default '(0 0 0 0.9)))
+  (headline greetd-wlgreet-headline (default '(1 1 1 1)))
+  (prompt greetd-wlgreet-prompt (default '(1 1 1 1)))
+  (prompt-error greetd-wlgreet-prompt-error (default '(1 1 1 1)))
+  (border greetd-wlgreet-border (default '(1 1 1 1)))
+  (extra-env greetd-wlgreet-extra-env (default '())))
+
+(define (greetd-wlgreet-wayland-session-command session)
+  (program-file "wlgreet-session-command"
+    #~(let* ((username (getenv "USER"))
+             (useruid (number->string
+                       (passwd:uid (getpwuid username))))
+             (command #$(greetd-wlgreet-command session)))
+        (use-modules (ice-9 match))
+        (setenv "XDG_SESSION_TYPE" "wayland")
+        (setenv "XDG_RUNTIME_DIR" (string-append "/run/user/" useruid))
+        (for-each (lambda (env) (setenv (car env) (cdr env)))
+                  '(#$@(greetd-wlgreet-extra-env session)))
+        (apply execl command command
+               (list #$@(greetd-wlgreet-command-args session))))))
+
+(define (make-wlgreet-config-color section-name color)
+  (match color
+    ((red green blue opacity)
+     (string-append
+      "[" section-name "]\n"
+      "red = " (number->string red) "\n"
+      "green = " (number->string green) "\n"
+      "blue = " (number->string blue) "\n"
+      "opacity = " (number->string opacity) "\n"))))
+
+(define (make-wlgreet-configuration-file session)
+  (let ((command (greetd-wlgreet-wayland-session-command session))
+        (output-mode (greetd-wlgreet-output-mode session))
+        (scale (greetd-wlgreet-scale session))
+        (background (greetd-wlgreet-background session))
+        (headline (greetd-wlgreet-headline session))
+        (prompt (greetd-wlgreet-prompt session))
+        (prompt-error (greetd-wlgreet-prompt-error session))
+        (border (greetd-wlgreet-border session)))
+    (mixed-text-file "wlgreet.toml"
+      "command = \"" command "\"\n"
+      "outputMode = \"" output-mode "\"\n"
+      "scale = " (number->string scale) "\n"
+      (apply string-append
+             (map (match-lambda
+                    ((section-name . color)
+                     (make-wlgreet-config-color section-name color)))
+                  `(("background" . ,background)
+                    ("headline" . ,headline)
+                    ("prompt" . ,prompt)
+                    ("prompt-error" . ,prompt-error)
+                    ("border" . ,border)))))))
+
+(define-record-type* <greetd-wlgreet-sway-session>
+  greetd-wlgreet-sway-session make-greetd-wlgreet-sway-session
+  greetd-wlgreet-sway-session?
+  (wlgreet-session greetd-wlgreet-sway-session-wlgreet-session       ;<greetd-wlgreet-session>
+                   (default (greetd-wlgreet-session)))
+  (sway greetd-wlgreet-sway-session-sway (default sway))             ;<package>
+  (sway-configuration greetd-wlgreet-sway-session-sway-configuration ;file-like
+                      (default (plain-file "wlgreet-sway-config" ""))))
+
+(define (make-wlgreet-sway-configuration-file session)
+  (let* ((wlgreet-session (greetd-wlgreet-sway-session-wlgreet-session session))
+         (wlgreet-config (make-wlgreet-configuration-file wlgreet-session))
+         (wlgreet (file-append (greetd-wlgreet wlgreet-session) "/bin/wlgreet"))
+         (sway-config (greetd-wlgreet-sway-session-sway-configuration session))
+         (swaymsg (file-append (greetd-wlgreet-sway-session-sway session)
+                               "/bin/swaymsg")))
+    (mixed-text-file "wlgreet-sway.conf"
+      "include " sway-config "\n"
+      "xwayland disable\n"
+      "exec \"" wlgreet " --config " wlgreet-config "; "
+      swaymsg " exit\"\n")))
+
+(define-gexp-compiler (greetd-wlgreet-sway-session-compiler
+                       (session <greetd-wlgreet-sway-session>)
+                       system target)
+  (let ((sway (file-append (greetd-wlgreet-sway-session-sway session)
+                           "/bin/sway"))
+        (config (make-wlgreet-sway-configuration-file session)))
+    (lower-object
+     (program-file "wlgreet-sway-session-command"
+       #~(let* ((log-file (open-output-file
+                           (string-append "/tmp/sway-greeter."
+                                          (number->string (getpid))
+                                          ".log")))
+                (username (getenv "USER"))
+                (useruid (number->string (passwd:uid (getpwuid username)))))
+           ;; redirect stdout/err to log-file
+           (dup2 (fileno log-file) 1)
+           (dup2 1 2)
+           (sleep 1) ;give seatd/logind some time to start up
+           (setenv "XDG_RUNTIME_DIR" (string-append "/run/user/" useruid))
+           (execl #$sway #$sway "-d" "-c" #$config))))))
 
 (define-record-type* <greetd-terminal-configuration>
   greetd-terminal-configuration make-greetd-terminal-configuration
@@ -2891,10 +3050,10 @@ to handle."
                  (default (default-log-file-name this-record)))
   (terminal-vt greetd-terminal-vt (default "7"))
   (terminal-switch greetd-terminal-switch (default #f))
+  (source-profile? greetd-source-profile? (default #t))
   (default-session-user greetd-default-session-user (default "greeter"))
   (default-session-command greetd-default-session-command
-    (default (greetd-agreety-session))
-    (sanitize make-greetd-default-session-command)))
+    (default (greetd-agreety-session))))
 
 (define (default-config-file-name config)
   (string-join (list "config-" (greetd-terminal-vt config) ".toml") ""))
@@ -2905,12 +3064,14 @@ to handle."
 (define (make-greetd-terminal-configuration-file config)
   (let*
       ((config-file-name (greetd-config-file-name config))
+       (source-profile? (greetd-source-profile? config))
        (terminal-vt (greetd-terminal-vt config))
        (terminal-switch (greetd-terminal-switch config))
        (default-session-user (greetd-default-session-user config))
        (default-session-command (greetd-default-session-command config)))
     (mixed-text-file
      config-file-name
+     "source_profile = " (if source-profile? "true" "false") "\n"
      "[terminal]\n"
      "vt = " terminal-vt "\n"
      "switch = " (if terminal-switch "true" "false") "\n"
diff --git a/gnu/services/certbot.scm b/gnu/services/certbot.scm
index 1c819bef48..8e6784df2b 100644
--- a/gnu/services/certbot.scm
+++ b/gnu/services/certbot.scm
@@ -148,12 +148,13 @@
 (define (certbot-renewal-jobs config)
   (list
    ;; Attempt to renew the certificates twice per day, at a random minute
-   ;; within the hour.  See https://certbot.eff.org/all-instructions/.
+   ;; within the hour.  See https://eff-certbot.readthedocs.io/.
    #~(job '(next-minute-from (next-hour '(0 12)) (list (random 60)))
           #$(certbot-command config))))
 
 (define (certbot-activation config)
   (let* ((certbot-directory "/var/lib/certbot")
+         (certbot-cert-directory "/etc/letsencrypt/live")
          (script (in-vicinity certbot-directory "renew-certificates"))
          (message (format #f (G_ "~a may need to be run~%") script)))
     (match config
@@ -164,6 +165,7 @@
              (use-modules (guix build utils))
              (mkdir-p #$webroot)
              (mkdir-p #$certbot-directory)
+             (mkdir-p #$certbot-cert-directory)
              (copy-file #$(certbot-command config) #$script)
              (display #$message)))))))
 
diff --git a/gnu/services/configuration.scm b/gnu/services/configuration.scm
index 636c49ccba..6b0291dc00 100644
--- a/gnu/services/configuration.scm
+++ b/gnu/services/configuration.scm
@@ -242,17 +242,17 @@ does not have a default value" field kind)))
                stem
                #,(id #'stem #'make- #'stem)
                #,(id #'stem #'stem #'?)
-               (%location #,(id #'stem #'stem #'-location)
-                          (default (and=> (current-source-location)
-                                          source-properties->location))
-                          (innate))
                #,@(map (lambda (name getter def)
                          #`(#,name #,getter (default #,def)
                                    (sanitize
                                     #,(id #'stem #'validate- #'stem #'- name))))
                        #'(field ...)
                        #'(field-getter ...)
-                       #'(field-default ...)))
+                       #'(field-default ...))
+               (%location #,(id #'stem #'stem #'-source-location)
+                          (default (and=> (current-source-location)
+                                          source-properties->location))
+                          (innate)))
 
              (define #,(id #'stem #'stem #'-fields)
                (list (configuration-field
@@ -436,7 +436,11 @@ the list result in @code{#t} when applying PRED? on them."
 (define list-of-strings?
   (list-of string?))
 
-(define alist? list?)
+(define alist?
+  (match-lambda
+    (() #t)
+    ((head . tail) (and (pair? head) (alist? tail)))
+    (_ #f)))
 
 (define serialize-file-like empty-serializer)
 
@@ -469,9 +473,6 @@ applied on the fields and values of FIELDS using the
 
 COMBINE is a procedure that takes one or more arguments and combines
 all the alist entries into one value, @code{string-append} or
-@code{append} are usually good candidates for this.
-
-See the @code{serialize-alist} procedure in `@code{(gnu home services
-version-control}' for an example usage.)}"
+@code{append} are usually good candidates for this."
   (apply combine
          (map (generic-serialize-alist-entry serialize-field) fields)))
diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm
index 52de5ca7c0..43b0e0946e 100644
--- a/gnu/services/cuirass.scm
+++ b/gnu/services/cuirass.scm
@@ -125,7 +125,7 @@
   (let ((cuirass          (cuirass-configuration-cuirass config))
         (cache-directory  (cuirass-configuration-cache-directory config))
         (web-log-file     (cuirass-configuration-web-log-file config))
-        (log-file         (cuirass-configuration-log-file config))
+        (main-log-file    (cuirass-configuration-log-file config))
         (user             (cuirass-configuration-user config))
         (group            (cuirass-configuration-group config))
         (interval         (cuirass-configuration-interval config))
@@ -169,7 +169,7 @@
 
                   #:user #$user
                   #:group #$group
-                  #:log-file #$log-file))
+                  #:log-file #$main-log-file))
         (stop #~(make-kill-destructor)))
       ,(shepherd-service
         (documentation "Run Cuirass web interface.")
@@ -302,8 +302,13 @@
 (define (cuirass-log-rotations config)
   "Return the list of log rotations that corresponds to CONFIG."
   (list (log-rotation
-         (files (list (cuirass-configuration-log-file config)
-                      (cuirass-configuration-web-log-file config)))
+         (files (append (list (cuirass-configuration-log-file config)
+                              (cuirass-configuration-web-log-file config))
+                        (let ((server
+                               (cuirass-configuration-remote-server config)))
+                          (if server
+                              (list (cuirass-remote-server-log-file server))
+                              '()))))
          (frequency 'weekly)
          (options `("rotate 40"                   ;worth keeping
                     ,@%default-log-rotation-options)))))
@@ -394,12 +399,21 @@ CONFIG."
                     #:log-file #$log-file))
            (stop #~(make-kill-destructor))))))
 
+(define (cuirass-remote-worker-log-rotations config)
+  "Return the list of log rotations that corresponds to CONFIG."
+  (list (log-rotation
+         (files (list (cuirass-remote-worker-log-file config)))
+         (frequency 'weekly)
+         (options `("rotate 4"                    ;don't keep too many of them
+                    ,@%default-log-rotation-options)))))
+
 (define cuirass-remote-worker-service-type
   (service-type
    (name 'cuirass-remote-worker)
    (extensions
-    (list
-     (service-extension shepherd-root-service-type
-                        cuirass-remote-worker-shepherd-service)))
+    (list (service-extension shepherd-root-service-type
+                             cuirass-remote-worker-shepherd-service)
+          (service-extension rottlog-service-type
+                             cuirass-remote-worker-log-rotations)))
    (description
     "Run the Cuirass remote build worker service.")))
diff --git a/gnu/services/databases.scm b/gnu/services/databases.scm
index fb3cd3c478..b7bd1e587e 100644
--- a/gnu/services/databases.scm
+++ b/gnu/services/databases.scm
@@ -6,8 +6,9 @@
 ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
 ;;; Copyright © 2018 Julien Lepiller <julien@lepiller.eu>
 ;;; Copyright © 2019 Robert Vollmert <rob@vllmrt.net>
-;;; Copyright © 2020 Marius Bakke <marius@gnu.org>
+;;; Copyright © 2020, 2022 Marius Bakke <marius@gnu.org>
 ;;; Copyright © 2021 David Larsson <david.larsson@selfhosted.xyz>
+;;; Copyright © 2021 Aljosha Papsch <ep@stern-data.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -29,6 +30,7 @@
   #:use-module (gnu services shepherd)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages admin)
+  #:use-module (gnu packages base)
   #:use-module (gnu packages databases)
   #:use-module (guix build-system trivial)
   #:use-module (guix build union)
@@ -532,6 +534,7 @@ applications.")))
   (bind-address mysql-configuration-bind-address (default "127.0.0.1"))
   (port mysql-configuration-port (default 3306))
   (socket mysql-configuration-socket (default "/run/mysqld/mysqld.sock"))
+  (datadir mysql-configuration-datadir (default "/var/lib/mysql"))
   (extra-content mysql-configuration-extra-content (default ""))
   (extra-environment mysql-configuration-extra-environment (default #~'()))
   (auto-upgrade? mysql-configuration-auto-upgrade? (default #t)))
@@ -549,112 +552,114 @@ applications.")))
 
 (define mysql-configuration-file
   (match-lambda
-    (($ <mysql-configuration> mysql bind-address port socket extra-content)
+    (($ <mysql-configuration> mysql bind-address port socket datadir extra-content)
      (mixed-text-file "my.cnf" "[mysqld]
-datadir=/var/lib/mysql
+datadir=" datadir "
 socket=" socket "
 bind-address=" bind-address "
 port=" (number->string port) "
 " extra-content "
 "))))
 
-(define (%mysql-activation config)
-  "Return an activation gexp for the MySQL or MariaDB database server."
-  (let ((mysql  (mysql-configuration-mysql config))
-        (my.cnf (mysql-configuration-file config)))
-    #~(begin
-        (use-modules (ice-9 popen)
-                     (guix build utils))
-        (let* ((mysqld  (string-append #$mysql "/bin/mysqld"))
-               (user    (getpwnam "mysql"))
-               (uid     (passwd:uid user))
-               (gid     (passwd:gid user))
-               (datadir "/var/lib/mysql")
-               (rundir  "/run/mysqld"))
-          (mkdir-p datadir)
-          (chown datadir uid gid)
-          (mkdir-p rundir)
-          (chown rundir uid gid)
-          ;; Initialize the database when it doesn't exist.
-          (when (not (file-exists? (string-append datadir "/mysql")))
-            (if (string-prefix? "mysql-" (strip-store-file-name #$mysql))
-                ;; For MySQL.
-                (system* mysqld
-                         (string-append "--defaults-file=" #$my.cnf)
-                         "--initialize"
-                         "--user=mysql")
-                ;; For MariaDB.
-                ;; XXX: The 'mysql_install_db' script doesn't work directly
-                ;;      due to missing 'mkdir' in PATH.
-                (let ((p (open-pipe* OPEN_WRITE mysqld
-                                     (string-append
-                                      "--defaults-file=" #$my.cnf)
-                                     "--bootstrap"
-                                     "--user=mysql")))
-                  ;; Create the system database, as does by 'mysql_install_db'.
-                  (display "create database mysql;\n" p)
-                  (display "use mysql;\n" p)
-                  (for-each
-                   (lambda (sql)
-                     (call-with-input-file
-                         (string-append #$mysql:lib "/share/mysql/" sql)
-                       (lambda (in) (dump-port in p))))
-                   '("mysql_system_tables.sql"
-                     "mysql_performance_tables.sql"
-                     "mysql_system_tables_data.sql"
-                     "fill_help_tables.sql"))
-                  ;; Remove the anonymous user and disable root access from
-                  ;; remote machines, as does by 'mysql_secure_installation'.
-                  (display "
-DELETE FROM user WHERE User='';
-DELETE FROM user WHERE User='root' AND
-  Host NOT IN  ('localhost', '127.0.0.1', '::1');
-FLUSH PRIVILEGES;
-" p)
-                  (close-pipe p))))))))
+(define (mysqld-wrapper config)
+  "Start mysqld, and initialize the system tables if necessary."
+  (program-file
+   "mysqld-wrapper"
+   (with-imported-modules (source-module-closure
+                           '((guix build utils)))
+     (let ((mysql     (mysql-configuration-mysql config))
+           (datadir   (mysql-configuration-datadir config))
+           (my.cnf    (mysql-configuration-file config)))
+       #~(begin
+           (use-modules (guix build utils))
+           (let* ((mysqld (string-append #$mysql "/bin/mysqld"))
+                  (user    (getpwnam "mysql"))
+                  (uid     (passwd:uid user))
+                  (gid     (passwd:gid user))
+                  (rundir  "/run/mysqld"))
+             (mkdir-p #$datadir)
+             (chown #$datadir uid gid)
+             (mkdir-p rundir)
+             (chown rundir uid gid)
+             (unless (file-exists? (string-append #$datadir "/mysql"))
+               (let ((init (system* #$(mysql-install config))))
+                 (unless (= 0 (status:exit-val init))
+                   (throw 'system-error "MySQL initialization failed."))))
+             ;; Drop privileges and start the server.
+             (setgid gid) (setuid uid)
+             (execl mysqld mysqld
+                    (string-append "--defaults-file=" #$my.cnf))))))))
 
 (define (mysql-shepherd-service config)
   (list (shepherd-service
          (provision '(mysql))
+         (requirement '(user-processes))
          (documentation "Run the MySQL server.")
-         (start (let ((mysql  (mysql-configuration-mysql config))
+         (start (let ((mysql (mysql-configuration-mysql config))
                       (extra-env (mysql-configuration-extra-environment config))
                       (my.cnf (mysql-configuration-file config)))
                   #~(make-forkexec-constructor
-                     (list (string-append #$mysql "/bin/mysqld")
-                           (string-append "--defaults-file=" #$my.cnf))
-                           #:user "mysql" #:group "mysql"
-                           #:log-file "/var/log/mysqld.log"
-                           #:environment-variables #$extra-env)))
+                     (list #$(mysqld-wrapper config))
+                     #:log-file "/var/log/mysqld.log"
+                     #:environment-variables #$extra-env)))
          (stop #~(make-kill-destructor)))))
 
-(define (mysql-upgrade-wrapper mysql socket-file)
+(define (mysql-install config)
+  "Install MySQL system database and secure the installation."
+  (let ((mysql   (mysql-configuration-mysql config))
+        (my.cnf  (mysql-configuration-file config)))
+    (program-file
+     "mysql-install"
+     (with-imported-modules (source-module-closure
+                             '((guix build utils)))
+       #~(begin
+           (use-modules (guix build utils))
+           ;; Make sed, mkdir, uname, etc available for mariadb-install-db.
+           (set-path-environment-variable "PATH" '("bin")
+                                          (list #$sed #$coreutils))
+           (if (string=? "mariadb" #$(package-name mysql))
+               ;; For MariaDB.
+               (system* #$(file-append mysql "/bin/mariadb-install-db")
+                        (string-append "--defaults-file=" #$my.cnf)
+                        "--skip-test-db"
+                        "--user=mysql")
+               ;; For MySQL.
+               (system* #$(file-append mysql "/bin/mysqld")
+                        (string-append "--defaults-file=" #$my.cnf)
+                        "--initialize"
+                        "--user=mysql")))))))
+
+(define (mysql-upgrade-wrapper config)
   ;; The MySQL socket and PID file may appear before the server is ready to
   ;; accept connections.  Ensure the socket is responsive before attempting
   ;; to run the upgrade script.
-  (program-file
-   "mysql-upgrade-wrapper"
-   #~(begin
-       (let ((mysql-upgrade #$(file-append mysql "/bin/mysql_upgrade"))
-             (timeout 10))
-         (begin
-           (let loop ((i 0))
-             (catch 'system-error
-               (lambda ()
-                 (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
-                   (connect sock AF_UNIX #$socket-file)
-                   (close-port sock)
-                   ;; The socket is ready!
-                   (execl mysql-upgrade mysql-upgrade
-                          (string-append "--socket=" #$socket-file))))
-               (lambda args
-                 (if (< i timeout)
-                     (begin
-                       (sleep 1)
-                       (loop (+ 1 i)))
-                     ;; No luck, give up.
-                     (throw 'timeout-error
-                            "MySQL server did not appear in time!"))))))))))
+  (let ((mysql (mysql-configuration-mysql config))
+        (socket-file (mysql-configuration-socket config))
+        (config-file (mysql-configuration-file config)))
+    (program-file
+     "mysql-upgrade-wrapper"
+     #~(begin
+         (let ((mysql-upgrade #$(file-append mysql "/bin/mysql_upgrade"))
+               (timeout 20))
+           (begin
+             (let loop ((i 0))
+               (catch 'system-error
+                 (lambda ()
+                   (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
+                     (connect sock AF_UNIX #$socket-file)
+                     (close-port sock)
+                     ;; The socket is ready!
+                     (execl mysql-upgrade mysql-upgrade
+                            (string-append "--defaults-file=" #$config-file)
+                            "--user=mysql")))
+                 (lambda args
+                   (if (< i timeout)
+                       (begin
+                         (sleep 1)
+                         (loop (+ 1 i)))
+                       ;; No luck, give up.
+                       (throw 'timeout-error
+                              "MySQL server did not appear in time!")))))))))))
 
 (define (mysql-upgrade-shepherd-service config)
   (list (shepherd-service
@@ -662,17 +667,17 @@ FLUSH PRIVILEGES;
          (requirement '(mysql))
          (one-shot? #t)
          (documentation "Upgrade MySQL database schemas.")
-         (start (let ((mysql (mysql-configuration-mysql config))
-                      (socket (mysql-configuration-socket config)))
-                  #~(make-forkexec-constructor
-                     (list #$(mysql-upgrade-wrapper mysql socket))
-                     #:user "mysql" #:group "mysql"))))))
+         (start #~(make-forkexec-constructor
+                   (list #$(mysql-upgrade-wrapper config))
+                   #:user "mysql" #:group "mysql"
+                   #:log-file "/var/log/mysql_upgrade.log")))))
 
 (define (mysql-shepherd-services config)
-  (if (mysql-configuration-auto-upgrade? config)
-      (append (mysql-shepherd-service config)
-              (mysql-upgrade-shepherd-service config))
-      (mysql-shepherd-service config)))
+  (let ((mysql-services (mysql-shepherd-service config)))
+    (if (mysql-configuration-auto-upgrade? config)
+        (append mysql-services
+                (mysql-upgrade-shepherd-service config))
+        mysql-services)))
 
 (define mysql-service-type
   (service-type
@@ -680,8 +685,6 @@ FLUSH PRIVILEGES;
    (extensions
     (list (service-extension account-service-type
                              (const %mysql-accounts))
-          (service-extension activation-service-type
-                             %mysql-activation)
           (service-extension shepherd-root-service-type
                              mysql-shepherd-services)))
    (default-value (mysql-configuration))
diff --git a/gnu/services/dbus.scm b/gnu/services/dbus.scm
index e4c719fe71..4b56b8f3eb 100644
--- a/gnu/services/dbus.scm
+++ b/gnu/services/dbus.scm
@@ -40,6 +40,8 @@
             dbus-service
             wrapped-dbus-service
 
+            polkit-configuration
+            polkit-configuration?
             polkit-service-type
             polkit-service))
 
diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index 1b087635d1..fe1f0fd20a 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2016 Sou Bunnbu <iyzsong@gmail.com>
 ;;; Copyright © 2017, 2020, 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;; Copyright © 2017 Nikita <nikita@n0.is>
-;;; Copyright © 2018, 2020 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2018, 2020, 2022 Efraim Flashner <efraim@flashner.co.il>
 ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
 ;;; Copyright © 2017, 2019 Christopher Baines <mail@cbaines.net>
 ;;; Copyright © 2019 Tim Gesthuizen <tim.gesthuizen@yahoo.de>
@@ -273,7 +273,8 @@
                      #:environment-variables
                      (list (string-append "UPOWER_CONF_FILE_NAME="
                                           #$config))))
-           (stop #~(make-kill-destructor))))))
+           (stop #~(make-kill-destructor))
+           (actions (list (shepherd-configuration-action config)))))))
 
 (define upower-service-type
   (let ((upower-package (compose list upower-configuration-upower)))
@@ -759,7 +760,7 @@ site} for more information."
                                                (bluetooth-configuration-enable-adv-mon-interleave-scan
                                                 config))
                                           1 0))
-   
+
    "\n[GATT]"
    "\nCache = " (symbol->string (bluetooth-configuration-cache config))
    "\nKeySize = " (number->string (bluetooth-configuration-key-size config))
@@ -837,9 +838,7 @@ Bluetooth devices and provides a number of D-Bus interfaces.")))
   "Return a service that runs the @command{bluetoothd} daemon, which manages
 all the Bluetooth devices and provides a number of D-Bus interfaces.  When
 AUTO-ENABLE? is true, the bluetooth controller is powered automatically at
-boot.
-
-Users need to be in the @code{lp} group to access the D-Bus service.
+boot, which can be useful when using a bluetooth keyboard or mouse.
 "
   (service bluetooth-service-type
            (bluetooth-configuration
@@ -1168,6 +1167,9 @@ seats.)"
 
 (define (elogind-shepherd-service config)
   "Return a Shepherd service to start elogind according to @var{config}."
+  (define config-file
+    (elogind-configuration-file config))
+
   (list (shepherd-service
          (requirement '(dbus-system))
          (provision '(elogind))
@@ -1176,9 +1178,9 @@ seats.)"
                                         "/libexec/elogind/elogind"))
                    #:environment-variables
                    (list (string-append "ELOGIND_CONF_FILE="
-                                        #$(elogind-configuration-file
-                                           config)))))
-         (stop #~(make-kill-destructor)))))
+                                        #$config-file))))
+         (stop #~(make-kill-destructor))
+         (actions (list (shepherd-configuration-action config-file))))))
 
 (define elogind-service-type
   (service-type (name 'elogind)
@@ -1539,6 +1541,11 @@ rules."
                                       (package-direct-input-selector
                                        "efl")
                                       enlightenment-package))
+          (service-extension udev-service-type
+                             (compose list
+                                      (package-direct-input-selector
+                                        "ddcutil")
+                                      enlightenment-package))
           (service-extension setuid-program-service-type
                              enlightenment-setuid-programs)
           (service-extension profile-service-type
diff --git a/gnu/services/dict.scm b/gnu/services/dict.scm
index f042219cbd..35253a0077 100644
--- a/gnu/services/dict.scm
+++ b/gnu/services/dict.scm
@@ -182,7 +182,8 @@ database {
            (stop #~(if (and (defined? 'make-inetd-destructor)
                             #$(= 1 (length interfaces))) ;XXX
                        (make-inetd-destructor)
-                       (make-kill-destructor)))))))
+                       (make-kill-destructor)))
+           (actions (list (shepherd-configuration-action dicod.conf)))))))
 
 (define dicod-service-type
   (service-type
diff --git a/gnu/services/games.scm b/gnu/services/games.scm
index 6c2af44b49..e63c1c1299 100644
--- a/gnu/services/games.scm
+++ b/gnu/services/games.scm
@@ -19,6 +19,7 @@
 
 (define-module (gnu services games)
   #:use-module (gnu services)
+  #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages games)
@@ -28,13 +29,46 @@
   #:autoload   (guix least-authority) (least-authority-wrapper)
   #:use-module (guix gexp)
   #:use-module (guix modules)
+  #:use-module (guix packages)
   #:use-module (guix records)
   #:use-module (ice-9 match)
-  #:export (wesnothd-configuration
+  #:export (joycond-configuration
+            joycond-configuration?
+            joycond-service-type
+
+            wesnothd-configuration
             wesnothd-configuration?
             wesnothd-service-type))
 
 ;;;
+;;; Joycond
+;;;
+
+(define-configuration/no-serialization joycond-configuration
+  (package (package joycond) "The joycond package to use"))
+
+(define (joycond-shepherd-service config)
+  (let ((joycond (joycond-configuration-package config)))
+    (list (shepherd-service
+           (documentation "Run joycond.")
+           (provision '(joycond))
+           (requirement '(bluetooth))
+           (start #~(make-forkexec-constructor
+                     (list #$(file-append joycond "/bin/joycond"))))
+           (stop #~(make-kill-destructor))))))
+
+(define joycond-service-type
+  (service-type
+   (name 'joycond)
+   (description
+    "Run @command{joycond} for pairing Nintendo joycons via Bluetooth.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             joycond-shepherd-service)))
+   (default-value (joycond-configuration))))
+
+
+;;;
 ;;; The Battle for Wesnoth server
 ;;;
 
diff --git a/gnu/services/ganeti.scm b/gnu/services/ganeti.scm
index 85adbd7362..f4fec3833e 100644
--- a/gnu/services/ganeti.scm
+++ b/gnu/services/ganeti.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2020 Marius Bakke <marius@gnu.org>
+;;; Copyright © 2020, 2022 Marius Bakke <marius@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -683,7 +683,8 @@ information to OS install scripts or instances.")))
                    #~(#$schedule))
                   ((? list?)
                    #~('#$schedule)))
-             #$(ganeti-watcher-command config))))))
+             #$(ganeti-watcher-command config)
+             "ganeti-watcher")))))
 
 (define ganeti-watcher-service-type
   (service-type (name 'ganeti-watcher)
@@ -725,7 +726,8 @@ is declared offline by known master candidates.")))
                    #~('#$master-schedule)))
              (lambda ()
               (system* #$(file-append ganeti "/sbin/ganeti-cleaner")
-                       "master")))
+                       "master"))
+             "ganeti master cleaner")
       #~(job #$@(match node-schedule
                   ((? string?)
                    #~(#$node-schedule))
@@ -733,7 +735,8 @@ is declared offline by known master candidates.")))
                    #~('#$node-schedule)))
              (lambda ()
                (system* #$(file-append ganeti "/sbin/ganeti-cleaner")
-                        "node")))))))
+                        "node"))
+             "ganeti node cleaner")))))
 
 (define ganeti-cleaner-service-type
   (service-type (name 'ganeti-cleaner)
@@ -777,6 +780,8 @@ than 21 days from @file{/var/lib/ganeti/queue/archive}.")))
                           (default (ganeti-cleaner-configuration)))
   (file-storage-paths     ganeti-configuration-file-storage-paths ;list of strings | gexp
                           (default '()))
+  (hooks                  ganeti-configuration-hooks  ;<file-like> | #f
+                          (default #f))
   (os                     ganeti-configuration-os  ;list of <ganeti-os>
                           (default '())))
 
@@ -819,8 +824,9 @@ than 21 days from @file{/var/lib/ganeti/queue/archive}.")))
 (define-record-type* <ganeti-os>
   ganeti-os make-ganeti-os ganeti-os?
   (name ganeti-os-name)                     ;string
-  (extension ganeti-os-extension)           ;string
-  (variants ganeti-os-variants              ;list of <ganeti-os-variant>
+  (extension ganeti-os-extension            ;#f | string
+             (default #f))
+  (variants ganeti-os-variants              ;<file-like> | list of <ganeti-os-variant>
             (default '())))
 
 (define-record-type* <ganeti-os-variant>
@@ -909,7 +915,7 @@ trap - EXIT
   (partition-alignment debootstrap-configuration-partition-alignment ;#f | integer
                        (default 2048)))
 
-(define (hooks->directory hooks)
+(define (debootstrap-hooks->directory hooks)
   (match hooks
     ((? file-like?)
      hooks)
@@ -917,7 +923,7 @@ trap - EXIT
      (let ((names (map car hooks))
            (files (map cdr hooks)))
        (with-imported-modules '((guix build utils))
-         (computed-file "hooks-union"
+         (computed-file "debootstrap-hooks"
                         #~(begin
                             (use-modules (guix build utils)
                                          (ice-9 match))
@@ -941,7 +947,7 @@ trap - EXIT
     (($ <debootstrap-configuration> hooks proxy mirror arch suite extra-pkgs
                                     components generate-cache? clean-cache
                                     partition-style partition-alignment)
-     (let ((customize-dir (hooks->directory hooks)))
+     (let ((customize-dir (debootstrap-hooks->directory hooks)))
        (gexp->derivation
         "debootstrap-variant"
         #~(call-with-output-file (ungexp output "out")
@@ -992,37 +998,48 @@ trap - EXIT
 (define (ganeti-os->directory os)
   "Return the derivation to build the configuration directory to be installed
 in /etc/ganeti/instance-$os for OS."
-  (let* ((name      (ganeti-os-name os))
-         (extension (ganeti-os-extension os))
-         (variants  (ganeti-os-variants os))
-         (names     (map ganeti-os-variant-name variants))
-         (configs   (map ganeti-os-variant-configuration variants)))
-    (with-imported-modules '((guix build utils))
-      (define builder
-        #~(begin
-            (use-modules (guix build utils)
-                         (ice-9 format)
-                         (ice-9 match)
-                         (srfi srfi-1))
-            (mkdir-p #$output)
-            (unless (null? '#$names)
-              (let ((variants-dir (string-append #$output "/variants")))
-                (mkdir-p variants-dir)
-                (call-with-output-file (string-append variants-dir "/variants.list")
-                  (lambda (port)
-                    (format port "~a~%"
-                            (string-join '#$names "\n"))))
-                (for-each (match-lambda
-                            ((name file)
-                             (symlink file
-                                      (string-append variants-dir "/" name
-                                                     #$extension))))
-
-                          '#$(zip names configs))))))
-
-      (computed-file (string-append name "-os") builder))))
-
-(define (ganeti-directory file-storage-file os)
+  (let ((name      (ganeti-os-name os))
+        (extension (ganeti-os-extension os))
+        (variants  (ganeti-os-variants os)))
+    (define builder
+      (with-imported-modules '((guix build utils))
+        (if (file-like? variants)
+            #~(begin
+                (use-modules (guix build utils))
+                (mkdir-p #$output)
+                (symlink #$variants
+                         (string-append #$output "/variants")))
+            #~(begin
+                (use-modules (guix build utils)
+                             (ice-9 format)
+                             (ice-9 match)
+                             (srfi srfi-1))
+                (mkdir-p #$output)
+                (let ((variants-dir (string-append #$output "/variants"))
+                      (names   '#$(map ganeti-os-variant-name variants))
+                      (configs '#$(map ganeti-os-variant-configuration variants)))
+                  (mkdir-p variants-dir)
+                  (unless (null? names)
+                    (call-with-output-file (string-append variants-dir
+                                                          "/variants.list")
+                      (lambda (port)
+                        (format port "~a~%"
+                                (string-join names "\n"))))
+                    (for-each (match-lambda
+                                ((name file)
+                                 (let ((file-name
+                                        (if #$extension
+                                            (string-append name #$extension)
+                                            name)))
+                                   (symlink file
+                                            (string-append variants-dir "/"
+                                                           file-name)))))
+                              (zip names configs))))))))
+
+    (computed-file (string-append name "-os") builder
+                   #:local-build? #t)))
+
+(define (ganeti-directory file-storage-file hooks os)
   (let ((dirs (map ganeti-os->directory os))
         (names (map ganeti-os-name os)))
     (define builder
@@ -1032,6 +1049,9 @@ in /etc/ganeti/instance-$os for OS."
           (when #$file-storage-file
             (symlink #$file-storage-file
                      (string-append #$output "/file-storage-paths")))
+          (when #$hooks
+            (symlink #$hooks
+                     (string-append #$output "/hooks")))
           (for-each (match-lambda
                       ((name dest)
                        (symlink dest
@@ -1051,6 +1071,7 @@ in /etc/ganeti/instance-$os for OS."
   (list `("ganeti" ,(ganeti-directory
                      (file-storage-file
                       (ganeti-configuration-file-storage-paths config))
+                     (ganeti-configuration-hooks config)
                      (ganeti-configuration-os config)))))
 
 (define (debootstrap-os variants)
diff --git a/gnu/services/getmail.scm b/gnu/services/getmail.scm
index 0a1c34cfd3..19faea782f 100644
--- a/gnu/services/getmail.scm
+++ b/gnu/services/getmail.scm
@@ -215,18 +215,6 @@ lines.")
    (parameter-alist '())
    "Extra options to include."))
 
-(define (serialize-getmail-configuration-file field-name val)
-  (match val
-    (($ <getmail-configuration-file> location
-                                     retriever destination options)
-     #~(string-append
-        "[retriever]\n"
-        #$(serialize-getmail-retriever-configuration #f retriever)
-        "\n[destination]\n"
-        #$(serialize-getmail-destination-configuration #f destination)
-        "\n[options]\n"
-        #$(serialize-getmail-options-configuration #f options)))))
-
 (define-configuration getmail-configuration-file
   (retriever
    (getmail-retriever-configuration (getmail-retriever-configuration))
@@ -238,6 +226,17 @@ lines.")
    (getmail-options-configuration (getmail-options-configuration))
    "Configure getmail."))
 
+(define (serialize-getmail-configuration-file field-name val)
+  (match-record val <getmail-configuration-file>
+    (retriever destination options)
+    #~(string-append
+       "[retriever]\n"
+       #$(serialize-getmail-retriever-configuration #f retriever)
+       "\n[destination]\n"
+       #$(serialize-getmail-destination-configuration #f destination)
+       "\n[options]\n"
+       #$(serialize-getmail-options-configuration #f options))))
+
 (define (serialize-symbol field-name val) "")
 (define (serialize-getmail-configuration field-name val) "")
 
@@ -339,29 +338,28 @@ notifications.  This depends on the server supporting the IDLE extension.")
 
 (define (getmail-shepherd-services configs)
   "Return a list of <shepherd-service> for CONFIGS."
-  (map (match-lambda
-         (($ <getmail-configuration> location name package
-                                     user group directory rcfile idle
-                                     environment-variables)
-          (shepherd-service
-           (documentation "Run getmail.")
-           (provision (list (symbol-append 'getmail- name)))
-           (requirement '(networking))
-           (start #~(make-forkexec-constructor
-                     `(#$(file-append package "/bin/getmail")
-                       ,(string-append "--getmaildir=" #$directory)
-                       #$@(map (lambda (idle)
-                                 (string-append "--idle=" idle))
-                               idle)
-                       ,(string-append "--rcfile=" #$rcfile))
-                     #:user #$user
-                     #:group #$group
-                     #:environment-variables
-                     (list #$@environment-variables)
-                     #:log-file
-                     #$(string-append "/var/log/getmail-"
-                                      (symbol->string name))))
-           (stop #~(make-kill-destructor)))))
+  (map (lambda (config)
+         (match-record config <getmail-configuration>
+           (name package user group directory rcfile idle environment-variables)
+           (shepherd-service
+            (documentation "Run getmail.")
+            (provision (list (symbol-append 'getmail- name)))
+            (requirement '(networking))
+            (start #~(make-forkexec-constructor
+                      `(#$(file-append package "/bin/getmail")
+                        ,(string-append "--getmaildir=" #$directory)
+                        #$@(map (lambda (idle)
+                                  (string-append "--idle=" idle))
+                                idle)
+                        ,(string-append "--rcfile=" #$rcfile))
+                      #:user #$user
+                      #:group #$group
+                      #:environment-variables
+                      (list #$@environment-variables)
+                      #:log-file
+                      #$(string-append "/var/log/getmail-"
+                                       (symbol->string name))))
+            (stop #~(make-kill-destructor)))))
        configs))
 
 (define getmail-service-type
diff --git a/gnu/services/guix.scm b/gnu/services/guix.scm
index dac1e5841a..65bf0b5a7f 100644
--- a/gnu/services/guix.scm
+++ b/gnu/services/guix.scm
@@ -59,6 +59,7 @@
             guix-build-coordinator-agent-configuration-authentication
             guix-build-coordinator-agent-configuration-systems
             guix-build-coordinator-agent-configuration-max-parallel-builds
+            guix-build-coordinator-agent-configuration-max-allocated-builds
             guix-build-coordinator-agent-configuration-max-1min-load-average
             guix-build-coordinator-agent-configuration-derivation-substitute-urls
             guix-build-coordinator-agent-configuration-non-derivation-substitute-urls
@@ -177,6 +178,9 @@
   (max-parallel-builds
    guix-build-coordinator-agent-configuration-max-parallel-builds
    (default 1))
+  (max-allocated-builds
+   guix-build-coordinator-agent-configuration-max-allocated-builds
+   (default #f))
   (max-1min-load-average
    guix-build-coordinator-agent-configuration-max-1min-load-average
    (default #f))
@@ -329,31 +333,38 @@
       (documentation "Guix Build Coordinator")
       (provision '(guix-build-coordinator))
       (requirement '(networking))
-      (start #~(make-forkexec-constructor
-                (list #$(make-guix-build-coordinator-start-script
-                         database-uri-string
-                         allocation-strategy
-                         "/var/run/guix-build-coordinator/pid"
-                         package
-                         #:agent-communication-uri-string
-                         agent-communication-uri-string
-                         #:client-communication-uri-string
-                         client-communication-uri-string
-                         #:hooks hooks
-                         #:parallel-hooks parallel-hooks
-                         #:guile guile))
-                #:user #$user
-                #:group #$group
-                #:pid-file "/var/run/guix-build-coordinator/pid"
-                ;; Allow time for migrations to run
-                #:pid-file-timeout 60
-                #:environment-variables
-                `(,(string-append
-                    "GUIX_LOCPATH=" #$glibc-utf8-locales "/lib/locale")
-                  "LC_ALL=en_US.utf8"
-                  "PATH=/run/current-system/profile/bin") ; for hooks
-                #:log-file "/var/log/guix-build-coordinator/coordinator.log"))
-      (stop #~(make-kill-destructor))))))
+      (start #~(lambda args
+                 (parameterize ((%current-logfile-date-format ""))
+                   (apply
+                    (make-forkexec-constructor
+                     (list #$(make-guix-build-coordinator-start-script
+                              database-uri-string
+                              allocation-strategy
+                              "/var/run/guix-build-coordinator/pid"
+                              package
+                              #:agent-communication-uri-string
+                              agent-communication-uri-string
+                              #:client-communication-uri-string
+                              client-communication-uri-string
+                              #:hooks hooks
+                              #:parallel-hooks parallel-hooks
+                              #:guile guile))
+                     #:user #$user
+                     #:group #$group
+                     #:pid-file "/var/run/guix-build-coordinator/pid"
+                     ;; Allow time for migrations to run
+                     #:pid-file-timeout 60
+                     #:environment-variables
+                     `(,(string-append
+                         "GUIX_LOCPATH=" #$glibc-utf8-locales "/lib/locale")
+                       "LC_ALL=en_US.utf8"
+                       "PATH=/run/current-system/profile/bin") ; for hooks
+                     #:log-file "/var/log/guix-build-coordinator/coordinator.log")
+                    args))))
+      (stop #~(make-kill-destructor))
+      (modules
+       `((shepherd comm)
+         ,@%default-modules))))))
 
 (define (guix-build-coordinator-activation config)
   #~(begin
@@ -406,6 +417,7 @@
 (define (guix-build-coordinator-agent-shepherd-services config)
   (match-record config <guix-build-coordinator-agent-configuration>
     (package user coordinator authentication max-parallel-builds
+             max-allocated-builds
              max-1min-load-average
              derivation-substitute-urls non-derivation-substitute-urls
              systems)
@@ -414,57 +426,67 @@
       (documentation "Guix Build Coordinator Agent")
       (provision '(guix-build-coordinator-agent))
       (requirement '(networking))
-      (start #~(make-forkexec-constructor
-                (list #$(file-append package "/bin/guix-build-coordinator-agent")
-                      #$(string-append "--coordinator=" coordinator)
-                      #$@(match authentication
-                           (($ <guix-build-coordinator-agent-password-auth>
-                               uuid password)
-                            #~(#$(string-append "--uuid=" uuid)
-                               #$(string-append "--password=" password)))
-                           (($ <guix-build-coordinator-agent-password-file-auth>
-                               uuid password-file)
-                            #~(#$(string-append "--uuid=" uuid)
-                               #$(string-append "--password-file="
-                                                password-file)))
-                           (($ <guix-build-coordinator-agent-dynamic-auth>
-                               agent-name token)
-                            #~(#$(string-append "--name=" agent-name)
-                               #$(string-append "--dynamic-auth-token=" token)))
-                           (($
-                             <guix-build-coordinator-agent-dynamic-auth-with-file>
-                             agent-name token-file)
-                            #~(#$(string-append "--name=" agent-name)
-                               #$(string-append "--dynamic-auth-token-file="
-                                                token-file))))
-                      #$(simple-format #f "--max-parallel-builds=~A"
-                                       max-parallel-builds)
-                      #$@(if max-1min-load-average
-                             #~(#$(simple-format #f "--max-1min-load-average=~A"
-                                                 max-1min-load-average))
-                             #~())
-                      #$@(if derivation-substitute-urls
-                             #~(#$(string-append
-                                   "--derivation-substitute-urls="
+      (start
+       #~(lambda _
+           (parameterize ((%current-logfile-date-format ""))
+             (fork+exec-command
+              (list #$(file-append package "/bin/guix-build-coordinator-agent")
+                    #$(string-append "--coordinator=" coordinator)
+                    #$@(match authentication
+                         (($ <guix-build-coordinator-agent-password-auth>
+                             uuid password)
+                          #~(#$(string-append "--uuid=" uuid)
+                             #$(string-append "--password=" password)))
+                         (($ <guix-build-coordinator-agent-password-file-auth>
+                             uuid password-file)
+                          #~(#$(string-append "--uuid=" uuid)
+                             #$(string-append "--password-file="
+                                              password-file)))
+                         (($ <guix-build-coordinator-agent-dynamic-auth>
+                             agent-name token)
+                          #~(#$(string-append "--name=" agent-name)
+                             #$(string-append "--dynamic-auth-token=" token)))
+                         (($
+                           <guix-build-coordinator-agent-dynamic-auth-with-file>
+                           agent-name token-file)
+                          #~(#$(string-append "--name=" agent-name)
+                             #$(string-append "--dynamic-auth-token-file="
+                                              token-file))))
+                    #$(simple-format #f "--max-parallel-builds=~A"
+                                     max-parallel-builds)
+                    #$@(if max-allocated-builds
+                           #~(#$(simple-format #f "--max-allocated-builds=~A"
+                                               max-allocated-builds))
+                           #~())
+                    #$@(if max-1min-load-average
+                           #~(#$(simple-format #f "--max-1min-load-average=~A"
+                                               max-1min-load-average))
+                           #~())
+                    #$@(if derivation-substitute-urls
+                           #~(#$(string-append
+                                 "--derivation-substitute-urls="
                                  (string-join derivation-substitute-urls " ")))
-                             #~())
-                      #$@(if non-derivation-substitute-urls
-                             #~(#$(string-append
-                                   "--non-derivation-substitute-urls="
-                                   (string-join non-derivation-substitute-urls " ")))
-                             #~())
-                      #$@(map (lambda (system)
-                                (string-append "--system=" system))
-                              (or systems '())))
-                #:user #$user
-                #:environment-variables
-                `(,(string-append
-                    "GUIX_LOCPATH=" #$glibc-utf8-locales "/lib/locale")
-                  ;; XDG_CACHE_HOME is used by Guix when caching narinfo files
-                  "XDG_CACHE_HOME=/var/cache/guix-build-coordinator-agent"
-                  "LC_ALL=en_US.utf8")
-                #:log-file "/var/log/guix-build-coordinator/agent.log"))
-      (stop #~(make-kill-destructor))))))
+                           #~())
+                    #$@(if non-derivation-substitute-urls
+                           #~(#$(string-append
+                                 "--non-derivation-substitute-urls="
+                                 (string-join non-derivation-substitute-urls " ")))
+                           #~())
+                    #$@(map (lambda (system)
+                              (string-append "--system=" system))
+                            (or systems '())))
+              #:user #$user
+              #:environment-variables
+              `(,(string-append
+                  "GUIX_LOCPATH=" #$glibc-utf8-locales "/lib/locale")
+                ;; XDG_CACHE_HOME is used by Guix when caching narinfo files
+                "XDG_CACHE_HOME=/var/cache/guix-build-coordinator-agent"
+                "LC_ALL=en_US.utf8")
+              #:log-file "/var/log/guix-build-coordinator/agent.log"))))
+      (stop #~(make-kill-destructor))
+      (modules
+       `((shepherd comm)
+         ,@%default-modules))))))
 
 (define (guix-build-coordinator-agent-activation config)
   #~(begin
@@ -517,39 +539,44 @@
       (provision '(guix-build-coordinator-queue-builds))
       (requirement '(networking))
       (start
-       #~(make-forkexec-constructor
-          (list
-           #$(file-append
-              package
-              "/bin/guix-build-coordinator-queue-builds-from-guix-data-service")
-           #$(string-append "--coordinator=" coordinator)
-           #$@(map (lambda (system)
-                     (string-append "--system=" system))
-                   (or systems '()))
-           #$@(map (match-lambda
-                     ((system . target)
-                      (string-append "--system-and-target=" system "=" target)))
-                   (or systems-and-targets '()))
-           #$@(if guix-data-service
-                  #~(#$(string-append "--guix-data-service=" guix-data-service))
-                  #~())
-           #$@(if guix-data-service-build-server-id
-                  #~(#$(simple-format
-                        #f
-                        "--guix-data-service-build-server-id=~A"
-                        guix-data-service-build-server-id))
-                  #~())
-           #$@(if processed-commits-file
-                  #~(#$(string-append "--processed-commits-file="
-                                      processed-commits-file))
-                  #~()))
-          #:user #$user
-          #:environment-variables
-          `(,(string-append
-              "GUIX_LOCPATH=" #$glibc-utf8-locales "/lib/locale")
-            "LC_ALL=en_US.utf8")
-          #:log-file "/var/log/guix-build-coordinator/queue-builds.log"))
-      (stop #~(make-kill-destructor))))))
+       #~(lambda _
+           (parameterize ((%current-logfile-date-format ""))
+             (fork+exec-command
+              (list
+               #$(file-append
+                  package
+                  "/bin/guix-build-coordinator-queue-builds-from-guix-data-service")
+               #$(string-append "--coordinator=" coordinator)
+               #$@(map (lambda (system)
+                         (string-append "--system=" system))
+                       (or systems '()))
+               #$@(map (match-lambda
+                         ((system . target)
+                          (string-append "--system-and-target=" system "=" target)))
+                       (or systems-and-targets '()))
+               #$@(if guix-data-service
+                      #~(#$(string-append "--guix-data-service=" guix-data-service))
+                      #~())
+               #$@(if guix-data-service-build-server-id
+                      #~(#$(simple-format
+                            #f
+                            "--guix-data-service-build-server-id=~A"
+                            guix-data-service-build-server-id))
+                      #~())
+               #$@(if processed-commits-file
+                      #~(#$(string-append "--processed-commits-file="
+                                          processed-commits-file))
+                      #~()))
+              #:user #$user
+              #:environment-variables
+              `(,(string-append
+                  "GUIX_LOCPATH=" #$glibc-utf8-locales "/lib/locale")
+                "LC_ALL=en_US.utf8")
+              #:log-file "/var/log/guix-build-coordinator/queue-builds.log"))))
+      (stop #~(make-kill-destructor))
+      (modules
+       `((shepherd comm)
+         ,@%default-modules))))))
 
 (define (guix-build-coordinator-queue-builds-activation config)
   #~(begin
diff --git a/gnu/services/ldap.scm b/gnu/services/ldap.scm
new file mode 100644
index 0000000000..49a33fac08
--- /dev/null
+++ b/gnu/services/ldap.scm
@@ -0,0 +1,317 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2018, 2019, 2022 Ricardo Wurmus <rekado@elephly.net>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of thye GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu services ldap)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu packages openldap)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu system shadow)
+  #:use-module (guix gexp)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 string-fun)
+  #:export (directory-server-service-type
+            directory-server-shepherd-service
+
+            directory-server-instance-configuration
+            slapd-configuration
+            backend-configuration))
+
+(define (uglify-field-name name)
+  (let ((str (string-map (match-lambda
+                           (#\- #\_)
+                           (chr chr))
+                         (symbol->string name))))
+    (if (string-suffix? "?" str)
+        (substring str 0 (1- (string-length str)))
+        str)))
+(define (serialize-field field-name val)
+  (format #t "~a = ~a\n" (uglify-field-name field-name) val))
+(define serialize-string serialize-field)
+(define-maybe string)
+(define (serialize-boolean field-name val)
+  (serialize-field field-name (if val "True" "False")))
+(define (serialize-number field-name val)
+  (serialize-field field-name (number->string val)))
+
+
+(define-configuration slapd-configuration
+  (instance-name
+   (string "localhost")
+   "Sets the name of the instance.  You can refer to this value in other
+parameters of this INF file using the \"{instance_name}\" variable.  Note that
+this name cannot be changed after the installation!")
+  (user
+   (string "dirsrv")
+   "Sets the user name the ns-slapd process will use after the service
+started.")
+  (group
+   (string "dirsrv")
+   "Sets the group name the ns-slapd process will use after the service
+started.")
+  (port
+   (number 389)
+   "Sets the TCP port the instance uses for LDAP connections.")
+  (secure-port
+   (number 636)
+   "Sets the TCP port the instance uses for TLS-secured LDAP
+connections (LDAPS).")
+  (root-dn
+   (string "cn=Directory Manager")
+   "Sets the Distinquished Name (DN) of the administrator account for this
+instance.")
+  (root-password
+   (string "{invalid}YOU-SHOULD-CHANGE-THIS")
+   "Sets the password of the account specified in the \"root-dn\" parameter.
+You can either set this parameter to a plain text password dscreate hashes
+during the installation or to a \"{algorithm}hash\" string generated by the
+pwdhash utility.  Note that setting a plain text password can be a security
+risk if unprivileged users can read this INF file!")
+  (self-sign-cert
+   (boolean #t)
+   "Sets whether the setup creates a self-signed certificate and enables TLS
+encryption during the installation.  This is not suitable for production, but
+it enables administrators to use TLS right after the installation.  You can
+replace the self-signed certificate with a certificate issued by a certificate
+authority.")
+  (self-sign-cert-valid-months
+   (number 24)
+   "Set the number of months the issued self-signed certificate will be valid.")
+  (backup-dir
+   (string "/var/lib/dirsrv/slapd-{instance_name}/bak")
+   "Set the backup directory of the instance.")
+  (cert-dir
+   (string "/etc/dirsrv/slapd-{instance_name}")
+   "Sets the directory of the instance's Network Security Services (NSS)
+database.")
+  (config-dir
+   (string "/etc/dirsrv/slapd-{instance_name}")
+   "Sets the configuration directory of the instance.")
+  (db-dir
+   (string "/var/lib/dirsrv/slapd-{instance_name}/db")
+   "Sets the database directory of the instance.")
+  (initconfig-dir
+   (string "/etc/dirsrv/registry")
+   "Sets the directory of the operating system's rc configuration directory.")
+  (ldif-dir
+   (string "/var/lib/dirsrv/slapd-{instance_name}/ldif")
+   "Sets the LDIF export and import directory of the instance.")
+  (lock-dir
+   (string "/var/lock/dirsrv/slapd-{instance_name}")
+   "Sets the lock directory of the instance.")
+  (log-dir
+   (string "/var/log/dirsrv/slapd-{instance_name}")
+   "Sets the log directory of the instance.")
+  (run-dir
+   (string "/run/dirsrv")
+   "Sets PID directory of the instance.")
+  (schema-dir
+   (string "/etc/dirsrv/slapd-{instance_name}/schema")
+   "Sets schema directory of the instance.")
+  (tmp-dir
+   (string "/tmp")
+   "Sets the temporary directory of the instance."))
+
+(define (serialize-slapd-configuration field-name val)
+  #t)
+
+
+(define-configuration backend-userroot-configuration
+  (create-suffix-entry?
+   (boolean #false)
+   "Set this parameter to #true to create a generic root node entry for the
+suffix in the database.")
+  (require-index?
+   (boolean #false)
+   "Set this parameter to #true to refuse unindexed searches in this
+database.")
+  (sample-entries
+   (string "no")
+   "Set this parameter to \"yes\" to add latest version of sample entries to
+this database.  Or, use \"001003006\" to use the 1.3.6 version sample entries.
+Use this option, for example, to create a database for testing purposes.")
+  (suffix
+   maybe-string
+   "Sets the root suffix stored in this database.  If you do not set the
+suffix attribute the install process will not create the backend/suffix.  You
+can also create multiple backends/suffixes by duplicating this section."))
+
+(define (serialize-backend-userroot-configuration field-name val)
+  #t)
+
+
+(define-configuration directory-server-instance-configuration
+  (package
+    (file-like 389-ds-base)
+    "The 389-ds-base package.")
+  ;; General settings
+  (config-version
+   (number 2)
+   "Sets the format version of the configuration file.  To use the INF file
+with dscreate, this parameter must be 2.")
+  (full-machine-name
+   (string "localhost")
+   "Sets the fully qualified hostname (FQDN) of this system.")
+  (selinux
+   (boolean #false)
+   "Enables SELinux detection and integration during the installation of this
+instance.  If set to #T, dscreate auto-detects whether SELinux is enabled.")
+  (strict-host-checking
+   (boolean #t)
+   "Sets whether the server verifies the forward and reverse record set in the
+\"full-machine-name\" parameter.  When installing this instance with GSSAPI
+authentication behind a load balancer, set this parameter to #F.")
+  (systemd
+   (boolean #false)
+   "Enables systemd platform features.  If set to #T, dscreate auto-detects
+whether systemd is installed.")
+  (slapd
+   (slapd-configuration (slapd-configuration))
+   "Configuration of slapd.")
+  (backend-userroot
+   (backend-userroot-configuration (backend-userroot-configuration))
+   "Configuration of the userroot backend."))
+
+(define (serialize-directory-server-instance-configuration x)
+  (format #t "[general]\n")
+  (serialize-configuration
+   x
+   (filter (lambda (field)
+             (not (member (configuration-field-name field)
+                          '(package slapd backend-userroot))))
+           directory-server-instance-configuration-fields))
+  ;; Do not start instance while running dscreate.  Do this later with
+  ;; shepherd.
+  (format #t "start = False\n")
+  (format #t "\n[slapd]\n")
+  (serialize-configuration
+   (directory-server-instance-configuration-slapd x)
+   slapd-configuration-fields)
+  (format #t "\n[backend-userroot]\n")
+  (serialize-configuration
+   (directory-server-instance-configuration-backend-userroot x)
+   backend-userroot-configuration-fields))
+
+(define (directory-server-instance-config-file config)
+  "Return an LDAP directory server instance configuration file."
+  (let* ((slapd   (directory-server-instance-configuration-slapd config))
+         (instance-name (slapd-configuration-instance-name slapd)))
+    (plain-file
+     (string-append "dirsrv-" instance-name ".inf")
+     (with-output-to-string
+       (lambda ()
+         (serialize-directory-server-instance-configuration config))))))
+
+(define (directory-server-shepherd-service config)
+  "Return a shepherd service for an LDAP directory server with CONFIG."
+  (let* ((389-ds-base (directory-server-instance-configuration-package config))
+         (slapd       (directory-server-instance-configuration-slapd config))
+         (instance-name
+          (slapd-configuration-instance-name slapd)))
+    (list (shepherd-service
+           (documentation "Run an 389 directory server instance.")
+           (provision (list (symbol-append 'directory-server-
+                                           (string->symbol instance-name))))
+           (requirement '())
+           (start #~(make-forkexec-constructor
+                     (list #$(file-append 389-ds-base "/sbin/dsctl")
+                           #$instance-name "start")
+                     #:pid-file
+                     (string-append
+                      #$(slapd-configuration-run-dir slapd)
+                      "/slapd-" #$instance-name ".pid")))
+           (stop #~(make-kill-destructor))))))
+
+(define (directory-server-accounts config)
+  (let* ((slapd (directory-server-instance-configuration-slapd config))
+         (user (slapd-configuration-user slapd))
+         (group (slapd-configuration-group slapd)))
+    (list (user-group
+           (name group)
+           (system? #true))
+          (user-account
+           (name user)
+           (group group)
+           (system? #true)
+           (comment "System user for the 389 directory server")
+           (home-directory "/var/empty")
+           (shell (file-append shadow "/sbin/nologin"))))))
+
+(define (directory-server-activation config)
+  (let* ((389-ds-base (directory-server-instance-configuration-package config))
+         (config-file (directory-server-instance-config-file config))
+         (slapd (directory-server-instance-configuration-slapd config))
+         (instance-name (slapd-configuration-instance-name slapd))
+         (user (slapd-configuration-user slapd))
+         (group (slapd-configuration-group slapd))
+         (instantiate (lambda (proc)
+                        (string-replace-substring
+                         (proc slapd) "{instance_name}" instance-name)))
+         (config-dir (instantiate slapd-configuration-config-dir))
+         (all-dirs (delete-duplicates
+                    (map (compose dirname instantiate)
+                         (list slapd-configuration-backup-dir
+                               slapd-configuration-cert-dir
+                               slapd-configuration-db-dir
+                               slapd-configuration-ldif-dir
+                               slapd-configuration-lock-dir
+                               slapd-configuration-log-dir
+                               slapd-configuration-run-dir
+                               slapd-configuration-schema-dir)))))
+    ;; 389-ds-base doesn't let us update an instance configuration, so bail
+    ;; out when the configuration directory already exists.
+    #~(begin
+        (use-modules (ice-9 match)
+                     (guix build utils))
+        (if (file-exists? #$config-dir)
+            (format #t
+                    "directory-server: Instance configuration for `~a' already exists.  Skipping.\n"
+                    #$instance-name)
+            (let ((owner (getpwnam #$user)))
+              (for-each (lambda (dir)
+                          (mkdir-p dir)
+                          (chown dir (passwd:uid owner) (passwd:gid owner)))
+                        (sort '#$all-dirs string<=))
+              (system* #$(file-append 389-ds-base "/sbin/dscreate")
+                       "from-file" #$config-file))))))
+
+(define directory-server-service-type
+  (service-type (name 'directory-server)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          directory-server-shepherd-service)
+                       (service-extension activation-service-type
+                                          directory-server-activation)
+                       (service-extension account-service-type
+                                          directory-server-accounts)))
+                (default-value (directory-server-instance-configuration))
+                (description
+                 "Run a directory server instance.")))
+
+(define (generate-directory-server-documentation)
+  (generate-documentation
+    `((directory-server-instance-configuration
+       ,directory-server-instance-configuration-fields
+       (slapd slapd-configuration)
+       (backend-userroot backend-userroot-configuration))
+      (slapd-configuration ,slapd-configuration-fields)
+      (backend-userroot-configuration
+       ,backend-userroot-configuration-fields))
+    'directory-server-instance-configuration))
diff --git a/gnu/services/lightdm.scm b/gnu/services/lightdm.scm
index 07f2e808dd..7e3864fec2 100644
--- a/gnu/services/lightdm.scm
+++ b/gnu/services/lightdm.scm
@@ -40,7 +40,6 @@
   #:use-module (guix records)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
-  #:use-module (oop goops)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
   #:export (lightdm-seat-configuration
@@ -177,17 +176,18 @@ Provider Interface (AT-SPI).")
    "Extra configuration values to append to the LightDM GTK Greeter
 configuration file."))
 
-(define (strip-class-name-brackets name)
-  "Remove the '<<' and '>>' brackets from NAME, a symbol."
-  (let ((name* (symbol->string name)))
-    (if (and (string-prefix? "<<" name*)
-             (string-suffix? ">>" name*))
-        (string->symbol (string-drop (string-drop-right name* 2) 2))
-        (error "unexpected class name" name*))))
+(define (strip-record-type-name-brackets name)
+  "Remove the '<' and '>' brackets from NAME, a symbol."
+  (let ((name (symbol->string name)))
+    (if (and (string-prefix? "<" name)
+             (string-suffix? ">" name))
+        (string->symbol (string-drop (string-drop-right name 1) 1))
+        (error "unexpected record type name" name))))
 
 (define (config->name config)
   "Return the constructor name (a symbol) from CONFIG."
-  (strip-class-name-brackets (class-name (class-of config))))
+  (strip-record-type-name-brackets
+   (record-type-name (struct-vtable config))))
 
 (define (greeter-configuration->greeter-fields config)
   "Return the fields of CONFIG, a greeter configuration."
@@ -323,7 +323,7 @@ a symbol."
 (define (list-of-greeter-configurations? greeter-configs)
   (and ((list-of greeter-configuration?) greeter-configs)
        ;; Greeter configurations must also not be provided more than once.
-       (let* ((types (map (cut (compose class-name class-of) <>)
+       (let* ((types (map (compose record-type-name struct-vtable)
                           greeter-configs))
               (dupes (filter (lambda (type)
                                (< 1 (count (cut eq? type <>) types)))
@@ -374,7 +374,7 @@ security:
              \" -SecurityTypes None\" ))
 @end lisp
 
-Or to set a PasswordFile for the classic (unsecure) VncAuth mecanism:
+Or to set a PasswordFile for the classic (unsecure) VncAuth mechanism:
 @lisp
 (vnc-server-command
  (file-append tigervnc-server \"/bin/Xvnc\"
diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm
index 43f144a42d..6f588679b1 100644
--- a/gnu/services/mail.scm
+++ b/gnu/services/mail.scm
@@ -35,6 +35,7 @@
   #:use-module (gnu packages admin)
   #:use-module (gnu packages dav)
   #:use-module (gnu packages tls)
+  #:use-module (guix modules)
   #:use-module (guix records)
   #:use-module (guix packages)
   #:use-module (guix gexp)
@@ -1512,64 +1513,61 @@ greyed out, instead of only later giving \"not selectable\" popup error.
              (lambda ()
                (serialize-configuration config
                                         dovecot-configuration-fields)))))))
-    #~(begin
-        (use-modules (guix build utils))
-        (define (mkdir-p/perms directory owner perms)
-          (mkdir-p directory)
-          (chown "/var/run/dovecot" (passwd:uid owner) (passwd:gid owner))
-          (chmod directory perms))
-        (define (build-subject parameters)
-          (string-concatenate
-           (map (lambda (pair)
-                  (let ((k (car pair)) (v (cdr pair)))
-                    (define (escape-char str chr)
-                      (string-join (string-split str chr) (string #\\ chr)))
-                    (string-append "/" k "="
-                                   (escape-char (escape-char v #\=) #\/))))
-                (filter (lambda (pair) (cdr pair)) parameters))))
-        (define* (create-self-signed-certificate-if-absent
-                  #:key private-key public-key (owner (getpwnam "root"))
-                  (common-name (gethostname))
-                  (organization-name "Guix")
-                  (organization-unit-name "Default Self-Signed Certificate")
-                  (subject-parameters `(("CN" . ,common-name)
-                                        ("O" . ,organization-name)
-                                        ("OU" . ,organization-unit-name)))
-                  (subject (build-subject subject-parameters)))
-          ;; Note that by default, OpenSSL outputs keys in PEM format.  This
-          ;; is what we want.
-          (unless (file-exists? private-key)
-            (cond
-             ((zero? (system* (string-append #$openssl "/bin/openssl")
-                              "genrsa" "-out" private-key "2048"))
-              (chown private-key (passwd:uid owner) (passwd:gid owner))
-              (chmod private-key #o400))
-             (else
-              (format (current-error-port)
-                      "Failed to create private key at ~a.\n" private-key))))
-          (unless (file-exists? public-key)
-            (cond
-             ((zero? (system* (string-append #$openssl "/bin/openssl")
-                              "req" "-new" "-x509" "-key" private-key
-                              "-out" public-key "-days" "3650"
-                              "-batch" "-subj" subject))
-              (chown public-key (passwd:uid owner) (passwd:gid owner))
-              (chmod public-key #o444))
-             (else
-              (format (current-error-port)
-                      "Failed to create public key at ~a.\n" public-key)))))
-        (let ((user (getpwnam "dovecot")))
-          (mkdir-p/perms "/var/run/dovecot" user #o755)
-          (mkdir-p/perms "/var/lib/dovecot" user #o755)
-          (mkdir-p/perms "/etc/dovecot" user #o755)
-          (copy-file #$(plain-file "dovecot.conf" config-str)
-                     "/etc/dovecot/dovecot.conf")
-          (mkdir-p/perms "/etc/dovecot/private" user #o700)
-          (create-self-signed-certificate-if-absent
-           #:private-key "/etc/dovecot/private/default.pem"
-           #:public-key "/etc/dovecot/default.pem"
-           #:owner (getpwnam "root")
-           #:common-name (format #f "Dovecot service on ~a" (gethostname)))))))
+    (with-imported-modules (source-module-closure '((gnu build activation)))
+      #~(begin
+          (use-modules (guix build utils) (gnu build activation))
+          (define (build-subject parameters)
+            (string-concatenate
+             (map (lambda (pair)
+                    (let ((k (car pair)) (v (cdr pair)))
+                      (define (escape-char str chr)
+                        (string-join (string-split str chr) (string #\\ chr)))
+                      (string-append "/" k "="
+                                     (escape-char (escape-char v #\=) #\/))))
+                  (filter (lambda (pair) (cdr pair)) parameters))))
+          (define* (create-self-signed-certificate-if-absent
+                    #:key private-key public-key (owner (getpwnam "root"))
+                    (common-name (gethostname))
+                    (organization-name "Guix")
+                    (organization-unit-name "Default Self-Signed Certificate")
+                    (subject-parameters `(("CN" . ,common-name)
+                                          ("O" . ,organization-name)
+                                          ("OU" . ,organization-unit-name)))
+                    (subject (build-subject subject-parameters)))
+            ;; Note that by default, OpenSSL outputs keys in PEM format.  This
+            ;; is what we want.
+            (unless (file-exists? private-key)
+              (cond
+               ((zero? (system* (string-append #$openssl "/bin/openssl")
+                                "genrsa" "-out" private-key "2048"))
+                (chown private-key (passwd:uid owner) (passwd:gid owner))
+                (chmod private-key #o400))
+               (else
+                (format (current-error-port)
+                        "Failed to create private key at ~a.\n" private-key))))
+            (unless (file-exists? public-key)
+              (cond
+               ((zero? (system* (string-append #$openssl "/bin/openssl")
+                                "req" "-new" "-x509" "-key" private-key
+                                "-out" public-key "-days" "3650"
+                                "-batch" "-subj" subject))
+                (chown public-key (passwd:uid owner) (passwd:gid owner))
+                (chmod public-key #o444))
+               (else
+                (format (current-error-port)
+                        "Failed to create public key at ~a.\n" public-key)))))
+          (let ((user (getpwnam "dovecot")))
+            (mkdir-p/perms "/var/run/dovecot" user #o755)
+            (mkdir-p/perms "/var/lib/dovecot" user #o755)
+            (mkdir-p/perms "/etc/dovecot" user #o755)
+            (copy-file #$(plain-file "dovecot.conf" config-str)
+                       "/etc/dovecot/dovecot.conf")
+            (mkdir-p/perms "/etc/dovecot/private" user #o700)
+            (create-self-signed-certificate-if-absent
+             #:private-key "/etc/dovecot/private/default.pem"
+             #:public-key "/etc/dovecot/default.pem"
+             #:owner (getpwnam "root")
+             #:common-name (format #f "Dovecot service on ~a" (gethostname))))))))
 
 (define (dovecot-shepherd-service config)
   "Return a list of <shepherd-service> for CONFIG."
@@ -1653,6 +1651,8 @@ by @code{dovecot-configuration}.  @var{config} may also be created by
   opensmtpd-configuration?
   (package     opensmtpd-configuration-package
                (default opensmtpd))
+  (shepherd-requirement opensmtpd-configuration-shepherd-requirement
+                        (default '())) ; list of symbols
   (config-file opensmtpd-configuration-config-file
                (default %default-opensmtpd-config-file))
   (setgid-commands? opensmtpd-setgid-commands? (default #t)))
@@ -1668,18 +1668,18 @@ action outbound relay
 match from local for any action outbound
 "))
 
-(define opensmtpd-shepherd-service
-  (match-lambda
-    (($ <opensmtpd-configuration> package config-file)
-     (list (shepherd-service
-            (provision '(smtpd))
-            (requirement '(loopback))
-            (documentation "Run the OpenSMTPD daemon.")
-            (start (let ((smtpd (file-append package "/sbin/smtpd")))
-                     #~(make-forkexec-constructor
-                        (list #$smtpd "-f" #$config-file)
-                        #:pid-file "/var/run/smtpd.pid")))
-            (stop #~(make-kill-destructor)))))))
+(define (opensmtpd-shepherd-service config)
+  (match-record config <opensmtpd-configuration>
+                       (package config-file shepherd-requirement)
+    (list (shepherd-service
+           (provision '(smtpd))
+           (requirement `(loopback ,@shepherd-requirement))
+           (documentation "Run the OpenSMTPD daemon.")
+           (start (let ((smtpd (file-append package "/sbin/smtpd")))
+                    #~(make-forkexec-constructor
+                       (list #$smtpd "-f" #$config-file)
+                       #:pid-file "/var/run/smtpd.pid")))
+           (stop #~(make-kill-destructor))))))
 
 (define %opensmtpd-accounts
   (list (user-group
@@ -1700,58 +1700,56 @@ match from local for any action outbound
          (home-directory "/var/empty")
          (shell (file-append shadow "/sbin/nologin")))))
 
-(define opensmtpd-activation
-  (match-lambda
-    (($ <opensmtpd-configuration> package config-file)
-     (let ((smtpd (file-append package "/sbin/smtpd")))
-       #~(begin
-           (use-modules (guix build utils))
-           ;; Create mbox and spool directories.
-           (mkdir-p "/var/mail")
-           (mkdir-p "/var/spool/smtpd")
-           (chmod "/var/spool/smtpd" #o711)
-           (mkdir-p "/var/spool/mail")
-           (chmod "/var/spool/mail" #o711))))))
+(define (opensmtpd-activation config)
+  (match-record config <opensmtpd-configuration> (package config-file)
+    (let ((smtpd (file-append package "/sbin/smtpd")))
+      #~(begin
+          (use-modules (guix build utils))
+          ;; Create mbox and spool directories.
+          (mkdir-p "/var/mail")
+          (mkdir-p "/var/spool/smtpd")
+          (chmod "/var/spool/smtpd" #o711)
+          (mkdir-p "/var/spool/mail")
+          (chmod "/var/spool/mail" #o711)))))
 
 (define %opensmtpd-pam-services
   (list (unix-pam-service "smtpd")))
 
-(define opensmtpd-set-gids
-  (match-lambda
-    (($ <opensmtpd-configuration> package config-file set-gids?)
-     (if set-gids?
-         (list
-          (setuid-program
-           (program (file-append package "/sbin/smtpctl"))
-           (setuid? #false)
-           (setgid? #true)
-           (group "smtpq"))
-          (setuid-program
-           (program (file-append package "/sbin/sendmail"))
-           (setuid? #false)
-           (setgid? #true)
-           (group "smtpq"))
-          (setuid-program
-           (program (file-append package "/sbin/send-mail"))
-           (setuid? #false)
-           (setgid? #true)
-           (group "smtpq"))
-          (setuid-program
-           (program (file-append package "/sbin/makemap"))
-           (setuid? #false)
-           (setgid? #true)
-           (group "smtpq"))
-          (setuid-program
-           (program (file-append package "/sbin/mailq"))
-           (setuid? #false)
-           (setgid? #true)
-           (group "smtpq"))
-          (setuid-program
-           (program (file-append package "/sbin/newaliases"))
-           (setuid? #false)
-           (setgid? #true)
-           (group "smtpq")))
-         '()))))
+(define (opensmtpd-set-gids config)
+  (match-record config <opensmtpd-configuration> (package config-file setgid-commands?)
+    (if setgid-commands?
+        (list
+         (setuid-program
+          (program (file-append package "/sbin/smtpctl"))
+          (setuid? #false)
+          (setgid? #true)
+          (group "smtpq"))
+         (setuid-program
+          (program (file-append package "/sbin/sendmail"))
+          (setuid? #false)
+          (setgid? #true)
+          (group "smtpq"))
+         (setuid-program
+          (program (file-append package "/sbin/send-mail"))
+          (setuid? #false)
+          (setgid? #true)
+          (group "smtpq"))
+         (setuid-program
+          (program (file-append package "/sbin/makemap"))
+          (setuid? #false)
+          (setgid? #true)
+          (group "smtpq"))
+         (setuid-program
+          (program (file-append package "/sbin/mailq"))
+          (setuid? #false)
+          (setgid? #true)
+          (group "smtpq"))
+         (setuid-program
+          (program (file-append package "/sbin/newaliases"))
+          (setuid? #false)
+          (setgid? #true)
+          (group "smtpq")))
+        '())))
 
 (define opensmtpd-service-type
   (service-type
diff --git a/gnu/services/mcron.scm b/gnu/services/mcron.scm
index 23760ebda4..52332d6123 100644
--- a/gnu/services/mcron.scm
+++ b/gnu/services/mcron.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -18,6 +19,7 @@
 
 (define-module (gnu services mcron)
   #:use-module (gnu services)
+  #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
   #:use-module (gnu packages guile-xyz)
   #:use-module (guix deprecation)
@@ -30,6 +32,8 @@
             mcron-configuration?
             mcron-configuration-mcron
             mcron-configuration-jobs
+            mcron-configuration-log?
+            mcron-configuration-log-format
 
             mcron-service-type))
 
@@ -48,13 +52,23 @@
 ;;;
 ;;; Code:
 
-(define-record-type* <mcron-configuration> mcron-configuration
-  make-mcron-configuration
-  mcron-configuration?
-  (mcron             mcron-configuration-mcron    ;file-like
-                     (default mcron))
-  (jobs              mcron-configuration-jobs     ;list of <mcron-job>
-                     (default '())))
+(define list-of-gexps?
+  (list-of gexp?))
+
+(define-configuration/no-serialization mcron-configuration
+  (mcron (file-like mcron) "The mcron package to use.")
+  (jobs
+   (list-of-gexps '())
+   "This is a list of gexps (@pxref{G-Expressions}), where each gexp
+corresponds to an mcron job specification (@pxref{Syntax, mcron job
+specifications,, mcron, GNU@tie{}mcron}).")
+  (log? (boolean #t) "Log messages to standard output.")
+  (log-format
+   (string "~1@*~a ~a: ~a~%")
+   "@code{(ice-9 format)} format string for log messages.  The default value
+produces messages like \"@samp{@var{pid} @var{name}:
+@var{message}\"} (@pxref{Invoking mcron, Invoking,, mcron, GNU@tie{}mcron}).
+Each message is also prefixed by a timestamp by GNU Shepherd."))
 
 (define (job-files mcron jobs)
   "Return a list of file-like object for JOBS, a list of gexps."
@@ -124,21 +138,25 @@ files."
 
 (define mcron-shepherd-services
   (match-lambda
-    (($ <mcron-configuration> mcron ())           ;nothing to do!
+    (($ <mcron-configuration> mcron ()) ;nothing to do!
      '())
-    (($ <mcron-configuration> mcron jobs)
+    (($ <mcron-configuration> mcron jobs log? log-format)
      (let ((files (job-files mcron jobs)))
        (list (shepherd-service
               (provision '(mcron))
               (requirement '(user-processes))
               (modules `((srfi srfi-1)
                          (srfi srfi-26)
-                         (ice-9 popen)            ;for the 'schedule' action
+                         (ice-9 popen)  ;for the 'schedule' action
                          (ice-9 rdelim)
                          (ice-9 match)
                          ,@%default-modules))
               (start #~(make-forkexec-constructor
-                        (list (string-append #$mcron "/bin/mcron") #$@files)
+                        (list (string-append #$mcron "/bin/mcron")
+                              #$@(if log?
+                                     #~("--log" "--log-format" #$log-format)
+                                     #~())
+                              #$@files)
 
                         ;; Disable auto-compilation of the job files and set a
                         ;; sane value for 'PATH'.
@@ -172,4 +190,11 @@ files."
                                          jobs)))))
                 (default-value (mcron-configuration)))) ;empty job list
 
+
+;;;
+;;; Generate documentation.
+;;;
+(define (generate-doc)
+  (configuration->documentation 'mcron-configuration))
+
 ;;; mcron.scm ends here
diff --git a/gnu/services/monitoring.scm b/gnu/services/monitoring.scm
index 9c8704092c..44e2e8886c 100644
--- a/gnu/services/monitoring.scm
+++ b/gnu/services/monitoring.scm
@@ -224,15 +224,12 @@ Prometheus.")
 
 
 (define (serialize-string field-name val)
-  (if (and (string? val) (string=? val ""))
+  (if (or (eq? 'user field-name)
+          (eq? 'group field-name)
+          (and (string? val) (string=? val "")))
       ""
       (serialize-field field-name val)))
 
-(define group? string?)
-
-(define serialize-group
-  (const ""))
-
 (define include-files? list?)
 
 (define (serialize-include-files field-name val)
@@ -256,8 +253,8 @@ Prometheus.")
   (user
    (string "zabbix")
    "User who will run the Zabbix server.")
-  (group ;for zabbix-server-account procedure
-   (group "zabbix")
+  (group
+   (string "zabbix")
    "Group who will run the Zabbix server.")
   (db-host
    (string "127.0.0.1")
@@ -407,7 +404,10 @@ configuration file."))
 /etc/ssl/certs"
                            "SSL_CERT_FILE=/run/current-system/profile\
 /etc/ssl/certs/ca-certificates.crt")))
-           (stop #~(make-kill-destructor))))))
+           (stop #~(make-kill-destructor
+                     ;; The server needs to finish database work on shutdown
+                     ;; which can take a while for big or busy databases.
+                     #:grace-period 60))))))
 
 (define zabbix-server-service-type
   (service-type
@@ -438,7 +438,7 @@ results in a Web interface.")))
    (string "zabbix")
    "User who will run the Zabbix agent.")
   (group
-   (group "zabbix")
+   (string "zabbix")
    "Group who will run the Zabbix agent.")
   (hostname
    (string "")
@@ -516,6 +516,18 @@ configuration file."))
            (format port #$(serialize-configuration
                            config zabbix-agent-configuration-fields)))))))
 
+(define (zabbix-agent-arguments config)
+  #~(let* ((config-file #$(zabbix-agent-config-file config))
+           (agent #$(zabbix-agent-configuration-zabbix-agent config))
+           (agent2? (file-exists? (string-append agent "/sbin/zabbix_agent2"))))
+      (if agent2?
+          (list (string-append agent "/sbin/zabbix_agent2")
+                "-config" config-file
+                "-foreground")
+          (list (string-append agent "/sbin/zabbix_agentd")
+                "--config" config-file
+                "--foreground"))))
+
 (define (zabbix-agent-shepherd-service config)
   "Return a <shepherd-service> for Zabbix agent with CONFIG."
   (list (shepherd-service
@@ -523,10 +535,7 @@ configuration file."))
          (requirement '(user-processes))
          (documentation "Run Zabbix agent daemon.")
          (start #~(make-forkexec-constructor
-                   (list #$(file-append (zabbix-agent-configuration-zabbix-agent config)
-                                        "/sbin/zabbix_agentd")
-                         "--config" #$(zabbix-agent-config-file config)
-                         "--foreground")
+                   #$(zabbix-agent-arguments config)
                    #:user #$(zabbix-agent-configuration-user config)
                    #:group #$(zabbix-agent-configuration-group config)
                    #:pid-file #$(zabbix-agent-configuration-pid-file config)
@@ -576,7 +585,7 @@ fastcgi_param PHP_VALUE \"post_max_size = 16M
 
 (define (zabbix-front-end-nginx-extension config)
   (match config
-    (($ <zabbix-front-end-configuration> _ server nginx)
+    (($ <zabbix-front-end-configuration> server nginx)
      (if (null? nginx)
          (list
           (nginx-server-configuration
@@ -622,8 +631,8 @@ create it manually.")
 
 (define (zabbix-front-end-config config)
   (match-record config <zabbix-front-end-configuration>
-    (%location db-host db-port db-name db-user db-password db-secret-file
-               zabbix-host zabbix-port)
+    (db-host db-port db-name db-user db-password db-secret-file
+             zabbix-host zabbix-port %location)
     (mixed-text-file "zabbix.conf.php"
                      "\
 <?php
diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm
index 9d85728371..89ce16f6af 100644
--- a/gnu/services/networking.scm
+++ b/gnu/services/networking.scm
@@ -18,6 +18,8 @@
 ;;; Copyright © 2021 Christine Lemmer-Webber <cwebber@dustycloud.org>
 ;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
 ;;; Copyright © 2021 Guillaume Le Vaillant <glv@posteo.net>
+;;; Copyright © 2022, 2023 Andrew Tropin <andrew@trop.in>
+;;; Copyright © 2023 Declan Tsien <declantsien@riseup.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -66,6 +68,9 @@
   #:use-module (guix modules)
   #:use-module (guix packages)
   #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
+  #:autoload   (guix ui) (display-hint)
+  #:use-module (guix i18n)
   #:use-module (rnrs enums)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
@@ -77,6 +82,10 @@
                static-networking-service-type)
   #:export (%facebook-host-aliases
             dhcp-client-service-type
+            dhcp-client-configuration
+            dhcp-client-configuration?
+            dhcp-client-configuration-package
+            dhcp-client-configuration-interfaces
 
             dhcpd-service-type
             dhcpd-configuration
@@ -259,52 +268,80 @@ fe80::1%lo0 connect.facebook.net
 fe80::1%lo0 www.connect.facebook.net
 fe80::1%lo0 apps.facebook.com\n")
 
+
+(define-record-type* <dhcp-client-configuration>
+  dhcp-client-configuration make-dhcp-client-configuration
+  dhcp-client-configuration?
+  (package      dhcp-client-configuration-package ;file-like
+                (default isc-dhcp))
+  (interfaces   dhcp-client-configuration-interfaces
+                (default 'all)))                  ;'all | list of strings
+
+(define dhcp-client-shepherd-service
+  (match-lambda
+    ((? dhcp-client-configuration? config)
+     (let ((package (dhcp-client-configuration-package config))
+           (interfaces (dhcp-client-configuration-interfaces config))
+           (pid-file "/var/run/dhclient.pid"))
+       (list (shepherd-service
+              (documentation "Set up networking via DHCP.")
+              (requirement '(user-processes udev))
+
+              ;; XXX: Running with '-nw' ("no wait") avoids blocking for a minute when
+              ;; networking is unavailable, but also means that the interface is not up
+              ;; yet when 'start' completes.  To wait for the interface to be ready, one
+              ;; should instead monitor udev events.
+              (provision '(networking))
+
+              (start #~(lambda _
+                         (define dhclient
+                           (string-append #$package "/sbin/dhclient"))
+
+                         ;; When invoked without any arguments, 'dhclient' discovers all
+                         ;; non-loopback interfaces *that are up*.  However, the relevant
+                         ;; interfaces are typically down at this point.  Thus we perform
+                         ;; our own interface discovery here.
+                         (define valid?
+                           (lambda (interface)
+                             (and (arp-network-interface? interface)
+                                  (not (loopback-network-interface? interface))
+                                  ;; XXX: Make sure the interfaces are up so that
+                                  ;; 'dhclient' can actually send/receive over them.
+                                  ;; Ignore those that cannot be activated.
+                                  (false-if-exception
+                                   (set-network-interface-up interface)))))
+                         (define ifaces
+                           (filter valid?
+                                   #$(match interfaces
+                                       ('all
+                                        #~(all-network-interface-names))
+                                       (_
+                                        #~'#$interfaces))))
+
+                         (false-if-exception (delete-file #$pid-file))
+                         (let ((pid (fork+exec-command
+                                     (cons* dhclient "-nw"
+                                            "-pf" #$pid-file ifaces))))
+                           (and (zero? (cdr (waitpid pid)))
+                                (read-pid-file #$pid-file)))))
+              (stop #~(make-kill-destructor))))))
+    (package
+     (warning (G_ "'dhcp-client' service now expects a \
+'dhcp-client-configuration' record~%"))
+     (display-hint (G_ "The value associated with instances of
+@code{dhcp-client-service-type} must now be a @code{dhcp-client-configuration}
+record instead of a package.  Please adjust your configuration accordingly."))
+     (dhcp-client-shepherd-service
+      (dhcp-client-configuration
+       (package package))))))
+
 (define dhcp-client-service-type
-  (shepherd-service-type
-   'dhcp-client
-   (lambda (dhcp)
-     (define dhclient
-       (file-append dhcp "/sbin/dhclient"))
-
-     (define pid-file
-       "/var/run/dhclient.pid")
-
-     (shepherd-service
-      (documentation "Set up networking via DHCP.")
-      (requirement '(user-processes udev))
-
-      ;; XXX: Running with '-nw' ("no wait") avoids blocking for a minute when
-      ;; networking is unavailable, but also means that the interface is not up
-      ;; yet when 'start' completes.  To wait for the interface to be ready, one
-      ;; should instead monitor udev events.
-      (provision '(networking))
-
-      (start #~(lambda _
-                 ;; When invoked without any arguments, 'dhclient' discovers all
-                 ;; non-loopback interfaces *that are up*.  However, the relevant
-                 ;; interfaces are typically down at this point.  Thus we perform
-                 ;; our own interface discovery here.
-                 (define valid?
-                   (lambda (interface)
-                     (and (arp-network-interface? interface)
-                          (not (loopback-network-interface? interface))
-                          ;; XXX: Make sure the interfaces are up so that
-                          ;; 'dhclient' can actually send/receive over them.
-                          ;; Ignore those that cannot be activated.
-                          (false-if-exception
-                           (set-network-interface-up interface)))))
-                 (define ifaces
-                   (filter valid? (all-network-interface-names)))
-
-                 (false-if-exception (delete-file #$pid-file))
-                 (let ((pid (fork+exec-command
-                             (cons* #$dhclient "-nw"
-                                    "-pf" #$pid-file ifaces))))
-                   (and (zero? (cdr (waitpid pid)))
-                        (read-pid-file #$pid-file)))))
-      (stop #~(make-kill-destructor))))
-   isc-dhcp
-   (description "Run @command{dhcp}, a Dynamic Host Configuration
+  (service-type (name 'dhcp-client)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          dhcp-client-shepherd-service)))
+                (default-value (dhcp-client-configuration))
+                (description "Run @command{dhcp}, a Dynamic Host Configuration
 Protocol (DHCP) client, on all the non-loopback network interfaces.")))
 
 (define-record-type* <dhcpd-configuration>
@@ -326,46 +363,46 @@ Protocol (DHCP) client, on all the non-loopback network interfaces.")))
   (interfaces dhcpd-configuration-interfaces
               (default '())))
 
-(define dhcpd-shepherd-service
-  (match-lambda
-    (($ <dhcpd-configuration> package config-file version run-directory
-                              lease-file pid-file interfaces)
-     (unless config-file
-       (error "Must supply a config-file"))
-     (list (shepherd-service
-            ;; Allow users to easily run multiple versions simultaneously.
-            (provision (list (string->symbol
-                              (string-append "dhcpv" version "-daemon"))))
-            (documentation (string-append "Run the DHCPv" version " daemon"))
-            (requirement '(networking))
-            (start #~(make-forkexec-constructor
-                      '(#$(file-append package "/sbin/dhcpd")
-                        #$(string-append "-" version)
-                        "-lf" #$lease-file
-                        "-pf" #$pid-file
-                        "-cf" #$config-file
-                        #$@interfaces)
-                      #:pid-file #$pid-file))
-            (stop #~(make-kill-destructor)))))))
+(define (dhcpd-shepherd-service config)
+  (match-record config <dhcpd-configuration>
+    (package config-file version run-directory
+             lease-file pid-file interfaces)
+    (unless config-file
+      (error "Must supply a config-file"))
+    (list (shepherd-service
+           ;; Allow users to easily run multiple versions simultaneously.
+           (provision (list (string->symbol
+                             (string-append "dhcpv" version "-daemon"))))
+           (documentation (string-append "Run the DHCPv" version " daemon"))
+           (requirement '(networking))
+           (start #~(make-forkexec-constructor
+                     '(#$(file-append package "/sbin/dhcpd")
+                       #$(string-append "-" version)
+                       "-lf" #$lease-file
+                       "-pf" #$pid-file
+                       "-cf" #$config-file
+                       #$@interfaces)
+                     #:pid-file #$pid-file))
+           (stop #~(make-kill-destructor))))))
 
-(define dhcpd-activation
-  (match-lambda
-    (($ <dhcpd-configuration> package config-file version run-directory
-                              lease-file pid-file interfaces)
-     (with-imported-modules '((guix build utils))
-       #~(begin
-           (unless (file-exists? #$run-directory)
-             (mkdir #$run-directory))
-           ;; According to the DHCP manual (man dhcpd.leases), the lease
-           ;; database must be present for dhcpd to start successfully.
-           (unless (file-exists? #$lease-file)
-             (with-output-to-file #$lease-file
-               (lambda _ (display ""))))
-           ;; Validate the config.
-           (invoke/quiet
-            #$(file-append package "/sbin/dhcpd")
-            #$(string-append "-" version)
-            "-t" "-cf" #$config-file))))))
+(define (dhcpd-activation config)
+  (match-record config <dhcpd-configuration>
+    (package config-file version run-directory
+             lease-file pid-file interfaces)
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (unless (file-exists? #$run-directory)
+            (mkdir #$run-directory))
+          ;; According to the DHCP manual (man dhcpd.leases), the lease
+          ;; database must be present for dhcpd to start successfully.
+          (unless (file-exists? #$lease-file)
+            (with-output-to-file #$lease-file
+              (lambda _ (display ""))))
+          ;; Validate the config.
+          (invoke/quiet
+           #$(file-append package "/sbin/dhcpd")
+           #$(string-append "-" version)
+           "-t" "-cf" #$config-file)))))
 
 (define dhcpd-service-type
   (service-type
@@ -416,16 +453,16 @@ daemon is responsible for allocating IP addresses to its client.")))
            (fold loop res x)
            (cons (format #f "~a" x) res)))))
 
-  (match ntp-server
-    (($ <ntp-server> type address options)
-     ;; XXX: It'd be neater if fields were validated at the syntax level (for
-     ;; static ones at least).  Perhaps the Guix record type could support a
-     ;; predicate property on a field?
-     (unless (enum-set-member? type ntp-server-types)
-       (error "Invalid NTP server type" type))
-     (string-join (cons* (symbol->string type)
-                         address
-                         (flatten options))))))
+  (match-record ntp-server <ntp-server>
+    (type address options)
+    ;; XXX: It'd be neater if fields were validated at the syntax level (for
+    ;; static ones at least).  Perhaps the Guix record type could support a
+    ;; predicate property on a field?
+    (unless (enum-set-member? type ntp-server-types)
+      (error "Invalid NTP server type" type))
+    (string-join (cons* (symbol->string type)
+                        address
+                        (flatten options)))))
 
 (define %ntp-servers
   ;; Default set of NTP servers. These URLs are managed by the NTP Pool project.
@@ -464,17 +501,16 @@ deprecated.  Please use <ntp-server> records instead.\n")
       ((($ <ntp-server>) ($ <ntp-server>) ...)
        ntp-servers))))
 
-(define ntp-shepherd-service
-  (lambda (config)
-    (match config
-      (($ <ntp-configuration> ntp servers allow-large-adjustment?)
-       (let ((servers (ntp-configuration-servers config)))
-         ;; TODO: Add authentication support.
-         (define config
-           (string-append "driftfile /var/run/ntpd/ntp.drift\n"
-                          (string-join (map ntp-server->string servers)
-                                       "\n")
-                          "
+(define (ntp-shepherd-service config)
+  (match-record config <ntp-configuration>
+    (ntp servers allow-large-adjustment?)
+    (let ((servers (ntp-configuration-servers config)))
+      ;; TODO: Add authentication support.
+      (define config
+        (string-append "driftfile /var/run/ntpd/ntp.drift\n"
+                       (string-join (map ntp-server->string servers)
+                                    "\n")
+                       "
 # Disable status queries as a workaround for CVE-2013-5211:
 # <http://support.ntp.org/bin/view/Main/SecurityNotice#DRDoS_Amplification_Attack_using>.
 restrict default kod nomodify notrap nopeer noquery limited
@@ -488,21 +524,21 @@ restrict -6 ::1
 # option by default, as documented in the 'ntp.conf' manual.
 restrict source notrap nomodify noquery\n"))
 
-         (define ntpd.conf
-           (plain-file "ntpd.conf" config))
-
-         (list (shepherd-service
-                (provision '(ntpd))
-                (documentation "Run the Network Time Protocol (NTP) daemon.")
-                (requirement '(user-processes networking))
-                (start #~(make-forkexec-constructor
-                          (list (string-append #$ntp "/bin/ntpd") "-n"
-                                "-c" #$ntpd.conf "-u" "ntpd"
-                                #$@(if allow-large-adjustment?
-                                       '("-g")
-                                       '()))
-                          #:log-file "/var/log/ntpd.log"))
-                (stop #~(make-kill-destructor)))))))))
+      (define ntpd.conf
+        (plain-file "ntpd.conf" config))
+
+      (list (shepherd-service
+             (provision '(ntpd))
+             (documentation "Run the Network Time Protocol (NTP) daemon.")
+             (requirement '(user-processes networking))
+             (start #~(make-forkexec-constructor
+                       (list (string-append #$ntp "/bin/ntpd") "-n"
+                             "-c" #$ntpd.conf "-u" "ntpd"
+                             #$@(if allow-large-adjustment?
+                                    '("-g")
+                                    '()))
+                       #:log-file "/var/log/ntpd.log"))
+             (stop #~(make-kill-destructor)))))))
 
 (define %ntp-accounts
   (list (user-account
@@ -619,7 +655,8 @@ will keep the system clock synchronized with that of the given servers.")
                      ;; while running, leading shepherd to disable it.  To
                      ;; prevent spamming stderr, redirect output to logfile.
                      #:log-file "/var/log/ntpd.log"))
-           (stop #~(make-kill-destructor))))))
+           (stop #~(make-kill-destructor))
+           (actions (list (shepherd-configuration-action ntpd.conf)))))))
 
 (define (openntpd-service-activation config)
   "Return the activation gexp for CONFIG."
@@ -708,19 +745,19 @@ daemon will keep the system clock synchronized with that of the given servers.")
                   " ") "\n")))
           entries)))
 
-(define inetd-shepherd-service
-  (match-lambda
-    (($ <inetd-configuration> program ()) '()) ; empty list of entries -> do nothing
-    (($ <inetd-configuration> program entries)
-     (list
-      (shepherd-service
-       (documentation "Run inetd.")
-       (provision '(inetd))
-       (requirement '(user-processes networking syslogd))
-       (start #~(make-forkexec-constructor
-                 (list #$program #$(inetd-config-file entries))
-                 #:pid-file "/var/run/inetd.pid"))
-       (stop #~(make-kill-destructor)))))))
+(define (inetd-shepherd-service config)
+  (let ((entries (inetd-configuration-entries config)))
+    (if (null? entries)
+        '()                                       ;do nothing
+        (let ((program (inetd-configuration-program config)))
+          (list (shepherd-service
+                 (documentation "Run inetd.")
+                 (provision '(inetd))
+                 (requirement '(user-processes networking syslogd))
+                 (start #~(make-forkexec-constructor
+                           (list #$program #$(inetd-config-file entries))
+                           #:pid-file "/var/run/inetd.pid"))
+                 (stop #~(make-kill-destructor))))))))
 
 (define-public inetd-service-type
   (service-type
@@ -904,102 +941,94 @@ applications in communication.  It is used by Jami, for example.")))
 
 (define (tor-configuration->torrc config)
   "Return a 'torrc' file for CONFIG."
-  (match config
-    (($ <tor-configuration> tor config-file services
-                            socks-socket-type control-socket?)
-     (computed-file
-      "torrc"
-      (with-imported-modules '((guix build utils))
-        #~(begin
-            (use-modules (guix build utils)
-                         (ice-9 match))
-
-            (call-with-output-file #$output
-              (lambda (port)
-                (display "\
+  (match-record config <tor-configuration>
+    (tor config-file hidden-services socks-socket-type control-socket?)
+    (computed-file
+     "torrc"
+     (with-imported-modules '((guix build utils))
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 match))
+
+           (call-with-output-file #$output
+             (lambda (port)
+               (display "\
 ### These lines were generated from your system configuration:
 DataDirectory /var/lib/tor
 Log notice syslog\n" port)
-                (when (eq? 'unix '#$socks-socket-type)
-                  (display "\
+               (when (eq? 'unix '#$socks-socket-type)
+                 (display "\
 SocksPort unix:/var/run/tor/socks-sock
 UnixSocksGroupWritable 1\n" port))
-                (when #$control-socket?
-                  (display "\
+               (when #$control-socket?
+                 (display "\
 ControlSocket unix:/var/run/tor/control-sock GroupWritable RelaxDirModeCheck
 ControlSocketsGroupWritable 1\n"
-                           port))
+                          port))
 
-                (for-each (match-lambda
-                            ((service (ports hosts) ...)
-                             (format port "\
+               (for-each (match-lambda
+                           ((service (ports hosts) ...)
+                            (format port "\
 HiddenServiceDir /var/lib/tor/hidden-services/~a~%"
-                                     service)
-                             (for-each (lambda (tcp-port host)
-                                         (format port "\
+                                    service)
+                            (for-each (lambda (tcp-port host)
+                                        (format port "\
 HiddenServicePort ~a ~a~%"
-                                                 tcp-port host))
-                                       ports hosts)))
-                          '#$(map (match-lambda
-                                    (($ <hidden-service> name mapping)
-                                     (cons name mapping)))
-                                  services))
-
-                (display "\
+                                                tcp-port host))
+                                      ports hosts)))
+                         '#$(map (match-lambda
+                                   (($ <hidden-service> name mapping)
+                                    (cons name mapping)))
+                                 hidden-services))
+
+               (display "\
 ### End of automatically generated lines.\n\n" port)
 
-                ;; Append the user's config file.
-                (call-with-input-file #$config-file
-                  (lambda (input)
-                    (dump-port input port)))
-                #t))))))))
+               ;; Append the user's config file.
+               (call-with-input-file #$config-file
+                 (lambda (input)
+                   (dump-port input port)))
+               #t)))))))
 
 (define (tor-shepherd-service config)
   "Return a <shepherd-service> running Tor."
-  (match config
-    (($ <tor-configuration> tor)
-     (let* ((torrc (tor-configuration->torrc config))
-            (tor   (least-authority-wrapper
-                    (file-append tor "/bin/tor")
-                    #:name "tor"
-                    #:mappings (list (file-system-mapping
-                                      (source "/var/lib/tor")
-                                      (target source)
-                                      (writable? #t))
-                                     (file-system-mapping
-                                      (source "/dev/log") ;for syslog
-                                      (target source))
-                                     (file-system-mapping
-                                      (source "/var/run/tor")
-                                      (target source)
-                                      (writable? #t))
-                                     (file-system-mapping
-                                      (source torrc)
-                                      (target source)))
-                    #:namespaces (delq 'net %namespaces))))
-       (with-imported-modules (source-module-closure
-                               '((gnu build shepherd)
-                                 (gnu system file-systems)))
-         (list (shepherd-service
-                (provision '(tor))
-
-                ;; Tor needs at least one network interface to be up, hence the
-                ;; dependency on 'loopback'.
-                (requirement '(user-processes loopback syslogd))
-
-                (modules '((gnu build shepherd)
-                           (gnu system file-systems)))
-
-                ;; XXX: #:pid-file won't work because the wrapped 'tor'
-                ;; program would print its PID within the user namespace
-                ;; instead of its actual PID outside.  There's no inetd or
-                ;; systemd socket activation support either (there's
-                ;; 'sd_notify' though), so we're stuck with that.
-                (start #~(make-forkexec-constructor
-                          (list #$tor "-f" #$torrc)
-                          #:user "tor" #:group "tor"))
-                (stop #~(make-kill-destructor))
-                (documentation "Run the Tor anonymous network overlay."))))))))
+  (let* ((torrc (tor-configuration->torrc config))
+         (tor   (least-authority-wrapper
+                 (file-append (tor-configuration-tor config) "/bin/tor")
+                 #:name "tor"
+                 #:mappings (list (file-system-mapping
+                                   (source "/var/lib/tor")
+                                   (target source)
+                                   (writable? #t))
+                                  (file-system-mapping
+                                   (source "/dev/log") ;for syslog
+                                   (target source))
+                                  (file-system-mapping
+                                   (source "/var/run/tor")
+                                   (target source)
+                                   (writable? #t))
+                                  (file-system-mapping
+                                   (source torrc)
+                                   (target source)))
+                 #:namespaces (delq 'net %namespaces))))
+    (list (shepherd-service
+           (provision '(tor))
+
+           ;; Tor needs at least one network interface to be up, hence the
+           ;; dependency on 'loopback'.
+           (requirement '(user-processes loopback syslogd))
+
+           ;; XXX: #:pid-file won't work because the wrapped 'tor'
+           ;; program would print its PID within the user namespace
+           ;; instead of its actual PID outside.  There's no inetd or
+           ;; systemd socket activation support either (there's
+           ;; 'sd_notify' though), so we're stuck with that.
+           (start #~(make-forkexec-constructor
+                     (list #$tor "-f" #$torrc)
+                     #:user "tor" #:group "tor"))
+           (stop #~(make-kill-destructor))
+           (actions (list (shepherd-configuration-action torrc)))
+           (documentation "Run the Tor anonymous network overlay.")))))
 
 (define (tor-activation config)
   "Set up directories for Tor and its hidden services, if any."
@@ -1114,19 +1143,20 @@ project's documentation} for more information."
   (dns network-manager-configuration-dns
        (default "default"))
   (vpn-plugins network-manager-configuration-vpn-plugins ;list of file-like
-               (default '())))
+               (default '()))
+  (iwd? network-manager-configuration-iwd? (default #f)))
 
-(define network-manager-activation
+(define (network-manager-activation config)
   ;; Activation gexp for NetworkManager
-  (match-lambda
-    (($ <network-manager-configuration> network-manager dns vpn-plugins)
-     #~(begin
-         (use-modules (guix build utils))
-         (mkdir-p "/etc/NetworkManager/system-connections")
-         #$@(if (equal? dns "dnsmasq")
-                ;; create directory to store dnsmasq lease file
-                '((mkdir-p "/var/lib/misc"))
-                '())))))
+  (match-record config <network-manager-configuration>
+    (network-manager dns vpn-plugins)
+    #~(begin
+        (use-modules (guix build utils))
+        (mkdir-p "/etc/NetworkManager/system-connections")
+        #$@(if (equal? dns "dnsmasq")
+               ;; create directory to store dnsmasq lease file
+               '((mkdir-p "/var/lib/misc"))
+               '()))))
 
 (define (vpn-plugin-directory plugins)
   "Return a directory containing PLUGINS, the NM VPN plugins."
@@ -1159,44 +1189,47 @@ project's documentation} for more information."
      (cons (user-group (name "network-manager") (system? #t))
            accounts))))
 
-(define network-manager-environment
-  (match-lambda
-    (($ <network-manager-configuration> network-manager dns vpn-plugins)
-     ;; Define this variable in the global environment such that
-     ;; "nmcli connection import type openvpn file foo.ovpn" works.
-     `(("NM_VPN_PLUGIN_DIR"
-        . ,(file-append (vpn-plugin-directory vpn-plugins)
-                        "/lib/NetworkManager/VPN"))))))
-
-(define network-manager-shepherd-service
-  (match-lambda
-    (($ <network-manager-configuration> network-manager dns vpn-plugins)
-     (let ((conf (plain-file "NetworkManager.conf"
-                             (string-append "[main]\ndns=" dns "\n")))
-           (vpn  (vpn-plugin-directory vpn-plugins)))
-       (list (shepherd-service
-              (documentation "Run the NetworkManager.")
-              (provision '(networking))
-              (requirement '(user-processes dbus-system wpa-supplicant loopback))
-              (start #~(make-forkexec-constructor
-                        (list (string-append #$network-manager
-                                             "/sbin/NetworkManager")
-                              (string-append "--config=" #$conf)
-                              "--no-daemon")
-                        #:environment-variables
-                        (list (string-append "NM_VPN_PLUGIN_DIR=" #$vpn
-                                             "/lib/NetworkManager/VPN")
-                              ;; Override non-existent default users
-                              "NM_OPENVPN_USER="
-                              "NM_OPENVPN_GROUP=")))
-              (stop #~(make-kill-destructor))))))))
+(define (network-manager-environment config)
+  (match-record config <network-manager-configuration>
+    (network-manager dns vpn-plugins)
+    ;; Define this variable in the global environment such that
+    ;; "nmcli connection import type openvpn file foo.ovpn" works.
+    `(("NM_VPN_PLUGIN_DIR"
+       . ,(file-append (vpn-plugin-directory vpn-plugins)
+                       "/lib/NetworkManager/VPN")))))
+
+(define (network-manager-shepherd-service config)
+  (match-record config <network-manager-configuration>
+    (network-manager dns vpn-plugins iwd?)
+    (let ((conf (plain-file "NetworkManager.conf"
+                            (string-append
+                             "[main]\ndns=" dns "\n"
+                             (if iwd? "[device]\nwifi.backend=iwd\n" ""))))
+          (vpn  (vpn-plugin-directory vpn-plugins)))
+      (list (shepherd-service
+             (documentation "Run the NetworkManager.")
+             (provision '(networking))
+             (requirement (append '(user-processes dbus-system loopback)
+                                  (if iwd? '(iwd) '(wpa-supplicant))))
+             (start #~(make-forkexec-constructor
+                       (list (string-append #$network-manager
+                                            "/sbin/NetworkManager")
+                             (string-append "--config=" #$conf)
+                             "--no-daemon")
+                       #:environment-variables
+                       (list (string-append "NM_VPN_PLUGIN_DIR=" #$vpn
+                                            "/lib/NetworkManager/VPN")
+                             ;; Override non-existent default users
+                             "NM_OPENVPN_USER="
+                             "NM_OPENVPN_GROUP=")))
+             (stop #~(make-kill-destructor)))))))
 
 (define network-manager-service-type
-  (let
-      ((config->packages
-        (match-lambda
-         (($ <network-manager-configuration> network-manager _ vpn-plugins)
-          `(,network-manager ,@vpn-plugins)))))
+  (let ((config->packages
+         (lambda (config)
+          (match-record config <network-manager-configuration>
+            (network-manager vpn-plugins)
+            `(,network-manager ,@vpn-plugins)))))
 
     (service-type
      (name 'network-manager)
@@ -1233,6 +1266,8 @@ wireless networking."))))
   (connman      connman-configuration-connman
                 (default connman))
   (disable-vpn? connman-configuration-disable-vpn?
+                (default #f))
+  (iwd?         connman-configuration-iwd?
                 (default #f)))
 
 (define (connman-activation config)
@@ -1249,18 +1284,21 @@ wireless networking."))))
   (and
    (connman-configuration? config)
    (let ((connman      (connman-configuration-connman config))
-         (disable-vpn? (connman-configuration-disable-vpn? config)))
+         (disable-vpn? (connman-configuration-disable-vpn? config))
+         (iwd?         (connman-configuration-iwd? config)))
      (list (shepherd-service
             (documentation "Run Connman")
             (provision '(networking))
             (requirement
-             '(user-processes dbus-system loopback wpa-supplicant))
+             (append '(user-processes dbus-system loopback)
+                     (if iwd? '(iwd) '())))
             (start #~(make-forkexec-constructor
                       (list (string-append #$connman
                                            "/sbin/connmand")
                             "--nodaemon"
                             "--nodnsproxy"
-                            #$@(if disable-vpn? '("--noplugin=vpn") '()))
+                            #$@(if disable-vpn? '("--noplugin=vpn") '())
+                            #$@(if iwd? '("--wifi=iwd_agent") '()))
 
                       ;; As connman(8) notes, when passing '-n', connman
                       ;; "directs log output to the controlling terminal in
@@ -1303,9 +1341,8 @@ a network connection manager."))))
 
 (define modem-manager-service-type
   (let ((config->package
-         (match-lambda
-          (($ <modem-manager-configuration> modem-manager)
-           (list modem-manager)))))
+         (lambda (config)
+           (list (modem-manager-configuration-modem-manager config)))))
     (service-type (name 'modem-manager)
                   (extensions
                    (list (service-extension dbus-root-service-type
@@ -1376,24 +1413,25 @@ device is detected."
 usb-modeswitch package specified in CONFIG.  The rules file will invoke
 usb_modeswitch.sh from the usb-modeswitch package, modified to pass the right
 config file."
-  (match config
-    (($ <usb-modeswitch-configuration> usb-modeswitch data config-file)
-     (computed-file
-      "usb_modeswitch.rules"
-      (with-imported-modules '((guix build utils))
-        #~(begin
-            (use-modules (guix build utils))
-            (let ((in (string-append #$data "/udev/40-usb_modeswitch.rules"))
-                  (out (string-append #$output "/lib/udev/rules.d"))
-                  (script #$(usb-modeswitch-sh usb-modeswitch config-file)))
-              (mkdir-p out)
-              (chdir out)
-              (install-file in out)
-              (substitute* "40-usb_modeswitch.rules"
-                (("PROGRAM=\"usb_modeswitch")
-                 (string-append "PROGRAM=\"" script "/usb_modeswitch"))
-                (("RUN\\+=\"usb_modeswitch")
-                 (string-append "RUN+=\"" script "/usb_modeswitch"))))))))))
+  (match-record config <usb-modeswitch-configuration>
+    (usb-modeswitch usb-modeswitch-data config-file)
+    (computed-file
+     "usb_modeswitch.rules"
+     (with-imported-modules '((guix build utils))
+       #~(begin
+           (use-modules (guix build utils))
+           (let ((in (string-append #$usb-modeswitch-data
+                                    "/udev/40-usb_modeswitch.rules"))
+                 (out (string-append #$output "/lib/udev/rules.d"))
+                 (script #$(usb-modeswitch-sh usb-modeswitch config-file)))
+             (mkdir-p out)
+             (chdir out)
+             (install-file in out)
+             (substitute* "40-usb_modeswitch.rules"
+               (("PROGRAM=\"usb_modeswitch")
+                (string-append "PROGRAM=\"" script "/usb_modeswitch"))
+               (("RUN\\+=\"usb_modeswitch")
+                (string-append "RUN+=\"" script "/usb_modeswitch")))))))))
 
 (define usb-modeswitch-service-type
   (service-type
@@ -1437,40 +1475,39 @@ whatever the thing is supposed to do).")))
   (extra-options      wpa-supplicant-configuration-extra-options  ;list of strings
                       (default '())))
 
-(define wpa-supplicant-shepherd-service
-  (match-lambda
-    (($ <wpa-supplicant-configuration> wpa-supplicant requirement pid-file dbus?
-                                       interface config-file extra-options)
-     (list (shepherd-service
-            (documentation "Run the WPA supplicant daemon")
-            (provision '(wpa-supplicant))
-            (requirement (if dbus?
-                             (cons 'dbus-system requirement)
-                             requirement))
-            (start #~(make-forkexec-constructor
-                      (list (string-append #$wpa-supplicant
-                                           "/sbin/wpa_supplicant")
-                            (string-append "-P" #$pid-file)
-                            "-B"        ;run in background
-                            "-s"        ;log to syslogd
-                            #$@(if dbus?
-                                   #~("-u")
-                                   #~())
-                            #$@(if interface
-                                   #~((string-append "-i" #$interface))
-                                   #~())
-                            #$@(if config-file
-                                   #~((string-append "-c" #$config-file))
-                                   #~())
-                            #$@extra-options)
-                      #:pid-file #$pid-file))
-            (stop #~(make-kill-destructor)))))))
+(define (wpa-supplicant-shepherd-service config)
+  (match-record config <wpa-supplicant-configuration>
+    (wpa-supplicant requirement pid-file dbus?
+                    interface config-file extra-options)
+    (list (shepherd-service
+           (documentation "Run the WPA supplicant daemon")
+           (provision '(wpa-supplicant))
+           (requirement (if dbus?
+                            (cons 'dbus-system requirement)
+                            requirement))
+           (start #~(make-forkexec-constructor
+                     (list (string-append #$wpa-supplicant
+                                          "/sbin/wpa_supplicant")
+                           (string-append "-P" #$pid-file)
+                           "-B"                   ;run in background
+                           "-s"                   ;log to syslogd
+                           #$@(if dbus?
+                                  #~("-u")
+                                  #~())
+                           #$@(if interface
+                                  #~((string-append "-i" #$interface))
+                                  #~())
+                           #$@(if config-file
+                                  #~((string-append "-c" #$config-file))
+                                  #~())
+                           #$@extra-options)
+                     #:pid-file #$pid-file))
+           (stop #~(make-kill-destructor))))))
 
 (define wpa-supplicant-service-type
   (let ((config->package
-         (match-lambda
-           (($ <wpa-supplicant-configuration> wpa-supplicant)
-            (list wpa-supplicant)))))
+         (lambda (config)
+           (list (wpa-supplicant-configuration-wpa-supplicant config)))))
     (service-type (name 'wpa-supplicant)
                   (extensions
                    (list (service-extension shepherd-root-service-type
@@ -1592,41 +1629,38 @@ simulation."
   (package openvswitch-configuration-package
            (default openvswitch)))
 
-(define openvswitch-activation
-  (match-lambda
-    (($ <openvswitch-configuration> package)
-     (let ((ovsdb-tool (file-append package "/bin/ovsdb-tool")))
-       (with-imported-modules '((guix build utils))
-         #~(begin
-             (use-modules (guix build utils))
-             (mkdir-p "/var/run/openvswitch")
-             (mkdir-p "/var/lib/openvswitch")
-             (let ((conf.db "/var/lib/openvswitch/conf.db"))
-               (unless (file-exists? conf.db)
-                 (system* #$ovsdb-tool "create" conf.db)))))))))
-
-(define openvswitch-shepherd-service
-  (match-lambda
-    (($ <openvswitch-configuration> package)
-     (let ((ovsdb-server (file-append package "/sbin/ovsdb-server"))
-           (ovs-vswitchd (file-append package "/sbin/ovs-vswitchd")))
-       (list
-        (shepherd-service
-         (provision '(ovsdb))
-         (documentation "Run the Open vSwitch database server.")
-         (start #~(make-forkexec-constructor
-                   (list #$ovsdb-server "--pidfile"
-                         "--remote=punix:/var/run/openvswitch/db.sock")
-                   #:pid-file "/var/run/openvswitch/ovsdb-server.pid"))
-         (stop #~(make-kill-destructor)))
-        (shepherd-service
-         (provision '(vswitchd))
-         (requirement '(ovsdb))
-         (documentation "Run the Open vSwitch daemon.")
-         (start #~(make-forkexec-constructor
-                   (list #$ovs-vswitchd "--pidfile")
-                   #:pid-file "/var/run/openvswitch/ovs-vswitchd.pid"))
-         (stop #~(make-kill-destructor))))))))
+(define (openvswitch-activation config)
+  (let ((ovsdb-tool (file-append (openvswitch-configuration-package config)
+                                 "/bin/ovsdb-tool")))
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils))
+          (mkdir-p "/var/run/openvswitch")
+          (mkdir-p "/var/lib/openvswitch")
+          (let ((conf.db "/var/lib/openvswitch/conf.db"))
+            (unless (file-exists? conf.db)
+              (system* #$ovsdb-tool "create" conf.db)))))))
+
+(define (openvswitch-shepherd-service config)
+  (let* ((package      (openvswitch-configuration-package config))
+         (ovsdb-server (file-append package "/sbin/ovsdb-server"))
+         (ovs-vswitchd (file-append package "/sbin/ovs-vswitchd")))
+    (list (shepherd-service
+           (provision '(ovsdb))
+           (documentation "Run the Open vSwitch database server.")
+           (start #~(make-forkexec-constructor
+                     (list #$ovsdb-server "--pidfile"
+                           "--remote=punix:/var/run/openvswitch/db.sock")
+                     #:pid-file "/var/run/openvswitch/ovsdb-server.pid"))
+           (stop #~(make-kill-destructor)))
+          (shepherd-service
+           (provision '(vswitchd))
+           (requirement '(ovsdb))
+           (documentation "Run the Open vSwitch daemon.")
+           (start #~(make-forkexec-constructor
+                     (list #$ovs-vswitchd "--pidfile")
+                     #:pid-file "/var/run/openvswitch/ovs-vswitchd.pid"))
+           (stop #~(make-kill-destructor))))))
 
 (define openvswitch-service-type
   (service-type
@@ -1666,20 +1700,20 @@ COMMIT
   (ipv6-rules iptables-configuration-ipv6-rules
               (default %iptables-accept-all-rules)))
 
-(define iptables-shepherd-service
-  (match-lambda
-    (($ <iptables-configuration> iptables ipv4-rules ipv6-rules)
-     (let ((iptables-restore (file-append iptables "/sbin/iptables-restore"))
-           (ip6tables-restore (file-append iptables "/sbin/ip6tables-restore")))
-       (shepherd-service
-        (documentation "Packet filtering framework")
-        (provision '(iptables))
-        (start #~(lambda _
-                   (invoke #$iptables-restore #$ipv4-rules)
-                   (invoke #$ip6tables-restore #$ipv6-rules)))
-        (stop #~(lambda _
-                  (invoke #$iptables-restore #$%iptables-accept-all-rules)
-                  (invoke #$ip6tables-restore #$%iptables-accept-all-rules))))))))
+(define (iptables-shepherd-service config)
+  (match-record config <iptables-configuration>
+    (iptables ipv4-rules ipv6-rules)
+    (let ((iptables-restore (file-append iptables "/sbin/iptables-restore"))
+          (ip6tables-restore (file-append iptables "/sbin/ip6tables-restore")))
+      (shepherd-service
+       (documentation "Packet filtering framework")
+       (provision '(iptables))
+       (start #~(lambda _
+                  (invoke #$iptables-restore #$ipv4-rules)
+                  (invoke #$ip6tables-restore #$ipv6-rules)))
+       (stop #~(lambda _
+                 (invoke #$iptables-restore #$%iptables-accept-all-rules)
+                 (invoke #$ip6tables-restore #$%iptables-accept-all-rules)))))))
 
 (define iptables-service-type
   (service-type
@@ -1738,17 +1772,17 @@ table inet filter {
   (ruleset nftables-configuration-ruleset ; file-like object
            (default %default-nftables-ruleset)))
 
-(define nftables-shepherd-service
-  (match-lambda
-    (($ <nftables-configuration> package ruleset)
-     (let ((nft (file-append package "/sbin/nft")))
-       (shepherd-service
-        (documentation "Packet filtering and classification")
-        (provision '(nftables))
-        (start #~(lambda _
-                   (invoke #$nft "--file" #$ruleset)))
-        (stop #~(lambda _
-                  (invoke #$nft "flush" "ruleset"))))))))
+(define (nftables-shepherd-service config)
+  (match-record config <nftables-configuration>
+    (package ruleset)
+    (let ((nft (file-append package "/sbin/nft")))
+      (shepherd-service
+       (documentation "Packet filtering and classification")
+       (provision '(nftables))
+       (start #~(lambda _
+                  (invoke #$nft "--file" #$ruleset)))
+       (stop #~(lambda _
+                 (invoke #$nft "flush" "ruleset")))))))
 
 (define nftables-service-type
   (service-type
@@ -2121,23 +2155,22 @@ of the IPFS peer-to-peer storage network.")))
   (config-file keepalived-configuration-config-file ;file-like
                (default #f)))
 
-(define keepalived-shepherd-service
-  (match-lambda
-    (($ <keepalived-configuration> keepalived config-file)
-     (list
-      (shepherd-service
-       (provision '(keepalived))
-       (documentation "Run keepalived.")
-       (requirement '(loopback))
-       (start #~(make-forkexec-constructor
-                 (list (string-append #$keepalived "/sbin/keepalived")
-                       "--dont-fork" "--log-console" "--log-detail"
-                       "--pid=/var/run/keepalived.pid"
-                       (string-append "--use-file=" #$config-file))
-                 #:pid-file "/var/run/keepalived.pid"
-                 #:log-file "/var/log/keepalived.log"))
-       (respawn? #f)
-       (stop #~(make-kill-destructor)))))))
+(define (keepalived-shepherd-service config)
+  (match-record config <keepalived-configuration>
+    (keepalived config-file)
+    (list (shepherd-service
+           (provision '(keepalived))
+           (documentation "Run keepalived.")
+           (requirement '(loopback))
+           (start #~(make-forkexec-constructor
+                     (list (string-append #$keepalived "/sbin/keepalived")
+                           "--dont-fork" "--log-console" "--log-detail"
+                           "--pid=/var/run/keepalived.pid"
+                           (string-append "--use-file=" #$config-file))
+                     #:pid-file "/var/run/keepalived.pid"
+                     #:log-file "/var/log/keepalived.log"))
+           (respawn? #f)
+           (stop #~(make-kill-destructor))))))
 
 (define %keepalived-log-rotation
   (list (log-rotation
diff --git a/gnu/services/nix.scm b/gnu/services/nix.scm
index df04a85c22..82853253f6 100644
--- a/gnu/services/nix.scm
+++ b/gnu/services/nix.scm
@@ -54,6 +54,8 @@
                        (default nix))
   (sandbox             nix-configuration-sandbox ;boolean
                        (default #t))
+  (build-directory     nix-configuration-build-directory ;string
+                       (default "/tmp"))
   (build-sandbox-items nix-configuration-build-sandbox-items ;list of strings
                        (default '()))
   (extra-config        nix-configuration-extra-config ;list of strings
@@ -106,7 +108,7 @@ GID."
 
 (define nix-service-etc
   (match-lambda
-    (($ <nix-configuration> package sandbox build-sandbox-items extra-config)
+    (($ <nix-configuration> package sandbox build-directory build-sandbox-items extra-config)
      (let ((ref-file (references-file package)))
        `(("nix/nix.conf"
           ,(computed-file
@@ -130,7 +132,7 @@ GID."
 (define nix-shepherd-service
   ;; Return a <shepherd-service> for Nix.
   (match-lambda
-    (($ <nix-configuration> package _ _ _ extra-options)
+    (($ <nix-configuration> package _ build-directory _ _ extra-options)
      (list
       (shepherd-service
        (provision '(nix-daemon))
@@ -138,7 +140,10 @@ GID."
        (requirement '())
        (start #~(make-forkexec-constructor
                  (list (string-append #$package "/bin/nix-daemon")
-                       #$@extra-options)))
+                       #$@extra-options)
+                 #:environment-variables
+                 (list (string-append "TMPDIR=" #$build-directory)
+                       "PATH=/run/current-system/profile/bin")))
        (respawn? #f)
        (stop #~(make-kill-destructor)))))))
 
diff --git a/gnu/services/samba.scm b/gnu/services/samba.scm
index 4e930d61dc..dfc7778570 100644
--- a/gnu/services/samba.scm
+++ b/gnu/services/samba.scm
@@ -17,14 +17,12 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services samba)
-
   #:use-module (gnu packages)
   #:use-module (gnu packages base)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages samba)
 
   #:use-module (gnu services)
-  #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
   #:use-module (gnu services base)
   #:use-module (gnu system shadow)
@@ -41,20 +39,10 @@
 
   #:export (samba-service-type
             samba-configuration
-            samba-smb-conf
 
             wsdd-service-type
             wsdd-configuration))
 
-(define %smb-conf
-  (plain-file "smb.conf" "[global]
-    workgroup = WORKGROUP
-    server string = Samba Server
-    server role = standalone server
-    log file = /var/log/samba/log.%m
-    logging = file
-"))
-
 (define-record-type* <samba-configuration>
   samba-configuration
   make-samba-configuration
diff --git a/gnu/services/security.scm b/gnu/services/security.scm
index 15fae7a628..8116072920 100644
--- a/gnu/services/security.scm
+++ b/gnu/services/security.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2022 muradm <mail@muradm.net>
+;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -41,11 +42,11 @@
   (max-count integer "Cache size.")
   (max-time integer "Cache time."))
 
-(define serialize-fail2ban-ignore-cache-configuration
-  (match-lambda
-    (($ <fail2ban-ignore-cache-configuration> _ key max-count max-time)
-     (format #f "key=\"~a\", max-count=~d, max-time=~d"
-             key max-count max-time))))
+(define (serialize-fail2ban-ignore-cache-configuration config)
+  (match-record config <fail2ban-ignore-cache-configuration>
+    (key max-count max-time)
+    (format #f "key=\"~a\", max-count=~d, max-time=~d"
+            key max-count max-time)))
 
 (define-maybe/no-serialization string)
 
@@ -53,10 +54,10 @@
   (name string "Filter to use.")
   (mode maybe-string "Mode for filter."))
 
-(define serialize-fail2ban-jail-filter-configuration
-  (match-lambda
-    (($ <fail2ban-jail-filter-configuration> _ name mode)
-     (format #f "~a~@[[mode=~a]~]" name (maybe-value mode)))))
+(define (serialize-fail2ban-jail-filter-configuration config)
+  (match-record config <fail2ban-jail-filter-configuration>
+    (name mode)
+    (format #f "~a~@[[mode=~a]~]" name (maybe-value mode))))
 
 (define (argument? a)
   (and (pair? a)
@@ -85,17 +86,17 @@
             (format #f "~a=~a" (car e) (any-value (cdr e))))))
     (format #f "~a" (string-join (map key-value args) ","))))
 
-(define serialize-fail2ban-jail-action-configuration
-  (match-lambda
-    (($ <fail2ban-jail-action-configuration> _ name arguments)
-     (format
-      #f "~a~a"
-      name
-      (if (null? arguments) ""
-          (format
-           #f "[~a]"
-           (serialize-fail2ban-jail-action-configuration-arguments
-            arguments)))))))
+(define (serialize-fail2ban-jail-action-configuration config)
+  (match-record config <fail2ban-jail-action-configuration>
+    (name arguments)
+    (format
+     #f "~a~a"
+     name
+     (if (null? arguments) ""
+         (format
+          #f "[~a]"
+          (serialize-fail2ban-jail-action-configuration-arguments
+           arguments))))))
 
 (define fail2ban-backend->string
   (match-lambda
@@ -351,28 +352,27 @@ provided as a list of file-like objects."))
   (match-record config <fail2ban-configuration>
     (fail2ban run-directory)
     (let* ((fail2ban-server (file-append fail2ban "/bin/fail2ban-server"))
+           (fail2ban-client (file-append fail2ban "/bin/fail2ban-client"))
            (pid-file (in-vicinity run-directory "fail2ban.pid"))
            (socket-file (in-vicinity run-directory "fail2ban.sock"))
            (config-dir (file-append (config->fail2ban-etc-directory config)
                                     "/etc/fail2ban"))
            (fail2ban-action (lambda args
-                              #~(lambda _
-                                  (invoke #$fail2ban-server
-                                          "-c" #$config-dir
-                                          "-p" #$pid-file
-                                          "-s" #$socket-file
-                                          "-b"
-                                          #$@args)))))
-
-      ;; TODO: Add 'reload' action.
+                              #~(invoke #$fail2ban-client #$@args))))
+
+      ;; TODO: Add 'reload' action (see 'fail2ban.service.in' in the source).
       (list (shepherd-service
              (provision '(fail2ban))
              (documentation "Run the fail2ban daemon.")
              (requirement '(user-processes))
-             (modules `((ice-9 match)
-                        ,@%default-modules))
-             (start (fail2ban-action "start"))
-             (stop (fail2ban-action "stop")))))))
+             (start #~(make-forkexec-constructor
+                       (list #$fail2ban-server
+                             "-c" #$config-dir "-s" #$socket-file
+                             "-p" #$pid-file "-xf" "start")
+                       #:pid-file #$pid-file))
+             (stop #~(lambda (_)
+                       #$(fail2ban-action "stop")
+                       #f)))))))                  ;successfully stopped
 
 (define fail2ban-service-type
   (service-type (name 'fail2ban)
diff --git a/gnu/services/shepherd.scm b/gnu/services/shepherd.scm
index 61f759a19d..b2601c0128 100644
--- a/gnu/services/shepherd.scm
+++ b/gnu/services/shepherd.scm
@@ -66,6 +66,8 @@
             shepherd-action-documentation
             shepherd-action-procedure
 
+            shepherd-configuration-action
+
             %default-modules
 
             shepherd-service-file
@@ -107,14 +109,15 @@
       (symlink (canonicalize-path "/run/current-system")
                "/run/booted-system")
 
-      ;; Close any remaining open file descriptors to be on the safe
-      ;; side.  This must be the very last thing we do, because
-      ;; Guile has internal FDs such as 'sleep_pipe' that need to be
-      ;; alive.
+      ;; Ensure open file descriptors are close-on-exec so shepherd doesn't
+      ;; inherit them.
       (let loop ((fd 3))
         (when (< fd 1024)
-          (false-if-exception (close-fdes fd))
-          (loop (+ 1 fd))))
+          (false-if-exception
+           (let ((flags (fcntl fd F_GETFD)))
+             (when (zero? (logand flags FD_CLOEXEC))
+               (fcntl fd F_SETFD (logior FD_CLOEXEC flags)))))
+          (loop (+ fd 1))))
 
       ;; Start shepherd.
       (execl #$(file-append shepherd "/bin/shepherd")
@@ -332,6 +335,16 @@ and return the resulting '.go' file. SHEPHERD is used as shepherd package."
                      #:options '(#:local-build? #t
                                  #:substitutable? #f)))))
 
+(define (shepherd-configuration-action file)
+  "Return a 'configuration' action to display FILE, which should be the name
+of the service's configuration file."
+  (shepherd-action
+   (name 'configuration)
+   (documentation "Display the name of this service's configuration file.")
+   (procedure #~(lambda (_)
+                  (format #t "~a~%" #$file)
+                  #$file))))
+
 (define (shepherd-configuration-file services shepherd)
   "Return the shepherd configuration file for SERVICES.  SHEPHERD is used
 as shepherd package."
diff --git a/gnu/services/sound.scm b/gnu/services/sound.scm
index 8410ba2418..3e778f3cea 100644
--- a/gnu/services/sound.scm
+++ b/gnu/services/sound.scm
@@ -204,17 +204,13 @@ computed-file object~%") file))))
                   '()
                   `(("default.pa.d" ,(extra-script-files->file-union
                                       extra-script-files))))
-            ,@(if (null? daemon-conf)
-                  '()
-                  `(("daemon.conf"
-                     ,(apply mixed-text-file "daemon.conf"
-                             "default-script-file = " default-script-file "\n"
-                             (map pulseaudio-conf-entry daemon-conf)))))
-            ,@(if (null? client-conf)
-                  '()
-                  `(("client.conf"
-                     ,(apply mixed-text-file "client.conf"
-                             (map pulseaudio-conf-entry client-conf))))))))))))
+            ("daemon.conf"
+             ,(apply mixed-text-file "daemon.conf"
+                     "default-script-file = /etc/pulse/default.pa\n"
+                     (map pulseaudio-conf-entry daemon-conf)))
+            ("client.conf"
+             ,(apply mixed-text-file "client.conf"
+                     (map pulseaudio-conf-entry client-conf))))))))))
 
 (define pulseaudio-service-type
   (service-type
diff --git a/gnu/services/ssh.scm b/gnu/services/ssh.scm
index 72e7183590..7b038e6ac6 100644
--- a/gnu/services/ssh.scm
+++ b/gnu/services/ssh.scm
@@ -524,9 +524,12 @@ of user-name/file-like tuples."
   (define max-connections
     (openssh-configuration-max-connections config))
 
+  (define config-file
+    (openssh-config-file config))
+
   (define openssh-command
     #~(list (string-append #$(openssh-configuration-openssh config) "/sbin/sshd")
-            "-D" "-f" #$(openssh-config-file config)))
+            "-D" "-f" #$config-file))
 
   (define inetd-style?
     ;; Whether to use 'make-inetd-constructor'.  That procedure appeared in
@@ -568,6 +571,7 @@ of user-name/file-like tuples."
          (stop #~(if #$inetd-style?
                      (make-inetd-destructor)
                      (make-kill-destructor)))
+         (actions (list (shepherd-configuration-action config-file)))
          (auto-start? (openssh-auto-start? config)))))
 
 (define (openssh-pam-services config)
diff --git a/gnu/services/version-control.scm b/gnu/services/version-control.scm
index 17a5f9c867..14ff0a59a6 100644
--- a/gnu/services/version-control.scm
+++ b/gnu/services/version-control.scm
@@ -29,6 +29,7 @@
   #:use-module (gnu system shadow)
   #:use-module (gnu packages version-control)
   #:use-module (gnu packages admin)
+  #:use-module (guix deprecation)
   #:use-module (guix records)
   #:use-module (guix gexp)
   #:use-module (guix store)
@@ -54,6 +55,7 @@
 
             <gitolite-rc-file>
             gitolite-rc-file
+            gitolite-rc-file-local-code
             gitolite-rc-file-umask
             gitolite-rc-file-unsafe-pattern
             gitolite-rc-file-git-config-keys
@@ -177,7 +179,8 @@
 protocol.")
    (default-value (git-daemon-configuration))))
 
-(define* (git-daemon-service #:key (config (git-daemon-configuration)))
+(define-deprecated (git-daemon-service #:key (config (git-daemon-configuration)))
+  git-daemon-service-type
   "Return a service that runs @command{git daemon}, a simple TCP server to
 expose repositories over the Git protocol for anonymous access.
 
@@ -242,6 +245,8 @@ access to exported repositories under @file{/srv/git}."
   gitolite-rc-file?
   (umask           gitolite-rc-file-umask
                    (default #o0077))
+  (local-code      gitolite-rc-file-local-code
+                   (default "$rc{GL_ADMIN_BASE}/local"))
   (unsafe-pattern  gitolite-rc-file-unsafe-pattern
                    (default #f))
   (git-config-keys gitolite-rc-file-git-config-keys
@@ -263,11 +268,14 @@ access to exported repositories under @file{/srv/git}."
 (define-gexp-compiler (gitolite-rc-file-compiler
                        (file <gitolite-rc-file>) system target)
   (match file
-    (($ <gitolite-rc-file> umask unsafe-pattern git-config-keys roles enable)
+    (($ <gitolite-rc-file> umask local-code unsafe-pattern git-config-keys roles enable)
      (apply text-file* "gitolite.rc"
       `("%RC = (\n"
         "    UMASK => " ,(format #f "~4,'0o" umask) ",\n"
         "    GIT_CONFIG_KEYS => '" ,git-config-keys "',\n"
+        ,(if local-code
+             (simple-format #f "    LOCAL_CODE => \"~A\",\n" local-code)
+             "")
         "    ROLES => {\n"
         ,@(map (match-lambda
                  ((role . value)
@@ -307,7 +315,7 @@ access to exported repositories under @file{/srv/git}."
     (($ <gitolite-configuration> package user group home-directory
                                  rc-file admin-pubkey)
      ;; User group and account to run Gitolite.
-     (list (user-group (name user) (system? #t))
+     (list (user-group (name group) (system? #t))
            (user-account
             (name user)
             (group group)
@@ -405,7 +413,7 @@ access to exported repositories under @file{/srv/git}."
                                (list
                                 (gitolite-configuration-package config))))))
    (description
-    "Setup @command{gitolite}, a Git hosting tool providing access over SSH..
+    "Set up @command{gitolite}, a Git hosting tool providing access over SSH.
 By default, the @code{git} user is used, but this is configurable.
 Additionally, Gitolite can integrate with with tools like gitweb or cgit to
 provide a web interface to view selected repositories.")))
diff --git a/gnu/services/virtualization.scm b/gnu/services/virtualization.scm
index cb6227403b..601c11b0d1 100644
--- a/gnu/services/virtualization.scm
+++ b/gnu/services/virtualization.scm
@@ -4,6 +4,7 @@
 ;;; Copyright © 2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
 ;;; Copyright © 2021 Timotej Lazar <timotej.lazar@araneo.si>
 ;;; Copyright © 2022 Oleg Pykhalov <go.wigust@gmail.com>
+;;; Copyright © 2022 Leo Nikkilä <hello@lnikki.la>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -611,6 +612,13 @@ used to manage logs from @acronym{VM, virtual machine} consoles.")))
    (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00"))
    (mask (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
+(define %x86_64
+  (qemu-platform
+   (name "x86_64")
+   (family "i386")
+   (magic (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
+
 (define %alpha
   (qemu-platform
    (name "alpha")
@@ -767,7 +775,7 @@ used to manage logs from @acronym{VM, virtual machine} consoles.")))
    (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %qemu-platforms
-  (list %i386 %alpha %arm %sparc32plus %ppc %ppc64 %ppc64le %m68k
+  (list %i386 %x86_64 %alpha %arm %sparc32plus %ppc %ppc64 %ppc64le %m68k
         %mips %mipsel %mipsn32 %mipsn32el %mips64 %mips64el
         %riscv32 %riscv64 %sh4 %sh4eb %s390x %aarch64 %hppa))
 
diff --git a/gnu/services/vnc.scm b/gnu/services/vnc.scm
new file mode 100644
index 0000000000..15c3c14fee
--- /dev/null
+++ b/gnu/services/vnc.scm
@@ -0,0 +1,247 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu services vnc)
+  #:use-module (gnu packages vnc)
+  #:use-module ((gnu services) #:hide (delete))
+  #:use-module (gnu system shadow)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services shepherd)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+
+  #:export (xvnc-configuration
+            xvnc-configuration-xvnc
+            xvnc-configuration-display-number
+            xvnc-configuration-geometry
+            xvnc-configuration-depth
+            xvnc-configuration-port
+            xvnc-configuration-ipv4?
+            xvnc-configuration-ipv6?
+            xvnc-configuration-password-file
+            xvnc-configuration-xdmcp?
+            xvnc-configuration-inetd?
+            xvnc-configuration-frame-rate
+            xvnc-configuration-security-types
+            xvnc-configuration-localhost?
+            xvnc-configuration-log-level
+            xvnc-configuration-extra-options
+
+            xvnc-service-type))
+
+;;;
+;;; Xvnc.
+;;;
+
+(define (color-depth? x)
+  (member x '(16 24 32)))
+
+(define (port? x)
+  (and (number? x)
+       (and (>= x 0) (<= x 65535))))
+
+(define-maybe/no-serialization port)
+
+(define-maybe/no-serialization string)
+
+(define %security-types '("None" "VncAuth" "Plain" "TLSNone" "TLSVnc" "TLSPlain"
+                          "X509None" "X509Vnc"))
+
+(define (security-type? x)
+  (member x %security-types))
+
+(define (security-types? x)
+  (and (list? x)
+       (and-map security-type? x)))
+
+(define (log-level? x)
+  (and (number? x)
+       (and (>= x 0) (<= x 100))))
+
+(define (strings? x)
+  (and (list? x)
+       (and-map string? x)))
+
+(define-configuration/no-serialization xvnc-configuration
+  (xvnc
+   (file-like tigervnc-server)
+   "The package that provides the Xvnc binary.")
+  (display-number
+   (number 0)
+   "The display number used by Xvnc.  You should set this to a number not
+already used by a Xorg server.  When remoting a complete desktop session via
+XDMCP and using a compatible VNC viewer as provided by the
+@code{tigervnc-client} or @code{turbovnc} packages, the geometry is
+automatically adjusted.")
+  (geometry
+   (string "1024x768")
+   "The size of the desktop to be created.")
+  (depth
+   (color-depth 24)
+   "The pixel depth in bits of the desktop to be created.  Accepted values are
+16, 24 or 32.")
+  (port
+   maybe-port
+   "The port on which to listen for connections from viewers.  When left
+unspecified, it defaults to 5900 plus the display number.")
+  (ipv4?
+   (boolean #t)
+   "Use IPv4 for incoming and outgoing connections.")
+  (ipv6?
+   (boolean #t)
+   "Use IPv6 for incoming and outgoing connections.")
+  (password-file
+   maybe-string
+   "The password file to use, if any.  Refer to vncpasswd(1) to learn how to
+generate such a file.")
+  (xdmcp?
+   (boolean #f)
+   "Query the XDMCP server for a session.  This enables users to log in a
+desktop session from the login manager screen.  For a multiple users scenario,
+you'll want to enable the @code{inetd?} option as well, so that each
+connection to the VNC server is handled separately rather than shared.")
+  (inetd?
+   (boolean #f)
+   "Use an Inetd-style service, which runs the Xvnc server on demand.")
+  (frame-rate
+   (number 60)
+   "The maximum number of updates per second sent to each client.")
+  (security-types
+   (security-types (list "None"))
+   (format #f "The allowed security schemes to use for incoming connections.
+The default is \"None\", which is safe given that Xvnc is configured to
+authenticate the user via the display manager, and only for local connections.
+Accepted values are any of the following: ~s" %security-types))
+  (localhost?
+   (boolean #t)
+   "Only allow connections from the same machine.  It is set to @code{#true}
+by default for security, which means SSH or another secure means should be
+used to expose the remote port.")
+  (log-level
+   (log-level 30)
+   "The log level, a number between 0 and 100, 100 meaning most verbose
+output.  The log messages are output to syslog.")
+  (extra-options
+   (strings '())
+   "This can be used to provide extra Xvnc options not exposed via this
+<xvnc-configuration> record."))
+
+(define (xvnc-configuration->command-line-arguments config)
+  "Derive the command line arguments to used to launch the Xvnc daemon from
+CONFIG, a <xvnc-configuration> object."
+  (match-record config <xvnc-configuration>
+    (xvnc display-number geometry depth port ipv4? ipv6? password-file xdmcp?
+          inetd? frame-rate security-types localhost? log-level extra-options)
+    #~(list #$(file-append xvnc "/bin/Xvnc")
+            #$(format #f ":~a" display-number)
+            "-geometry" #$geometry
+            "-depth" #$(number->string depth)
+            #$@(if inetd?
+                   (list "-inetd")
+                   '())
+            #$@(if (not inetd?)
+                   (if (maybe-value-set? port)
+                       (list "-rfbport" (number->string port))
+                       '())
+                   '())
+            #$@(if (not inetd?)
+                   (if ipv4?
+                       (list "-UseIPv4")
+                       '())
+                   '())
+            #$@(if (not inetd?)
+                   (if ipv6?
+                       (list "-UseIPv6")
+                       '())
+                   '())
+            #$@(if (maybe-value-set? password-file)
+                   (list "-PasswordFile" password-file)
+                   '())
+            "-FrameRate" #$(number->string frame-rate)
+            "-SecurityTypes" #$(string-join security-types ",")
+            #$@(if localhost?
+                   (list "-localhost")
+                   '())
+            "-Log" #$(format #f "*:syslog:~a" log-level)
+            #$@(if xdmcp?
+                   (list "-query" "localhost" "-once")
+                   '())
+            #$@extra-options)))
+
+(define %xvnc-accounts
+  (list (user-group
+         (name "xvnc")
+         (system? #t))
+        (user-account
+         (name "xvnc")
+         (group "xvnc")
+         (system? #t)
+         (comment "User for Xvnc server"))))
+
+(define (xvnc-shepherd-service config)
+  "Return a <shepherd-service> for Xvnc with CONFIG."
+  (let* ((display-number (xvnc-configuration-display-number config))
+         (port (if (maybe-value-set? (xvnc-configuration-port config))
+                   (xvnc-configuration-port config)
+                   #f))
+         (port* (or port (+ 5900 display-number))))
+    (shepherd-service
+     (provision '(xvnc vncserver))
+     (documentation "Run the Xvnc server.")
+     (requirement '(networking syslogd))
+     (start (if (xvnc-configuration-inetd? config)
+                #~(let* ((inaddr (if #$(xvnc-configuration-localhost? config)
+                                     INADDR_LOOPBACK
+                                     INADDR_ANY))
+                         (in6addr (if #$(xvnc-configuration-localhost? config)
+                                      IN6ADDR_LOOPBACK
+                                      IN6ADDR_ANY))
+                         (ipv4-socket (and #$(xvnc-configuration-ipv4? config)
+                                           (make-socket-address AF_INET inaddr
+                                                                #$port*)))
+                         (ipv6-socket (and #$(xvnc-configuration-ipv6? config)
+                                           (make-socket-address AF_INET6 in6addr
+                                                                #$port*))))
+                    (make-inetd-constructor
+                     #$(xvnc-configuration->command-line-arguments config)
+                     `(,@(if ipv4-socket
+                             (list (endpoint ipv4-socket))
+                             '())
+                       ,@(if ipv6-socket
+                             (list (endpoint ipv6-socket))
+                             '()))
+                     #:user "xvnc"
+                     #:group "xvnc"))
+                #~(make-forkexec-constructor
+                   #$(xvnc-configuration->command-line-arguments config)
+                   #:user "xvnc"
+                   #:group "xvnc")))
+     (stop #~(make-inetd-destructor)))))
+
+(define xvnc-service-type
+  (service-type
+   (name 'xvnc)
+   (default-value (xvnc-configuration))
+   (description "Run the Xvnc server, which creates a virtual X11 session and
+allow remote clients connecting to it via the remote framebuffer (RFB)
+protocol.")
+   (extensions (list (service-extension
+                      shepherd-root-service-type
+                      (compose list xvnc-shepherd-service))
+                     (service-extension account-service-type
+                                        (const %xvnc-accounts))))))
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm
index 82ff05b351..4103f89ecf 100644
--- a/gnu/services/vpn.scm
+++ b/gnu/services/vpn.scm
@@ -10,6 +10,7 @@
 ;;; Copyright © 2021 jgart <jgart@dismail.de>
 ;;; Copyright © 2021 Nathan Dehnel <ncdehnel@gmail.com>
 ;;; Copyright © 2022 Cameron V Chaparro <cameron@cameronchaparro.com>
+;;; Copyright © 2022 Timo Wilken <guix@twilken.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -61,6 +62,7 @@
             wireguard-peer-endpoint
             wireguard-peer-allowed-ips
             wireguard-peer-public-key
+            wireguard-peer-preshared-key
             wireguard-peer-keep-alive
 
             wireguard-configuration
@@ -72,6 +74,11 @@
             wireguard-configuration-dns
             wireguard-configuration-private-key
             wireguard-configuration-peers
+            wireguard-configuration-pre-up
+            wireguard-configuration-post-up
+            wireguard-configuration-pre-down
+            wireguard-configuration-post-down
+            wireguard-configuration-table
 
             wireguard-service-type))
 
@@ -704,6 +711,8 @@ strongSwan.")))
   (endpoint          wireguard-peer-endpoint
                      (default #f))     ;string
   (public-key        wireguard-peer-public-key)   ;string
+  (preshared-key     wireguard-peer-preshared-key
+                     (default #f))     ;string
   (allowed-ips       wireguard-peer-allowed-ips) ;list of strings
   (keep-alive        wireguard-peer-keep-alive
                      (default #f)))    ;integer
@@ -724,7 +733,17 @@ strongSwan.")))
   (peers              wireguard-configuration-peers ;list of <wiregard-peer>
                       (default '()))
   (dns                wireguard-configuration-dns ;list of strings
-                      (default #f)))
+                      (default #f))
+  (pre-up             wireguard-configuration-pre-up ;list of strings
+                      (default '()))
+  (post-up            wireguard-configuration-post-up ;list of strings
+                      (default '()))
+  (pre-down           wireguard-configuration-pre-down ;list of strings
+                      (default '()))
+  (post-down          wireguard-configuration-post-down ;list of strings
+                      (default '()))
+  (table              wireguard-configuration-table ;string
+                      (default "auto")))
 
 (define (wireguard-configuration-file config)
   (define (peer->config peer)
@@ -747,9 +766,18 @@ AllowedIPs = ~a
                   (format #f "PersistentKeepalive = ~a\n" keep-alive)
                   "\n"))))
 
+  (define (peers->preshared-keys peer keys)
+    (let ((public-key (wireguard-peer-public-key peer))
+          (preshared-key (wireguard-peer-preshared-key peer)))
+      (if preshared-key
+          (cons* public-key preshared-key keys)
+          keys)))
+
   (match-record config <wireguard-configuration>
-    (wireguard interface addresses port private-key peers dns)
+    (wireguard interface addresses port private-key peers dns
+               pre-up post-up pre-down post-down table)
     (let* ((config-file (string-append interface ".conf"))
+           (peer-keys (fold peers->preshared-keys (list) peers))
            (peers (map peer->config peers))
            (config
             (computed-file
@@ -762,13 +790,50 @@ AllowedIPs = ~a
                      (let ((format (@ (ice-9 format) format)))
                        (format port "[Interface]
 Address = ~a
-PostUp = ~a set %i private-key ~a
+~a
+~a
+PostUp = ~a set %i private-key ~a~{ peer ~a preshared-key ~a~}
+~a
+~a
+~a
 ~a
 ~a
 ~{~a~^~%~}"
                                #$(string-join addresses ",")
+                               #$(if table
+                                     (format #f "Table = ~a" table)
+                                     "")
+                               #$(if (null? pre-up)
+                                     ""
+                                     (string-join
+                                      (map (lambda (command)
+                                             (format #f "PreUp = ~a" command))
+                                           pre-up)
+                                      "\n"))
                                #$(file-append wireguard "/bin/wg")
                                #$private-key
+                               '#$peer-keys
+                               #$(if (null? post-up)
+                                     ""
+                                     (string-join
+                                      (map (lambda (command)
+                                             (format #f "PostUp = ~a" command))
+                                           post-up)
+                                      "\n"))
+                               #$(if (null? pre-down)
+                                     ""
+                                     (string-join
+                                      (map (lambda (command)
+                                             (format #f "PreDown = ~a" command))
+                                           pre-down)
+                                      "\n"))
+                               #$(if (null? post-down)
+                                     ""
+                                     (string-join
+                                      (map (lambda (command)
+                                             (format #f "PostDown = ~a" command))
+                                           post-down)
+                                      "\n"))
                                #$(if port
                                      (format #f "ListenPort = ~a" port)
                                      "")
@@ -781,7 +846,7 @@ PostUp = ~a set %i private-key ~a
 
 (define (wireguard-activation config)
   (match-record config <wireguard-configuration>
-    (private-key)
+    (private-key wireguard)
     #~(begin
         (use-modules (guix build utils)
                      (ice-9 popen)
@@ -790,7 +855,7 @@ PostUp = ~a set %i private-key ~a
         (unless (file-exists? #$private-key)
           (let* ((pipe
                   (open-input-pipe (string-append
-                                    #$(file-append wireguard-tools "/bin/wg")
+                                    #$(file-append wireguard "/bin/wg")
                                     " genkey")))
                  (key (read-line pipe)))
             (call-with-output-file #$private-key
@@ -823,6 +888,9 @@ PostUp = ~a set %i private-key ~a
     (list (service-extension shepherd-root-service-type
                              wireguard-shepherd-service)
           (service-extension activation-service-type
-                             wireguard-activation)))
+                             wireguard-activation)
+          (service-extension profile-service-type
+                             (compose list
+                                      wireguard-configuration-wireguard))))
    (description "Set up Wireguard @acronym{VPN, Virtual Private Network}
 tunnels.")))
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index e347f5dbcc..d56e893527 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -1,6 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
-;;; Copyright © 2015-2022 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2015-2023 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016 Nikita <nikita@n0.is>
 ;;; Copyright © 2016, 2017, 2018 Julien Lepiller <julien@lepiller.eu>
 ;;; Copyright © 2017, 2018, 2019 Christopher Baines <mail@cbaines.net>
@@ -790,13 +790,11 @@ of index files."
                 (nginx file run-directory shepherd-requirement)
    (let* ((nginx-binary (file-append nginx "/sbin/nginx"))
           (pid-file (in-vicinity run-directory "pid"))
+          (config-file (or file (default-nginx-config config)))
           (nginx-action
            (lambda args
              #~(lambda _
-                 (invoke #$nginx-binary "-c"
-                         #$(or file
-                               (default-nginx-config config))
-                         #$@args)
+                 (invoke #$nginx-binary "-c" #$config-file #$@args)
                  (match '#$args
                    (("-s" . _) #f)
                    (_
@@ -807,7 +805,6 @@ of index files."
                           #~#t
                           #~(read-pid-file #$pid-file))))))))
 
-     ;; TODO: Add 'reload' action.
      (list (shepherd-service
             (provision '(nginx))
             (documentation "Run the nginx daemon.")
@@ -815,7 +812,18 @@ of index files."
             (modules `((ice-9 match)
                        ,@%default-modules))
             (start (nginx-action "-p" run-directory))
-            (stop (nginx-action "-s" "stop")))))))
+            (stop (nginx-action "-s" "stop"))
+            (actions
+              (list
+               (shepherd-configuration-action config-file)
+               (shepherd-action
+                 (name 'reload)
+                 (documentation "Reload nginx configuration file and restart worker processes.
+This has the effect of killing old worker processes and starting new ones, using
+the same configuration file.  It is useful for situations where the same nginx
+configuration file can point to different things after a reload, such as
+renewed TLS certificates, or @code{include}d files.")
+                 (procedure (nginx-action "-s" "reload"))))))))))
 
 (define nginx-service-type
   (service-type (name 'nginx)
@@ -978,7 +986,7 @@ and the back-end of a Web service.")))
 
 (define php-fpm-accounts
   (match-lambda
-    (($ <php-fpm-configuration> php socket user group socket-user socket-group _ _ _ _ _ _)
+    (($ <php-fpm-configuration> php socket user group socket-user socket-group)
      `(,@(if (equal? group "php-fpm")
              '()
              (list (user-group (name "php-fpm") (system? #t))))
@@ -1147,8 +1155,7 @@ a webserver.")
 
   (package  hpcguix-web-package (default hpcguix-web)) ;file-like
 
-  ;; Specs is gexp of hpcguix-web configuration file
-  (specs    hpcguix-web-configuration-specs)
+  (specs    hpcguix-web-configuration-specs (default #f)) ;#f | gexp
   (address  hpcguix-web-configuration-address (default "127.0.0.1"))
   (port     hpcguix-web-configuration-port (default 5000)))
 
@@ -1209,8 +1216,11 @@ a webserver.")
                        "-p"
                        #$(number->string
                           (hpcguix-web-configuration-port config))
-                       (string-append "--config="
-                                      #$(scheme-file "hpcguix-web.scm" specs)))
+                       #$@(if specs
+                              #~((string-append "--config="
+                                                #$(scheme-file
+                                                   "hpcguix-web.scm" specs)))
+                              #~()))
                  #:user "hpcguix-web"
                  #:group "hpcguix-web"
                  #:environment-variables
@@ -1231,7 +1241,8 @@ a webserver.")
           (service-extension rottlog-service-type
                              (const %hpcguix-web-log-rotations))
           (service-extension shepherd-root-service-type
-                             (compose list hpcguix-web-shepherd-service))))))
+                             (compose list hpcguix-web-shepherd-service))))
+   (default-value (hpcguix-web-configuration))))
 
 
 ;;;
@@ -1438,32 +1449,40 @@ files.")
       (documentation
        "Anonimyze the given log file location with anonip.")
       (start
-       #~(lambda _
-           (unless (file-exists? #$input)
-             (mknod #$input 'fifo #o600 0))
-           (let ((pid
-                  (fork+exec-command
-                   (append
-                    (list #$(file-append (anonip-configuration-anonip config)
-                                         "/bin/anonip")
-                          (string-append "--input=" #$input)
-                          (string-append "--output=" #$output))
-                    (if #$(anonip-configuration-skip-private? config)
-                        '("--skip-private") (list))
-                    '#$(optional anonip-configuration-column "--column")
-                    '#$(optional anonip-configuration-ipv4mask "--ipv4mask")
-                    '#$(optional anonip-configuration-ipv6mask "--ipv6mask")
-                    '#$(optional anonip-configuration-increment "--increment")
-                    '#$(optional anonip-configuration-replacement
-                                 "--replacement")
-                    '#$(optional anonip-configuration-delimiter "--delimiter")
-                    '#$(optional anonip-configuration-regex "--regex"))
-                   ;; Run in a UTF-8 locale
-                   #:environment-variables
-                   (list (string-append "GUIX_LOCPATH=" #$glibc-utf8-locales
-                                        "/lib/locale")
-                         "LC_ALL=en_US.utf8"))))
-             pid)))
+       #~(lambda ()
+           (define (spawn)
+             (fork+exec-command
+              (append
+               (list #$(file-append (anonip-configuration-anonip config)
+                                    "/bin/anonip")
+                     (string-append "--input=" #$input)
+                     (string-append "--output=" #$output))
+               (if #$(anonip-configuration-skip-private? config)
+                   '("--skip-private") (list))
+               '#$(optional anonip-configuration-column "--column")
+               '#$(optional anonip-configuration-ipv4mask "--ipv4mask")
+               '#$(optional anonip-configuration-ipv6mask "--ipv6mask")
+               '#$(optional anonip-configuration-increment "--increment")
+               '#$(optional anonip-configuration-replacement
+                            "--replacement")
+               '#$(optional anonip-configuration-delimiter "--delimiter")
+               '#$(optional anonip-configuration-regex "--regex"))
+              ;; Run in a UTF-8 locale
+              #:environment-variables
+              (list (string-append "GUIX_LOCPATH=" #$glibc-utf8-locales
+                                   "/lib/locale")
+                    "LC_ALL=en_US.utf8")))
+
+           (let ((stat (stat #$input #f)))
+             (cond ((not stat)
+                    (mknod #$input 'fifo #o600 0)
+                    (spawn))
+                   ((eq? 'fifo (stat:type stat))
+                    (spawn))
+                   (else
+                    (format #t "'~a' is not a FIFO; bailing out~%"
+                            #$input)
+                    #f)))))
       (stop #~(make-kill-destructor))))))
 
 (define anonip-service-type
@@ -2126,24 +2145,23 @@ root=/srv/gemini
             (stop #~(make-kill-destructor)))))))
 
 (define agate-accounts
-  (match-lambda
-    (($ <agate-configuration> _ _ _ _ _
-                              _ _ _ _
-                              _ user group _)
-     `(,@(if (equal? group "agate")
-             '()
-             (list (user-group (name "agate") (system? #t))))
-       ,(user-group
-         (name group)
-         (system? #t))
-       ,(user-account
-         (name user)
-         (group group)
-         (supplementary-groups '("agate"))
-         (system? #t)
-         (comment "agate server user")
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))))
+  (lambda (config)
+    (let ((group (agate-configuration-group config))
+          (user (agate-configuration-user config)))
+      `(,@(if (equal? group "agate")
+              '()
+              (list (user-group (name "agate") (system? #t))))
+        ,(user-group
+          (name group)
+          (system? #t))
+        ,(user-account
+          (name user)
+          (group group)
+          (supplementary-groups '("agate"))
+          (system? #t)
+          (comment "agate server user")
+          (home-directory "/var/empty")
+          (shell (file-append shadow "/sbin/nologin")))))))
 
 (define agate-service-type
   (service-type
diff --git a/gnu/services/xorg.scm b/gnu/services/xorg.scm
index 3ff290c197..5f073d05d3 100644
--- a/gnu/services/xorg.scm
+++ b/gnu/services/xorg.scm
@@ -1,6 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Andy Wingo <wingo@igalia.com>
-;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013-2017, 2019-2020, 2022 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
 ;;; Copyright © 2018, 2019 Timothy Sample <samplet@ngyro.com>
 ;;; Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
@@ -12,6 +12,7 @@
 ;;; Copyright © 2021 Oleg Pykhalov <go.wigust@gmail.com>
 ;;; Copyright © 2021 Josselin Poiret <josselin.poiret@protonmail.ch>
 ;;; Copyright © 2022 Chris Marusich <cmmarusich@gmail.com>
+;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -32,6 +33,7 @@
   #:autoload   (gnu services sddm) (sddm-service-type)
   #:use-module (gnu artwork)
   #:use-module (gnu services)
+  #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
   #:use-module (gnu system pam)
   #:use-module (gnu system setuid)
@@ -63,6 +65,7 @@
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-26)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (xorg-configuration
             xorg-configuration?
@@ -113,6 +116,13 @@
             localed-configuration?
             localed-service-type
 
+            dconf-keyfile
+            dconf-profile
+            dconf-profile-name
+            dconf-profile-content
+            dconf-profile-keyfile
+            dconf-service-type
+
             gdm-configuration
             gdm-service-type
 
@@ -663,13 +673,7 @@ reboot_cmd " shepherd "/sbin/reboot\n"
                    (list (service-extension shepherd-root-service-type
                                             slim-shepherd-service)
                          (service-extension pam-root-service-type
-                                            slim-pam-service)
-
-                         ;; Unconditionally add xterm to the system profile, to
-                         ;; avoid bad surprises.
-                         (service-extension profile-service-type
-                                            (const (list xterm)))))
-
+                                            slim-pam-service)))
                   (default-value (slim-configuration))
                   (description
                    "Run the SLiM graphical login manager for X11."))))
@@ -804,6 +808,106 @@ the GNOME desktop environment.")
 
 
 ;;;
+;;; Dconf.
+;;;
+
+(define-maybe text-config)
+
+(define-configuration/no-serialization dconf-keyfile
+  (name string
+        "The file name of the associated keyfile, e.g. \"00-login-screen\".")
+  (content text-config "The content of the associated keyfile."))
+
+(define-configuration/no-serialization dconf-profile
+  (name string "The file name of the dconf system profile, which should match
+the name of a user for which the profile is to be used with.  To have the
+profile used, the environment variable \"DCONF_PROFILE\" should be set to the
+profile file, e.g.:
+@example
+ export DCONF_PROFILE=/etc/dconf/profile/gdm
+@end example")
+  (content maybe-text-config "The content of the Dconf profile.  Unless
+provided, it defaults to include the user database (\"user-db:NAME\") as well
+as the system database (\"system-db:NAME\"), which corresponds to the
+generated database, @file{/etc/dconf/db/NAME}.")
+  (keyfile dconf-keyfile "The keyfile associated with the profile"))
+
+(define dconf-profiles?
+  (list-of dconf-profile?))
+
+(define-configuration/no-serialization dconf-configuration
+  (profiles dconf-profiles "The list of <dconf-profile> objects to populate."))
+
+(define (dconf-profile->profile-file profile)
+  "Given PROFILE, a <dconf-profile> object, return a dconf profile file."
+  (let ((name (dconf-profile-name profile))
+        (content (dconf-profile-content profile)))
+    (apply mixed-text-file
+           name
+           (if (maybe-value-set? content)
+               (interpose content "\n" 'suffix)
+               (interpose (list (string-append "user-db:" name)
+                                (string-append "system-db:" name))
+                          "\n" 'suffix)))))
+
+(define (dconf-profile->db-keyfile profile)
+  "Given PROFILE, a <dconf-profile> object, return a dconf profile file."
+  (let ((keyfile (dconf-profile-keyfile profile)))
+    (apply mixed-text-file (dconf-keyfile-name keyfile)
+           (interpose (dconf-keyfile-content keyfile) "\n" 'suffix))))
+
+(define (dconf-profile->db-keyfile-dir profile)
+  "Wrap the keyfile in a directory, to satisfy 'dconf compile'."
+  (let ((name (dconf-profile-name profile))
+        (keyfile-name (dconf-keyfile-name (dconf-profile-keyfile profile))))
+    (computed-file name
+                   #~(begin
+                       (mkdir #$output)
+                       (symlink #$(dconf-profile->db-keyfile profile)
+                                (string-append #$output "/" #$keyfile-name))))))
+
+(define (dconf-profile->db profile)
+  "Compile the a <dconf-profile> object into a GVariant Database file."
+  (let ((name (dconf-profile-name profile)))
+    (computed-file
+     name
+     (with-imported-modules '((guix build utils))
+       #~(begin
+           (use-modules (guix build utils))
+           (setenv "DCONF_PROFILE" #$(dconf-profile->profile-file profile))
+           (invoke #$(file-append dconf "/bin/dconf") "compile"
+                   #$output #$(dconf-profile->db-keyfile-dir profile)))))))
+
+(define (dconf-profile->files profile)
+  "Given PROFILE, a <dconf-profile> object, return a dconf directory
+containing the associated profile, keyfile and database files to be assembled
+under /etc."
+  (let ((name (dconf-profile-name profile))
+        (keyfile-name (dconf-keyfile-name (dconf-profile-keyfile profile))))
+    (list (list (string-append "dconf/profile/" name)
+                (dconf-profile->profile-file profile))
+          (list (string-append "dconf/db/" name ".d/" keyfile-name)
+                (dconf-profile->db-keyfile profile))
+          (list (string-append "dconf/db/" name)
+                (dconf-profile->db profile)))))
+
+(define dconf-service-type
+  (service-type
+   (name 'dconf-profile)
+   (extensions
+    (list (service-extension etc-service-type
+                             (lambda (dconf-profiles)
+                               (append-map dconf-profile->files
+                                           dconf-profiles)))))
+   (compose concatenate)
+   (extend append)
+   (default-value '())
+   (description "Extend the @code{etc-service-type} to populate the file
+hierarchy under @file{/etc/dconf} with the <dconf-profile> objects provided as
+argument.")))
+
+
+;;;
 ;;; GNOME Desktop Manager.
 ;;;
 
@@ -876,6 +980,7 @@ the GNOME desktop environment.")
   (gdm gdm-configuration-gdm (default gdm))
   (allow-empty-passwords? gdm-configuration-allow-empty-passwords? (default #t))
   (auto-login? gdm-configuration-auto-login? (default #f))
+  (auto-suspend? gdm-configuration-auto-suspend? (default #t))
   (dbus-daemon gdm-configuration-dbus-daemon (default dbus-daemon-wrapper))
   (debug? gdm-configuration-debug? (default #f))
   (default-user gdm-configuration-default-user (default #f))
@@ -885,10 +990,36 @@ the GNOME desktop environment.")
                       (default (xorg-configuration)))
   (x-session gdm-configuration-x-session
              (default (xinitrc)))
+  (xdmcp? gdm-configuration-xdmcp?
+          (default #f))
   (wayland? gdm-configuration-wayland? (default #f))
   (wayland-session gdm-configuration-wayland-session
                    (default gdm-wayland-session-wrapper)))
 
+(define (gdm-dconf-profiles config)
+  (if (gdm-configuration-auto-suspend? config)
+      '()
+      ;; This custom gconf profile works around a lack of configuration option
+      ;; to disable auto-suspend when no users are physically logged in (see:
+      ;; https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/22).
+      (list (dconf-profile
+             (name "gdm")
+             (content (list #~(begin
+                                (use-modules (ice-9 textual-ports))
+                                (string-trim
+                                 (call-with-input-file
+                                     #$(file-append gdm "/share/dconf/profile/gdm")
+                                   get-string-all)))
+                            "system-db:gdm"))
+             (keyfile (dconf-keyfile
+                       (name "00-disable-suspend")
+                       (content
+                        (list "[org/gnome/settings-daemon/plugins/power]"
+                              "sleep-inactive-ac-type='nothing'"
+                              "sleep-inactive-battery-type='nothing'"
+                              "sleep-inactive-ac-timeout=0"
+                              "sleep-inactive-battery-timeout=0"))))))))
+
 (define (gdm-configuration-file config)
   (mixed-text-file "gdm-custom.conf"
                    "[daemon]\n"
@@ -913,18 +1044,20 @@ the GNOME desktop environment.")
                    ;; See also
                    ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=39281>.
                    "InitialSetupEnable=false\n"
-                   "WaylandEnable=" (if (gdm-configuration-wayland? config)
-                                        "true"
-                                        "false") "\n"
+                   (format #f "WaylandEnable=~:[false~;true~]~%"
+                           (gdm-configuration-wayland? config))
                    "\n"
                    "[debug]\n"
-                   "Enable=" (if (gdm-configuration-debug? config)
-                                 "true"
-                                 "false") "\n"
+                   (format #f "Enable=~:[false~;true~]~%"
+                           (gdm-configuration-debug? config))
                    "\n"
                    "[security]\n"
                    "#DisallowTCP=true\n"
-                   "#AllowRemoteAutoLogin=false\n"))
+                   "#AllowRemoteAutoLogin=false\n"
+                   "\n"
+                   "[xdmcp]\n"
+                   (format #f "Enable=~:[false~;true~]~%"
+                           (gdm-configuration-xdmcp? config))))
 
 (define (gdm-pam-service config)
   "Return a PAM service for @command{gdm}."
@@ -950,6 +1083,9 @@ the GNOME desktop environment.")
                      (gdm-configuration-allow-empty-passwords? config))))
 
 (define (gdm-shepherd-service config)
+  (define config-file
+    (gdm-configuration-file config))
+
   (list (shepherd-service
          (documentation "Xorg display server (GDM)")
          (provision '(xorg-server))
@@ -959,9 +1095,10 @@ the GNOME desktop environment.")
                      (list #$(file-append (gdm-configuration-gdm config)
                                           "/bin/gdm"))
                      #:environment-variables
-                     (list (string-append
-                            "GDM_CUSTOM_CONF="
-                            #$(gdm-configuration-file config))
+                     (list #$@(if (gdm-configuration-auto-suspend? config)
+                                  #~()
+                                  #~("DCONF_PROFILE=/etc/dconf/profile/gdm"))
+                           (string-append "GDM_CUSTOM_CONF=" #$config-file)
                            (string-append
                             "GDM_DBUS_DAEMON="
                             #$(gdm-configuration-dbus-daemon config))
@@ -993,8 +1130,44 @@ the GNOME desktop environment.")
                             "GDM_WAYLAND_SESSION="
                             #$(gdm-configuration-wayland-session config))))))
          (stop #~(make-kill-destructor))
+         (actions (list (shepherd-configuration-action config-file)))
          (respawn? #t))))
 
+(define gdm-polkit-rules
+  (lambda (config)
+    (if (gdm-configuration-xdmcp? config)
+        ;; Allow remote (XDMCP) users to use colord; otherwise an
+        ;; authentication dialog would appear on the GDM screen (see the
+        ;; upstream bug:
+        ;; https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/issues/273).
+        (list (computed-file
+               "02-allow-colord.rules"
+               (with-imported-modules '((guix build utils))
+                 #~(begin
+                     (use-modules (guix build utils))
+
+                     (let* ((rules.d
+                             (string-append #$output
+                                            "/share/polkit-1"
+                                            "/rules.d"))
+                            (allow-colord.rules (string-append
+                                                 rules.d
+                                                 "/02-allow-colord.rules")))
+                       (mkdir-p rules.d)
+                       (call-with-output-file allow-colord.rules
+                         (lambda (port)
+                           ;; This workaround enables any local or remote in
+                           ;; the "users" group to use colord (see:
+                           ;; https://c-nergy.be/blog/?p=12073).
+                           (format port "\
+polkit.addRule(function(action, subject) {
+   if (action.id.match(\"org.freedesktop.color-manager\")) {
+      polkit.log(\"POLKIT DEBUG returning YES for action: \" + action);
+      return polkit.Result.YES;
+   }
+});~%"))))))))
+        '())))
+
 (define gdm-service-type
   (handle-xorg-configuration gdm-configuration
     (service-type (name 'gdm)
@@ -1003,8 +1176,12 @@ the GNOME desktop environment.")
                                             gdm-shepherd-service)
                          (service-extension account-service-type
                                             (const %gdm-accounts))
+                         (service-extension dconf-service-type
+                                            gdm-dconf-profiles)
                          (service-extension pam-root-service-type
                                             gdm-pam-service)
+                         (service-extension polkit-service-type
+                                            gdm-polkit-rules)
                          (service-extension profile-service-type
                                             gdm-configuration-gnome-shell-assets)
                          (service-extension dbus-root-service-type