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/authentication.scm22
-rw-r--r--gnu/services/base.scm3
-rw-r--r--gnu/services/cuirass.scm50
-rw-r--r--gnu/services/cups.scm12
-rw-r--r--gnu/services/databases.scm113
-rw-r--r--gnu/services/dbus.scm37
-rw-r--r--gnu/services/dns.scm35
-rw-r--r--gnu/services/file-sharing.scm804
-rw-r--r--gnu/services/guix.scm88
-rw-r--r--gnu/services/networking.scm13
-rw-r--r--gnu/services/shepherd.scm10
-rw-r--r--gnu/services/sysctl.scm10
-rw-r--r--gnu/services/virtualization.scm227
-rw-r--r--gnu/services/vpn.scm138
-rw-r--r--gnu/services/web.scm112
-rw-r--r--gnu/services/xorg.scm37
16 files changed, 1421 insertions, 290 deletions
diff --git a/gnu/services/authentication.scm b/gnu/services/authentication.scm
index 73969a5a6d..d7efc48cd0 100644
--- a/gnu/services/authentication.scm
+++ b/gnu/services/authentication.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2018 Danny Milosavljevic <dannym@scratchpost.org>
 ;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -31,6 +32,7 @@
   #:use-module (guix gexp)
   #:use-module (guix records)
   #:use-module (guix packages)
+  #:use-module (guix modules)
   #:use-module (ice-9 match)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
@@ -521,6 +523,16 @@ password.")
 (define (pam-ldap-pam-services config)
   (list (pam-ldap-pam-service config)))
 
