summary refs log tree commit diff
path: root/gnu/services
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2017-01-23 22:33:10 +0100
committerLudovic Courtès <ludo@gnu.org>2017-01-23 22:33:10 +0100
commit58ea4d407c2e4adbe51b2d7b71dc8bef095677c7 (patch)
tree0fd70c0cb82d7980a7ff82500dec7bfd0d535d3f /gnu/services
parentfcd75bdbfa99d14363b905afbf914eec20e69df8 (diff)
parent84b60a7cdfca1421a478894e279104a0c18a7c6d (diff)
downloadguix-58ea4d407c2e4adbe51b2d7b71dc8bef095677c7.tar.gz
Merge branch 'master' into core-updates
Diffstat (limited to 'gnu/services')
-rw-r--r--gnu/services/configuration.scm3
-rw-r--r--gnu/services/cuirass.scm5
-rw-r--r--gnu/services/databases.scm81
-rw-r--r--gnu/services/messaging.scm727
-rw-r--r--gnu/services/networking.scm86
-rw-r--r--gnu/services/vpn.scm491
-rw-r--r--gnu/services/web.scm97
7 files changed, 1446 insertions, 44 deletions
diff --git a/gnu/services/configuration.scm b/gnu/services/configuration.scm
index 94c5f21557..a98db64fa5 100644
--- a/gnu/services/configuration.scm
+++ b/gnu/services/configuration.scm
@@ -28,10 +28,13 @@
   #:use-module (srfi srfi-35)
   #:export (configuration-field
             configuration-field-name
+            configuration-field-type
             configuration-missing-field
             configuration-field-error
             configuration-field-serializer
             configuration-field-getter
+            configuration-field-default-value-thunk
+            configuration-field-documentation
             serialize-configuration
             define-configuration
             validate-configuration
diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm
index c15a846bad..1194133f63 100644
--- a/gnu/services/cuirass.scm
+++ b/gnu/services/cuirass.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
 ;;; Copyright © 2016, 2017 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -56,6 +57,8 @@
                     (default 60))
   (database         cuirass-configuration-database ;string (file-name)
                     (default "/var/run/cuirass/cuirass.db"))
+  (port             cuirass-configuration-port ;integer (port)
+                    (default 8080))
   (specifications   cuirass-configuration-specifications)
                                   ;gexp that evaluates to specification-alist
   (use-substitutes? cuirass-configuration-use-substitutes? ;boolean
@@ -74,6 +77,7 @@
          (group            (cuirass-configuration-group config))
          (interval         (cuirass-configuration-interval config))
          (database         (cuirass-configuration-database config))
+         (port             (cuirass-configuration-port config))
          (specs            (cuirass-configuration-specifications config))
          (use-substitutes? (cuirass-configuration-use-substitutes? config))
          (one-shot?        (cuirass-configuration-one-shot? config)))
@@ -87,6 +91,7 @@
                             "--specifications"
                             #$(scheme-file "cuirass-specs.scm" specs)
                             "--database" #$database
+                            "--port" #$(number->string port)
                             "--interval" #$(number->string interval)
                             #$@(if use-substitutes? '("--use-substitutes") '())
                             #$@(if one-shot? '("--one-shot") '()))
diff --git a/gnu/services/databases.scm b/gnu/services/databases.scm
index d88c839f7d..3ecc8aff78 100644
--- a/gnu/services/databases.scm
+++ b/gnu/services/databases.scm
@@ -2,6 +2,7 @@
 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
 ;;; Copyright © 2015, 2016 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016 Leo Famulari <leo@famulari.name>
+;;; Copyright © 2017 Christopher Baines <mail@cbaines.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -35,7 +36,11 @@
             mysql-service
             mysql-service-type
             mysql-configuration
-            mysql-configuration?))
+            mysql-configuration?
+
+            redis-configuration
+            redis-configuration?
+            redis-service-type))
 
 ;;; Commentary:
 ;;;
@@ -287,3 +292,77 @@ database server.
 The optional @var{config} argument specifies the configuration for
 @command{mysqld}, which should be a @code{<mysql-configuration>} object."
   (service mysql-service-type config))
