services: 'references-file' depends on Guile-Gcrypt.
[jackhill/guix/guix.git] / gnu / services / messaging.scm
index aa39897..8f2f391 100644 (file)
@@ -1,5 +1,8 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
+;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org>
+;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
+;;; Copyright © 2015, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018 Pierre-Antoine Rouby <contact@parouby.fr>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
 (define-module (gnu services messaging)
   #:use-module (gnu packages messaging)
   #:use-module (gnu packages admin)
+  #:use-module (gnu packages irc)
+  #:use-module (gnu packages tls)
   #:use-module (gnu services)
   #:use-module (gnu services shepherd)
   #:use-module (gnu services configuration)
   #:use-module (gnu system shadow)
   #:use-module (guix gexp)
+  #:use-module (guix modules)
   #:use-module (guix records)
   #:use-module (guix packages)
+  #:use-module (guix deprecation)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-35)
   #:use-module (ice-9 match)
             mod-muc-configuration
             ssl-configuration
 
-            %default-modules-enabled))
+            %default-modules-enabled
+            prosody-configuration-pidfile
+
+            bitlbee-configuration
+            bitlbee-configuration?
+            bitlbee-service
+            bitlbee-service-type
+
+            quassel-configuration
+            quassel-service-type))
 
 ;;; Commentary:
 ;;;
 ;;;
 ;;; Code:
 
