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/base.scm233
-rw-r--r--gnu/services/cuirass.scm3
-rw-r--r--gnu/services/dbus.scm4
-rw-r--r--gnu/services/desktop.scm376
-rw-r--r--gnu/services/monitoring.scm2
-rw-r--r--gnu/services/pm.scm5
-rw-r--r--gnu/services/sound.scm87
-rw-r--r--gnu/services/ssh.scm22
-rw-r--r--gnu/services/virtualization.scm56
-rw-r--r--gnu/services/vpn.scm4
-rw-r--r--gnu/services/web.scm22
11 files changed, 645 insertions, 169 deletions
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index fbd01e84d6..f278cb76de 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -15,7 +15,7 @@
 ;;; Copyright © 2020, 2021 Brice Waegeneire <brice@waegenei.re>
 ;;; Copyright © 2021 qblade <qblade@protonmail.com>
 ;;; Copyright © 2021 Hui Lu <luhuins@163.com>
-;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;; Copyright © 2021, 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;; Copyright © 2022 Guillaume Le Vaillant <glv@posteo.net>
 ;;;
 ;;; This file is part of GNU Guix.
@@ -183,6 +183,7 @@
             guix-configuration-authorized-keys
             guix-configuration-use-substitutes?
             guix-configuration-substitute-urls
+            guix-configuration-generate-substitute-key?
             guix-configuration-extra-options
             guix-configuration-log-file
 