+
+
+;;;
+;;; Redis
+;;;
+
+(define-record-type* <redis-configuration>
+  redis-configuration make-redis-configuration
+  redis-configuration?
+  (redis             redis-configuration-redis ;<package>
+                     (default redis))
+  (bind              redis-configuration-bind
+                     (default "127.0.0.1"))
+  (port              redis-configuration-port
+                     (default 6379))
+  (working-directory redis-configuration-working-directory
+                     (default "/var/lib/redis"))
+  (config-file       redis-configuration-config-file
+                     (default #f)))
+
+(define (default-redis.conf bind port working-directory)
+  (mixed-text-file "redis.conf"
+                   "bind " bind "\n"
+                   "port " (number->string port) "\n"
+                   "dir " working-directory "\n"
+                   "daemonize no\n"))
+
+(define %redis-accounts
+  (list (user-group (name "redis") (system? #t))
+        (user-account
+         (name "redis")
+         (group "redis")
+         (system? #t)
+         (comment "Redis server user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
+
+(define redis-activation
+  (match-lambda
+    (($ <redis-configuration> redis bind port working-directory config-file)
+     #~(begin
+         (use-modules (guix build utils)
+                      (ice-9 match))
+
+         (let ((user (getpwnam "redis")))
+           (mkdir-p #$working-directory)
+           (chown #$working-directory (passwd:uid user) (passwd:gid user)))))))
+
+(define redis-shepherd-service
+  (match-lambda
+    (($ <redis-configuration> redis bind port working-directory config-file)
+     (let ((config-file
+            (or config-file
+                (default-redis.conf bind port working-directory))))
+       (list (shepherd-service
+              (provision '(redis))
+              (documentation "Run the Redis daemon.")
+              (requirement '(user-processes syslogd))
+              (start #~(make-forkexec-constructor
+                        '(#$(file-append redis "/bin/redis-server")
+                          #$config-file)
+                        #:user "redis"
+                        #:group "redis"))
+              (stop #~(make-kill-destructor))))))))
+
+(define redis-service-type
+  (service-type (name 'redis)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          redis-shepherd-service)
+                       (service-extension activation-service-type
+                                          redis-activation)
+                       (service-extension account-service-type
+                                          (const %redis-accounts))))))
diff --git a/gnu/services/messaging.scm b/gnu/services/messaging.scm
new file mode 100644
index 0000000000..aa398970b6
--- /dev/null
+++ b/gnu/services/messaging.scm
@@ -0,0 +1,727 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
+;;;
+;;; 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 messaging)
+  #:use-module (gnu packages messaging)
+  #:use-module (gnu packages admin)
+  #: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 records)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-35)
+  #:use-module (ice-9 match)
+  #:export (prosody-service-type
+            prosody-configuration
+            opaque-prosody-configuration
+
+            virtualhost-configuration
+            int-component-configuration
+            ext-component-configuration
+
+            mod-muc-configuration
+            ssl-configuration
+
+            %default-modules-enabled))
+
+;;; Commentary:
+;;;
+;;; Messaging services.
+;;;
+;;; 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 (make-pred arg)
+      (lambda (field target)
+        (and (memq (syntax->datum target) `(common ,arg)) field)))
+    (syntax-case stx ()
+      ((_ stem (field (field-type def) doc target) ...)
+       (with-syntax (((new-field-type ...)
+                      (map (lambda (field-type target)
+                             (if (and (eq? 'common (syntax->datum target))
+                                      (not (string-prefix?
+                                            "maybe-"
+                                            (symbol->string
+                                             (syntax->datum field-type)))))
+                                 (id #'stem #'maybe- field-type) field-type))
+                           #'(field-type ...) #'(target ...)))
+                     ((new-def ...)
+                      (map (lambda (def target)
+                             (if (eq? 'common (syntax->datum target))
+                                 #''disabled def))
+                           #'(def ...) #'(target ...)))
+                     ((new-doc ...)
+                      (map (lambda (doc target)
+                             (if (eq? 'common (syntax->datum target))
+                                 "" doc))
+                           #'(doc ...) #'(target ...))))
+         #`(begin
+             (define common-fields
+               '(#,@(filter-map (make-pred #f) #'(field ...) #'(target ...))))
+             (define-configuration prosody-configuration
+               #,@(filter-map (make-pred 'global)
+                              #'((field (field-type def) doc) ...)
+                              #'(target ...)))
+             (define-configuration virtualhost-configuration
+               #,@(filter-map (make-pred 'virtualhost)
+                              #'((field (new-field-type new-def) new-doc) ...)
+                              #'(target ...)))
+             (define-configuration int-component-configuration
+               #,@(filter-map (make-pred 'int-component)
+                              #'((field (new-field-type new-def) new-doc) ...)
+                              #'(target ...)))
+             (define-configuration ext-component-configuration
+               #,@(filter-map (make-pred 'ext-component)
+                              #'((field (new-field-type new-def) new-doc) ...)
+                              #'(target ...)))))))))
+
+(define (uglify-field-name field-name)
+  (let ((str (symbol->string field-name)))
+    (string-join (string-split (if (string-suffix? "?" str)
+                                   (substring str 0 (1- (string-length str)))
+                                   str)
+                               #\-)
+                 "_")))
+
+(define (serialize-field field-name val)
+  (format #t "~a = ~a;\n" (uglify-field-name field-name) val))
+(define (serialize-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 "}")))))
+
+(define (serialize-boolean field-name val)
+  (serialize-field field-name (if val "true" "false")))
+(define-maybe boolean)
+
+(define (string-or-boolean? val)
+  (or (string? val) (boolean? val)))
+(define (serialize-string-or-boolean field-name val)
+  (if (string? val)
+      (serialize-string field-name val)
+      (serialize-boolean field-name val)))
+
+(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))
+(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))
+(define-maybe non-negative-integer-list)
+
+(define (enclose-quotes s)
+  (format #f "\"~a\"" s))
+(define (serialize-string field-name val)
+  (serialize-field field-name (enclose-quotes val)))
+(define-maybe string)
+
+(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-list field-name (map enclose-quotes val)))
+(define-maybe string-list)
+
+(define (module-list? val)
+  (string-list? val))
+(define (serialize-module-list field-name val)
+  (serialize-string-list field-name (cons "posix" val)))
+(define-maybe module-list)
+
+(define (file-name? val)
+  (and (string? val)
+       (string-prefix? "/" val)))
+(define (serialize-file-name field-name val)
+  (serialize-string field-name val))
+(define-maybe file-name)
+
+(define (file-name-list? val)
+  (and (list? val) (and-map file-name? val)))
+(define (serialize-file-name-list field-name val)
+  (serialize-string-list field-name val))
+(define-maybe file-name)
+
+(define-configuration mod-muc-configuration
+  (name
+   (string "Prosody Chatrooms")
+   "The name to return in service discovery responses.")
+
+  (restrict-room-creation
+   (string-or-boolean #f)
+   "If @samp{#t}, this will only allow admins to create new chatrooms.
+Otherwise anyone can create a room.  The value @samp{\"local\"} restricts room
+creation to users on the service's parent domain.  E.g. @samp{user@@example.com}
+can create rooms on @samp{rooms.example.com}.  The value @samp{\"admin\"}
+restricts to service administrators only.")
+
+  (max-history-messages
+   (non-negative-integer 20)
+   "Maximum number of history messages that will be sent to the member that has
+just joined the room."))
+(define (serialize-mod-muc-configuration field-name val)
+  (serialize-configuration val mod-muc-configuration-fields))
+(define-maybe mod-muc-configuration)
+
+(define-configuration ssl-configuration
+  (protocol
+   (maybe-string 'disabled)
+   "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}.")
+
+  (certificate
+   (file-name "/etc/prosody/certs/cert.pem")
+   "Path to your certificate file, relative to @code{/etc/prosody}.")
+
+  (capath
+   (file-name "/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)
+   "Path to a file containing root certificates that you wish Prosody to trust.
+Similar to @code{capath} but with all certificates concatenated together.")
+
+  (verify
+   (maybe-string-list 'disabled)
+   "A list of verification options (these mostly map to OpenSSL's
+@code{set_verify()} flags).")
+
+  (options
+   (maybe-string-list 'disabled)
+   "A list of general options relating to SSL/TLS.  These map to OpenSSL's
+@code{set_options()}.  For a full list of options available in LuaSec, see the
+LuaSec source.")
+
+  (depth
+   (maybe-non-negative-integer 'disabled)
+   "How long a chain of certificate authorities to check when looking for a
+trusted root certificate.")
+
+  (ciphers
+   (maybe-string 'disabled)
+   "An OpenSSL cipher string.  This selects what ciphers Prosody will offer to
+clients, and in what order.")
+
+  (dhparam
+   (maybe-file-name 'disabled)
+   "A path to a file containing parameters for Diffie-Hellman key exchange.  You
+can create such a file with:
+@code{openssl dhparam -out /etc/prosody/certs/dh-2048.pem 2048}")
+
+  (curve
+   (maybe-string 'disabled)
+   "Curve for Elliptic curve Diffie-Hellman. Prosody's default is
+@samp{\"secp384r1\"}.")
+
+  (verifyext
+   (maybe-string-list 'disabled)
+   "A list of \"extra\" verification options.")
+
+  (password
+   (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"))
+(define-maybe ssl-configuration)
+
+(define %default-modules-enabled
+  '("roster"
+    "saslauth"
+    "tls"
+    "dialback"
+    "disco"
+    "private"
+    "vcard"
+    "version"
+    "uptime"
+    "time"
+    "ping"
+    "pep"
+    "register"
+    "admin_adhoc"))
+
+;; Guile bug.  Use begin wrapper, because otherwise virtualhost-configuration
+;; is assumed to be a function.  See
+;; https://www.gnu.org/software/guile/manual/html_node/R6RS-Incompatibilities.html
+(begin
+  (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))
+
+  (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))
+
+  (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))
+
+  (define-all-configurations prosody-configuration
+    (prosody
+     (package prosody)
+     "The Prosody package."
+     global)
+
+    (data-path
+     (file-name "/var/lib/prosody")
+     "Location of the Prosody data storage directory.  See
+@url{http://prosody.im/doc/configure}."
+     global)
+
+    (plugin-paths
+     (file-name-list '())
+     "Additional plugin directories.  They are searched in all the specified
+paths in order.  See @url{http://prosody.im/doc/plugins_directory}."
+     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}.
+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}."
+     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}."
+     common)
+
+    (modules-disabled
+     (string-list '())
+     "@samp{\"offline\"}, @samp{\"c2s\"} and @samp{\"s2s\"} are auto-loaded, but
+should you want to disable them then add them to this list."
+     common)
+
+    (groups-file
+     (file-name "/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}."
+     common)
+
+    (allow-registration?
+     (boolean #f)
+     "Disable account creation by default, for security.  See
+@url{http://prosody.im/doc/creating_accounts}."
+     common)
+
+    (ssl
+     (maybe-ssl-configuration (ssl-configuration))
+     "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}."
+     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}."
+     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}."
+     common)
+
+    (s2s-secure-auth?
+     (boolean #f)
+     "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}."
+     common)
+
+    (s2s-insecure-domains
+     (string-list '())
+     "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}."
+     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}."
+     common)
+
+    (authentication
+     (string "internal_plain")
+     "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
+about using the hashed backend.  See also
+@url{http://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}."
+     common)
+
+    (pidfile
+     (file-name "/var/run/prosody/prosody.pid")
+     "File to write pid in.  See @url{http://prosody.im/doc/modules/mod_posix}."
+     global)
+
+    (virtualhosts
+     (virtualhost-configuration-list
+      (list (virtualhost-configuration
+             (domain "localhost"))))
+     "A host in Prosody is a domain on which user accounts can be created.  For
+example if you want your users to have addresses like
+@samp{\"john.smith@@example.com\"} then you need to add a host
+@samp{\"example.com\"}.  All options in this list will apply only to this host.
+
+Note: the name \"virtual\" host is used in configuration to avoid confusion with
+the actual physical host that Prosody is installed on.  A single Prosody
+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}."
+     global)
+
+    (int-components
+     (int-component-configuration-list '())
+     "Components are extra services on a server which are available to clients,
+usually on a subdomain of the main server (such as
+@samp{\"mycomponent.example.com\"}).  Example components might be chatroom
+servers, user directories, or gateways to other protocols.
+
+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}."
+     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}."
+     global)
+
+    (component-secret
+     (string (configuration-missing-field 'ext-component 'component-secret))
+     "Password which the component will use to log in."
+     ext-component)
+
+    (component-ports
+     (non-negative-integer-list '(5347))
+     "Port(s) Prosody listens on for component connections."
+     global)
+
+    (component-interface
+     (string "127.0.0.1")
+     "Interface Prosody listens on for component connections."
+     global)
+
+    (domain
+     (string (configuration-missing-field 'virtualhost 'domain))
+     "Domain you wish Prosody to serve."
+     virtualhost)
+
+    (hostname
+     (string (configuration-missing-field 'int-component 'hostname))
+     "Hostname of the component."
+     int-component)
+
+    (plugin
+     (string (configuration-missing-field 'int-component 'plugin))
+     "Plugin you wish to use for the component."
+     int-component)
+
+    (mod-muc
+     (maybe-mod-muc-configuration 'disabled)
+     "Multi-user chat (MUC) is Prosody's module for allowing you to create
+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}),
+which you should read if you are new to XMPP chatrooms.
+
+See also @url{http://prosody.im/doc/modules/mod_muc}."
+     int-component)
+
+    (hostname
+     (string (configuration-missing-field 'ext-component 'hostname))
+     "Hostname of the component."
+     ext-component)))
+
+;; Serialize Virtualhost line first.
+(define (serialize-virtualhost-configuration config)
+  (define (rest? field)
+    (not (memq (configuration-field-name field)
+               '(domain))))
+  (let ((domain (virtualhost-configuration-domain config))
+        (rest (filter rest? virtualhost-configuration-fields)))
+    (format #t "VirtualHost \"~a\"\n" domain)
+    (serialize-configuration config rest)))
+
+;; Serialize Component line first.
+(define (serialize-int-component-configuration config)
+  (define (rest? field)
+    (not (memq (configuration-field-name field)
+               '(hostname plugin))))
+  (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)))
+
+;; Serialize Component line first.
+(define (serialize-ext-component-configuration config)
+  (define (rest? field)
+    (not (memq (configuration-field-name field)
+               '(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)))
+
+;; 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)))
+
+(define-configuration opaque-prosody-configuration
+  (prosody
+   (package prosody)
+   "The prosody package.")
+
+  (prosody.cfg.lua
+   (string (configuration-missing-field 'opaque-prosody-configuration
+                                        'prosody.cfg.lua))
+   "The contents of the @code{prosody.cfg.lua} to use."))
+
+(define (prosody-shepherd-service config)
+  "Return a <shepherd-service> for Prosody with CONFIG."
+  (let* ((prosody (if (opaque-prosody-configuration? config)
+                      (opaque-prosody-configuration-prosody config)
+                      (prosody-configuration-prosody config)))
+         (prosodyctl-bin (file-append prosody "/bin/prosodyctl"))
+         (prosodyctl-action (lambda args
+                              #~(lambda _
+                                  (zero? (system* #$prosodyctl-bin #$@args))))))
+    (list (shepherd-service
+           (documentation "Run the Prosody XMPP server")
+           (provision '(prosody))
+           (requirement '(networking syslogd user-processes))
+           (start (prosodyctl-action "start"))
+           (stop (prosodyctl-action "stop"))))))
+
+(define %prosody-accounts
+  (list (user-group (name "prosody") (system? #t))
+        (user-account
+         (name "prosody")
+         (group "prosody")
+         (system? #t)
+         (comment "Prosody daemon user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
+
+(define (prosody-activation config)
+  "Return the activation gexp for CONFIG."
+  (let* ((config-dir "/etc/prosody")
+         (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)))
+    #~(begin
+        (use-modules (guix build utils))
+        (define %user (getpw "prosody"))
+
+        (mkdir-p #$config-dir)
+        (chown #$config-dir (passwd:uid %user) (passwd:gid %user))
+        (copy-file #$config-file (string-append #$config-dir
+                                                "/prosody.cfg.lua"))
+
+        (mkdir-p #$default-certs-dir)
+        (chown #$default-certs-dir (passwd:uid %user) (passwd:gid %user))
+        (chmod #$default-certs-dir #o750)
+
+        (mkdir-p #$data-path)
+        (chown #$data-path (passwd:uid %user) (passwd:gid %user))
+        (chmod #$data-path #o750)
+
+        (mkdir-p #$pidfile-dir)
+        (chown #$pidfile-dir (passwd:uid %user) (passwd:gid %user)))))
+
+(define prosody-service-type
+  (service-type (name 'prosody)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          prosody-shepherd-service)
+                       (service-extension account-service-type
+                                          (const %prosody-accounts))
+                       (service-extension activation-service-type
+                                          prosody-activation)))))
+
+;; A little helper to make it easier to document all those fields.
+(define (generate-documentation)
+  (define documentation
+    `((prosody-configuration
+       ,prosody-configuration-fields
+       (ssl ssl-configuration)
+       (virtualhosts virtualhost-configuration)
+       (int-components int-component-configuration)
+       (ext-components ext-component-configuration))
+      (ssl-configuration ,ssl-configuration-fields)
+      (int-component-configuration ,int-component-configuration-fields
+                                   (mod-muc mod-muc-configuration))
+      (ext-component-configuration ,ext-component-configuration-fields)
+      (mod-muc-configuration ,mod-muc-configuration-fields)
+      (virtualhost-configuration ,virtualhost-configuration-fields)
+      (opaque-prosody-configuration ,opaque-prosody-configuration-fields)))
+  (define (generate configuration-name)
+    (match (assq-ref documentation configuration-name)
+      ((fields . sub-documentation)
+       (format #t "\nAvailable @code{~a} fields are:\n\n" configuration-name)
+       (when (memq configuration-name
+                   '(virtualhost-configuration
+                     int-component-configuration
+                     ext-component-configuration))
+         (format #t "all these @code{prosody-configuration} fields: ~a, plus:\n"
+                 (string-join (map (lambda (s)
+                                     (format #f "@code{~a}" s)) common-fields)
+                              ", ")))
+       (for-each
+        (lambda (f)
+          (let ((field-name (configuration-field-name f))
+                (field-type (configuration-field-type f))
+                (field-docs (string-trim-both
+                             (configuration-field-documentation f)))
+                (default (catch #t
+                           (configuration-field-default-value-thunk f)
+                           (lambda _ 'nope))))
+            (define (escape-chars str chars escape)
+              (with-output-to-string
+                (lambda ()
+                  (string-for-each (lambda (c)
+                                     (when (char-set-contains? chars c)
+                                       (display escape))
+                                     (display c))
+                                   str))))
+            (define (show-default? val)
+              (or (string? default) (number? default) (boolean? default)
+                  (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)
+            (when (show-default? default)
+              (format #t "Defaults to @samp{~a}.\n"
+                      (escape-chars (format #f "~s" default)
+                                    (char-set #\@ #\{ #\})
+                                    #\@)))
+            (for-each generate (or (assq-ref sub-documentation field-name) '()))
+            (format #t "@end deftypevr\n\n")))
+        (filter (lambda (f)
+                  (not (string=? "" (configuration-field-documentation f))))
+                fields)))))
+  (generate 'prosody-configuration)
+  (format #t "It could be that you just want to get a @code{prosody.cfg.lua}
+up and running.  In that case, you can pass an
+@code{opaque-prosody-configuration} record as the value of
+@code{prosody-service-type}.  As its name indicates, an opaque configuration
+does not have easy reflective capabilities.")
+  (generate 'opaque-prosody-configuration)
+  (format #t "For example, if your @code{prosody.cfg.lua} is just the empty
+string, you could instantiate a prosody service like this:
+
+@example
+(service prosody-service-type
+         (opaque-prosody-configuration
+          (prosody.cfg.lua \"\")))
+@end example"))
diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm
index d672ecf687..8f136f0dc1 100644
--- a/gnu/services/networking.scm
+++ b/gnu/services/networking.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
 ;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;; Copyright © 2016 John Darrington <jmd@gnu.org>
@@ -64,7 +64,12 @@
 
             wicd-service-type
             wicd-service
-            network-manager-service
+
+            network-manager-configuration
+            network-manager-configuration?
+            network-manager-configuration-dns
+            network-manager-service-type
+
             connman-service
             wpa-supplicant-service-type))
 
@@ -633,7 +638,12 @@ configuration file."
       (let ((file-name "/etc/wicd/dhclient.conf.template.default"))
         (unless (file-exists? file-name)
           (copy-file (string-append #$wicd file-name)
-                     file-name)))))
+                     file-name)))
+
+      ;; Wicd invokes 'wpa_supplicant', which needs this directory for its
+      ;; named socket files.
+      (mkdir-p "/var/run/wpa_supplicant")
+      (chmod "/var/run/wpa_supplicant" #o750)))
 
 (define (wicd-shepherd-service wicd)
   "Return a shepherd service for WICD."
@@ -674,40 +684,58 @@ and @command{wicd-curses} user interfaces."
 ;;; NetworkManager
 ;;;
 
+(define-record-type* <network-manager-configuration>
+  network-manager-configuration make-network-manager-configuration
+  network-manager-configuration?
+  (network-manager network-manager-configuration-network-manager
+                   (default network-manager))
+  (dns network-manager-configuration-dns
+       (default "default")))
+
 (define %network-manager-activation
   ;; Activation gexp for NetworkManager.
   #~(begin
       (use-modules (guix build utils))
       (mkdir-p "/etc/NetworkManager/system-connections")))
 
-(define (network-manager-shepherd-service network-manager)
-  "Return a shepherd service for NETWORK-MANAGER."
-  (list (shepherd-service
-         (documentation "Run the NetworkManager.")
-         (provision '(networking))
-         (requirement '(user-processes dbus-system wpa-supplicant loopback))
-         (start #~(make-forkexec-constructor
-                   (list (string-append #$network-manager
-                                        "/sbin/NetworkManager")
-                         "--no-daemon")))
-         (stop #~(make-kill-destructor)))))
+(define network-manager-shepherd-service
+  (match-lambda
+    (($ <network-manager-configuration> network-manager dns)
+     (let
+         ((conf (plain-file "NetworkManager.conf"
+                            (string-append "
+[main]
+dns=" dns "
+"))))
+     (list (shepherd-service
+            (documentation "Run the NetworkManager.")
+            (provision '(networking))
+            (requirement '(user-processes dbus-system wpa-supplicant loopback))
+            (start #~(make-forkexec-constructor
+                      (list (string-append #$network-manager
+                                           "/sbin/NetworkManager")
+                            (string-append "--config=" #$conf)
+                            "--no-daemon")))
+            (stop #~(make-kill-destructor))))))))
 
 (define network-manager-service-type
-  (service-type (name 'network-manager)
-                (extensions
-                 (list (service-extension shepherd-root-service-type
-                                          network-manager-shepherd-service)
-                       (service-extension dbus-root-service-type list)
-                       (service-extension polkit-service-type list)
-                       (service-extension activation-service-type
-                                          (const %network-manager-activation))
-                       ;; Add network-manager to the system profile.
-                       (service-extension profile-service-type list)))))
-
-(define* (network-manager-service #:key (network-manager network-manager))
-  "Return a service that runs NetworkManager, a network connection manager
-that attempting to keep active network connectivity when available."
-  (service network-manager-service-type network-manager))
+  (let
+      ((config->package
+        (match-lambda
+         (($ <network-manager-configuration> network-manager)
+          (list network-manager)))))
+
+    (service-type
+     (name 'network-manager)
+     (extensions
+      (list (service-extension shepherd-root-service-type
+                               network-manager-shepherd-service)
+            (service-extension dbus-root-service-type config->package)
+            (service-extension polkit-service-type config->package)
+            (service-extension activation-service-type
+                               (const %network-manager-activation))
+            ;; Add network-manager to the system profile.
+            (service-extension profile-service-type config->package))))))
 
 
 ;;;
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm
new file mode 100644
index 0000000000..f577e0851e
--- /dev/null
+++ b/gnu/services/vpn.scm
@@ -0,0 +1,491 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
+;;;
+;;; 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 vpn)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu system shadow)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu packages vpn)
+  #:use-module (guix packages)
+  #:use-module (guix records)
+  #:use-module (guix gexp)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:export (openvpn-client-service
+            openvpn-server-service
+            openvpn-client-service-type
+            openvpn-server-service-type
+            openvpn-client-configuration
+            openvpn-server-configuration
+            openvpn-remote-configuration
+            openvpn-ccd-configuration
+            generate-openvpn-client-documentation
+            generate-openvpn-server-documentation))
+
+;;;
+;;; OpenVPN.
+;;;
+
+(define (uglify-field-name name)
+  (match name
+    ('verbosity "verb")
+    (_ (let ((str (symbol->string name)))
+         (if (string-suffix? "?" str)
+             (substring str 0 (1- (string-length str)))
+             str)))))
+
+(define (serialize-field field-name val)
+  (if (eq? field-name 'pid-file)
+      (format #t "")
+      (format #t "~a ~a\n" (uglify-field-name field-name) val)))
+(define serialize-string serialize-field)
+(define (serialize-boolean field-name val)
+  (if val
+      (serialize-field field-name val)
+      (format #t "")))
+
+(define (ip-mask? val)
+  (and (string? val)
+       (if (string-match "^([0-9]+\\.){3}[0-9]+ ([0-9]+\\.){3}[0-9]+$" val)
+           (let ((numbers (string-tokenize val char-set:digit)))
+             (all-lte numbers (list 255 255 255 255 255 255 255 255)))
+           #f)))
+(define serialize-ip-mask serialize-string)
+
+(define-syntax define-enumerated-field-type
+  (lambda (x)
+    (define (id-append ctx . parts)
+      (datum->syntax ctx (apply symbol-append (map syntax->datum parts))))
+    (syntax-case x ()
+      ((_ name (option ...))
+       #`(begin
+           (define (#,(id-append #'name #'name #'?) x)
+             (memq x '(option ...)))
+           (define (#,(id-append #'name #'serialize- #'name) field-name val)
+             (serialize-field field-name val)))))))
+
+(define-enumerated-field-type proto
+  (udp tcp udp6 tcp6))
+(define-enumerated-field-type dev
+  (tun tap))
+
+(define key-usage? boolean?)
+(define (serialize-key-usage field-name value)
+  (if value
+      (format #t "remote-cert-tls server\n")
+      #f))
+
+(define bind? boolean?)
+(define (serialize-bind field-name value)
+  (if value
+      #f
+      (format #t "nobind\n")))
+
+(define resolv-retry? boolean?)
+(define (serialize-resolv-retry field-name value)
+  (if value
+      (format #t "resolv-retry infinite\n")
+      #f))
+
+(define (serialize-tls-auth role location)
+  (serialize-field 'tls-auth
+                   (string-append location " " (match role
+                                                 ('server "0")
+                                                 ('client "1")))))
+(define (tls-auth? val)
+  (or (eq? val #f)
+      (string? val)))
+(define (serialize-tls-auth-server field-name val)
+  (serialize-tls-auth 'server val))
+(define (serialize-tls-auth-client field-name val)
+  (serialize-tls-auth 'client val))
+(define tls-auth-server? tls-auth?)
+(define tls-auth-client? tls-auth?)
+
+(define (serialize-number field-name val)
+  (serialize-field field-name (number->string val)))
+
+(define (all-lte left right)
+  (if (eq? left '())
+      (eq? right '())
+      (and (<= (string->number (car left)) (car right))
+           (all-lte (cdr left) (cdr right)))))
+
+(define (cidr4? val)
+  (if (string? val)
+      (if (string-match "^([0-9]+\\.){3}[0-9]+/[0-9]+$" val)
+          (let ((numbers (string-tokenize val char-set:digit)))
+            (all-lte numbers (list 255 255 255 255 32)))
+          #f)
+      (eq? val #f)))
+
+(define (cidr6? val)
+  (if (string? val)
+      (string-match "^([0-9a-f]{0,4}:){0,8}/[0-9]{1,3}$" val)
+      (eq? val #f)))
+
+(define (serialize-cidr4 field-name val)
+  (if (eq? val #f) #f (serialize-field field-name val)))
+
+(define (serialize-cidr6 field-name val)
+  (if (eq? val #f) #f (serialize-field field-name val)))
+
+(define (ip? val)
+  (if (string? val)
+      (if (string-match "^([0-9]+\\.){3}[0-9]+$" val)
+          (let ((numbers (string-tokenize val char-set:digit)))
+            (all-lte numbers (list 255 255 255 255)))
+          #f)
+      (eq? val #f)))
+(define (serialize-ip field-name val)
+  (if (eq? val #f) #f (serialize-field field-name val)))
+
+(define (keepalive? val)
+  (and (list? val)
+       (and (number? (car val))
+            (number? (car (cdr val))))))
+(define (serialize-keepalive field-name val)
+  (format #t "~a ~a ~a\n" (uglify-field-name field-name)
+          (number->string (car val)) (number->string (car (cdr val)))))
+
+(define gateway? boolean?)
+(define (serialize-gateway field-name val)
+  (and val
+       (format #t "push \"redirect-gateway\"\n")))
+
+
+(define-configuration openvpn-remote-configuration
+  (name
+   (string "my-server")
+   "Server name.")
+  (port
+   (number 1194)
+   "Port number the server listens to."))
+
+(define-configuration openvpn-ccd-configuration
+  (name
+   (string "client")
+   "Client name.")
+  (iroute
+   (ip-mask #f)
+   "Client own network")
+  (ifconfig-push
+   (ip-mask #f)
+   "Client VPN IP."))
+
+(define (openvpn-remote-list? val)
+  (and (list? val)
+       (or (eq? val '())
+           (and (openvpn-remote-configuration? (car val))
+                (openvpn-remote-list? (cdr val))))))
+(define (serialize-openvpn-remote-list field-name val)
+  (for-each (lambda (remote)
+              (format #t "remote ~a ~a\n" (openvpn-remote-configuration-name remote)
+                      (number->string (openvpn-remote-configuration-port remote))))
+            val))
+
+(define (openvpn-ccd-list? val)
+  (and (list? val)
+       (or (eq? val '())
+           (and (openvpn-ccd-configuration? (car val))
+                (openvpn-ccd-list? (cdr val))))))
+(define (serialize-openvpn-ccd-list field-name val)
+  #f)
+
+(define (create-ccd-directory val)
+  "Create a ccd directory containing files for the ccd configuration option
+of OpenVPN.  Each file in this directory represents particular settings for a
+client.  Each file is named after the name of the client."
+  (let ((files (map (lambda (ccd)
+                      (list (openvpn-ccd-configuration-name ccd)
+                            (with-output-to-string
+                              (lambda ()
+                                (serialize-configuration
+                                 ccd openvpn-ccd-configuration-fields)))))
+                    val)))
+    (computed-file "ccd"
+                   (with-imported-modules '((guix build utils))
+                     #~(begin
+                         (use-modules (guix build utils))
+                         (use-modules (ice-9 match))
+                         (mkdir-p #$output)
+                         (for-each
+                          (lambda (ccd)
+                            (match ccd
+                              ((name config-string)
+                               (call-with-output-file
+                                   (string-append #$output "/" name)
+                                 (lambda (port) (display config-string port))))))
+                          '#$files))))))
+
+(define-syntax define-split-configuration
+  (lambda (x)
+    (syntax-case x ()
+      ((_ name1 name2 (common-option ...) (first-option ...) (second-option ...))
+       #`(begin
+           (define-configuration #,#'name1
+             common-option ...
+             first-option ...)
+           (define-configuration #,#'name2
+             common-option ...
+             second-option ...))))))
+
+(define-split-configuration openvpn-client-configuration
+  openvpn-server-configuration
+  ((openvpn
+    (package openvpn)
+    "The OpenVPN package.")
+
+   (pid-file
+    (string "/var/run/openvpn/openvpn.pid")
+    "The OpenVPN pid file.")
+
+   (proto
+    (proto 'udp)
+    "The protocol (UDP or TCP) used to open a channel between clients and
+servers.")
+
+   (dev
+    (dev 'tun)
+    "The device type used to represent the VPN connection.")
+
+   (ca
+    (string "/etc/openvpn/ca.crt")
+    "The certificate authority to check connections against.")
+
+   (cert
+    (string "/etc/openvpn/client.crt")
+    "The certificate of the machine the daemon is running on. It should be signed
+by the authority given in @code{ca}.")
+
+   (key
+    (string "/etc/openvpn/client.key")
+    "The key of the machine the daemon is running on. It must be the whose
+certificate is @code{cert}.")
+
+   (comp-lzo?
+    (boolean #t)
+    "Whether to use the lzo compression algorithm.")
+
+   (persist-key?
+    (boolean #t)
+    "Don't re-read key files across SIGUSR1 or --ping-restart.")
+
+   (persist-tun?
+    (boolean #t)
+    "Don't close and reopen TUN/TAP device or run up/down scripts across
+SIGUSR1 or --ping-restart restarts.")
+
+   (verbosity
+    (number 3)
+    "Verbosity level."))
+  ;; client-specific configuration
+  ((tls-auth
+    (tls-auth-client #f)
+    "Add an additional layer of HMAC authentication on top of the TLS control
+channel to protect against DoS attacks.")
+
+   (verify-key-usage?
+    (key-usage #t)
+    "Whether to check the server certificate has server usage extension.")
+
+   (bind?
+    (bind #f)
+    "Bind to a specific local port number.")
+
+   (resolv-retry?
+    (resolv-retry #t)
+    "Retry resolving server address.")
+
+   (remote
+    (openvpn-remote-list '())
+    "A list of remote servers to connect to."))
+  ;; server-specific configuration
+  ((tls-auth
+    (tls-auth-server #f)
+    "Add an additional layer of HMAC authentication on top of the TLS control
+channel to protect against DoS attacks.")
+
+   (port
+    (number 1194)
+    "Specifies the port number on which the server listens.")
+
+   (server
+    (ip-mask "10.8.0.0 255.255.255.0")
+    "An ip and mask specifying the subnet inside the virtual network.")
+
+   (server-ipv6
+    (cidr6 #f)
+    "A CIDR notation specifying the IPv6 subnet inside the virtual network.")
+
+   (dh
+    (string "/etc/openvpn/dh2048.pem")
+    "The Diffie-Hellman parameters file.")
+
+   (ifconfig-pool-persist
+    (string "/etc/openvpn/ipp.txt")
+    "The file that records client IPs.")
+
+   (redirect-gateway?
+    (gateway #f)
+    "When true, the server will act as a gateway for its clients.")
+
+   (client-to-client?
+    (boolean #f)
+    "When true, clients are alowed to talk to each other inside the VPN.")
+
+   (keepalive
+    (keepalive '(10 120))
+    "Causes ping-like messages to be sent back and forth over the link so that
+each side knows when the other side has gone down. @code{keepalive} requires
+a pair. The first element is the period of the ping sending, and the second
+element is the timeout before considering the other side down.")
+
+   (max-clients
+    (number 100)
+    "The maximum number of clients.")
+
+   (status
+    (string "/var/run/openvpn/status")
+    "The status file. This file shows a small report on current connection. It
+is trunkated and rewritten every minute.")
+
+   (client-config-dir
+    (openvpn-ccd-list '())
+    "The list of configuration for some clients.")))
+
+(define (openvpn-config-file role config)
+  (let ((config-str
+         (with-output-to-string
+           (lambda ()
+             (serialize-configuration config
+                                      (match role
+                                        ('server
+                                         openvpn-server-configuration-fields)
+                                        ('client
+                                         openvpn-client-configuration-fields))))))
+        (ccd-dir (match role
+                   ('server (create-ccd-directory
+                             (openvpn-server-configuration-client-config-dir
+                              config)))
+                   ('client #f))))
+    (computed-file "openvpn.conf"
+                   #~(begin
+                       (use-modules (ice-9 match))
+                       (call-with-output-file #$output
+                         (lambda (port)
+                           (match '#$role
+                             ('server (display "" port))
+                             ('client (display "client\n" port)))
+                           (display #$config-str port)
+                           (match '#$role
+                             ('server (display
+                                       (string-append "client-config-dir "
+                                                      #$ccd-dir "\n") port))
+                             ('client (display "" port)))))))))
+
+(define (openvpn-shepherd-service role)
+  (lambda (config)
+    (let* ((config-file (openvpn-config-file role config))
+           (pid-file ((match role
+                        ('server openvpn-server-configuration-pid-file)
+                        ('client openvpn-client-configuration-pid-file))
+                      config))
+           (openvpn ((match role
+                       ('server openvpn-server-configuration-openvpn)
+                       ('client openvpn-client-configuration-openvpn))
+                     config))
+           (log-file (match role
+                       ('server "/var/log/openvpn-server.log")
+                       ('client "/var/log/openvpn-client.log"))))
+      (list (shepherd-service
+             (documentation (string-append "Run the OpenVPN "
+                                           (match role
+                                             ('server "server")
+                                             ('client "client"))
+                                           " daemon."))
+             (provision (match role
+                          ('server '(vpn-server))
+                          ('client '(vpn-client))))
+             (requirement '(networking))
+             (start #~(make-forkexec-constructor
+                       (list (string-append #$openvpn "/sbin/openvpn")
+                             "--writepid" #$pid-file "--config" #$config-file
+                             "--daemon")
+                       #:pid-file #$pid-file))
+             (stop #~(make-kill-destructor)))))))
+
+(define %openvpn-accounts
+  (list (user-group (name "openvpn") (system? #t))
+        (user-account
+         (name "openvpn")
+         (group "openvpn")
+         (system? #t)
+         (comment "Openvpn daemon user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
+
+(define %openvpn-activation
+  #~(mkdir-p "/var/run/openvpn"))
+
+(define openvpn-server-service-type
+  (service-type (name 'openvpn-server)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          (openvpn-shepherd-service 'server))
+                       (service-extension account-service-type
+                                          (const %openvpn-accounts))
+                       (service-extension activation-service-type
+                                          (const %openvpn-activation))))))
+
+(define openvpn-client-service-type
+  (service-type (name 'openvpn-client)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          (openvpn-shepherd-service 'client))
+                       (service-extension account-service-type
+                                          (const %openvpn-accounts))
+                       (service-extension activation-service-type
+                                          (const %openvpn-activation))))))
+
+(define* (openvpn-client-service #:key (config (openvpn-client-configuration)))
+  (validate-configuration config openvpn-client-configuration-fields)
+  (service openvpn-client-service-type config))
+
+(define* (openvpn-server-service #:key (config (openvpn-server-configuration)))
+  (validate-configuration config openvpn-server-configuration-fields)
+  (service openvpn-server-service-type config))
+
+(define (generate-openvpn-server-documentation)
+  (generate-documentation
+   `((openvpn-server-configuration
+      ,openvpn-server-configuration-fields
+      (ccd openvpn-ccd-configuration))
+     (openvpn-ccd-configuration ,openvpn-ccd-configuration-fields))
+   'openvpn-server-configuration))
+
+(define (generate-openvpn-client-documentation)
+  (generate-documentation
+   `((openvpn-client-configuration
+      ,openvpn-client-configuration-fields
+      (remote openvpn-remote-configuration))
+     (openvpn-remote-configuration ,openvpn-remote-configuration-fields))
+   'openvpn-client-configuration))
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index db895405a2..11408d7b0e 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -1,8 +1,9 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
-;;; Copyright © 2015, 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016 ng0 <ng0@we.make.ritual.n0.is>
 ;;; Copyright © 2016 Julien Lepiller <julien@lepiller.eu>
+;;; Copyright © 2017 Christopher Baines <mail@cbaines.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -33,6 +34,12 @@
             nginx-configuration?
             nginx-server-configuration
             nginx-server-configuration?
+            nginx-upstream-configuration
+            nginx-upstream-configuration?
+            nginx-location-configuration
+            nginx-location-configuration?
+            nginx-named-location-configuration
+            nginx-named-location-configuration?
             nginx-service
             nginx-service-type))
 
@@ -53,6 +60,8 @@
                        (default (list 'default)))
   (root                nginx-server-configuration-root
                        (default "/srv/http"))
+  (locations           nginx-server-configuration-locations
+                       (default '()))
   (index               nginx-server-configuration-index
                        (default (list "index.html")))
   (ssl-certificate     nginx-server-configuration-ssl-certificate
@@ -62,14 +71,41 @@
   (server-tokens?      nginx-server-configuration-server-tokens?
                        (default #f)))
 
+(define-record-type* <nginx-upstream-configuration>
+  nginx-upstream-configuration make-nginx-upstream-configuration
+  nginx-upstream-configuration?
+  (name                nginx-upstream-configuration-name)
+  (servers             nginx-upstream-configuration-servers))
+
+(define-record-type* <nginx-location-configuration>
+  nginx-location-configuration make-nginx-location-configuration
+  nginx-location-configuration?
+  (uri                 nginx-location-configuration-uri
+                       (default #f))
+  (body                nginx-location-configuration-body))
+
+(define-record-type* <nginx-named-location-configuration>
+  nginx-named-location-configuration make-nginx-named-location-configuration
+  nginx-named-location-configuration?
+  (name                nginx-named-location-configuration-name
+                       (default #f))
+  (body                nginx-named-location-configuration-body))
+
 (define-record-type* <nginx-configuration>
   nginx-configuration make-nginx-configuration
   nginx-configuration?
-  (nginx         nginx-configuration-nginx)         ;<package>
-  (log-directory nginx-configuration-log-directory) ;string
-  (run-directory nginx-configuration-run-directory) ;string
-  (server-blocks nginx-configuration-server-blocks) ;list
-  (file          nginx-configuration-file))         ;string | file-like
+  (nginx         nginx-configuration-nginx          ;<package>
+                 (default nginx))
+  (log-directory nginx-configuration-log-directory  ;string
+                 (default "/var/log/nginx"))
+  (run-directory nginx-configuration-run-directory  ;string
+                 (default "/var/run/nginx"))
+  (server-blocks nginx-configuration-server-blocks
+                 (default '()))          ;list of <nginx-server-configuration>
+  (upstream-blocks nginx-configuration-upstream-blocks
+                   (default '()))      ;list of <nginx-upstream-configuration>
+  (file          nginx-configuration-file         ;#f | string | file-like
+                 (default #f)))
 
 (define (config-domain-strings names)
  "Return a string denoting the nginx config representation of NAMES, a list
@@ -88,6 +124,19 @@ of index files."
         ((? string? str) (string-append str " ")))
        names)))
 
+(define nginx-location-config
+  (match-lambda
+    (($ <nginx-location-configuration> uri body)
+     (string-append
+      "      location " uri " {\n"
+      "        " (string-join body "\n    ") "\n"
+      "      }\n"))
+    (($ <nginx-named-location-configuration> name body)
+     (string-append
+      "      location @" name " {\n"
+      "        " (string-join body "\n    ") "\n"
+      "      }\n"))))
+
 (define (default-nginx-server-config server)
   (string-append
    "    server {\n"
@@ -116,11 +165,23 @@ of index files."
    "      index " (config-index-strings (nginx-server-configuration-index server)) ";\n"
    "      server_tokens " (if (nginx-server-configuration-server-tokens? server)
                               "on" "off") ";\n"
+   "\n"
+   (string-join
+    (map nginx-location-config (nginx-server-configuration-locations server))
+    "\n")
+   "    }\n"))
+
+(define (nginx-upstream-config upstream)
+  (string-append
+   "    upstream " (nginx-upstream-configuration-name upstream) " {\n"
+   (string-concatenate
+    (map (lambda (server)
+           (simple-format #f "      server ~A;\n" server))
+         (nginx-upstream-configuration-servers upstream)))
    "    }\n"))
 
-(define (default-nginx-config log-directory run-directory server-list)
-  (plain-file "nginx.conf"
-              (string-append
+(define (default-nginx-config log-directory run-directory server-list upstream-list)
+  (mixed-text-file "nginx.conf"
                "user nginx nginx;\n"
                "pid " run-directory "/pid;\n"
                "error_log " log-directory "/error.log info;\n"
@@ -131,12 +192,18 @@ of index files."
                "    uwsgi_temp_path " run-directory "/uwsgi_temp;\n"
                "    scgi_temp_path " run-directory "/scgi_temp;\n"
                "    access_log " log-directory "/access.log;\n"
+               "\n"
+               (string-join
+                (filter (lambda (section) (not (null? section)))
+                        (map nginx-upstream-config upstream-list))
+                "\n")
+               "\n"
                (let ((http (map default-nginx-server-config server-list)))
                  (do ((http http (cdr http))
                       (block "" (string-append (car http) "\n" block )))
                      ((null? http) block)))
                "}\n"
-               "events {}\n")))
+               "events {}\n"))
 
 (define %nginx-accounts
   (list (user-group (name "nginx") (system? #t))
@@ -151,7 +218,7 @@ of index files."
 (define nginx-activation
   (match-lambda
     (($ <nginx-configuration> nginx log-directory run-directory server-blocks
-                              config-file)
+                              upstream-blocks config-file)
      #~(begin
          (use-modules (guix build utils))
 
@@ -169,13 +236,13 @@ of index files."
          (system* (string-append #$nginx "/sbin/nginx")
                   "-c" #$(or config-file
                              (default-nginx-config log-directory
-                               run-directory server-blocks))
+                               run-directory server-blocks upstream-blocks))
                   "-t")))))
 
 (define nginx-shepherd-service
   (match-lambda
     (($ <nginx-configuration> nginx log-directory run-directory server-blocks
-                              config-file)
+                              upstream-blocks config-file)
      (let* ((nginx-binary (file-append nginx "/sbin/nginx"))
             (nginx-action
              (lambda args
@@ -184,7 +251,7 @@ of index files."
                     (system* #$nginx-binary "-c"
                              #$(or config-file
                                    (default-nginx-config log-directory
-                                     run-directory server-blocks))
+                                     run-directory server-blocks upstream-blocks))
                              #$@args))))))
 
        ;; TODO: Add 'reload' action.
@@ -216,6 +283,7 @@ of index files."
                         (log-directory "/var/log/nginx")
                         (run-directory "/var/run/nginx")
                         (server-list '())
+                        (upstream-list '())
                         (config-file #f))
   "Return a service that runs NGINX, the nginx web server.
 
@@ -227,4 +295,5 @@ files in LOG-DIRECTORY, and stores temporary runtime files in RUN-DIRECTORY."
             (log-directory log-directory)
             (run-directory run-directory)
             (server-blocks server-list)
+            (upstream-blocks upstream-list)
             (file config-file))))