-(define (id ctx . parts)
-  (datum->syntax ctx (apply symbol-append (map syntax->datum parts))))
-
-(define-syntax define-maybe
-  (lambda (x)
-    (syntax-case x ()
-      ((_ stem)
-       (with-syntax
-           ((stem?                (id #'stem #'stem #'?))
-            (maybe-stem?          (id #'stem #'maybe- #'stem #'?))
-            (serialize-stem       (id #'stem #'serialize- #'stem))
-            (serialize-maybe-stem (id #'stem #'serialize-maybe- #'stem)))
-         #'(begin
-             (define (maybe-stem? val)
-               (or (eq? val 'disabled) (stem? val)))
-             (define (serialize-maybe-stem field-name val)
-               (when (stem? val) (serialize-stem field-name val)))))))))
-
 (define-syntax define-all-configurations
   (lambda (stx)
+    (define-syntax-rule (id ctx parts ...)
+      "Assemble PARTS into a raw (unhygienic) identifier."
+      (datum->syntax ctx (symbol-append (syntax->datum parts) ...)))
     (define (make-pred arg)
       (lambda (field target)
         (and (memq (syntax->datum target) `(common ,arg)) field)))
                                  "" doc))
                            #'(doc ...) #'(target ...))))
          #`(begin
-             (define common-fields
+             (define #,(id #'stem #'common-fields)
                '(#,@(filter-map (make-pred #f) #'(field ...) #'(target ...))))
-             (define-configuration prosody-configuration
+             (define-configuration #,(id #'stem #'prosody-configuration)
                #,@(filter-map (make-pred 'global)
                               #'((field (field-type def) doc) ...)
                               #'(target ...)))
-             (define-configuration virtualhost-configuration
+             (define-configuration #,(id #'stem #'virtualhost-configuration)
                #,@(filter-map (make-pred 'virtualhost)
                               #'((field (new-field-type new-def) new-doc) ...)
                               #'(target ...)))
-             (define-configuration int-component-configuration
+             (define-configuration #,(id #'stem #'int-component-configuration)
                #,@(filter-map (make-pred 'int-component)
                               #'((field (new-field-type new-def) new-doc) ...)
                               #'(target ...)))
-             (define-configuration ext-component-configuration
+             (define-configuration #,(id #'stem #'ext-component-configuration)
                #,@(filter-map (make-pred 'ext-component)
                               #'((field (new-field-type new-def) new-doc) ...)
                               #'(target ...)))))))))
                  "_")))
 
 (define (serialize-field field-name val)
-  (format #t "~a = ~a;\n" (uglify-field-name field-name) val))
+  #~(format #f "~a = ~a;\n" #$(uglify-field-name field-name) #$val))
 (define (serialize-field-list field-name val)
-  (serialize-field field-name
-                   (with-output-to-string
-                     (lambda ()
-                       (format #t "{\n")
-                       (for-each (lambda (x)
-                                   (format #t "~a;\n" x))
-                                 val)
-                       (format #t "}")))))
+  (serialize-field field-name #~(format #f "{\n~@{~a;\n~}}" #$@val)))
 
 (define (serialize-boolean field-name val)
   (serialize-field field-name (if val "true" "false")))
 (define (non-negative-integer? val)
   (and (exact-integer? val) (not (negative? val))))
 (define (serialize-non-negative-integer field-name val)
-  (serialize-field field-name val))
+  (serialize-field field-name (number->string val)))
 (define-maybe non-negative-integer)
 
 (define (non-negative-integer-list? val)
   (and (list? val) (and-map non-negative-integer? val)))
 (define (serialize-non-negative-integer-list field-name val)
-  (serialize-field-list field-name val))
+  (serialize-field-list field-name (map number->string val)))
 (define-maybe non-negative-integer-list)
 
 (define (enclose-quotes s)
-  (format #f "\"~a\"" s))
+  #~(string-append "\"" #$s "\""))
 (define (serialize-string field-name val)
   (serialize-field field-name (enclose-quotes val)))
 (define-maybe string)
 (define (module-list? val)
   (string-list? val))
 (define (serialize-module-list field-name val)
-  (serialize-string-list field-name (cons "posix" val)))
+  (serialize-string-list field-name val))
 (define-maybe module-list)
 
 (define (file-name? val)
   (serialize-string-list field-name val))
 (define-maybe file-name)
 
+(define (file-object? val)
+  (or (file-like? val) (file-name? val)))
+(define (serialize-file-object field-name val)
+  (serialize-string field-name val))
+(define-maybe file-object)
+
+(define (file-object-list? val)
+  (and (list? val) (and-map file-object? val)))
+(define (serialize-file-object-list field-name val)
+  (serialize-string-list field-name val))
+(define-maybe file-object)
+
+(define (raw-content? val)
+  (not (eq? val 'disabled)))
+(define (serialize-raw-content field-name val)
+  val)
+(define-maybe raw-content)
+
 (define-configuration mod-muc-configuration
   (name
    (string "Prosody Chatrooms")
@@ -216,20 +228,20 @@ just joined the room."))
    "This determines what handshake to use.")
 
   (key
-   (file-name "/etc/prosody/certs/key.pem")
-   "Path to your private key file, relative to @code{/etc/prosody}.")
+   (maybe-file-name 'disabled)
+   "Path to your private key file.")
 
   (certificate
-   (file-name "/etc/prosody/certs/cert.pem")
-   "Path to your certificate file, relative to @code{/etc/prosody}.")
+   (maybe-file-name 'disabled)
+   "Path to your certificate file.")
 
   (capath
-   (file-name "/etc/ssl/certs")
+   (file-object "/etc/ssl/certs")
    "Path to directory containing root certificates that you wish Prosody to
 trust when verifying the certificates of remote servers.")
 
   (cafile
-   (maybe-file-name 'disabled)
+   (maybe-file-object 'disabled)
    "Path to a file containing root certificates that you wish Prosody to trust.
 Similar to @code{capath} but with all certificates concatenated together.")
 
@@ -273,9 +285,8 @@ can create such a file with:
    (maybe-string 'disabled)
    "Password for encrypted private keys."))
 (define (serialize-ssl-configuration field-name val)
-  (format #t "ssl = {\n")
-  (serialize-configuration val ssl-configuration-fields)
-  (format #t "};\n"))
+  #~(format #f "ssl = {\n~a};\n"
+            #$(serialize-configuration val ssl-configuration-fields)))
 (define-maybe ssl-configuration)
 
 (define %default-modules-enabled
@@ -284,7 +295,9 @@ can create such a file with:
     "tls"
     "dialback"
     "disco"
+    "carbons"
     "private"
+    "blocklist"
     "vcard"
     "version"
     "uptime"
@@ -301,20 +314,23 @@ can create such a file with:
   (define (virtualhost-configuration-list? val)
     (and (list? val) (and-map virtualhost-configuration? val)))
   (define (serialize-virtualhost-configuration-list l)
-    (for-each
-     (lambda (val) (serialize-virtualhost-configuration val)) l))
+    #~(string-append
+       #$@(map (lambda (val)
+                 (serialize-virtualhost-configuration val)) l)))
 
   (define (int-component-configuration-list? val)
     (and (list? val) (and-map int-component-configuration? val)))
   (define (serialize-int-component-configuration-list l)
-    (for-each
-     (lambda (val) (serialize-int-component-configuration val)) l))
+    #~(string-append
+       #$@(map (lambda (val)
+                 (serialize-int-component-configuration val)) l)))
 
   (define (ext-component-configuration-list? val)
     (and (list? val) (and-map ext-component-configuration? val)))
   (define (serialize-ext-component-configuration-list l)
-    (for-each
-     (lambda (val) (serialize-ext-component-configuration val)) l))
+    #~(string-append
+       #$@(map (lambda (val)
+                 (serialize-ext-component-configuration val)) l)))
 
   (define-all-configurations prosody-configuration
     (prosody
@@ -325,35 +341,42 @@ can create such a file with:
     (data-path
      (file-name "/var/lib/prosody")
      "Location of the Prosody data storage directory.  See
-@url{http://prosody.im/doc/configure}."
+@url{https://prosody.im/doc/configure}."
      global)
 
     (plugin-paths
-     (file-name-list '())
+     (file-object-list '())
      "Additional plugin directories.  They are searched in all the specified
-paths in order.  See @url{http://prosody.im/doc/plugins_directory}."
+paths in order.  See @url{https://prosody.im/doc/plugins_directory}."
+     global)
+
+    (certificates
+     (file-name "/etc/prosody/certs")
+     "Every virtual host and component needs a certificate so that clients and
+servers can securely verify its identity.  Prosody will automatically load
+certificates/keys from the directory specified here."
      global)
 
     (admins
      (string-list '())
      "This is a list of accounts that are admins for the server.  Note that you
-must create the accounts separately.  See @url{http://prosody.im/doc/admins} and
-@url{http://prosody.im/doc/creating_accounts}.
+must create the accounts separately.  See @url{https://prosody.im/doc/admins} and
+@url{https://prosody.im/doc/creating_accounts}.
 Example: @code{(admins '(\"user1@@example.com\" \"user2@@example.net\"))}"
      common)
 
     (use-libevent?
      (boolean #f)
      "Enable use of libevent for better performance under high load.  See
-@url{http://prosody.im/doc/libevent}."
+@url{https://prosody.im/doc/libevent}."
      common)
 
     (modules-enabled
      (module-list %default-modules-enabled)
      "This is the list of modules Prosody will load on startup.  It looks for
 @code{mod_modulename.lua} in the plugins folder, so make sure that exists too.
-Documentation on modules can be found at: @url{http://prosody.im/doc/modules}.
-Defaults to @samp{%default-modules-enabled}."
+Documentation on modules can be found at:
+@url{https://prosody.im/doc/modules}."
      common)
 
     (modules-disabled
@@ -363,16 +386,16 @@ should you want to disable them then add them to this list."
      common)
 
     (groups-file
-     (file-name "/var/lib/prosody/sharedgroups.txt")
+     (file-object "/var/lib/prosody/sharedgroups.txt")
      "Path to a text file where the shared groups are defined.  If this path is
 empty then @samp{mod_groups} does nothing.  See
-@url{http://prosody.im/doc/modules/mod_groups}."
+@url{https://prosody.im/doc/modules/mod_groups}."
      common)
 
     (allow-registration?
      (boolean #f)
      "Disable account creation by default, for security.  See
-@url{http://prosody.im/doc/creating_accounts}."
+@url{https://prosody.im/doc/creating_accounts}."
      common)
 
     (ssl
@@ -380,19 +403,25 @@ empty then @samp{mod_groups} does nothing.  See
      "These are the SSL/TLS-related settings.  Most of them are disabled so to
 use Prosody's defaults.  If you do not completely understand these options, do
 not add them to your config, it is easy to lower the security of your server
-using them.  See @url{http://prosody.im/doc/advanced_ssl_config}."
+using them.  See @url{https://prosody.im/doc/advanced_ssl_config}."
      common)
 
     (c2s-require-encryption?
      (boolean #f)
      "Whether to force all client-to-server connections to be encrypted or not.
-See @url{http://prosody.im/doc/modules/mod_tls}."
+See @url{https://prosody.im/doc/modules/mod_tls}."
+     common)
+
+    (disable-sasl-mechanisms
+     (string-list '("DIGEST-MD5"))
+     "Set of mechanisms that will never be offered.  See
+@url{https://prosody.im/doc/modules/mod_saslauth}."
      common)
 
     (s2s-require-encryption?
      (boolean #f)
      "Whether to force all server-to-server connections to be encrypted or not.
-See @url{http://prosody.im/doc/modules/mod_tls}."
+See @url{https://prosody.im/doc/modules/mod_tls}."
      common)
 
     (s2s-secure-auth?
@@ -400,7 +429,7 @@ See @url{http://prosody.im/doc/modules/mod_tls}."
      "Whether to require encryption and certificate authentication.  This
 provides ideal security, but requires servers you communicate with to support
 encryption AND present valid, trusted certificates.  See
-@url{http://prosody.im/doc/s2s#security}."
+@url{https://prosody.im/doc/s2s#security}."
      common)
 
     (s2s-insecure-domains
@@ -408,14 +437,14 @@ encryption AND present valid, trusted certificates.  See
      "Many servers don't support encryption or have invalid or self-signed
 certificates.  You can list domains here that will not be required to
 authenticate using certificates.  They will be authenticated using DNS.  See
-@url{http://prosody.im/doc/s2s#security}."
+@url{https://prosody.im/doc/s2s#security}."
      common)
 
     (s2s-secure-domains
      (string-list '())
      "Even if you leave @code{s2s-secure-auth?} disabled, you can still require
 valid certificates for some domains by specifying a list here.  See
-@url{http://prosody.im/doc/s2s#security}."
+@url{https://prosody.im/doc/s2s#security}."
      common)
 
     (authentication
@@ -423,23 +452,36 @@ valid certificates for some domains by specifying a list here.  See
      "Select the authentication backend to use.  The default provider stores
 passwords in plaintext and uses Prosody's configured data storage to store the
 authentication data.  If you do not trust your server please see
-@url{http://prosody.im/doc/modules/mod_auth_internal_hashed} for information
+@url{https://prosody.im/doc/modules/mod_auth_internal_hashed} for information
 about using the hashed backend.  See also
-@url{http://prosody.im/doc/authentication}"
+@url{https://prosody.im/doc/authentication}"
      common)
 
     ;; TODO: Handle more complicated log structures.
     (log
      (maybe-string "*syslog")
      "Set logging options.  Advanced logging configuration is not yet supported
-by the GuixSD Prosody Service.  See @url{http://prosody.im/doc/logging}."
+by the Prosody service.  See @url{https://prosody.im/doc/logging}."
      common)
 
     (pidfile
      (file-name "/var/run/prosody/prosody.pid")
-     "File to write pid in.  See @url{http://prosody.im/doc/modules/mod_posix}."
+     "File to write pid in.  See @url{https://prosody.im/doc/modules/mod_posix}."
      global)
 
+    (http-max-content-size
+     (maybe-non-negative-integer 'disabled)
+     "Maximum allowed size of the HTTP body (in bytes)."
+     common)
+
+    (http-external-url
+     (maybe-string 'disabled)
+     "Some modules expose their own URL in various ways.  This URL is built
+from the protocol, host and port used.  If Prosody sits behind a proxy, the
+public URL will be @code{http-external-url} instead.  See
+@url{https://prosody.im/doc/http#external_url}."
+     common)
+
     (virtualhosts
      (virtualhost-configuration-list
       (list (virtualhost-configuration
@@ -455,7 +497,7 @@ instance can serve many domains, each one defined as a VirtualHost entry in
 Prosody's configuration.  Conversely a server that hosts a single domain would
 have just one VirtualHost entry.
 
-See @url{http://prosody.im/doc/configure#virtual_host_settings}."
+See @url{https://prosody.im/doc/configure#virtual_host_settings}."
      global)
 
     (int-components
@@ -469,14 +511,14 @@ Internal components are implemented with Prosody-specific plugins.  To add an
 internal component, you simply fill the hostname field, and the plugin you wish
 to use for the component.
 
-See @url{http://prosody.im/doc/components}."
+See @url{https://prosody.im/doc/components}."
      global)
 
     (ext-components
      (ext-component-configuration-list '())
      "External components use XEP-0114, which most standalone components
 support.  To add an external component, you simply fill the hostname field.  See
-@url{http://prosody.im/doc/components}."
+@url{https://prosody.im/doc/components}."
      global)
 
     (component-secret
@@ -515,16 +557,21 @@ support.  To add an external component, you simply fill the hostname field.  See
 hosted chatrooms/conferences for XMPP users.
 
 General information on setting up and using multi-user chatrooms can be found
-in the \"Chatrooms\" documentation (@url{http://prosody.im/doc/chatrooms}),
+in the \"Chatrooms\" documentation (@url{https://prosody.im/doc/chatrooms}),
 which you should read if you are new to XMPP chatrooms.
 
-See also @url{http://prosody.im/doc/modules/mod_muc}."
+See also @url{https://prosody.im/doc/modules/mod_muc}."
      int-component)
 
     (hostname
      (string (configuration-missing-field 'ext-component 'hostname))
      "Hostname of the component."
-     ext-component)))
+     ext-component)
+
+    (raw-content
+     (maybe-raw-content 'disabled)
+     "Raw content that will be added to the configuration file."
+     common)))
 
 ;; Serialize Virtualhost line first.
 (define (serialize-virtualhost-configuration config)
@@ -533,8 +580,9 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
                '(domain))))
   (let ((domain (virtualhost-configuration-domain config))
         (rest (filter rest? virtualhost-configuration-fields)))
-    (format #t "VirtualHost \"~a\"\n" domain)
-    (serialize-configuration config rest)))
+    #~(string-append
+       #$(format #f "VirtualHost \"~a\"\n" domain)
+       #$(serialize-configuration config rest))))
 
 ;; Serialize Component line first.
 (define (serialize-int-component-configuration config)
@@ -544,8 +592,9 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
   (let ((hostname (int-component-configuration-hostname config))
         (plugin (int-component-configuration-plugin config))
         (rest (filter rest? int-component-configuration-fields)))
-    (format #t "Component \"~a\" \"~a\"\n" hostname plugin)
-    (serialize-configuration config rest)))
+    #~(string-append
+       #$(format #f "Component \"~a\" \"~a\"\n" hostname plugin)
+       #$(serialize-configuration config rest))))
 
 ;; Serialize Component line first.
 (define (serialize-ext-component-configuration config)
@@ -554,22 +603,24 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
                '(hostname))))
   (let ((hostname (ext-component-configuration-hostname config))
         (rest (filter rest? ext-component-configuration-fields)))
-    (format #t "Component \"~a\"\n" hostname)
-    (serialize-configuration config rest)))
+    #~(string-append
+       #$(format #f "Component \"~a\"\n" hostname)
+       #$(serialize-configuration config rest))))
 
 ;; Serialize virtualhosts and components last.
 (define (serialize-prosody-configuration config)
   (define (rest? field)
     (not (memq (configuration-field-name field)
                '(virtualhosts int-components ext-components))))
-  (let ((rest (filter rest? prosody-configuration-fields)))
-    (serialize-configuration config rest))
-  (serialize-virtualhost-configuration-list
-   (prosody-configuration-virtualhosts config))
-  (serialize-int-component-configuration-list
-   (prosody-configuration-int-components config))
-  (serialize-ext-component-configuration-list
-   (prosody-configuration-ext-components config)))
+  #~(string-append
+     #$(let ((rest (filter rest? prosody-configuration-fields)))
+         (serialize-configuration config rest))
+     #$(serialize-virtualhost-configuration-list
+        (prosody-configuration-virtualhosts config))
+     #$(serialize-int-component-configuration-list
+        (prosody-configuration-int-components config))
+     #$(serialize-ext-component-configuration-list
+        (prosody-configuration-ext-components config))))
 
 (define-configuration opaque-prosody-configuration
   (prosody
@@ -587,13 +638,20 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
                       (opaque-prosody-configuration-prosody config)
                       (prosody-configuration-prosody config)))
          (prosodyctl-bin (file-append prosody "/bin/prosodyctl"))
+         (pid-file (prosody-configuration-pidfile config))
          (prosodyctl-action (lambda args
                               #~(lambda _
-                                  (zero? (system* #$prosodyctl-bin #$@args))))))
+                                  (invoke #$prosodyctl-bin #$@args)
+                                  (match '#$args
+                                    (("start")
+                                     (call-with-input-file #$pid-file read))
+                                    (_ #t))))))
     (list (shepherd-service
            (documentation "Run the Prosody XMPP server")
-           (provision '(prosody))
+           (provision '(prosody xmpp-daemon))
            (requirement '(networking syslogd user-processes))
+           (modules `((ice-9 match)
+                      ,@%default-modules))
            (start (prosodyctl-action "start"))
            (stop (prosodyctl-action "stop"))))))
 
@@ -613,13 +671,12 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
          (default-certs-dir "/etc/prosody/certs")
          (data-path (prosody-configuration-data-path config))
          (pidfile-dir (dirname (prosody-configuration-pidfile config)))
-         (config-str
-          (if (opaque-prosody-configuration? config)
-              (opaque-prosody-configuration-prosody.cfg.lua config)
-              (with-output-to-string
-                (lambda ()
-                  (serialize-prosody-configuration config)))))
-         (config-file (plain-file "prosody.cfg.lua" config-str)))
+         (config-str (if (opaque-prosody-configuration? config)
+                         (opaque-prosody-configuration-prosody.cfg.lua config)
+                         #~(begin
+                             (use-modules (ice-9 format))
+                             #$(serialize-prosody-configuration config))))
+         (config-file (mixed-text-file "prosody.cfg.lua" config-str)))
     #~(begin
         (use-modules (guix build utils))
         (define %user (getpw "prosody"))
@@ -648,7 +705,14 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
                        (service-extension account-service-type
                                           (const %prosody-accounts))
                        (service-extension activation-service-type
-                                          prosody-activation)))))
+                                          prosody-activation)))
+                (default-value (prosody-configuration
+                                (virtualhosts
+                                 (list
+                                  (virtualhost-configuration
+                                   (domain "localhost"))))))
+                (description
+                 "Run Prosody, a modern XMPP communication server.")))
 
 ;; A little helper to make it easier to document all those fields.
 (define (generate-documentation)
@@ -696,7 +760,7 @@ See also @url{http://prosody.im/doc/modules/mod_muc}."
                                      (display c))
                                    str))))
             (define (show-default? val)
-              (or (string? default) (number? default) (boolean? default)
+              (or (string? val) (number? val) (boolean? val)
                   (and (list? val) (and-map show-default? val))))
             (format #t "@deftypevr {@code{~a} parameter} ~a ~a\n~a\n"
                     configuration-name field-type field-name field-docs)
@@ -725,3 +789,205 @@ string, you could instantiate a prosody service like this:
          (opaque-prosody-configuration
           (prosody.cfg.lua \"\")))
 @end example"))
+
+\f
+;;;
+;;; BitlBee.
+;;;
+
+(define-record-type* <bitlbee-configuration>
+  bitlbee-configuration make-bitlbee-configuration
+  bitlbee-configuration?
+  (bitlbee bitlbee-configuration-bitlbee
+           (default bitlbee))
+  (interface bitlbee-configuration-interface
+             (default "127.0.0.1"))
+  (port bitlbee-configuration-port
+        (default 6667))
+  (plugins bitlbee-plugins
+           (default '()))
+  (extra-settings bitlbee-configuration-extra-settings
+                  (default "")))
+
+(define bitlbee-shepherd-service
+  (match-lambda
+    (($ <bitlbee-configuration> bitlbee interface port
+                                plugins extra-settings)
+     (let* ((plugins (directory-union "bitlbee-plugins" plugins))
+            (conf    (mixed-text-file "bitlbee.conf"
+                                  "
+  [settings]
+  User = bitlbee
+  ConfigDir = /var/lib/bitlbee
+  DaemonInterface = " interface "
+  DaemonPort = " (number->string port) "
+  PluginDir = " plugins "/lib/bitlbee
+" extra-settings)))
+
+       (with-imported-modules (source-module-closure
+                               '((gnu build shepherd)
+                                 (gnu system file-systems)))
+         (list (shepherd-service
+                (provision '(bitlbee))
+
+                ;; Note: If networking is not up, then /etc/resolv.conf
+                ;; doesn't get mapped in the container, hence the dependency
+                ;; on 'networking'.
+                (requirement '(user-processes networking))
+
+                (modules '((gnu build shepherd)
+                           (gnu system file-systems)))
+                (start #~(make-forkexec-constructor/container
+                          (list #$(file-append bitlbee "/sbin/bitlbee")
+                                "-n" "-F" "-u" "bitlbee" "-c" #$conf)
+
+                          ;; Allow 'bitlbee-purple' to use libpurple plugins.
+                          #:environment-variables
+                          (list (string-append "PURPLE_PLUGIN_PATH="
+                                               #$plugins "/lib/purple-2"))
+
+                          #:pid-file "/var/run/bitlbee.pid"
+                          #:mappings (list (file-system-mapping
+                                            (source "/var/lib/bitlbee")
+                                            (target source)
+                                            (writable? #t)))))
+                (stop  #~(make-kill-destructor)))))))))
+
+(define %bitlbee-accounts
+  ;; User group and account to run BitlBee.
+  (list (user-group (name "bitlbee") (system? #t))
+        (user-account
+         (name "bitlbee")
+         (group "bitlbee")
+         (system? #t)
+         (comment "BitlBee daemon user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
+
+(define %bitlbee-activation
+  ;; Activation gexp for BitlBee.
+  #~(begin
+      (use-modules (guix build utils))
+
+      ;; This directory is used to store OTR data.
+      (mkdir-p "/var/lib/bitlbee")
+      (let ((user (getpwnam "bitlbee")))
+        (chown "/var/lib/bitlbee"
+               (passwd:uid user) (passwd:gid user)))))
+
+(define bitlbee-service-type
+  (service-type (name 'bitlbee)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          bitlbee-shepherd-service)
+                       (service-extension account-service-type
+                                          (const %bitlbee-accounts))
+                       (service-extension activation-service-type
+                                          (const %bitlbee-activation))))
+                (default-value (bitlbee-configuration))
+                (description
+                 "Run @url{http://bitlbee.org,BitlBee}, a daemon that acts as
+a gateway between IRC and chat networks.")))
+
+(define-deprecated (bitlbee-service #:key (bitlbee bitlbee)
+                                    (interface "127.0.0.1") (port 6667)
+                                    (extra-settings ""))
+  bitlbee-service-type
+  "Return a service that runs @url{http://bitlbee.org,BitlBee}, a daemon that
+acts as a gateway between IRC and chat networks.
+
+The daemon will listen to the interface corresponding to the IP address
+specified in @var{interface}, on @var{port}.  @code{127.0.0.1} means that only
+local clients can connect, whereas @code{0.0.0.0} means that connections can
+come from any networking interface.
+
+In addition, @var{extra-settings} specifies a string to append to the
+configuration file."
+  (service bitlbee-service-type
+           (bitlbee-configuration
+            (bitlbee bitlbee)
+            (interface interface) (port port)
+            (extra-settings extra-settings))))
+
+\f
+;;;
+;;; Quassel.
+;;;
+
+(define-record-type* <quassel-configuration>
+  quassel-configuration make-quassel-configuration
+  quassel-configuration?
+  (quassel quassel-configuration-quassel
+           (default quassel))
+  (interface quassel-configuration-interface
+             (default "::,0.0.0.0"))
+  (port quassel-configuration-port
+        (default 4242))
+  (loglevel quassel-configuration-loglevel
+            (default "Info")))
+
+(define quassel-shepherd-service
+  (match-lambda
+    (($ <quassel-configuration> quassel interface port loglevel)
+     (with-imported-modules (source-module-closure
+                              '((gnu build shepherd)
+                                (gnu system file-systems)))
+       (list (shepherd-service
+               (provision '(quassel))
+               (requirement '(user-processes networking))
+               (modules '((gnu build shepherd)
+                          (gnu system file-systems)))
+               (start #~(make-forkexec-constructor/container
+                          (list #$(file-append quassel "/bin/quasselcore")
+                                "--configdir=/var/lib/quassel"
+                                "--logfile=/var/log/quassel/core.log"
+                                (string-append "--loglevel=" #$loglevel)
+                                (string-append "--port=" (number->string #$port))
+                                (string-append "--listen=" #$interface))
+                          #:mappings (list (file-system-mapping
+                                             (source "/var/lib/quassel")
+                                             (target source)
+                                             (writable? #t))
+                                           (file-system-mapping
+                                             (source "/var/log/quassel")
+                                             (target source)
+                                             (writable? #t)))))
+               (stop  #~(make-kill-destructor))))))))
+
+(define %quassel-account
+  (list (user-group (name "quassel") (system? #t))
+        (user-account
+          (name "quasselcore")
+          (group "quassel")
+          (system? #t)
+          (comment "Quassel daemon user")
+          (home-directory "/var/lib/quassel")
+          (shell (file-append shadow "/sbin/nologin")))))
+
+(define %quassel-activation
+  #~(begin
+      (use-modules (guix build utils))
+      (mkdir-p "/var/lib/quassel")
+      (mkdir-p "/var/log/quassel")
+      (let ((cert "/var/lib/quassel/quasselCert.pem"))
+        (unless (file-exists? cert)
+          (invoke #$(file-append openssl "/bin/openssl")
+                  "req" "-x509" "-nodes" "-batch" "-days" "680" "-newkey"
+                  "rsa" "-keyout" cert "-out" cert)))))
+
+(define quassel-service-type
+  (service-type (name 'quassel)
+                (extensions
+                  (list (service-extension shepherd-root-service-type
+                                           quassel-shepherd-service)
+                        (service-extension profile-service-type
+                                           (compose list quassel-configuration-quassel))
+                        (service-extension account-service-type
+                                           (const %quassel-account))
+                        (service-extension activation-service-type
+                                           (const %quassel-activation))))
+                (default-value (quassel-configuration))
+                (description
+                 "Run @url{https://quassel-irc.org/,quasselcore}, the backend
+for the distributed IRC client quassel, which allows you to connect from
+multiple machines simultaneously.")))