+(define %nslcd-activation
+  (with-imported-modules (source-module-closure '((gnu build activation)))
+    #~(begin
+        (use-modules (gnu build activation))
+        (let ((rundir "/var/run/nslcd")
+              (user (getpwnam "nslcd")))
+          (mkdir-p/perms rundir user #o755)
+          (when (file-exists? "/etc/nslcd.conf")
+            (chmod "/etc/nslcd.conf" #o400))))))
+
 (define nslcd-service-type
   (service-type
    (name 'nslcd)
@@ -531,15 +543,7 @@ password.")
           (service-extension etc-service-type
                              nslcd-etc-service)
           (service-extension activation-service-type
-                             (const #~(begin
-                                        (use-modules (guix build utils))
-                                        (let ((rundir "/var/run/nslcd")
-                                              (user (getpwnam "nslcd")))
-                                          (mkdir-p rundir)
-                                          (chown rundir (passwd:uid user) (passwd:gid user))
-                                          (chmod rundir #o755)
-                                          (when (file-exists? "/etc/nslcd.conf")
-                                            (chmod "/etc/nslcd.conf" #o400))))))
+                             (const %nslcd-activation))
           (service-extension pam-root-service-type
                              pam-ldap-pam-services)
           (service-extension nscd-service-type
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index f6a490f712..f50bcfdcb4 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -35,6 +35,7 @@
   #:use-module (gnu services)
   #:use-module (gnu services admin)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu services sysctl)
   #:use-module (gnu system pam)
   #:use-module (gnu system shadow)                ; 'user-account', etc.
   #:use-module (gnu system uuid)
@@ -2532,6 +2533,8 @@ to handle."
                  (udev-configuration
                    (rules (list lvm2 fuse alsa-utils crda))))
 
+        (service sysctl-service-type)
+
         (service special-files-service-type
                  `(("/bin/sh" ,(file-append bash "/bin/sh"))
                    ("/usr/bin/env" ,(file-append coreutils "/bin/env"))))))
diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm
index d291d494fc..317d877459 100644
--- a/gnu/services/cuirass.scm
+++ b/gnu/services/cuirass.scm
@@ -22,11 +22,13 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services cuirass)
+  #:use-module (guix channels)
   #:use-module (guix gexp)
   #:use-module (guix records)
   #:use-module (guix utils)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages ci)
+  #:use-module (gnu packages databases)
   #:use-module (gnu packages version-control)
   #:use-module (gnu services)
   #:use-module (gnu services base)
@@ -34,6 +36,8 @@
   #:use-module (gnu services shepherd)
   #:use-module (gnu services admin)
   #:use-module (gnu system shadow)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (<cuirass-remote-server-configuration>
             cuirass-remote-server-configuration
             cuirass-remote-server-configuration?
@@ -93,6 +97,8 @@
                     (default "cuirass"))
   (interval         cuirass-configuration-interval ;integer (seconds)
                     (default 60))
+  (parameters       cuirass-configuration-parameters ;string
+                    (default #f))
   (remote-server    cuirass-configuration-remote-server
                     (default #f))
   (database         cuirass-configuration-database ;string
@@ -109,8 +115,6 @@
                     (default #f))
   (fallback?        cuirass-configuration-fallback? ;boolean
                     (default #f))
-  (zabbix-uri       cuirass-configuration-zabbix-uri ;string
-                    (default #f))
   (extra-options    cuirass-configuration-extra-options
                     (default '())))
 
@@ -123,6 +127,7 @@
         (user             (cuirass-configuration-user config))
         (group            (cuirass-configuration-group config))
         (interval         (cuirass-configuration-interval config))
+        (parameters       (cuirass-configuration-parameters config))
         (remote-server    (cuirass-configuration-remote-server config))
         (database         (cuirass-configuration-database config))
         (port             (cuirass-configuration-port config))
@@ -131,12 +136,11 @@
         (use-substitutes? (cuirass-configuration-use-substitutes? config))
         (one-shot?        (cuirass-configuration-one-shot? config))
         (fallback?        (cuirass-configuration-fallback? config))
-        (zabbix-uri       (cuirass-configuration-zabbix-uri config))
         (extra-options    (cuirass-configuration-extra-options config)))
     `(,(shepherd-service
         (documentation "Run Cuirass.")
         (provision '(cuirass))
-        (requirement '(guix-daemon postgres networking))
+        (requirement '(guix-daemon postgres postgres-roles networking))
         (start #~(make-forkexec-constructor
                   (list (string-append #$cuirass "/bin/cuirass")
                         "--cache-directory" #$cache-directory
@@ -144,15 +148,15 @@
                         #$(scheme-file "cuirass-specs.scm" specs)
                         "--database" #$database
                         "--interval" #$(number->string interval)
+                        #$@(if parameters
+                               (list (string-append
+                                      "--parameters="
+                                      parameters))
+                               '())
                         #$@(if remote-server '("--build-remote") '())
                         #$@(if use-substitutes? '("--use-substitutes") '())
                         #$@(if one-shot? '("--one-shot") '())
                         #$@(if fallback? '("--fallback") '())
-                        #$@(if zabbix-uri
-                               (list (string-append
-                                      "--zabbix-uri="
-                                      zabbix-uri))
-                               '())
                         #$@extra-options)
 
                   #:environment-variables
@@ -167,7 +171,7 @@
       ,(shepherd-service
         (documentation "Run Cuirass web interface.")
         (provision '(cuirass-web))
-        (requirement '(guix-daemon postgres networking))
+        (requirement '(cuirass))
         (start #~(make-forkexec-constructor
                   (list (string-append #$cuirass "/bin/cuirass")
                         "--cache-directory" #$cache-directory
@@ -176,6 +180,11 @@
                         "--port" #$(number->string port)
                         "--listen" #$host
                         "--interval" #$(number->string interval)
+                        #$@(if parameters
+                               (list (string-append
+                                      "--parameters="
+                                      parameters))
+                               '())
                         #$@(if use-substitutes? '("--use-substitutes") '())
                         #$@(if fallback? '("--fallback") '())
                         #$@extra-options)
@@ -192,7 +201,7 @@
                (shepherd-service
                 (documentation "Run Cuirass remote build server.")
                 (provision '(cuirass-remote-server))
-                (requirement '(avahi-daemon cuirass guix-daemon networking))
+                (requirement '(avahi-daemon cuirass))
                 (start #~(make-forkexec-constructor
                           (list (string-append #$cuirass "/bin/remote-server")
                                 (string-append "--database=" #$database)
@@ -208,6 +217,11 @@
                                               "--publish-port="
                                               (number->string publish-port)))
                                        '())
+                                #$@(if parameters
+                                       (list (string-append
+                                              "--parameters="
+                                              parameters))
+                                       '())
                                 #$@(if trigger-url
                                        (list
                                         (string-append
@@ -256,8 +270,6 @@
          (remote-cache   (and remote-server
                               (cuirass-remote-server-configuration-cache
                                remote-server)))
-         (db             (dirname
-                          (cuirass-configuration-database config)))
          (user           (cuirass-configuration-user config))
          (log            "/var/log/cuirass")
          (group          (cuirass-configuration-group config)))
@@ -266,7 +278,6 @@
           (use-modules (guix build utils))
 
           (mkdir-p #$cache)
-          (mkdir-p #$db)
           (mkdir-p #$log)
 
           (when #$remote-cache
@@ -275,7 +286,6 @@
           (let ((uid (passwd:uid (getpw #$user)))
                 (gid (group:gid (getgr #$group))))
             (chown #$cache uid gid)
-            (chown #$db uid gid)
             (chown #$log uid gid)
 
             (when #$remote-cache
@@ -299,6 +309,8 @@
      (service-extension activation-service-type cuirass-activation)
      (service-extension shepherd-root-service-type cuirass-shepherd-service)
      (service-extension account-service-type cuirass-account)
+     ;; Make sure postgresql and postgresql-role are instantiated.
+     (service-extension postgresql-service-type (const #t))
      (service-extension postgresql-role-service-type
                         cuirass-postgresql-role)))
    (description
@@ -311,6 +323,8 @@
                     (default cuirass))
   (workers          cuirass-remote-worker-workers ;int
                     (default 1))
+  (server           cuirass-remote-worker-server ;string
+                    (default #f))
   (systems          cuirass-remote-worker-systems ;list
                     (default (list (%current-system))))
   (log-file         cuirass-remote-worker-log-file ;string
@@ -326,7 +340,8 @@
   "Return a <shepherd-service> for the Cuirass remote worker service with
 CONFIG."
   (match-record config <cuirass-remote-worker-configuration>
-    (cuirass workers systems log-file publish-port public-key private-key)
+    (cuirass workers server systems log-file publish-port
+             public-key private-key)
     (list (shepherd-service
            (documentation "Run Cuirass remote build worker.")
            (provision '(cuirass-remote-worker))
@@ -335,6 +350,9 @@ CONFIG."
                      (list (string-append #$cuirass "/bin/remote-worker")
                            (string-append "--workers="
                                           #$(number->string workers))
+                           #$@(if server
+                                  (list (string-append "--server=" server))
+                                  '())
                            #$@(if systems
                                   (list (string-append
                                          "--systems="
diff --git a/gnu/services/cups.scm b/gnu/services/cups.scm
index 17ed04e58b..20e3917b93 100644
--- a/gnu/services/cups.scm
+++ b/gnu/services/cups.scm
@@ -4,6 +4,7 @@
 ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
 ;;; Copyright © 2019 Alex Griffin <a@ajgrf.com>
 ;;; Copyright © 2019 Tobias Geerinckx-Rice <me@tobias.gr>
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -31,6 +32,7 @@
   #:use-module (guix packages)
   #:use-module (guix records)
   #:use-module (guix gexp)
+  #:use-module (guix modules)
   #:use-module (ice-9 match)
   #:use-module ((srfi srfi-1) #:select (append-map find))
   #:export (cups-service-type
@@ -871,13 +873,11 @@ IPP specifications.")
 
 (define %cups-activation
   ;; Activation gexp.
-  (with-imported-modules '((guix build utils))
+  (with-imported-modules (source-module-closure '((gnu build activation)
+                                                  (guix build utils)))
     #~(begin
-        (use-modules (guix build utils))
-        (define (mkdir-p/perms directory owner perms)
-          (mkdir-p directory)
-          (chown directory (passwd:uid owner) (passwd:gid owner))
-          (chmod directory perms))
+        (use-modules (gnu build activation)
+                     (guix build utils))
         (define (build-subject parameters)
           (string-concatenate
            (map (lambda (pair)
diff --git a/gnu/services/databases.scm b/gnu/services/databases.scm
index d908b86af8..a841e7a50e 100644
--- a/gnu/services/databases.scm
+++ b/gnu/services/databases.scm
@@ -79,13 +79,6 @@
             memcached-configuration-udp-port
             memcached-configuration-additional-options
 
-            mongodb-configuration
-            mongodb-configuration?
-            mongodb-configuration-mongodb
-            mongodb-configuration-config-file
-            mongodb-configuration-data-directory
-            mongodb-service-type
-
             mysql-service
             mysql-service-type
             mysql-configuration
@@ -331,7 +324,9 @@ host	all	all	::1/128 	md5"))
                              (const %postgresql-accounts))
           (service-extension
            profile-service-type
-           (compose list postgresql-configuration-postgresql))))))
+           (compose list postgresql-configuration-postgresql))))
+   (default-value (postgresql-configuration
+                   (postgresql postgresql-10)))))
 
 (define-deprecated (postgresql-service #:key (postgresql postgresql)
                                        (port 5432)
@@ -408,13 +403,8 @@ rolname = '" ,name "')) as not_exists;\n"
 
   (let ((host (postgresql-role-configuration-host config))
         (roles (postgresql-role-configuration-roles config)))
-    (program-file
-     "postgresql-create-roles"
-     #~(begin
-         (let ((psql #$(file-append postgresql "/bin/psql")))
-           (execl psql psql "-a"
-                  "-h" #$host
-                  "-f" #$(roles->queries roles)))))))
+    #~(let ((psql #$(file-append postgresql "/bin/psql")))
+        (list psql "-a" "-h" #$host "-f" #$(roles->queries roles)))))
 
 (define (postgresql-role-shepherd-service config)
   (match-record config <postgresql-role-configuration>
@@ -423,10 +413,14 @@ rolname = '" ,name "')) as not_exists;\n"
            (requirement '(postgres))
            (provision '(postgres-roles))
            (one-shot? #t)
-           (start #~(make-forkexec-constructor
-                     (list #$(postgresql-create-roles config))
-                     #:user "postgres" #:group "postgres"
-                     #:log-file #$log))
+           (start
+            #~(lambda args
+                (let ((pid (fork+exec-command
+                            #$(postgresql-create-roles config)
+                            #:user "postgres"
+                            #:group "postgres"
+                            #:log-file #$log)))
+                  (zero? (cdr (waitpid pid))))))
            (documentation "Create PostgreSQL roles.")))))
 
 (define postgresql-role-service-type
@@ -522,87 +516,6 @@ created after the PostgreSQL database is started.")))
 
 
 ;;;
-;;; MongoDB
-;;;
-
-(define %default-mongodb-configuration-file
-  (plain-file
-   "mongodb.yaml"
-   "# GNU Guix: MongoDB default configuration file
-processManagement:
-  pidFilePath: /var/run/mongodb/pid
-storage:
-  dbPath: /var/lib/mongodb
-"))
-
-
-(define-record-type* <mongodb-configuration>
-  mongodb-configuration make-mongodb-configuration
-  mongodb-configuration?
-  (mongodb             mongodb-configuration-mongodb
-                       (default mongodb))
-  (config-file         mongodb-configuration-config-file
-                       (default %default-mongodb-configuration-file))
-  (data-directory      mongodb-configuration-data-directory
-                       (default "/var/lib/mongodb")))
-
-(define %mongodb-accounts
-  (list (user-group (name "mongodb") (system? #t))
-        (user-account
-         (name "mongodb")
-         (group "mongodb")
-         (system? #t)
-         (comment "Mongodb server user")
-         (home-directory "/var/lib/mongodb")
-         (shell (file-append shadow "/sbin/nologin")))))
-
-(define mongodb-activation
-  (match-lambda
-    (($ <mongodb-configuration> mongodb config-file data-directory)
-     #~(begin
-         (use-modules (guix build utils))
-         (let ((user (getpwnam "mongodb")))
-           (for-each
-            (lambda (directory)
-              (mkdir-p directory)
-              (chown directory
-                     (passwd:uid user) (passwd:gid user)))
-            '("/var/run/mongodb" #$data-directory)))))))
-
-(define mongodb-shepherd-service
-  (match-lambda
-    (($ <mongodb-configuration> mongodb config-file data-directory)
-     (shepherd-service
-      (provision '(mongodb))
-      (documentation "Run the Mongodb daemon.")
-      (requirement '(user-processes loopback))
-      (start #~(make-forkexec-constructor
-                `(,(string-append #$mongodb "/bin/mongod")
-                  "--config"
-                  ,#$config-file)
-                #:user "mongodb"
-                #:group "mongodb"
-                #:pid-file "/var/run/mongodb/pid"
-                #:log-file "/var/log/mongodb.log"))
-      (stop #~(make-kill-destructor))))))
-
-(define mongodb-service-type
-  (service-type
-   (name 'mongodb)
-   (description "Run the MongoDB document database server.")
-   (extensions
-    (list (service-extension shepherd-root-service-type
-                             (compose list
-                                      mongodb-shepherd-service))
-          (service-extension activation-service-type
-                             mongodb-activation)
-          (service-extension account-service-type
-                             (const %mongodb-accounts))))
-   (default-value
-     (mongodb-configuration))))
-
-
-;;;
 ;;; MySQL.
 ;;;
 
diff --git a/gnu/services/dbus.scm b/gnu/services/dbus.scm
index e015d3f68d..af1a1e4c3a 100644
--- a/gnu/services/dbus.scm
+++ b/gnu/services/dbus.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -28,6 +29,7 @@
   #:use-module (guix gexp)
   #:use-module ((guix packages) #:select (package-name))
   #:use-module (guix records)
+  #:use-module (guix modules)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 match)
   #:export (dbus-configuration
@@ -161,24 +163,23 @@ includes the @code{etc/dbus-1/system.d} directories of each package listed in
 
 (define (dbus-activation config)
   "Return an activation gexp for D-Bus using @var{config}."
-  #~(begin
-      (use-modules (guix build utils))
-
-      (mkdir-p "/var/run/dbus")
-
-      (let ((user (getpwnam "messagebus")))
-        (chown "/var/run/dbus"
-               (passwd:uid user) (passwd:gid user))
-
-        ;; This directory contains the daemon's socket so it must be
-        ;; world-readable.
-        (chmod "/var/run/dbus" #o755))
-
-      (unless (file-exists? "/etc/machine-id")
-        (format #t "creating /etc/machine-id...~%")
-        (invoke (string-append #$(dbus-configuration-dbus config)
-                               "/bin/dbus-uuidgen")
-                "--ensure=/etc/machine-id"))))
+  (with-imported-modules (source-module-closure
+                          '((gnu build activation)
+                            (guix build utils)))
+    #~(begin
+        (use-modules (gnu build activation)
+                     (guix build utils))
+
+        (let ((user (getpwnam "messagebus")))
+          ;; This directory contains the daemon's socket so it must be
+          ;; world-readable.
+          (mkdir-p/perms "/var/run/dbus" user #o755))
+
+        (unless (file-exists? "/etc/machine-id")
+          (format #t "creating /etc/machine-id...~%")
+          (invoke (string-append #$(dbus-configuration-dbus config)
+                                 "/bin/dbus-uuidgen")
+                  "--ensure=/etc/machine-id")))))
 
 (define dbus-shepherd-service
   (match-lambda
diff --git a/gnu/services/dns.scm b/gnu/services/dns.scm
index b339eb0619..55211cb08f 100644
--- a/gnu/services/dns.scm
+++ b/gnu/services/dns.scm
@@ -2,6 +2,7 @@
 ;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
 ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
 ;;; Copyright © 2020 Pierre Langlois <pierre.langlois@gmx.com>
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -28,6 +29,7 @@
   #:use-module (guix packages)
   #:use-module (guix records)
   #:use-module (guix gexp)
+  #:use-module (guix modules)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-34)
@@ -256,9 +258,9 @@
   (let ((id (knot-key-configuration-id key)))
     (unless (and (string? id) (not (equal? id "")))
       (error-out "key id must be a non empty string.")))
-  (unless (memq '(#f hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512)
-                (knot-key-configuration-algorithm key))
-          (error-out "algorithm must be one of: #f, 'hmac-md5, 'hmac-sha1,
+  (unless (memq (knot-key-configuration-algorithm key)
+                '(#f hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512))
+    (error-out "algorithm must be one of: #f, 'hmac-md5, 'hmac-sha1,
 'hmac-sha224, 'hmac-sha256, 'hmac-sha384 or 'hmac-sha512")))
 
 (define (verify-knot-keystore-configuration keystore)
@@ -267,9 +269,9 @@
   (let ((id (knot-keystore-configuration-id keystore)))
     (unless (and (string? id) (not (equal? id "")))
       (error-out "keystore id must be a non empty string.")))
-  (unless (memq '(pem pkcs11)
-                (knot-keystore-configuration-backend keystore))
-          (error-out "backend must be one of: 'pem or 'pkcs11")))
+  (unless (memq (knot-keystore-configuration-backend keystore)
+                '(pem pkcs11))
+    (error-out "backend must be one of: 'pem or 'pkcs11")))
 
 (define (verify-knot-policy-configuration policy)
   (unless (knot-policy-configuration? policy)
@@ -288,7 +290,7 @@
     (unless (and (string? id) (not (equal? id "")))
       (error-out "acl id must be a non empty string."))
     (unless (and (list? address)
-                 (fold (lambda (x1 x2) (and (string? x1) (string? x2))) "" address))
+                 (every string? address))
       (error-out "acl address must be a list of strings.")))
   (unless (boolean? (knot-acl-configuration-deny? acl))
     (error-out "deny? must be #t or #f.")))
@@ -607,17 +609,14 @@
           (shell (file-append shadow "/sbin/nologin")))))
 
 (define (knot-activation config)
-  #~(begin
-      (use-modules (guix build utils))
-      (define (mkdir-p/perms directory owner perms)
-        (mkdir-p directory)
-        (chown directory (passwd:uid owner) (passwd:gid owner))
-        (chmod directory perms))
-      (mkdir-p/perms #$(knot-configuration-run-directory config)
-                     (getpwnam "knot") #o755)
-      (mkdir-p/perms "/var/lib/knot" (getpwnam "knot") #o755)
-      (mkdir-p/perms "/var/lib/knot/keys" (getpwnam "knot") #o755)
-      (mkdir-p/perms "/var/lib/knot/keys/keys" (getpwnam "knot") #o755)))
+  (with-imported-modules (source-module-closure '((gnu build activation)))
+    #~(begin
+        (use-modules (gnu build activation))
+        (mkdir-p/perms #$(knot-configuration-run-directory config)
+                       (getpwnam "knot") #o755)
+        (mkdir-p/perms "/var/lib/knot" (getpwnam "knot") #o755)
+        (mkdir-p/perms "/var/lib/knot/keys" (getpwnam "knot") #o755)
+        (mkdir-p/perms "/var/lib/knot/keys/keys" (getpwnam "knot") #o755))))
 
 (define (knot-shepherd-service config)
   (let* ((config-file (knot-config-file config))
diff --git a/gnu/services/file-sharing.scm b/gnu/services/file-sharing.scm
new file mode 100644
index 0000000000..72cd6478d6
--- /dev/null
+++ b/gnu/services/file-sharing.scm
@@ -0,0 +1,804 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2020 Simon South <simon@simonsouth.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 the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu services file-sharing)
+  #:use-module (gcrypt base16)
+  #:use-module (gcrypt hash)
+  #:use-module (gcrypt random)
+  #:use-module (gnu services)
+  #:use-module (gnu services admin)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu packages bittorrent)
+  #:use-module (gnu packages gnupg)
+  #:use-module (gnu packages guile)
+  #:use-module (gnu system shadow)
+  #:use-module (guix diagnostics)
+  #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix modules)
+  #:use-module (guix packages)
+  #:use-module (guix records)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
+  #:export (transmission-daemon-configuration
+            transmission-daemon-service-type
+            transmission-password-hash
+            transmission-random-salt))
+
+;;;
+;;; Transmission Daemon.
+;;;
+
+(define %transmission-daemon-user "transmission")
+(define %transmission-daemon-group "transmission")
+
+(define %transmission-daemon-configuration-directory
+  "/var/lib/transmission-daemon")
+(define %transmission-daemon-log-file
+  "/var/log/transmission.log")
+
+(define %transmission-salt-length 8)
+
+(define (transmission-password-hash password salt)
+  "Returns a string containing the result of hashing @var{password} together
+with @var{salt}, in the format recognized by Transmission clients for their
+@code{rpc-password} configuration setting.
+
+@var{salt} must be an eight-character string.  The
+@code{transmission-random-salt} procedure can be used to generate a suitable
+salt value at random."
+  (if (not (and (string? salt)
+                (eq? (string-length salt) %transmission-salt-length)))
+      (raise (formatted-message
+              (G_ "salt value must be a string of ~d characters")
+              %transmission-salt-length))
+      (string-append "{"
+                     (bytevector->base16-string
+                      (sha1 (string->utf8 (string-append password salt))))
+                     salt)))
+
+(define (transmission-random-salt)
+  "Returns a string containing a random, eight-character salt value of the
+type generated and used by Transmission clients, suitable for passing to the
+@code{transmission-password-hash} procedure."
+  ;; This implementation matches a portion of Transmission's tr_ssha1
+  ;; function.  See libtransmission/crypto-utils.c in the Transmission source
+  ;; distribution.
+  (let ((salter (string-append "0123456789"
+                               "abcdefghijklmnopqrstuvwxyz"
+                               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                               "./")))
+    (list->string
+     (map (lambda (u8)
+            (string-ref salter (modulo u8 (string-length salter))))
+          (bytevector->u8-list
+           (gen-random-bv %transmission-salt-length %gcry-strong-random))))))
+
+(define (uglify-field-name field-name)
+  (string-delete #\? (symbol->string field-name)))
+
+(define (serialize-field field-name val)
+  ;; "Serialize" each configuration field as a G-expression containing a
+  ;; name-value pair, the collection of which will subsequently be serialized
+  ;; to disk as a JSON object.
+  #~(#$(uglify-field-name field-name) . #$val))
+
+(define serialize-boolean serialize-field)
+(define serialize-integer serialize-field)
+(define serialize-rational serialize-field)
+
+(define serialize-string serialize-field)
+(define-maybe string)
+;; Override the definition of "serialize-maybe-string", as we need to output a
+;; name-value pair for the JSON builder.
+(set! serialize-maybe-string
+  (lambda (field-name val)
+    (serialize-string field-name
+                      (if (and (symbol? val)
+                               (eq? val 'disabled))
+                          ""
+                          val))))
+
+(define (string-list? val)
+  (and (list? val)
+       (and-map (lambda (x)
+                  (and (string? x)
+                       (not (string-index x #\,))))
+                val)))
+(define (serialize-string-list field-name val)
+  (serialize-field field-name (string-join val ",")))
+
+(define days
+  '((sunday    . #b0000001)
+    (monday    . #b0000010)
+    (tuesday   . #b0000100)
+    (wednesday . #b0001000)
+    (thursday  . #b0010000)
+    (friday    . #b0100000)
+    (saturday  . #b1000000)))
+(define day-lists
+  (list (cons 'weekdays '(monday tuesday wednesday thursday friday))
+        (cons 'weekends '(saturday sunday))
+        (cons 'all (map car days))))
+(define (day-list? val)
+  (or (and (symbol? val)
+           (assq val day-lists))
+      (and (list? val)
+           (and-map (lambda (x)
+                      (and (symbol? x)
+                           (assq x days)))
+                    val))))
+(define (serialize-day-list field-name val)
+  (serialize-integer field-name
+                     (reduce logior
+                             #b0000000
+                             (map (lambda (day)
+                                    (assq-ref days day))
+                                  (if (symbol? val)
+                                      (assq-ref day-lists val)
+                                      val)))))
+
+(define encryption-modes
+  '((prefer-unencrypted-connections . 0)
+    (prefer-encrypted-connections   . 1)
+    (require-encrypted-connections  . 2)))
+(define (encryption-mode? val)
+  (and (symbol? val)
+       (assq val encryption-modes)))
+(define (serialize-encryption-mode field-name val)
+  (serialize-integer field-name (assq-ref encryption-modes val)))
+
+(define serialize-file-like serialize-field)
+
+(define (file-object? val)
+  (or (string? val)
+      (file-like? val)))
+(define (serialize-file-object field-name val)
+  (if (file-like? val)
+      (serialize-file-like field-name val)
+      (serialize-string field-name val)))
+(define-maybe file-object)
+(set! serialize-maybe-file-object
+  (lambda (field-name val)
+    (if (and (symbol? val)
+             (eq? val 'disabled))
+        (serialize-string field-name "")
+        (serialize-file-object field-name val))))
+
+(define (file-object-list? val)
+  (and (list? val)
+       (and-map file-object? val)))
+(define serialize-file-object-list serialize-field)
+
+(define message-levels
+  '((none  . 0)
+    (error . 1)
+    (info  . 2)
+    (debug . 3)))
+(define (message-level? val)
+  (and (symbol? val)
+       (assq val message-levels)))
+(define (serialize-message-level field-name val)
+  (serialize-integer field-name (assq-ref message-levels val)))
+
+(define (non-negative-integer? val)
+  (and (integer? val)
+       (not (negative? val))))
+(define serialize-non-negative-integer serialize-integer)
+
+(define (non-negative-rational? val)
+  (and (rational? val)
+       (not (negative? val))))
+(define serialize-non-negative-rational serialize-rational)
+
+(define (port-number? val)
+  (and (integer? val)
+       (>= val 1)
+       (<= val 65535)))
+(define serialize-port-number serialize-integer)
+
+(define preallocation-modes
+  '((none   . 0)
+    (fast   . 1)
+    (sparse . 1)
+    (full   . 2)))
+(define (preallocation-mode? val)
+  (and (symbol? val)
+       (assq val preallocation-modes)))
+(define (serialize-preallocation-mode field-name val)
+  (serialize-integer field-name (assq-ref preallocation-modes val)))
+
+(define tcp-types-of-service
+  '((default     . "default")
+    (low-cost    . "lowcost")
+    (throughput  . "throughput")
+    (low-delay   . "lowdelay")
+    (reliability . "reliability")))
+(define (tcp-type-of-service? val)
+  (and (symbol? val)
+       (assq val tcp-types-of-service)))
+(define (serialize-tcp-type-of-service field-name val)
+  (serialize-string field-name (assq-ref tcp-types-of-service val)))
+
+(define (transmission-password-hash? val)
+  (and (string? val)
+       (= (string-length val) 49)
+       (eqv? (string-ref val 0) #\{)
+       (string-every char-set:hex-digit val 1 41)))
+(define serialize-transmission-password-hash serialize-string)
+(define-maybe transmission-password-hash)
+(set! serialize-maybe-transmission-password-hash serialize-maybe-string)
+
+(define (umask? val)
+  (and (integer? val)
+       (>= val #o000)
+       (<= val #o777)))
+(define serialize-umask serialize-integer) ; must use decimal representation
+
+(define-configuration transmission-daemon-configuration
+  ;; Settings internal to this service definition.
+  (transmission
+   (package transmission)
+   "The Transmission package to use.")
+  (stop-wait-period
+   (non-negative-integer 10)
+   "The period, in seconds, to wait when stopping the service for
+@command{transmission-daemon} to exit before killing its process.  This allows
+the daemon time to complete its housekeeping and send a final update to
+trackers as it shuts down.  On slow hosts, or hosts with a slow network
+connection, this value may need to be increased.")
+
+  ;; Files and directories.
+  (download-dir
+   (string (string-append %transmission-daemon-configuration-directory
+                          "/downloads"))
+   "The directory to which torrent files are downloaded.")
+  (incomplete-dir-enabled?
+   (boolean #f)
+   "If @code{#t}, files will be held in @code{incomplete-dir} while their
+torrent is being downloaded, then moved to @code{download-dir} once the
+torrent is complete.  Otherwise, files for all torrents (including those still
+being downloaded) will be placed in @code{download-dir}.")
+  (incomplete-dir
+   (maybe-string 'disabled)
+   "The directory in which files from incompletely downloaded torrents will be
+held when @code{incomplete-dir-enabled?} is @code{#t}.")
+  (umask
+   (umask #o022)
+   "The file mode creation mask used for downloaded files.  (See the
+@command{umask} man page for more information.)")
+  (rename-partial-files?
+   (boolean #t)
+   "When @code{#t}, ``.part'' is appended to the name of partially downloaded
+files.")
+  (preallocation
+   (preallocation-mode 'fast)
+   "The mode by which space should be preallocated for downloaded files, one
+of @code{none}, @code{fast} (or @code{sparse}) and @code{full}.  Specifying
+@code{full} will minimize disk fragmentation at a cost to file-creation
+speed.")
+  (watch-dir-enabled?
+   (boolean #f)
+   "If @code{#t}, the directory specified by @code{watch-dir} will be watched
+for new @file{.torrent} files and the torrents they describe added
+automatically (and the original files removed, if
+@code{trash-original-torrent-files?} is @code{#t}).")
+  (watch-dir
+   (maybe-string 'disabled)
+   "The directory to be watched for @file{.torrent} files indicating new
+torrents to be added, when @code{watch-dir-enabled} is @code{#t}.")
+  (trash-original-torrent-files?
+   (boolean #f)
+   "When @code{#t}, @file{.torrent} files will be deleted from the watch
+directory once their torrent has been added (see
+@code{watch-directory-enabled?}).")
+
+  ;; Bandwidth limits.
+  (speed-limit-down-enabled?
+   (boolean #f)
+   "When @code{#t}, the daemon's download speed will be limited to the rate
+specified by @code{speed-limit-down}.")
+  (speed-limit-down
+   (non-negative-integer 100)
+   "The default global-maximum download speed, in kilobytes per second.")
+  (speed-limit-up-enabled?
+   (boolean #f)
+   "When @code{#t}, the daemon's upload speed will be limited to the rate
+specified by @code{speed-limit-up}.")
+  (speed-limit-up
+   (non-negative-integer 100)
+   "The default global-maximum upload speed, in kilobytes per second.")
+  (alt-speed-enabled?
+   (boolean #f)
+   "When @code{#t}, the alternate speed limits @code{alt-speed-down} and
+@code{alt-speed-up} are used (in place of @code{speed-limit-down} and
+@code{speed-limit-up}, if they are enabled) to constrain the daemon's
+bandwidth usage.  This can be scheduled to occur automatically at certain
+times during the week; see @code{alt-speed-time-enabled?}.")
+  (alt-speed-down
+   (non-negative-integer 50)
+   "The alternate global-maximum download speed, in kilobytes per second.")
+  (alt-speed-up
+   (non-negative-integer 50)
+   "The alternate global-maximum upload speed, in kilobytes per second.")
+
+  ;; Bandwidth-limit scheduling.
+  (alt-speed-time-enabled?
+   (boolean #f)
+   "When @code{#t}, the alternate speed limits @code{alt-speed-down} and
+@code{alt-speed-up} will be enabled automatically during the periods specified
+by @code{alt-speed-time-day}, @code{alt-speed-time-begin} and
+@code{alt-time-speed-end}.")
+  (alt-speed-time-day
+   (day-list 'all)
+   "The days of the week on which the alternate-speed schedule should be used,
+specified either as a list of days (@code{sunday}, @code{monday}, and so on)
+or using one of the symbols @code{weekdays}, @code{weekends} or @code{all}.")
+  (alt-speed-time-begin
+   (non-negative-integer 540)
+   "The time of day at which to enable the alternate speed limits,
+expressed as a number of minutes since midnight.")
+  (alt-speed-time-end
+   (non-negative-integer 1020)
+   "The time of day at which to disable the alternate speed limits,
+expressed as a number of minutes since midnight.")
+
+  ;; Peer networking.
+  (bind-address-ipv4
+   (string "0.0.0.0")
+   "The IP address at which to listen for peer connections, or ``0.0.0.0'' to
+listen at all available IP addresses.")
+  (bind-address-ipv6
+   (string "::")
+   "The IPv6 address at which to listen for peer connections, or ``::'' to
+listen at all available IPv6 addresses.")
+  (peer-port-random-on-start?
+   (boolean #f)
+   "If @code{#t}, when the daemon starts it will select a port at random on
+which to listen for peer connections, from the range specified (inclusively)
+by @code{peer-port-random-low} and @code{peer-port-random-high}.  Otherwise,
+it listens on the port specified by @code{peer-port}.")
+  (peer-port-random-low
+   (port-number 49152)
+   "The lowest selectable port number when @code{peer-port-random-on-start?}
+is @code{#t}.")
+  (peer-port-random-high
+   (port-number 65535)
+   "The highest selectable port number when @code{peer-port-random-on-start}
+is @code{#t}.")
+  (peer-port
+   (port-number 51413)
+   "The port on which to listen for peer connections when
+@code{peer-port-random-on-start?} is @code{#f}.")
+  (port-forwarding-enabled?
+   (boolean #t)
+   "If @code{#t}, the daemon will attempt to configure port-forwarding on an
+upstream gateway automatically using @acronym{UPnP} and @acronym{NAT-PMP}.")
+  (encryption
+   (encryption-mode 'prefer-encrypted-connections)
+   "The encryption preference for peer connections, one of
+@code{prefer-unencrypted-connections}, @code{prefer-encrypted-connections} or
+@code{require-encrypted-connections}.")
+  (peer-congestion-algorithm
+   (maybe-string 'disabled)
+   "The TCP congestion-control algorithm to use for peer connections,
+specified using a string recognized by the operating system in calls to
+@code{setsockopt} (or set to @code{disabled}, in which case the
+operating-system default is used).
+
+Note that on GNU/Linux systems, the kernel must be configured to allow
+processes to use a congestion-control algorithm not in the default set;
+otherwise, it will deny these requests with ``Operation not permitted''.  To
+see which algorithms are available on your system and which are currently
+permitted for use, look at the contents of the files
+@file{tcp_available_congestion_control} and
+@file{tcp_allowed_congestion_control} in the @file{/proc/sys/net/ipv4}
+directory.
+
+As an example, to have Transmission Daemon use
+@uref{http://www-ece.rice.edu/networks/TCP-LP/, the TCP Low Priority
+congestion-control algorithm}, you'll need to modify your kernel configuration
+to build in support for the algorithm, then update your operating-system
+configuration to allow its use by adding a @code{sysctl-service-type}
+service (or updating the existing one's configuration) with lines like the
+following:
+
+@lisp
+(service sysctl-service-type
+         (sysctl-configuration
+          (settings
+           (\"net.ipv4.tcp_allowed_congestion_control\" .
+            \"reno cubic lp\"))))
+@end lisp
+
+The Transmission Daemon configuration can then be updated with
+
+@lisp
+(peer-congestion-algorithm \"lp\")
+@end lisp
+
+and the system reconfigured to have the changes take effect.")
+  (peer-socket-tos
+   (tcp-type-of-service 'default)
+   "The type of service to request in outgoing @acronym{TCP} packets,
+one of @code{default}, @code{low-cost}, @code{throughput}, @code{low-delay}
+and @code{reliability}.")
+  (peer-limit-global
+   (non-negative-integer 200)
+   "The global limit on the number of connected peers.")
+  (peer-limit-per-torrent
+   (non-negative-integer 50)
+   "The per-torrent limit on the number of connected peers.")
+  (upload-slots-per-torrent
+   (non-negative-integer 14)
+   "The maximum number of peers to which the daemon will upload data
+simultaneously for each torrent.")
+  (peer-id-ttl-hours
+   (non-negative-integer 6)
+   "The maximum lifespan, in hours, of the peer ID associated with each public
+torrent before it is regenerated.")
+
+  ;; Peer blocklists.
+  (blocklist-enabled?
+   (boolean #f)
+   "When @code{#t}, the daemon will ignore peers mentioned in the blocklist it
+has most recently downloaded from @code{blocklist-url}.")
+  (blocklist-url
+   (maybe-string 'disabled)
+   "The URL of a peer blocklist (in @acronym{P2P}-plaintext or eMule
+@file{.dat} format) to be periodically downloaded and applied when
+@code{blocklist-enabled?} is @code{#t}.")
+
+  ;; Queueing.
+  (download-queue-enabled?
+   (boolean #t)
+   "If @code{#t}, the daemon will be limited to downloading at most
+@code{download-queue-size} non-stalled torrents simultaneously.")
+  (download-queue-size
+   (non-negative-integer 5)
+   "The size of the daemon's download queue, which limits the number of
+non-stalled torrents it will download at any one time when
+@code{download-queue-enabled?} is @code{#t}.")
+  (seed-queue-enabled?
+   (boolean #f)
+   "If @code{#t}, the daemon will be limited to seeding at most
+@code{seed-queue-size} non-stalled torrents simultaneously.")
+  (seed-queue-size
+   (non-negative-integer 10)
+   "The size of the daemon's seed queue, which limits the number of
+non-stalled torrents it will seed at any one time when
+@code{seed-queue-enabled?} is @code{#t}.")
+  (queue-stalled-enabled?
+   (boolean #t)
+   "When @code{#t}, the daemon will consider torrents for which it has not
+shared data in the past @code{queue-stalled-minutes} minutes to be stalled and
+not count them against its @code{download-queue-size} and
+@code{seed-queue-size} limits.")
+  (queue-stalled-minutes
+   (non-negative-integer 30)
+   "The maximum period, in minutes, a torrent may be idle before it is
+considered to be stalled, when @code{queue-stalled-enabled?} is @code{#t}.")
+
+  ;; Seeding limits.
+  (ratio-limit-enabled?
+   (boolean #f)
+   "When @code{#t}, a torrent being seeded will automatically be paused once
+it reaches the ratio specified by @code{ratio-limit}.")
+  (ratio-limit
+   (non-negative-rational 2.0)
+   "The ratio at which a torrent being seeded will be paused, when
+@code{ratio-limit-enabled?} is @code{#t}.")
+  (idle-seeding-limit-enabled?
+   (boolean #f)
+   "When @code{#t}, a torrent being seeded will automatically be paused once
+it has been idle for @code{idle-seeding-limit} minutes.")
+  (idle-seeding-limit
+   (non-negative-integer 30)
+   "The maximum period, in minutes, a torrent being seeded may be idle before
+it is paused, when @code{idle-seeding-limit-enabled?} is @code{#t}.")
+
+  ;; BitTorrent extensions.
+  (dht-enabled?
+   (boolean #t)
+   "Enable @uref{http://bittorrent.org/beps/bep_0005.html, the distributed
+hash table (@acronym{DHT}) protocol}, which supports the use of trackerless
+torrents.")
+  (lpd-enabled?
+   (boolean #f)
+   "Enable @url{https://en.wikipedia.org/wiki/Local_Peer_Discovery, local peer
+discovery} (@acronym{LPD}), which allows the discovery of peers on the local
+network and may reduce the amount of data sent over the public Internet.")
+  (pex-enabled?
+   (boolean #t)
+   "Enable @url{https://en.wikipedia.org/wiki/Peer_exchange, peer
+exchange} (@acronym{PEX}), which reduces the daemon's reliance on external
+trackers and may improve its performance.")
+  (utp-enabled?
+   (boolean #t)
+   "Enable @url{http://bittorrent.org/beps/bep_0029.html, the micro transport
+protocol} (@acronym{uTP}), which aims to reduce the impact of BitTorrent
+traffic on other users of the local network while maintaining full utilization
+of the available bandwidth.")
+
+  ;; Remote procedure call (RPC) interface.
+  (rpc-enabled?
+   (boolean #t)
+   "If @code{#t}, enable the remote procedure call (@acronym{RPC}) interface,
+which allows remote control of the daemon via its Web interface, the
+@command{transmission-remote} command-line client, and similar tools.")
+  (rpc-bind-address
+   (string "0.0.0.0")
+   "The IP address at which to listen for @acronym{RPC} connections, or
+``0.0.0.0'' to listen at all available IP addresses.")
+  (rpc-port
+   (port-number 9091)
+   "The port on which to listen for @acronym{RPC} connections.")
+  (rpc-url
+   (string "/transmission/")
+   "The path prefix to use in the @acronym{RPC}-endpoint @acronym{URL}.")
+  (rpc-authentication-required?
+   (boolean #f)
+   "When @code{#t}, clients must authenticate (see @code{rpc-username} and
+@code{rpc-password}) when using the @acronym{RPC} interface.  Note this has
+the side effect of disabling host-name whitelisting (see
+@code{rpc-host-whitelist-enabled?}.")
+  (rpc-username
+   (maybe-string 'disabled)
+   "The username required by clients to access the @acronym{RPC} interface
+when @code{rpc-authentication-required?} is @code{#t}.")
+  (rpc-password
+   (maybe-transmission-password-hash 'disabled)
+   "The password required by clients to access the @acronym{RPC} interface
+when @code{rpc-authentication-required?} is @code{#t}.  This must be specified
+using a password hash in the format recognized by Transmission clients, either
+copied from an existing @file{settings.json} file or generated using the
+@code{transmission-password-hash} procedure.")
+  (rpc-whitelist-enabled?
+   (boolean #t)
+   "When @code{#t}, @acronym{RPC} requests will be accepted only when they
+originate from an address specified in @code{rpc-whitelist}.")
+  (rpc-whitelist
+   (string-list '("127.0.0.1" "::1"))
+   "The list of IP and IPv6 addresses from which @acronym{RPC} requests will
+be accepted when @code{rpc-whitelist-enabled?} is @code{#t}.  Wildcards may be
+specified using @samp{*}.")
+  (rpc-host-whitelist-enabled?
+   (boolean #t)
+   "When @code{#t}, @acronym{RPC} requests will be accepted only when they are
+addressed to a host named in @code{rpc-host-whitelist}.  Note that requests to
+``localhost'' or ``localhost.'', or to a numeric address, are always accepted
+regardless of these settings.
+
+Note also this functionality is disabled when
+@code{rpc-authentication-required?} is @code{#t}.")
+  (rpc-host-whitelist
+   (string-list '())
+   "The list of host names recognized by the @acronym{RPC} server when
+@code{rpc-host-whitelist-enabled?} is @code{#t}.")
+
+  ;; Miscellaneous.
+  (message-level
+   (message-level 'info)
+   "The minimum severity level of messages to be logged (to
+@file{/var/log/transmission.log}) by the daemon, one of @code{none} (no
+logging), @code{error}, @code{info} and @code{debug}.")
+  (start-added-torrents?
+   (boolean #t)
+   "When @code{#t}, torrents are started as soon as they are added; otherwise,
+they are added in ``paused'' state.")
+  (script-torrent-done-enabled?
+   (boolean #f)
+   "When @code{#t}, the script specified by
+@code{script-torrent-done-filename} will be invoked each time a torrent
+completes.")
+  (script-torrent-done-filename
+   (maybe-file-object 'disabled)
+   "A file name or file-like object specifying a script to run each time a
+torrent completes, when @code{script-torrent-done-enabled?} is @code{#t}.")
+  (scrape-paused-torrents-enabled?
+   (boolean #t)
+   "When @code{#t}, the daemon will scrape trackers for a torrent even when
+the torrent is paused.")
+  (cache-size-mb
+   (non-negative-integer 4)
+   "The amount of memory, in megabytes, to allocate for the daemon's in-memory
+cache.  A larger value may increase performance by reducing the frequency of
+disk I/O.")
+  (prefetch-enabled?
+   (boolean #t)
+   "When @code{#t}, the daemon will try to improve I/O performance by hinting
+to the operating system which data is likely to be read next from disk to
+satisfy requests from peers."))
+
+(define (transmission-daemon-shepherd-service config)
+  "Return a <shepherd-service> for Transmission Daemon with CONFIG."
+  (let ((transmission
+         (transmission-daemon-configuration-transmission config))
+        (stop-wait-period
+         (transmission-daemon-configuration-stop-wait-period config)))
+    (list
+     (shepherd-service
+      (provision '(transmission-daemon transmission bittorrent))
+      (requirement '(networking))
+      (documentation "Share files using the BitTorrent protocol.")
+      (start #~(make-forkexec-constructor
+                '(#$(file-append transmission "/bin/transmission-daemon")
+                  "--config-dir"
+                  #$%transmission-daemon-configuration-directory
+                  "--foreground")
+                #:user #$%transmission-daemon-user
+                #:group #$%transmission-daemon-group
+                #:directory #$%transmission-daemon-configuration-directory
+                #:log-file #$%transmission-daemon-log-file
+                #:environment-variables
+                '("CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt")))
+      (stop #~(lambda (pid)
+                (kill pid SIGTERM)
+
+                ;; Transmission Daemon normally needs some time to shut down,
+                ;; as it will complete some housekeeping and send a final
+                ;; update to trackers before it exits.
+                ;;
+                ;; Wait a reasonable period for it to stop before continuing.
+                ;; If we don't do this, restarting the service can fail as the
+                ;; new daemon process finds the old one still running and
+                ;; attached to the port used for peer connections.
+                (let wait-before-killing ((period #$stop-wait-period))
+                  (if (zero? (car (waitpid pid WNOHANG)))
+                      (if (positive? period)
+                          (begin
+                            (sleep 1)
+                            (wait-before-killing (- period 1)))
+                          (begin
+                            (format #t
+                                    #$(G_ "Wait period expired; killing \
+transmission-daemon (pid ~a).~%")
+                                    pid)
+                            (display #$(G_ "(If you see this message \
+regularly, you may need to increase the value
+of 'stop-wait-period' in the service configuration.)\n"))
+                            (kill pid SIGKILL)))))
+                #f))
+      (actions
+       (list
+        (shepherd-action
+         (name 'reload)
+         (documentation "Reload the settings file from disk.")
+         (procedure #~(lambda (pid)
+                        (if pid
+                            (begin
+                              (kill pid SIGHUP)
+                              (display #$(G_ "Service transmission-daemon has \
+been asked to reload its settings file.")))
+                            (display #$(G_ "Service transmission-daemon is not \
+running."))))))))))))
+
+(define %transmission-daemon-accounts
+  (list (user-group
+         (name %transmission-daemon-group)
+         (system? #t))
+        (user-account
+         (name %transmission-daemon-user)
+         (group %transmission-daemon-group)
+         (comment "Transmission Daemon service account")
+         (home-directory %transmission-daemon-configuration-directory)
+         (shell (file-append shadow "/sbin/nologin"))
+         (system? #t))))
+
+(define %transmission-daemon-log-rotations
+  (list (log-rotation
+         (files (list %transmission-daemon-log-file)))))
+
+(define (transmission-daemon-computed-settings-file config)
+  "Return a @code{computed-file} object that, when unquoted in a G-expression,
+produces a Transmission settings file (@file{settings.json}) matching CONFIG."
+  (let ((settings
+         ;; "Serialize" the configuration settings as a list of G-expressions
+         ;; containing a name-value pair, which will ultimately be sorted and
+         ;; serialized to the settings file as a JSON object.
+         (map
+          (lambda (field)
+            ((configuration-field-serializer field)
+             (configuration-field-name field)
+             ((configuration-field-getter field) config)))
+          (filter
+           (lambda (field)
+             ;; Omit configuration fields that are used only internally by
+             ;; this service definition.
+             (not (memq (configuration-field-name field)
+                        '(transmission stop-wait-period))))
+           transmission-daemon-configuration-fields))))
+    (computed-file
+     "settings.json"
+     (with-extensions (list guile-gcrypt guile-json-4)
+       (with-imported-modules (source-module-closure '((json builder)))
+         #~(begin
+             (use-modules (json builder))
+
+             (with-output-to-file #$output
+               (lambda ()
+                 (scm->json (sort-list '(#$@settings)
+                                       (lambda (x y)
+                                         (string<=? (car x) (car y))))
+                            #:pretty #t)))))))))
+
+(define (transmission-daemon-activation config)
+  "Return the Transmission Daemon activation GEXP for CONFIG."
+  (let ((config-dir %transmission-daemon-configuration-directory)
+        (incomplete-dir-enabled
+         (transmission-daemon-configuration-incomplete-dir-enabled? config))
+        (incomplete-dir
+         (transmission-daemon-configuration-incomplete-dir config))
+        (watch-dir-enabled
+         (transmission-daemon-configuration-watch-dir-enabled? config))
+        (watch-dir
+         (transmission-daemon-configuration-watch-dir config)))
+    (with-imported-modules (source-module-closure '((guix build utils)))
+      #~(begin
+          (use-modules (guix build utils))
+
+          (let ((owner (getpwnam #$%transmission-daemon-user)))
+            (define (mkdir-p/perms directory perms)
+              (mkdir-p directory)
+              (chown directory (passwd:uid owner) (passwd:gid owner))
+              (chmod directory perms))
+
+            ;; Create the directories Transmission Daemon is configured to use
+            ;; and assign them suitable permissions.
+            (for-each (lambda (directory-specification)
+                        (apply mkdir-p/perms directory-specification))
+                      '(#$@(append
+                            `((,config-dir #o750))
+                            (if incomplete-dir-enabled
+                                `((,incomplete-dir #o750))
+                                '())
+                            (if watch-dir-enabled
+                                `((,watch-dir #o770))
+                                '())))))
+
+          ;; Generate and activate the daemon's settings file, settings.json.
+          (activate-special-files
+           '((#$(string-append config-dir "/settings.json")
+              #$(transmission-daemon-computed-settings-file config))))))))
+
+(define transmission-daemon-service-type
+  (service-type
+   (name 'transmission)
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             transmission-daemon-shepherd-service)
+          (service-extension account-service-type
+                             (const %transmission-daemon-accounts))
+          (service-extension rottlog-service-type
+                             (const %transmission-daemon-log-rotations))
+          (service-extension activation-service-type
+                             transmission-daemon-activation)))
+   (default-value (transmission-daemon-configuration))
+   (description "Share files using the BitTorrent protocol.")))
+
+(define (generate-transmission-daemon-documentation)
+  (generate-documentation
+   `((transmission-daemon-configuration
+      ,transmission-daemon-configuration-fields))
+   'transmission-daemon-configuration))
diff --git a/gnu/services/guix.scm b/gnu/services/guix.scm
index 88d23f746a..d1d31febdc 100644
--- a/gnu/services/guix.scm
+++ b/gnu/services/guix.scm
@@ -55,14 +55,32 @@
             guix-build-coordinator-agent-configuration-package
             guix-build-coordinator-agent-configuration-user
             guix-build-coordinator-agent-configuration-coordinator
-            guix-build-coordinator-agent-configuration-uuid
-            guix-build-coordinator-agent-configuration-password
-            guix-build-coordinator-agent-configuration-password-file
+            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-derivation-substitute-urls
             guix-build-coordinator-agent-configuration-non-derivation-substitute-urls
 
+            guix-build-coordinator-agent-password-auth
+            guix-build-coordinator-agent-password-auth?
+            guix-build-coordinator-agent-password-auth-uuid
+            guix-build-coordinator-agent-password-auth-password
+
+            guix-build-coordinator-agent-password-file-auth
+            guix-build-coordinator-agent-password-file-auth?
+            guix-build-coordinator-agent-password-file-auth-uuid
+            guix-build-coordinator-agent-password-file-auth-password-file
+
+            guix-build-coordinator-agent-dynamic-auth
+            guix-build-coordinator-agent-dynamic-auth?
+            guix-build-coordinator-agent-dynamic-auth-agent-name
+            guix-build-coordinator-agent-dynamic-auth-token
+
+            guix-build-coordinator-agent-dynamic-auth-with-file
+            guix-build-coordinator-agent-dynamic-auth-with-file?
+            guix-build-coordinator-agent-dynamic-auth-with-file-agent-name
+            guix-build-coordinator-agent-dynamic-auth-with-file-token-file
+
             guix-build-coordinator-agent-service-type
 
             guix-build-coordinator-queue-builds-configuration
@@ -132,11 +150,7 @@
                        (default "guix-build-coordinator-agent"))
   (coordinator         guix-build-coordinator-agent-configuration-coordinator
                        (default "http://localhost:8745"))
-  (uuid                guix-build-coordinator-agent-configuration-uuid)
-  (password            guix-build-coordinator-agent-configuration-password
-                       (default #f))
-  (password-file       guix-build-coordinator-agent-configuration-password-file
-                       (default #f))
+  (authentication      guix-build-coordinator-agent-configuration-authentication)
   (systems             guix-build-coordinator-agent-configuration-systems
                        (default #f))
   (max-parallel-builds
@@ -149,6 +163,35 @@
    guix-build-coordinator-agent-configuration-non-derivation-substitute-urls
    (default #f)))
 
+(define-record-type* <guix-build-coordinator-agent-password-auth>
+  guix-build-coordinator-agent-password-auth
+  make-guix-build-coordinator-agent-password-auth
+  guix-build-coordinator-agent-password-auth?
+  (uuid                guix-build-coordinator-agent-password-auth-uuid)
+  (password            guix-build-coordinator-agent-password-auth-password))
+
+(define-record-type* <guix-build-coordinator-agent-password-file-auth>
+  guix-build-coordinator-agent-password-file-auth
+  make-guix-build-coordinator-agent-password-file-auth
+  guix-build-coordinator-agent-password-file-auth?
+  (uuid                guix-build-coordinator-agent-password-file-auth-uuid)
+  (password-file
+   guix-build-coordinator-agent-password-file-auth-password-file))
+
+(define-record-type* <guix-build-coordinator-agent-dynamic-auth>
+  guix-build-coordinator-agent-dynamic-auth
+  make-guix-build-coordinator-agent-dynamic-auth
+  guix-build-coordinator-agent-dynamic-auth?
+  (agent-name          guix-build-coordinator-agent-dynamic-auth-agent-name)
+  (token               guix-build-coordinator-agent-dynamic-auth-token))
+
+(define-record-type* <guix-build-coordinator-agent-dynamic-auth-with-file>
+  guix-build-coordinator-agent-dynamic-auth-with-file
+  make-guix-build-coordinator-agent-dynamic-auth-with-file
+  guix-build-coordinator-agent-dynamic-auth-with-file?
+  (agent-name      guix-build-coordinator-agent-dynamic-auth-with-file-agent-name)
+  (token-file      guix-build-coordinator-agent-dynamic-auth-with-file-token-file))
+
 (define-record-type* <guix-build-coordinator-queue-builds-configuration>
   guix-build-coordinator-queue-builds-configuration
   make-guix-build-coordinator-queue-builds-configuration
@@ -326,7 +369,7 @@
 
 (define (guix-build-coordinator-agent-shepherd-services config)
   (match-record config <guix-build-coordinator-agent-configuration>
-    (package user coordinator uuid password password-file max-parallel-builds
+    (package user coordinator authentication max-parallel-builds
              derivation-substitute-urls non-derivation-substitute-urls
              systems)
     (list
@@ -337,13 +380,26 @@
       (start #~(make-forkexec-constructor
                 (list #$(file-append package "/bin/guix-build-coordinator-agent")
                       #$(string-append "--coordinator=" coordinator)
-                      #$(string-append "--uuid=" uuid)
-                      #$@(if password
-                             #~(#$(string-append "--password=" password))
-                             #~())
-                      #$@(if password-file
-                             #~(#$(string-append "--password-file=" password-file))
-                             #~())
+                      #$@(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 derivation-substitute-urls
diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm
index a4d4ac0646..231a9f66c7 100644
--- a/gnu/services/networking.scm
+++ b/gnu/services/networking.scm
@@ -15,6 +15,7 @@
 ;;; Copyright © 2019 Alex Griffin <a@ajgrf.com>
 ;;; Copyright © 2020 Brice Waegeneire <brice@waegenei.re>
 ;;; Copyright © 2021 Oleg Pykhalov <go.wigust@gmail.com>
+;;; Copyright © 2021 Christopher Lemmer Webber <cwebber@dustycloud.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -744,7 +745,9 @@ demand.")))
   (hidden-services  tor-configuration-hidden-services
                     (default '()))
   (socks-socket-type tor-configuration-socks-socket-type ; 'tcp or 'unix
-                     (default 'tcp)))
+                     (default 'tcp))
+  (control-socket?  tor-control-socket-path
+                    (default #f)))
 
 (define %tor-accounts
   ;; User account and groups for Tor.
@@ -766,7 +769,8 @@ demand.")))
 (define (tor-configuration->torrc config)
   "Return a 'torrc' file for CONFIG."
   (match config
-    (($ <tor-configuration> tor config-file services socks-socket-type)
+    (($ <tor-configuration> tor config-file services
+                            socks-socket-type control-socket?)
      (computed-file
       "torrc"
       (with-imported-modules '((guix build utils))
@@ -786,6 +790,11 @@ Log notice syslog\n" port)
                   (display "\
 SocksPort unix:/var/run/tor/socks-sock
 UnixSocksGroupWritable 1\n" port))
+                (when #$control-socket?
+                  (display "\
+ControlSocket unix:/var/run/tor/control-sock GroupWritable RelaxDirModeCheck
+ControlSocketsGroupWritable 1\n"
+                           port))
 
                 (for-each (match-lambda
                             ((service (ports hosts) ...)
diff --git a/gnu/services/shepherd.scm b/gnu/services/shepherd.scm
index e2ec59f5aa..7277273686 100644
--- a/gnu/services/shepherd.scm
+++ b/gnu/services/shepherd.scm
@@ -73,7 +73,9 @@
             shepherd-service-back-edges
             shepherd-service-upgrade
 
-            user-processes-service-type))
+            user-processes-service-type
+
+            assert-valid-graph))
 
 ;;; Commentary:
 ;;;
@@ -97,7 +99,11 @@
   #~(begin
       ;; Keep track of the booted system.
       (false-if-exception (delete-file "/run/booted-system"))
-      (symlink (readlink "/run/current-system")
+
+      ;; Make /run/booted-system, an indirect GC root, point to the store item
+      ;; /run/current-system points to.  Use 'canonicalize-path' rather than
+      ;; 'readlink' to make sure we get the store item.
+      (symlink (canonicalize-path "/run/current-system")
                "/run/booted-system")
 
       ;; Close any remaining open file descriptors to be on the safe
diff --git a/gnu/services/sysctl.scm b/gnu/services/sysctl.scm
index eb7a61b2a9..aaea7cc30d 100644
--- a/gnu/services/sysctl.scm
+++ b/gnu/services/sysctl.scm
@@ -25,20 +25,26 @@
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 match)
   #:export (sysctl-configuration
-            sysctl-service-type))
+            sysctl-service-type
+            %default-sysctl-settings))
 
 
 ;;;
 ;;; System Control Service.
 ;;;
 
+(define %default-sysctl-settings
+  ;; Default kernel parameters enabled with sysctl.
+  '(("fs.protected_hardlinks" . "1")
+    ("fs.protected_symlinks" . "1")))
+
 (define-record-type* <sysctl-configuration>
   sysctl-configuration make-sysctl-configuration
   sysctl-configuration?
   (sysctl   sysctl-configuration-sysctl    ; path of the 'sysctl' command
             (default (file-append procps "/sbin/sysctl")))
   (settings sysctl-configuration-settings  ; alist of string pairs
-            (default '())))
+            (default %default-sysctl-settings)))
 
 (define (sysctl-configuration-settings->sysctl.conf settings)
   "Return a file for @command{sysctl} to set kernel parameters as specified by
diff --git a/gnu/services/virtualization.scm b/gnu/services/virtualization.scm
index a45da14a80..36e9feb05c 100644
--- a/gnu/services/virtualization.scm
+++ b/gnu/services/virtualization.scm
@@ -554,13 +554,14 @@ potential infinite waits blocking libvirt."))
 ;;;
 
 ;; Platforms that QEMU can emulate.
-(define-record-type <qemu-platform>
-  (qemu-platform name family magic mask)
+(define-record-type* <qemu-platform>
+  qemu-platform make-qemu-platform
   qemu-platform?
   (name     qemu-platform-name)                   ;string
   (family   qemu-platform-family)                 ;string
   (magic    qemu-platform-magic)                  ;bytevector
-  (mask     qemu-platform-mask))                  ;bytevector
+  (mask     qemu-platform-mask)                   ;bytevector
+  (flags    qemu-platform-flags (default "F")))   ;string
 
 (define-syntax bv
   (lambda (s)
@@ -577,125 +578,173 @@ potential infinite waits blocking libvirt."))
 ;;; 'scripts/qemu-binfmt-conf.sh' in QEMU.
 
 (define %i386
-  (qemu-platform "i386" "i386"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00")
-                 (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "i386")
+   (family "i386")
+   (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 %i486
-  (qemu-platform "i486" "i386"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00")
-                 (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "i486")
+   (family "i386")
+   (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %alpha
-  (qemu-platform "alpha" "alpha"
-                 (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90")
-                 (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "alpha")
+   (family "alpha")
+   (magic (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90"))
+   (mask (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %arm
-  (qemu-platform "arm" "arm"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "arm")
+   (family "arm")
+   (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %armeb
-  (qemu-platform "armeb" "arm"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "armeb")
+   (family "arm")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %sparc
-  (qemu-platform "sparc" "sparc"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "sparc")
+   (family "sparc")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %sparc32plus
-  (qemu-platform "sparc32plus" "sparc"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "sparc32plus")
+   (family "sparc")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %ppc
-  (qemu-platform "ppc" "ppc"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "ppc")
+   (family "ppc")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %ppc64
-  (qemu-platform "ppc64" "ppc"
-                 (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "ppc64")
+   (family "ppc")
+   (magic (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %ppc64le
-  (qemu-platform "ppc64le" "ppcle"
-                 (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00")))
+  (qemu-platform
+   (name "ppc64le")
+   (family "ppcle")
+   (magic (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00"))))
 
 (define %m68k
-  (qemu-platform "m68k" "m68k"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x04")
-                 (bv "\xff\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "m68k")
+   (family "m68k")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x04"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 ;; XXX: We could use the other endianness on a MIPS host.
 (define %mips
-  (qemu-platform "mips" "mips"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "mips")
+   (family "mips")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %mipsel
-  (qemu-platform "mipsel" "mips"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "mipsel")
+   (family "mips")
+   (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %mipsn32
-  (qemu-platform "mipsn32" "mips"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "mipsn32")
+   (family "mips")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %mipsn32el
-  (qemu-platform "mipsn32el" "mips"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "mipsn32el")
+   (family "mips")
+   (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %mips64
-  (qemu-platform "mips64" "mips"
-                 (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "mips64")
+   (family "mips")
+   (magic (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %mips64el
-  (qemu-platform "mips64el" "mips"
-                 (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "mips64el")
+   (family "mips")
+   (magic (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %riscv32
-  (qemu-platform "riscv32" "riscv"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "riscv32")
+   (family "riscv")
+   (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %riscv64
-  (qemu-platform "riscv64" "riscv"
-                 (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "riscv64")
+   (family "riscv")
+   (magic (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %sh4
-  (qemu-platform "sh4" "sh4"
-                 (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "sh4")
+   (family "sh4")
+   (magic (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %sh4eb
-  (qemu-platform "sh4eb" "sh4"
-                 (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "sh4eb")
+   (family "sh4")
+   (magic (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %s390x
-  (qemu-platform "s390x" "s390x"
-                 (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x16")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "s390x")
+   (family "s390x")
+   (magic (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x16"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))))
 
 (define %aarch64
-  (qemu-platform "aarch64" "arm"
-                 (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff")))
+  (qemu-platform
+   (name "aarch64")
+   (family "arm")
+   (magic (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00"))
+   (mask (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))))
 
 (define %hppa
-  (qemu-platform "hppa" "hppa"
-                 (bv "\x7f\x45\x4c\x46\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x0f")
-                 (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff")))
+  (qemu-platform
+   (name "hppa")
+   (family "hppa")
+   (magic (bv "\x7f\x45\x4c\x46\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x0f"))
+   (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 %i486 %alpha %arm %sparc32plus %ppc %ppc64 %ppc64le %m68k
@@ -715,9 +764,7 @@ potential infinite waits blocking libvirt."))
   (qemu        qemu-binfmt-configuration-qemu
                (default qemu))
   (platforms   qemu-binfmt-configuration-platforms
-               (default '()))                     ;safest default
-  (guix-support? qemu-binfmt-configuration-guix-support?
-                 (default #t)))
+               (default '())))          ;safest default
 
 (define (qemu-platform->binfmt qemu platform)
   "Return a gexp that evaluates to a binfmt string for PLATFORM, using the
@@ -733,14 +780,13 @@ given QEMU package."
           (bytevector->u8-list bv))))
 
   (match platform
-    (($ <qemu-platform> name family magic mask)
+    (($ <qemu-platform> name family magic mask flags)
      ;; See 'Documentation/binfmt_misc.txt' in the kernel.
      #~(string-append ":qemu-" #$name ":M::"
                       #$(bytevector->binfmt-string magic)
                       ":" #$(bytevector->binfmt-string mask)
-                      ":" #$(file-append qemu "/bin/qemu-" name)
-                      ":"                         ;FLAGS go here
-                      ))))
+                      ":" #$qemu:static "/bin/qemu-" #$name
+                      ":" #$flags))))
 
 (define %binfmt-mount-point
   (file-system-mount-point %binary-format-file-system))
@@ -779,19 +825,6 @@ given QEMU package."
                                 '#$(map qemu-platform-name platforms))
                       #f)))))))
 
-(define qemu-binfmt-guix-chroot
-  (match-lambda
-    ;; Add QEMU and its dependencies to the guix-daemon chroot so that our
-    ;; binfmt_misc handlers work in the chroot (otherwise 'execve' would fail
-    ;; with ENOENT.)
-    ;;
-    ;; The 'F' flag of binfmt_misc is meant to address this problem by loading
-    ;; the interpreter upfront rather than lazily, but apparently that is
-    ;; insufficient (perhaps it loads the 'qemu-ARCH' binary upfront but looks
-    ;; up its dependencies lazily?).
-    (($ <qemu-binfmt-configuration> qemu platforms guix?)
-     (if guix? (list qemu) '()))))
-
 (define qemu-binfmt-service-type
   ;; TODO: Make a separate binfmt_misc service out of this?
   (service-type (name 'qemu-binfmt)
@@ -800,9 +833,7 @@ given QEMU package."
                                           (const
                                            (list %binary-format-file-system)))
                        (service-extension shepherd-root-service-type
-                                          qemu-binfmt-shepherd-services)
-                       (service-extension guix-service-type
-                                          qemu-binfmt-guix-chroot)))
+                                          qemu-binfmt-shepherd-services)))
                 (default-value (qemu-binfmt-configuration))
                 (description
                  "This service supports transparent emulation of binaries
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm
index 70f2617c7e..3e315a6df2 100644
--- a/gnu/services/vpn.scm
+++ b/gnu/services/vpn.scm
@@ -40,7 +40,24 @@
             openvpn-remote-configuration
             openvpn-ccd-configuration
             generate-openvpn-client-documentation
-            generate-openvpn-server-documentation))
+            generate-openvpn-server-documentation
+
+            wireguard-peer
+            wireguard-peer?
+            wireguard-peer-name
+            wireguard-peer-endpoint
+            wireguard-peer-allowed-ips
+
+            wireguard-configuration
+            wireguard-configuration?
+            wireguard-configuration-wireguard
+            wireguard-configuration-interface
+            wireguard-configuration-addresses
+            wireguard-configuration-port
+            wireguard-configuration-private-key
+            wireguard-configuration-peers
+
+            wireguard-service-type))
 
 ;;;
 ;;; OpenVPN.
@@ -507,3 +524,122 @@ is truncated and rewritten every minute.")
       (remote openvpn-remote-configuration))
      (openvpn-remote-configuration ,openvpn-remote-configuration-fields))
    'openvpn-client-configuration))
+
+
+;;;
+;;; Wireguard.
+;;;
+
+(define-record-type* <wireguard-peer>
+  wireguard-peer make-wireguard-peer
+  wireguard-peer?
+  (name              wireguard-peer-name)
+  (endpoint          wireguard-peer-endpoint
+                     (default #f))     ;string
+  (public-key        wireguard-peer-public-key)   ;string
+  (allowed-ips       wireguard-peer-allowed-ips)) ;list of strings
+
+(define-record-type* <wireguard-configuration>
+  wireguard-configuration make-wireguard-configuration
+  wireguard-configuration?
+  (wireguard          wireguard-configuration-wireguard ;<package>
+                      (default wireguard-tools))
+  (interface          wireguard-configuration-interface ;string
+                      (default "wg0"))
+  (addresses          wireguard-configuration-addresses ;string
+                      (default '("10.0.0.1/32")))
+  (port               wireguard-configuration-port ;integer
+                      (default 51820))
+  (private-key        wireguard-configuration-private-key ;string
+                      (default "/etc/wireguard/private.key"))
+  (peers              wireguard-configuration-peers ;list of <wiregard-peer>
+                      (default '())))
+
+(define (wireguard-configuration-file config)
+  (define (peer->config peer)
+    (let ((name (wireguard-peer-name peer))
+          (public-key (wireguard-peer-public-key peer))
+          (endpoint (wireguard-peer-endpoint peer))
+          (allowed-ips (wireguard-peer-allowed-ips peer)))
+      (format #f "[Peer] #~a
+PublicKey = ~a
+AllowedIPs = ~a
+~a"
+              name
+              public-key
+              (string-join allowed-ips ",")
+              (if endpoint
+                  (format #f "Endpoint = ~a\n" endpoint)
+                  "\n"))))
+
+  (match-record config <wireguard-configuration>
+    (wireguard interface addresses port private-key peers)
+    (let* ((config-file (string-append interface ".conf"))
+           (peers (map peer->config peers))
+           (config
+            (computed-file
+             "wireguard-config"
+             #~(begin
+                 (mkdir #$output)
+                 (chdir #$output)
+                 (call-with-output-file #$config-file
+                   (lambda (port)
+                     (let ((format (@ (ice-9 format) format)))
+                       (format port "[Interface]
+Address = ~a
+PostUp = ~a set %i private-key ~a
+~a
+~{~a~^~%~}"
+                               #$(string-join addresses ",")
+                               #$(file-append wireguard "/bin/wg")
+                               #$private-key
+                               #$(if port
+                                     (format #f "ListenPort = ~a" port)
+                                     "")
+                               (list #$@peers)))))))))
+      (file-append config "/" config-file))))
+
+(define (wireguard-activation config)
+  (match-record config <wireguard-configuration>
+    (private-key)
+    #~(begin
+        (use-modules (guix build utils)
+                     (ice-9 popen)
+                     (ice-9 rdelim))
+        (mkdir-p (dirname #$private-key))
+        (unless (file-exists? #$private-key)
+          (let* ((pipe
+                  (open-input-pipe (string-append
+                                    #$(file-append wireguard-tools "/bin/wg")
+                                    " genkey")))
+                 (key (read-line pipe)))
+            (call-with-output-file #$private-key
+              (lambda (port)
+                (display key port)))
+            (chmod #$private-key #o400)
+            (close-pipe pipe))))))
+
+(define (wireguard-shepherd-service config)
+  (match-record config <wireguard-configuration>
+    (wireguard interface)
+    (let ((wg-quick (file-append wireguard "/bin/wg-quick"))
+          (config (wireguard-configuration-file config)))
+      (list (shepherd-service
+             (requirement '(networking))
+             (provision (list
+                         (symbol-append 'wireguard-
+                                        (string->symbol interface))))
+             (start #~(lambda _
+                       (invoke #$wg-quick "up" #$config)))
+             (stop #~(lambda _
+                       (invoke #$wg-quick "down" #$config)))
+             (documentation "Run the Wireguard VPN tunnel"))))))
+
+(define wireguard-service-type
+  (service-type
+   (name 'wireguard)
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             wireguard-shepherd-service)
+          (service-extension activation-service-type
+                             wireguard-activation)))))
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index ff7b262b6a..aa688a4328 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -14,7 +14,7 @@
 ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
 ;;; Copyright © 2020 Arun Isaac <arunisaac@systemreboot.net>
 ;;; Copyright © 2020 Oleg Pykhalov <go.wigust@gmail.com>
-;;; Copyright © 2020 Alexandru-Sergiu Marton <brown121407@posteo.ro>
+;;; Copyright © 2020, 2021 Alexandru-Sergiu Marton <brown121407@posteo.ro>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -50,6 +50,7 @@
   #:use-module (gnu packages guile)
   #:use-module (gnu packages logging)
   #:use-module (gnu packages mail)
+  #:use-module (gnu packages rust-apps)
   #:use-module (guix packages)
   #:use-module (guix records)
   #:use-module (guix modules)
@@ -263,7 +264,25 @@
             gmnisrv-configuration-package
             gmnisrv-configuration-config-file
 
-            gmnisrv-service-type))
+            gmnisrv-service-type
+
+            agate-configuration
+            agate-configuration?
+            agate-configuration-package
+            agate-configuration-content
+            agate-configuration-cert
+            agate-configuration-key
+            agate-configuration-addr
+            agate-configuration-hostname
+            agate-configuration-lang
+            agate-configuration-silent
+            agate-configuration-serve-secret
+            agate-configuration-log-ip
+            agate-configuration-user
+            agate-configuration-group
+            agate-configuration-log-file
+
+            agate-service-type))
 
 ;;; Commentary:
 ;;;
@@ -1885,3 +1904,92 @@ root=/srv/gemini
     "Run the gmnisrv Gemini server.")
    (default-value
      (gmnisrv-configuration))))
+
+(define-record-type* <agate-configuration>
+  agate-configuration make-agate-configuration
+  agate-configuration?
+  (package  agate-configuration-package
+            (default agate))
+  (content  agate-configuration-content
+            (default "/srv/gemini"))
+  (cert     agate-configuration-cert
+            (default #f))
+  (key      agate-configuration-key
+            (default #f))
+  (addr     agate-configuration-addr
+            (default '("0.0.0.0:1965" "[::]:1965")))
+  (hostname agate-configuration-hostname
+            (default #f))
+  (lang     agate-configuration-lang
+            (default #f))
+  (silent?  agate-configuration-silent
+            (default #f))
+  (serve-secret? agate-configuration-serve-secret
+                 (default #f))
+  (log-ip?  agate-configuration-log-ip
+            (default #t))
+  (user     agate-configuration-user
+            (default "agate"))
+  (group    agate-configuration-group
+            (default "agate"))
+  (log-file agate-configuration-log
+            (default "/var/log/agate.log")))
+
+(define agate-shepherd-service
+  (match-lambda
+    (($ <agate-configuration> package content cert key addr
+                              hostname lang silent? serve-secret?
+                              log-ip? user group log-file)
+     (list (shepherd-service
+            (provision '(agate))
+            (requirement '(networking))
+            (documentation "Run the agate Gemini server.")
+            (start (let ((agate (file-append package "/bin/agate")))
+                     #~(make-forkexec-constructor
+			(list #$agate
+			      "--content" #$content
+			      "--cert" #$cert
+			      "--key" #$key
+			      "--addr" #$@addr
+                              #$@(if lang
+                                     (list "--lang" lang)
+                                     '())
+			      #$@(if hostname
+				     (list "--hostname" hostname)
+				     '())
+			      #$@(if silent? '("--silent") '())
+			      #$@(if serve-secret? '("--serve-secret") '())
+			      #$@(if log-ip? '("--log-ip") '()))
+			#:user #$user #:group #$group
+			#:log-file #$log-file)))
+            (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")))))))
+
+(define agate-service-type
+  (service-type
+   (name 'guix)
+   (extensions
+    (list (service-extension account-service-type
+                             agate-accounts)
+          (service-extension shepherd-root-service-type
+                             agate-shepherd-service)))
+   (default-value (agate-configuration))))
diff --git a/gnu/services/xorg.scm b/gnu/services/xorg.scm
index 4590709187..60611dc77d 100644
--- a/gnu/services/xorg.scm
+++ b/gnu/services/xorg.scm
@@ -45,6 +45,7 @@
   #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu system shadow)
+  #:use-module (guix build-system trivial)
   #:use-module (guix gexp)
   #:use-module (guix store)
   #:use-module (guix packages)
@@ -70,6 +71,7 @@
             xorg-wrapper
             xorg-start-command
             xinitrc
+            xorg-server-service-type
 
             %default-slim-theme
             %default-slim-theme-name
@@ -483,6 +485,41 @@ a `service-extension', as used by `set-xorg-configuration'."
                       (xorg-configuration xorg-configuration))
                      config)))))))
 
+(define (xorg-server-profile-service config)
+  ;; XXX: profile-service-type only accepts <package> objects.
+  (list
+   (package
+     (name "xorg-wrapper")
+     (version (package-version xorg-server))
+     (source (xorg-wrapper config))
+     (build-system trivial-build-system)
+     (arguments
+      '(#:modules ((guix build utils))
+        #:builder
+        (begin
+          (use-modules (guix build utils))
+          (let* ((source (assoc-ref %build-inputs "source"))
+                 (out (assoc-ref %outputs "out"))
+                 (bin (string-append out "/bin")))
+            (mkdir-p bin)
+            (symlink source (string-append bin "/X"))
+            (symlink source (string-append bin "/Xorg"))
+            #t))))
+     (home-page (package-home-page xorg-server))
+     (synopsis (package-synopsis xorg-server))
+     (description (package-description xorg-server))
+     (license (package-license xorg-server)))))
+
+(define xorg-server-service-type
+  (service-type
+   (name 'xorg-server)
+   (extensions
+    (list (service-extension profile-service-type
+                             xorg-server-profile-service)))
+   (default-value (xorg-configuration))
+   (description "Add @command{X} to the system profile, to be used with
+@command{sx} or @command{xinit}.")))
+
 
 ;;;
 ;;; SLiM log-in manager.