@@ -876,6 +877,8 @@ the message of the day, among other things."
   ;; "Escape hatch" for passing arbitrary command-line arguments.
   (extra-options    agetty-extra-options          ;list of strings
                     (default '()))
+  (shepherd-requirement agetty-shepherd-requirement  ;list of SHEPHERD requirements
+                    (default '()))
 ;;; XXX Unimplemented for now!
 ;;; (issue-file     agetty-issue-file             ;file-like
 ;;;                 (default #f))
@@ -924,17 +927,19 @@ to use as the tty.  This is primarily useful for headless systems."
         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)
+        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 "auto")))))
+         (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 '(user-processes host-name udev))
+         (requirement (cons* 'user-processes 'host-name 'udev
+                             shepherd-requirement))
 
          (modules '((ice-9 match) (gnu build linux-boot)))
          (start
@@ -1561,6 +1566,8 @@ archive' public keys, with GUIX."
                     (default #t))
   (substitute-urls  guix-configuration-substitute-urls ;list of strings
                     (default %default-substitute-urls))
+  (generate-substitute-key? guix-configuration-generate-substitute-key?
+                            (default #t))         ;Boolean
   (chroot-directories guix-configuration-chroot-directories ;list of file-like/strings
                       (default '()))
   (max-silent-time  guix-configuration-max-silent-time ;integer
@@ -1745,14 +1752,15 @@ proxy of 'guix-daemon'...~%")
 (define (guix-activation config)
   "Return the activation gexp for CONFIG."
   (match-record config <guix-configuration>
-    (guix authorize-key? authorized-keys)
+    (guix generate-substitute-key? authorize-key? authorized-keys)
     #~(begin
         ;; Assume that the store has BUILD-GROUP as its group.  We could
         ;; otherwise call 'chown' here, but the problem is that on a COW overlayfs,
         ;; chown leads to an entire copy of the tree, which is a bad idea.
 
         ;; Generate a key pair and optionally authorize substitute server keys.
-        (unless (file-exists? "/etc/guix/signing-key.pub")
+        (unless (or #$(not generate-substitute-key?)
+                    (file-exists? "/etc/guix/signing-key.pub"))
           (system* #$(file-append guix "/bin/guix") "archive"
                    "--generate-key"))
 
@@ -1995,8 +2003,7 @@ item of @var{packages}."
             (find directory-exists?
                   (map (cut string-append directory <>) %standard-locations)))
 
-          (mkdir-p (string-append #$output "/lib/udev"))
-          (union-build (string-append #$output "/lib/udev/rules.d")
+          (union-build #$output
                        (filter-map rules-sub-directory '#$packages)))))
 
   (computed-file "udev-rules" build))
@@ -2046,115 +2053,114 @@ item of @var{packages}."
 (define udev-shepherd-service
   ;; 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)))))))
+
+(define udev.conf
+  (computed-file "udev.conf"
+                 #~(call-with-output-file #$output
+                     (lambda (port)
+                       (format port "udev_rules=\"/etc/udev/rules.d\"~%")))))
+
+(define udev-etc
+  (match-lambda
     (($ <udev-configuration> udev rules)
-     (let* ((rules     (udev-rules-union (cons* udev kvm-udev-rule rules)))
-            (udev.conf (computed-file "udev.conf"
-                                      #~(call-with-output-file #$output
-                                          (lambda (port)
-                                            (format port
-                                                    "udev_rules=\"~a/lib/udev/rules.d\"\n"
-                                                    #$rules))))))
-       (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.
-                             (string-append "UDEV_CONFIG_FILE=" #$udev.conf)
-                             (string-append "EUDEV_RULES_DIRECTORY="
-                                            #$(file-append
-                                               rules "/lib/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))
-
-         (actions (list (shepherd-action
-                         (name 'rules)
-                         (documentation "Display the directory containing
-the udev rules in use.")
-                         (procedure #~(lambda (_)
-                                        (display #$rules)
-                                        (newline))))))))))))
+     `(("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)
                 (extensions
                  (list (service-extension shepherd-root-service-type
-                                          udev-shepherd-service)))
-
+                                          udev-shepherd-service)
+                       (service-extension etc-service-type udev-etc)))
                 (compose concatenate)           ;concatenate the list of rules
                 (extend (lambda (config rules)
                           (match config
@@ -2783,10 +2789,12 @@ to handle."
                         (cons tty %default-console-font))
                       '("tty1" "tty2" "tty3" "tty4" "tty5" "tty6")))
 
+        (syslog-service)
         (service agetty-service-type (agetty-configuration
                                        (extra-options '("-L")) ; no carrier detect
                                        (term "vt100")
-                                       (tty #f))) ; automatic
+                                       (tty #f) ; automatic
+                                       (shepherd-requirement '(syslogd))))
 
         (service mingetty-service-type (mingetty-configuration
                                          (tty "tty1")))
@@ -2803,7 +2811,6 @@ to handle."
 
         (service static-networking-service-type
                  (list %loopback-static-networking))
-        (syslog-service)
         (service urandom-seed-service-type)
         (service guix-service-type)
         (service nscd-service-type)
diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm
index 96f28a9670..d666d6243b 100644
--- a/gnu/services/cuirass.scm
+++ b/gnu/services/cuirass.scm
@@ -302,7 +302,8 @@
 (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)))
+         (files (list (cuirass-configuration-log-file config)
+                      (cuirass-configuration-web-log-file config)))
          (frequency 'weekly)
          (options '("rotate 40")))))              ;worth keeping
 
diff --git a/gnu/services/dbus.scm b/gnu/services/dbus.scm
index d2daf60497..ef6b82c572 100644
--- a/gnu/services/dbus.scm
+++ b/gnu/services/dbus.scm
@@ -106,10 +106,10 @@ includes the @code{etc/dbus-1/system.d} directories of each package listed in
         (define (services->sxml services)
           ;; Return the SXML 'includedir' clauses for DIRS.
           `(busconfig
-             ;; Increase this timeout to 60 seconds to work around race-y
+             ;; Increase this timeout to 300 seconds to work around race-y
              ;; failures such as <https://issues.guix.gnu.org/52051> on slow
              ;; computers with slow I/O.
-            (limit (@ (name "auth_timeout")) "60000")
+            (limit (@ (name "auth_timeout")) "300000")
             (servicehelper "/run/setuid-programs/dbus-daemon-launch-helper")
 
             ;; First, the '.service' files of services subject to activation.
diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index c2ee3a3d80..ecadb16b2f 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -44,6 +44,7 @@
   #:use-module (gnu system)
   #:use-module (gnu system setuid)
   #:use-module (gnu system shadow)
+  #:use-module (gnu system uuid)
   #:use-module (gnu system pam)
   #:use-module (gnu packages glib)
   #:use-module (gnu packages admin)
@@ -68,6 +69,7 @@
   #:use-module (guix utils)
   #:use-module (guix gexp)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (<upower-configuration>
             upower-configuration
@@ -403,14 +405,380 @@ site} for more information."
   bluetooth-configuration make-bluetooth-configuration
   bluetooth-configuration?
   (bluez bluetooth-configuration-bluez (default bluez))
-  (auto-enable? bluetooth-configuration-auto-enable? (default #f)))
+
+  ;;; [General]
+  (name bluetooth-configuration-name (default "BlueZ"))
+  (class bluetooth-configuration-class (default #x000000))
+  (discoverable-timeout
+   bluetooth-configuration-discoverable-timeout (default 180))
+  (always-pairable? bluetooth-configuration-always-pairable? (default #f))
+  (pairable-timeout bluetooth-configuration-pairable-timeout (default 0))
+
+  ;;; MAYBE: Exclude into separate <device-id> record-type?
+  (device-id bluetooth-configuration-device-id (default #f))
+  (reverse-service-discovery?
+   bluetooth-configuration-reverse-service-discovery (default #t))
+  (name-resolving? bluetooth-configuration-name-resolving? (default #t))
+  (debug-keys? bluetooth-configuration-debug-keys? (default #f))
+
+  ;;; Possible values:
+  ;;; 'dual, 'bredr, 'le
+  (controller-mode bluetooth-configuration-controller-mode (default 'dual))
+
+  ;;; Possible values:
+  ;;; 'off, 'single, 'multiple
+  (multi-profile bluetooth-configuration-multi-profile (default 'off))
+  (fast-connectable? bluetooth-configuration-fast-connectable? (default #f))
+
+  ;;; Possible values:
+  ;;; for LE mode: 'off, 'network/on, 'device
+  ;;; for Dual mode: 'off, 'network/on', 'device, 'limited-network, 'limited-device
+  ;;; Source: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/src/main.conf#n68
+  (privacy bluetooth-configuration-privacy (default 'off))
+
+  ;;; Possible values:
+  ;;; 'never, 'confirm, 'always
+  (just-works-repairing
+   bluetooth-configuration-just-works-repairing (default 'never))
+  (temporary-timeout bluetooth-configuration-temporary-timeout (default 30))
+  (refresh-discovery? bluetooth-configuration-refresh-discovery (default #t))
+
+  ;;; Possible values: #t, #f, (uuid <uuid>)
+  ;;; Possible UUIDs:
+  ;;; d4992530-b9ec-469f-ab01-6c481c47da1c (BlueZ Experimental Debug)
+  ;;; 671b10b5-42c0-4696-9227-eb28d1b049d6 (BlueZ Experimental Simultaneous Central and Peripheral)
+  ;;; 15c0a148-c273-11ea-b3de-0242ac130004 (BlueZ Experimental LL privacy)
+  ;;; 330859bc-7506-492d-9370-9a6f0614037f (BlueZ Experimental Bluetooth Quality Report)
+  ;;; a6695ace-ee7f-4fb9-881a-5fac66c629af (BlueZ Experimental Offload Codecs)
+  ;;; Source: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/src/main.conf#n110
+  (experimental bluetooth-configuration-experimental (default #f))
+  (remote-name-request-retry-delay
+   bluetooth-configuration-remote-name-request-retry-delay (default 300))
+
+  ;;; [BR]
+  (page-scan-type bluetooth-configuration-page-scan-type (default #f))
+  (page-scan-interval bluetooth-configuration-page-scan-interval (default #f))
+  (page-scan-window bluetooth-configuration-page-scan-window (default #f))
+  (inquiry-scan-type bluetooth-configuration-inquiry-scan-type (default #f))
+  (inquiry-scan-interval bluetooth-configuration-inquiry-scan-interval (default #f))
+  (inquiry-scan-window bluetooth-configuration-inquiry-scan-window (default #f))
+  (link-supervision-timeout bluetooth-configuration-link-supervision-timeout (default #f))
+  (page-timeout bluetooth-configuration-page-timeout (default #f))
+  (min-sniff-interval bluetooth-configuration-min-sniff-interval (default #f))
+  (max-sniff-interval bluetooth-configuration-max-sniff-interval (default #f))
+
+  ;;; [LE]
+  (min-advertisement-interval
+   bluetooth-configuration-min-advertisement-interval (default #f))
+  (max-advertisement-interval
+   bluetooth-configuration-max-advertisement-interval (default #f))
+  (multi-advertisement-rotation-interval
+   bluetooth-configuration-multi-advertisement-rotation-interval (default #f))
+  (scan-interval-auto-connect
+   bluetooth-configuration-scan-interval-auto-connect (default #f))
+  (scan-window-auto-connect
+   bluetooth-configuration-scan-window-auto-connect (default #f))
+  (scan-interval-suspend
+   bluetooth-configuration-scan-interval-suspend (default #f))
+  (scan-window-suspend
+   bluetooth-configuration-scan-window-suspend (default #f))
+  (scan-interval-discovery
+   bluetooth-configuration-scan-interval-discovery (default #f))
+  (scan-window-discovery
+   bluetooth-configuration-scan-window-discovery (default #f))
+  (scan-interval-adv-monitor
+   bluetooth-configuration-scan-interval-adv-monitor (default #f))
+  (scan-window-adv-monitor
+   bluetooth-configuration-scan-window-adv-monitor (default #f))
+  (scan-interval-connect
+   bluetooth-configuration-scan-interval-connect (default #f))
+  (scan-window-connect
+   bluetooth-configuration-scan-window-connect (default #f))
+  (min-connection-interval
+   bluetooth-configuration-min-connection-interval (default #f))
+  (max-connection-interval
+   bluetooth-configuration-max-connection-interval (default #f))
+  (connection-latency
+   bluetooth-configuration-connection-latency (default #f))
+  (connection-supervision-timeout
+   bluetooth-configuration-connection-supervision-timeout (default #f))
+  (autoconnect-timeout
+   bluetooth-configuration-autoconnect-timeout (default #f))
+  (adv-mon-allowlist-scan-duration
+   bluetooth-configuration-adv-mon-allowlist-scan-duration (default 300))
+  (adv-mon-no-filter-scan-duration
+   bluetooth-configuration-adv-mon-no-filter-scan-duration (default 500))
+  (enable-adv-mon-interleave-scan?
+   bluetooth-configuration-enable-adv-mon-interleave-scan (default #t))
+
+  ;;; [GATT]
+  ;;; Possible values: 'yes, 'no, 'always
+  (cache bluetooth-configuration-cache (default 'always))
+
+  ;;; Possible values: 7 ... 16, 0 (don't care)
+  (key-size bluetooth-configuration-key-size (default 0))
+
+  ;;; Possible values: 23 ... 517
+  (exchange-mtu bluetooth-configuration-exchange-mtu (default 517))
+
+  ;;; Possible values: 1 ... 5
+  (att-channels bluetooth-configuration-att-channels (default 3))
+
+  ;;; [AVDTP]
+  ;;; Possible values: 'basic, 'ertm
+  (session-mode bluetooth-configuration-session-mode (default 'basic))
+
+  ;;; Possible values: 'basic, 'streaming
+  (stream-mode bluetooth-configuration-stream-mode (default 'basic))
+
+  ;;; [Policy]
+  (reconnect-uuids bluetooth-configuration-reconnect-uuids (default '()))
+  (reconnect-attempts bluetooth-configuration-reconnect-attempts (default 7))
+  (reconnect-intervals bluetooth-configuration-reconnect-intervals
+                       (default (list 1 2 4 8 16 32 64)))
+  (auto-enable? bluetooth-configuration-auto-enable? (default #f))
+  (resume-delay bluetooth-configuration-resume-delay (default 2))
+
+  ;;; [AdvMon]
+  ;;; Possible values:
+  ;;; "0x00", "0xFF",
+  ;;; "N = 0x00" ... "N = 0xFF"
+  ;;; Source: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/src/main.conf#n286
+  (rssi-sampling-period bluetooth-configuration-rssi-sampling-period
+                        (default #xFF)))
 
 (define (bluetooth-configuration-file config)
   "Return a configuration file for the systemd bluetooth service, as a string."
   (string-append
-   "[Policy]\n"
-   "AutoEnable=" (bool (bluetooth-configuration-auto-enable?
-                        config))))
+   "[General]"
+   "\nName = " (bluetooth-configuration-name config)
+   "\nClass = " (string-append
+                 "0x"
+                 (format #f "~6,'0x" (bluetooth-configuration-class config)))
+   "\nDiscoverableTimeout = " (number->string
+                               (bluetooth-configuration-discoverable-timeout
+                                config))
+   "\nAlwaysPairable = " (bool (bluetooth-configuration-always-pairable?
+                                config))
+   "\nPairableTimeout = " (number->string
+                           (bluetooth-configuration-pairable-timeout
+                            config))
+   (if (bluetooth-configuration-device-id config)
+       (string-append "\nDeviceID = " (bluetooth-configuration-device-id config))
+       "")
+   "\nReverseServiceDiscovery = " (bool
+                                   (bluetooth-configuration-reverse-service-discovery
+                                    config))
+   "\nNameResolving = " (bool (bluetooth-configuration-name-resolving? config))
+   "\nDebugKeys = " (bool (bluetooth-configuration-debug-keys? config))
+   "\nControllerMode = " (symbol->string
+                          (bluetooth-configuration-controller-mode config))
+   "\nMultiProfile = " (symbol->string (bluetooth-configuration-multi-profile
+                                        config))
+   "\nFastConnectable = " (bool (bluetooth-configuration-fast-connectable? config))
+   "\nPrivacy = " (symbol->string (bluetooth-configuration-privacy config))
+   "\nJustWorksRepairing = " (symbol->string
+                              (bluetooth-configuration-just-works-repairing config))
+   "\nTemporaryTimeout = " (number->string
+                            (bluetooth-configuration-temporary-timeout config))
+   "\nRefreshDiscovery = " (bool (bluetooth-configuration-refresh-discovery config))
+   "\nExperimental = " (let ((experimental (bluetooth-configuration-experimental config)))
+                         (cond ((or (eq? experimental #t)
+                                    (eq? experimental #f)) (bool experimental))
+                               ((list? experimental)
+                                (string-join (map uuid->string experimental) ","))))
+   "\nRemoteNameRequestRetryDelay = " (number->string
+                                       (bluetooth-configuration-remote-name-request-retry-delay
+                                        config))
+   "\n[BR]"
+   (if (bluetooth-configuration-page-scan-type config)
+       (string-append
+        "\nPageScanType = "
+        (number->string (bluetooth-configuration-page-scan-type config)))
+       "")
+   (if (bluetooth-configuration-page-scan-interval config)
+       (string-append
+        "\nPageScanInterval = "
+        (number->string (bluetooth-configuration-page-scan-interval config)))
+       "")
+   (if (bluetooth-configuration-page-scan-window config)
+       (string-append
+        "\nPageScanWindow = "
+        (number->string (bluetooth-configuration-page-scan-window config)))
+       "")
+   (if (bluetooth-configuration-inquiry-scan-type config)
+       (string-append
+        "\nInquiryScanType = "
+        (number->string (bluetooth-configuration-inquiry-scan-type config)))
+       "")
+   (if (bluetooth-configuration-inquiry-scan-interval config)
+       (string-append
+        "\nInquiryScanInterval = "
+        (number->string (bluetooth-configuration-inquiry-scan-interval config)))
+       "")
+   (if (bluetooth-configuration-inquiry-scan-window config)
+       (string-append
+        "\nInquiryScanWindow = "
+        (number->string (bluetooth-configuration-inquiry-scan-window config)))
+       "")
+   (if (bluetooth-configuration-link-supervision-timeout config)
+       (string-append
+        "\nLinkSupervisionTimeout = "
+        (number->string (bluetooth-configuration-link-supervision-timeout config)))
+       "")
+   (if (bluetooth-configuration-page-timeout config)
+       (string-append
+        "\nPageTimeout = "
+        (number->string (bluetooth-configuration-page-timeout config)))
+       "")
+   (if (bluetooth-configuration-min-sniff-interval config)
+       (string-append
+        "\nMinSniffInterval = "
+        (number->string (bluetooth-configuration-min-sniff-interval config)))
+       "")
+   (if (bluetooth-configuration-max-sniff-interval config)
+       (string-append
+        "\nMaxSniffInterval = "
+        (number->string (bluetooth-configuration-max-sniff-interval config)))
+       "")
+
+   "\n[LE]"
+   (if (bluetooth-configuration-min-advertisement-interval config)
+       (string-append
+        "\nMinAdvertisementInterval = "
+        (number->string (bluetooth-configuration-min-advertisement-interval config)))
+       "")
+   (if (bluetooth-configuration-max-advertisement-interval config)
+       (string-append
+        "\nMaxAdvertisementInterval = "
+        (number->string (bluetooth-configuration-max-advertisement-interval config)))
+       "")
+   (if (bluetooth-configuration-multi-advertisement-rotation-interval config)
+       (string-append
+        "\nMultiAdvertisementRotationInterval = "
+        (number->string
+         (bluetooth-configuration-multi-advertisement-rotation-interval config)))
+       "")
+   (if (bluetooth-configuration-scan-interval-auto-connect config)
+       (string-append
+        "\nScanIntervalAutoConnect = "
+        (number->string (bluetooth-configuration-scan-interval-auto-connect config)))
+       "")
+   (if (bluetooth-configuration-scan-window-auto-connect config)
+       (string-append
+        "\nScanWindowAutoConnect = "
+        (number->string (bluetooth-configuration-scan-window-auto-connect config)))
+       "")
+   (if (bluetooth-configuration-scan-interval-suspend config)
+       (string-append
+        "\nScanIntervalSuspend = "
+        (number->string (bluetooth-configuration-scan-interval-suspend config)))
+       "")
+   (if (bluetooth-configuration-scan-window-suspend config)
+       (string-append
+        "\nScanWindowSuspend = "
+        (number->string (bluetooth-configuration-scan-window-suspend config)))
+       "")
+   (if (bluetooth-configuration-scan-interval-discovery config)
+       (string-append
+        "\nScanIntervalDiscovery = "
+        (number->string (bluetooth-configuration-scan-interval-discovery config)))
+       "")
+   (if (bluetooth-configuration-scan-window-discovery config)
+       (string-append
+        "\nScanWindowDiscovery = "
+        (number->string (bluetooth-configuration-scan-window-discovery config)))
+       "")
+   (if (bluetooth-configuration-scan-interval-adv-monitor config)
+       (string-append
+        "\nScanIntervalAdvMonitor = "
+        (number->string (bluetooth-configuration-scan-interval-adv-monitor config)))
+       "")
+   (if (bluetooth-configuration-scan-window-adv-monitor config)
+       (string-append
+        "\nScanWindowAdvMonitor = "
+        (number->string (bluetooth-configuration-scan-window-adv-monitor config)))
+       "")
+   (if (bluetooth-configuration-scan-interval-connect config)
+       (string-append
+        "\nScanIntervalConnect = "
+        (number->string (bluetooth-configuration-scan-interval-connect config)))
+       "")
+   (if (bluetooth-configuration-scan-window-connect config)
+       (string-append
+        "\nScanWindowConnect = "
+        (number->string (bluetooth-configuration-scan-window-connect config)))
+       "")
+   (if (bluetooth-configuration-min-connection-interval config)
+       (string-append
+        "\nMinConnectionInterval = "
+        (number->string (bluetooth-configuration-min-connection-interval config)))
+       "")
+   (if (bluetooth-configuration-max-connection-interval config)
+       (string-append
+        "\nMaxConnectionInterval = "
+        (number->string (bluetooth-configuration-max-connection-interval config)))
+       "")
+   (if (bluetooth-configuration-connection-latency config)
+       (string-append
+        "\nConnectionLatency = "
+        (number->string (bluetooth-configuration-connection-latency config)))
+       "")
+   (if (bluetooth-configuration-connection-supervision-timeout config)
+       (string-append
+        "\nConnectionSupervisionTimeout = "
+        (number->string (bluetooth-configuration-connection-supervision-timeout config)))
+       "")
+   (if (bluetooth-configuration-autoconnect-timeout config)
+       (string-append
+        "\nAutoconnecttimeout = "
+        (number->string (bluetooth-configuration-autoconnect-timeout config)))
+       "")
+   "\nAdvMonAllowlistScanDuration = " (number->string
+                                       (bluetooth-configuration-adv-mon-allowlist-scan-duration
+                                        config))
+   "\nAdvMonNoFilterScanDuration = " (number->string
+                                      (bluetooth-configuration-adv-mon-no-filter-scan-duration
+                                       config))
+   "\nEnableAdvMonInterleaveScan = " (number->string
+                                      (if (eq? #t
+                                               (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))
+   "\nExchangeMTU = " (number->string (bluetooth-configuration-exchange-mtu config))
+   "\nChannels = " (number->string (bluetooth-configuration-att-channels config))
+
+   "\n[AVDTP]"
+   "\nSessionMode = " (symbol->string (bluetooth-configuration-session-mode config))
+   "\nStreamMode = " (symbol->string (bluetooth-configuration-stream-mode config))
+
+   "\n[Policy]"
+   (let ((uuids (bluetooth-configuration-reconnect-uuids config)))
+     (if (not (eq? '() uuids))
+         (string-append
+          "\nReconnectUUIDs = "
+          (string-join (map uuid->string uuids) ","))
+         ""))
+   "\nReconnectAttempts = " (number->string
+                             (bluetooth-configuration-reconnect-attempts config))
+   "\nReconnectIntervals = " (string-join
+                              (map number->string
+                                   (bluetooth-configuration-reconnect-intervals
+                                    config))
+                              ",")
+   "\nAutoEnable = " (bool (bluetooth-configuration-auto-enable?
+                            config))
+   "\nResumeDelay = " (number->string (bluetooth-configuration-resume-delay config))
+
+   "\n[AdvMon]"
+   "\nRSSISamplingPeriod = " (string-append
+                              "0x"
+                              (format #f "~2,'0x"
+                                      (bluetooth-configuration-rssi-sampling-period config)))))
 
 (define (bluetooth-directory config)
   (computed-file "etc-bluetooth"
diff --git a/gnu/services/monitoring.scm b/gnu/services/monitoring.scm
index 92c49c513b..0e6aed2cac 100644
--- a/gnu/services/monitoring.scm
+++ b/gnu/services/monitoring.scm
@@ -392,6 +392,7 @@ configuration file."))
         (config-file (zabbix-server-config-file config)))
     (list (shepherd-service
            (provision '(zabbix-server))
+           (requirement '(user-processes))
            (documentation "Run the Zabbix server daemon.")
            (actions (zabbix-server-actions zabbix-server config-file))
            (start #~(make-forkexec-constructor
@@ -516,6 +517,7 @@ configuration file."))
   "Return a <shepherd-service> for Zabbix agent with CONFIG."
   (list (shepherd-service
          (provision '(zabbix-agent))
+         (requirement '(user-processes))
          (documentation "Run Zabbix agent daemon.")
          (start #~(make-forkexec-constructor
                    (list #$(file-append (zabbix-agent-configuration-zabbix-agent config)
diff --git a/gnu/services/pm.scm b/gnu/services/pm.scm
index 3da3c0b961..e48236dbca 100644
--- a/gnu/services/pm.scm
+++ b/gnu/services/pm.scm
@@ -435,6 +435,8 @@ shutdown on system startup."))
 (define-record-type* <thermald-configuration>
   thermald-configuration make-thermald-configuration
   thermald-configuration?
+  (adaptive?           thermald-adaptive?              ;boolean
+                       (default #f))
   (ignore-cpuid-check? thermald-ignore-cpuid-check?    ;boolean
                        (default #f))
   (thermald            thermald-thermald               ;file-like
@@ -448,6 +450,9 @@ shutdown on system startup."))
     (start #~(make-forkexec-constructor
               '(#$(file-append (thermald-thermald config) "/sbin/thermald")
                 "--no-daemon"
+                #$@(if (thermald-adaptive? config)
+                       '("--adaptive")
+                       '())
                 #$@(if (thermald-ignore-cpuid-check? config)
                        '("--ignore-cpuid-check")
                        '()))))
diff --git a/gnu/services/sound.scm b/gnu/services/sound.scm
index 03e62a1e36..8410ba2418 100644
--- a/gnu/services/sound.scm
+++ b/gnu/services/sound.scm
@@ -2,6 +2,7 @@
 ;;; Copyright © 2018, 2020 Oleg Pykhalov <go.wigust@gmail.com>
 ;;; Copyright © 2020 Liliana Marie Prikler <liliana.prikler@gmail.com>
 ;;; Copyright © 2020 Marius Bakke <mbakke@fastmail.com>
+;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -25,14 +26,17 @@
   #:use-module (gnu services)
   #:use-module (gnu system pam)
   #:use-module (gnu system shadow)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
   #:use-module (guix packages)
   #:use-module (guix records)
   #:use-module (guix store)
+  #:use-module (guix ui)
   #:use-module (gnu packages audio)
   #:use-module (gnu packages linux)
   #:use-module (gnu packages pulseaudio)
   #:use-module (ice-9 match)
+  #:use-module (srfi srfi-1)
   #:export (alsa-configuration
             alsa-service-type
 
@@ -115,16 +119,18 @@ ctl.!default {
 (define-record-type* <pulseaudio-configuration>
   pulseaudio-configuration make-pulseaudio-configuration
   pulseaudio-configuration?
-  (client-conf pulseaudio-client-conf
+  (client-conf pulseaudio-configuration-client-conf
                (default '()))
-  (daemon-conf pulseaudio-daemon-conf
+  (daemon-conf pulseaudio-configuration-daemon-conf
                ;; Flat volumes may cause unpleasant experiences to users
                ;; when applications inadvertently max out the system volume
                ;; (see e.g. <https://bugs.gnu.org/38172>).
                (default '((flat-volumes . no))))
-  (script-file pulseaudio-script-file
+  (script-file pulseaudio-configuration-script-file
                (default (file-append pulseaudio "/etc/pulse/default.pa")))
-  (system-script-file pulseaudio-system-script-file
+  (extra-script-files pulseaudio-configuration-extra-script-files
+                      (default '()))
+  (system-script-file pulseaudio-configuration-system-script-file
                       (default
                         (file-append pulseaudio "/etc/pulse/system.pa"))))
 
@@ -138,20 +144,77 @@ ctl.!default {
 (define pulseaudio-environment
   (match-lambda
     (($ <pulseaudio-configuration> client-conf daemon-conf default-script-file)
-     `(("PULSE_CONFIG" . ,(apply mixed-text-file "daemon.conf"
-                                 "default-script-file = " default-script-file "\n"
-                                 (map pulseaudio-conf-entry daemon-conf)))
-       ("PULSE_CLIENTCONFIG" . ,(apply mixed-text-file "client.conf"
-                                       (map pulseaudio-conf-entry client-conf)))))))
+     ;; These config files kept at a fixed location, so that the following
+     ;; environment values are stable and do not require the user to reboot to
+     ;; effect their PulseAudio configuration changes.
+     '(("PULSE_CONFIG" . "/etc/pulse/daemon.conf")
+       ("PULSE_CLIENTCONFIG" . "/etc/pulse/client.conf")))))
+
+(define (extra-script-files->file-union extra-script-files)
+  "Return a G-exp obtained by processing EXTRA-SCRIPT-FILES with FILE-UNION."
+
+  (define (file-like->name file)
+    (match file
+      ((? local-file?)
+       (local-file-name file))
+      ((? plain-file?)
+       (plain-file-name file))
+      ((? computed-file?)
+       (computed-file-name file))
+      (_ (leave (G_ "~a is not a local-file, plain-file or \
+computed-file object~%") file))))
+
+  (define (assert-pulseaudio-script-file-name name)
+    (unless (string-suffix? ".pa" name)
+      (leave (G_ "`~a' lacks the required `.pa' file name extension~%") name))
+    name)
+
+  (let ((labels (map (compose assert-pulseaudio-script-file-name
+                              file-like->name)
+                     extra-script-files)))
+    (file-union "default.pa.d" (zip labels extra-script-files))))
+
+(define (append-include-directive script-file)
+  "Append an include directive to source scripts under /etc/pulse/default.pa.d."
+  (computed-file "default.pa"
+                 #~(begin
+                     (use-modules (ice-9 textual-ports))
+                     (define script-text
+                       (call-with-input-file #$script-file get-string-all))
+                     (call-with-output-file #$output
+                       (lambda (port)
+                         (format port (string-append script-text "
+### Added by Guix to include scripts specified in extra-script-files.
+.nofail
+.include /etc/pulse/default.pa.d~%")))))))
 
 (define pulseaudio-etc
   (match-lambda
-    (($ <pulseaudio-configuration> _ _ default-script-file system-script-file)
+    (($ <pulseaudio-configuration> client-conf daemon-conf default-script-file
+                                   extra-script-files system-script-file)
      `(("pulse"
         ,(file-union
           "pulse"
-          `(("default.pa" ,default-script-file)
-            ("system.pa" ,system-script-file))))))))
+          `(("default.pa"
+             ,(if (null? extra-script-files)
+                  default-script-file
+                  (append-include-directive default-script-file)))
+            ("system.pa" ,system-script-file)
+            ,@(if (null? extra-script-files)
+                  '()
+                  `(("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))))))))))))
 
 (define pulseaudio-service-type
   (service-type
diff --git a/gnu/services/ssh.scm b/gnu/services/ssh.scm
index 97f74a00f7..5c8fe4eef4 100644
--- a/gnu/services/ssh.scm
+++ b/gnu/services/ssh.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2014-2019, 2022 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016 David Craven <david@craven.ch>
 ;;; Copyright © 2016 Julien Lepiller <julien@lepiller.eu>
 ;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
@@ -348,10 +348,14 @@ The other options should be self-descriptive."
                          (default ""))
 
   ;; list of user-name/file-like tuples
-  (authorized-keys       openssh-authorized-keys
+  (authorized-keys       openssh-configuration-authorized-keys
                          (default '()))
 
   ;; Boolean
+  (generate-host-keys?   openssh-configuration-generate-host-keys?
+                         (default #t))
+
+  ;; Boolean
   ;; XXX: This should really be handled in an orthogonal way, for instance as
   ;; proposed in <https://bugs.gnu.org/27155>.  Keep it internal/undocumented
   ;; for now.
@@ -392,7 +396,7 @@ The other options should be self-descriptive."
             (unless (= ENOENT (system-error-errno args))
               (apply throw args))))
         (copy-recursively #$(authorized-key-directory
-                             (openssh-authorized-keys config))
+                             (openssh-configuration-authorized-keys config))
                           "/etc/ssh/authorized_keys.d")
 
         (chmod "/etc/ssh/authorized_keys.d" #o555)
@@ -402,9 +406,10 @@ The other options should be self-descriptive."
             (unless (file-exists? lastlog)
               (touch lastlog))))
 
-        ;; Generate missing host keys.
-        (system* (string-append #$(openssh-configuration-openssh config)
-                                "/bin/ssh-keygen") "-A"))))
+        (when #$(openssh-configuration-generate-host-keys? config)
+          ;; Generate missing host keys.
+          (system* (string-append #$(openssh-configuration-openssh config)
+                                  "/bin/ssh-keygen") "-A")))))
 
 (define (authorized-key-directory keys)
   "Return a directory containing the authorized keys specified in KEYS, a list
@@ -536,10 +541,11 @@ of user-name/file-like tuples."
   (openssh-configuration
    (inherit config)
    (authorized-keys
-    (match (openssh-authorized-keys config)
+    (match (openssh-configuration-authorized-keys config)
       (((users _ ...) ...)
        ;; Build a user/key-list mapping.
-       (let ((user-keys (alist->vhash (openssh-authorized-keys config))))
+       (let ((user-keys (alist->vhash
+                         (openssh-configuration-authorized-keys config))))
          ;; Coalesce the key lists associated with each user.
          (map (lambda (user)
                 `(,user
diff --git a/gnu/services/virtualization.scm b/gnu/services/virtualization.scm
index 66ae1a1565..70d4d6c34c 100644
--- a/gnu/services/virtualization.scm
+++ b/gnu/services/virtualization.scm
@@ -1,6 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Ryan Moe <ryan.moe@gmail.com>
-;;; Copyright © 2018, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018, 2020-2022 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
 ;;; Copyright © 2021 Timotej Lazar <timotej.lazar@araneo.si>
 ;;;
@@ -866,23 +866,23 @@ functionality of the kernel Linux.")))
    "Path to device or socket used to communicate with the host.  If not
 specified, the QEMU default path is used."))
 
-(define qemu-guest-agent-shepherd-service
-  (match-lambda
-    (($ <qemu-guest-agent-configuration> qemu device)
-     (list
-      (shepherd-service
-       (provision '(qemu-guest-agent))
-       (documentation "Run the QEMU guest agent.")
-       (start #~(make-forkexec-constructor
-                 `(,(string-append #$qemu "/bin/qemu-ga") "--daemon"
-                   "--pidfile=/var/run/qemu-ga.pid"
-                   "--statedir=/var/run"
-                   ,@(if #$device
-                         (list (string-append "--path=" #$device))
-                         '()))
-                 #:pid-file "/var/run/qemu-ga.pid"
-                 #:log-file "/var/log/qemu-ga.log"))
-       (stop #~(make-kill-destructor)))))))
+(define (qemu-guest-agent-shepherd-service config)
+  (let ((qemu   (qemu-guest-agent-configuration-qemu config))
+        (device (qemu-guest-agent-configuration-device config)))
+    (list
+     (shepherd-service
+      (provision '(qemu-guest-agent))
+      (documentation "Run the QEMU guest agent.")
+      (start #~(make-forkexec-constructor
+                `(,(string-append #$qemu "/bin/qemu-ga") "--daemon"
+                  "--pidfile=/var/run/qemu-ga.pid"
+                  "--statedir=/var/run"
+                  ,@(if #$device
+                        (list (string-append "--path=" #$device))
+                        '()))
+                #:pid-file "/var/run/qemu-ga.pid"
+                #:log-file "/var/log/qemu-ga.log"))
+      (stop #~(make-kill-destructor))))))
 
 (define qemu-guest-agent-service-type
   (service-type
@@ -946,12 +946,20 @@ can only be accessed by their host.")))
 that will be listening to receive secret keys on port 1004, TCP."
   (operating-system
     (inherit os)
-    ;; Arrange so that the secret service activation snippet shows up before
-    ;; the OpenSSH and Guix activation snippets.  That way, we receive OpenSSH
-    ;; and Guix keys before the activation snippets try to generate fresh keys
-    ;; for nothing.
-    (services (append (operating-system-user-services os)
-                      (list (service secret-service-type 1004))))))
+    (services
+     ;; Turn off SSH and Guix key generation that normally happens during
+     ;; activation: that requires entropy and thus takes time during boot, and
+     ;; those keys are going to be overwritten by secrets received from the
+     ;; host anyway.
+     (cons (service secret-service-type 1004)
+           (modify-services (operating-system-user-services os)
+             (openssh-service-type
+              config => (openssh-configuration
+                         (inherit config)
+                         (generate-host-keys? #f)))
+             (guix-service-type
+              config => (guix-configuration
+                         (generate-substitute-key? #f))))))))
 
 
 ;;;
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm
index 3e370ba4be..b24e9cffb3 100644
--- a/gnu/services/vpn.scm
+++ b/gnu/services/vpn.scm
@@ -9,6 +9,7 @@
 ;;; Copyright © 2021 Raghav Gururajan <rg@raghavgururajan.name>
 ;;; Copyright © 2021 jgart <jgart@dismail.de>
 ;;; Copyright © 2021 Nathan Dehnel <ncdehnel@gmail.com>
+;;; Copyright © 2022 Cameron V Chaparro <cameron@cameronchaparro.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -494,7 +495,8 @@ is truncated and rewritten every minute.")
                        (list (string-append #$openvpn "/sbin/openvpn")
                              "--writepid" #$pid-file "--config" #$config-file
                              "--daemon")
-                       #:pid-file #$pid-file))
+                       #:pid-file #$pid-file
+                       #:log-file #$log-file))
              (stop #~(make-kill-destructor)))))))
 
 (define %openvpn-accounts
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index 3fe58d98e6..2c7df19222 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -1782,6 +1782,12 @@ WSGIPassAuthorization On
          (home-directory "/var/empty")
          (shell (file-append shadow "/sbin/nologin")))))
 
+(define %mumi-log "/var/log/mumi.log")
+
+(define %mumi-mailer-log "/var/log/mumi.mailer.log")
+
+(define %mumi-worker-log "/var/log/mumi.worker.log")
+
 (define (mumi-shepherd-services config)
   (define environment
     #~(list "LC_ALL=en_US.utf8"
@@ -1799,7 +1805,7 @@ WSGIPassAuthorization On
                         ,@(if #$mailer? '() '("--disable-mailer")))
                       #:environment-variables #$environment
                       #:user "mumi" #:group "mumi"
-                      #:log-file "/var/log/mumi.log"))
+                      #:log-file #$%mumi-log))
             (stop #~(make-kill-destructor)))
            (shepherd-service
             (provision '(mumi-worker))
@@ -1809,7 +1815,7 @@ WSGIPassAuthorization On
                       '(#$(file-append mumi "/bin/mumi") "worker")
                       #:environment-variables #$environment
                       #:user "mumi" #:group "mumi"
-                      #:log-file "/var/log/mumi.worker.log"))
+                      #:log-file #$%mumi-worker-log))
             (stop #~(make-kill-destructor)))
            (shepherd-service
             (provision '(mumi-mailer))
@@ -1825,9 +1831,15 @@ WSGIPassAuthorization On
                               '()))
                       #:environment-variables #$environment
                       #:user "mumi" #:group "mumi"
-                      #:log-file "/var/log/mumi.mailer.log"))
+                      #:log-file #$%mumi-mailer-log))
             (stop #~(make-kill-destructor)))))))
 
+(define %mumi-log-rotations
+  (list (log-rotation
+         (files (list %mumi-log
+                      %mumi-mailer-log
+                      %mumi-worker-log)))))
+
 (define mumi-service-type
   (service-type
    (name 'mumi)
@@ -1837,7 +1849,9 @@ WSGIPassAuthorization On
           (service-extension account-service-type
                              (const %mumi-accounts))
           (service-extension shepherd-root-service-type
-                             mumi-shepherd-services)))
+                             mumi-shepherd-services)
+          (service-extension rottlog-service-type
+                             (const %mumi-log-rotations))))
    (description
     "Run Mumi, a Web interface to the Debbugs bug-tracking server.")
    (default-value