about summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/shells-environment.nix8
-rw-r--r--nixos/modules/config/users-groups.nix25
-rw-r--r--nixos/modules/installer/tools/nixos-install.sh10
-rw-r--r--nixos/modules/installer/tools/tools.nix1
-rw-r--r--nixos/modules/misc/ids.nix4
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/programs/bash/bash.nix2
-rw-r--r--nixos/modules/programs/shadow.nix18
-rw-r--r--nixos/modules/security/acme.nix2
-rw-r--r--nixos/modules/services/audio/squeezelite.nix67
-rw-r--r--nixos/modules/services/mail/opendkim.nix2
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix324
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix2
-rw-r--r--nixos/modules/services/networking/coturn.nix327
-rw-r--r--nixos/modules/services/networking/ejabberd.nix2
-rw-r--r--nixos/modules/services/networking/pptpd.nix2
-rw-r--r--nixos/modules/services/networking/xl2tpd.nix143
-rw-r--r--nixos/modules/services/web-servers/lighttpd/inginious.nix262
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix9
-rw-r--r--nixos/modules/system/boot/systemd.nix1
-rw-r--r--nixos/modules/tasks/filesystems.nix20
-rw-r--r--nixos/modules/virtualisation/lxd.nix2
24 files changed, 1174 insertions, 74 deletions
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 9642981803bf..f458bc39adaa 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -1,7 +1,7 @@
 # This module defines a global environment configuration and
 # a common configuration for all shells.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, utils, pkgs, ... }:
 
 with lib;
 
@@ -135,13 +135,13 @@ in
 
     environment.shells = mkOption {
       default = [];
-      example = [ "/run/current-system/sw/bin/zsh" ];
+      example = literalExample "[ pkgs.bashInteractive pkgs.zsh ]";
       description = ''
         A list of permissible login shells for user accounts.
         No need to mention <literal>/bin/sh</literal>
         here, it is placed into this list implicitly.
       '';
-      type = types.listOf types.path;
+      type = types.listOf (types.either types.shellPackage types.path);
     };
 
   };
@@ -158,7 +158,7 @@ in
 
     environment.etc."shells".text =
       ''
-        ${concatStringsSep "\n" cfg.shells}
+        ${concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
         /bin/sh
       '';
 
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 8231907d7999..277a4264137b 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -1,9 +1,8 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, utils, pkgs, ... }:
 
 with lib;
 
 let
-
   ids = config.ids;
   cfg = config.users;
 
@@ -103,7 +102,7 @@ let
       };
 
       home = mkOption {
-        type = types.str;
+        type = types.path;
         default = "/var/empty";
         description = "The user's home directory.";
       };
@@ -118,8 +117,10 @@ let
       };
 
       shell = mkOption {
-        type = types.str;
-        default = "/run/current-system/sw/bin/nologin";
+        type = types.either types.shellPackage types.path;
+        default = pkgs.nologin;
+        defaultText = "pkgs.nologin";
+        example = literalExample "pkgs.bashInteractive";
         description = "The path to the user's shell.";
       };
 
@@ -359,11 +360,12 @@ let
 
   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
     inherit (cfg) mutableUsers;
-    users = mapAttrsToList (n: u:
+    users = mapAttrsToList (_: u:
       { inherit (u)
-          name uid group description home shell createHome isSystemUser
+          name uid group description home createHome isSystemUser
           password passwordFile hashedPassword
           initialPassword initialHashedPassword;
+        shell = utils.toShellPath u.shell;
       }) cfg.users;
     groups = mapAttrsToList (n: g:
       { inherit (g) name gid;
@@ -373,6 +375,12 @@ let
       }) cfg.groups;
   });
 
+  systemShells =
+    let
+      shells = mapAttrsToList (_: u: u.shell) cfg.users;
+    in
+      filter types.shellPackage.check shells;
+
 in {
 
   ###### interface
@@ -477,6 +485,9 @@ in {
       };
     };
 
+    # Install all the user shells
+    environment.systemPackages = systemShells;
+
     users.groups = {
       root.gid = ids.gids.root;
       wheel.gid = ids.gids.wheel;
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index c23d7e5b509d..7962be137875 100644
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -91,12 +91,10 @@ ln -s /run $mountPoint/var/run
 rm -f $mountPoint/etc/{resolv.conf,hosts}
 cp -Lf /etc/resolv.conf /etc/hosts $mountPoint/etc/
 
-if [ -e "$SSL_CERT_FILE" ]; then
-    cp -Lf "$SSL_CERT_FILE" "$mountPoint/tmp/ca-cert.crt"
-    export SSL_CERT_FILE=/tmp/ca-cert.crt
-    # For Nix 1.7
-    export CURL_CA_BUNDLE=/tmp/ca-cert.crt
-fi
+cp -Lf "@cacert@" "$mountPoint/tmp/ca-cert.crt"
+export SSL_CERT_FILE=/tmp/ca-cert.crt
+# For Nix 1.7
+export CURL_CA_BUNDLE=/tmp/ca-cert.crt
 
 if [ -n "$runChroot" ]; then
     if ! [ -L $mountPoint/nix/var/nix/profiles/system ]; then
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index b8fd9deaf1e4..d8622b510522 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -23,6 +23,7 @@ let
 
     inherit (pkgs) perl pathsFromGraph;
     nix = config.nix.package.out;
+    cacert = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
 
     nixClosure = pkgs.runCommand "closure"
       { exportReferencesGraph = ["refs" config.nix.package.out]; }
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 61c49f07abbb..8da421447624 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -268,6 +268,8 @@
       nzbget = 245;
       mosquitto = 246;
       toxvpn = 247;
+      squeezelite = 248;
+      turnserver = 249;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -506,6 +508,8 @@
       nzbget = 245;
       mosquitto = 246;
       #toxvpn = 247; # unused
+      #squeezelite = 248; #unused
+      turnserver = 249;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 51c43b8c7c3b..be72c0ef29c0 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -110,6 +110,7 @@
   ./services/audio/liquidsoap.nix
   ./services/audio/mpd.nix
   ./services/audio/mopidy.nix
+  ./services/audio/squeezelite.nix
   ./services/backup/almir.nix
   ./services/backup/bacula.nix
   ./services/backup/crashplan.nix
@@ -316,6 +317,7 @@
   ./services/networking/cntlm.nix
   ./services/networking/connman.nix
   ./services/networking/consul.nix
+  ./services/networking/coturn.nix
   ./services/networking/ddclient.nix
   ./services/networking/dhcpcd.nix
   ./services/networking/dhcpd.nix
@@ -413,6 +415,7 @@
   ./services/networking/wicd.nix
   ./services/networking/wpa_supplicant.nix
   ./services/networking/xinetd.nix
+  ./services/networking/xl2tpd.nix
   ./services/networking/zerobin.nix
   ./services/networking/zerotierone.nix
   ./services/networking/znc.nix
@@ -459,6 +462,7 @@
   ./services/web-servers/lighttpd/cgit.nix
   ./services/web-servers/lighttpd/default.nix
   ./services/web-servers/lighttpd/gitweb.nix
+  ./services/web-servers/lighttpd/inginious.nix
   ./services/web-servers/nginx/default.nix
   ./services/web-servers/phpfpm.nix
   ./services/web-servers/shellinabox.nix
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index e4e264ec0036..c09bcfb70e24 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -200,7 +200,7 @@ in
     # Configuration for readline in bash.
     environment.etc."inputrc".source = ./inputrc;
 
-    users.defaultUserShell = mkDefault "/run/current-system/sw/bin/bash";
+    users.defaultUserShell = mkDefault pkgs.bashInteractive;
 
     environment.pathsToLink = optionals cfg.enableCompletion [
       "/etc/bash_completion.d"
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index 566398d839fd..6398509357a6 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -1,6 +1,6 @@
 # Configuration for the pwdutils suite of tools: passwd, useradd, etc.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, utils, pkgs, ... }:
 
 with lib;
 
@@ -43,13 +43,13 @@ in
     users.defaultUserShell = lib.mkOption {
       description = ''
         This option defines the default shell assigned to user
-        accounts.  This must not be a store path, since the path is
+        accounts. This can be either a full system path or a shell package.
+
+        This must not be a store path, since the path is
         used outside the store (in particular in /etc/passwd).
-        Rather, it should be the path of a symlink that points to the
-        actual shell in the Nix store.
       '';
-      example = "/run/current-system/sw/bin/zsh";
-      type = types.path;
+      example = literalExample "pkgs.zsh";
+      type = types.either types.path types.shellPackage;
     };
 
   };
@@ -60,7 +60,9 @@ in
   config = {
 
     environment.systemPackages =
-      lib.optional config.users.mutableUsers pkgs.shadow;
+      lib.optional config.users.mutableUsers pkgs.shadow ++
+      lib.optional (types.shellPackage.check config.users.defaultUserShell)
+        config.users.defaultUserShell;
 
     environment.etc =
       [ { # /etc/login.defs: global configuration for pwdutils.  You
@@ -74,7 +76,7 @@ in
             ''
               GROUP=100
               HOME=/home
-              SHELL=${config.users.defaultUserShell}
+              SHELL=${utils.toShellPath config.users.defaultUserShell}
             '';
           target = "default/useradd";
         }
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index ef6da788e619..f646602221a4 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -187,7 +187,7 @@ in
                   script = ''
                     cd '${cpath}'
                     set +e
-                    simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
+                    simp_le ${escapeShellArgs cmdline}
                     EXITCODE=$?
                     set -e
                     echo "$EXITCODE" > /tmp/lastExitCode
diff --git a/nixos/modules/services/audio/squeezelite.nix b/nixos/modules/services/audio/squeezelite.nix
new file mode 100644
index 000000000000..f1a60be992d8
--- /dev/null
+++ b/nixos/modules/services/audio/squeezelite.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  uid = config.ids.uids.squeezelite;
+  cfg = config.services.squeezelite;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.squeezelite= {
+
+      enable = mkEnableOption "Squeezelite, a software Squeezebox emulator";
+
+      dataDir = mkOption {
+        default = "/var/lib/squeezelite";
+        type = types.str;
+        description = ''
+          The directory where Squeezelite stores its name file.
+        '';
+      };
+
+      extraArguments = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Additional command line arguments to pass to Squeezelite.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.squeezelite= {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "sound.target" ];
+      description = "Software Squeezebox emulator";
+      preStart = "mkdir -p ${cfg.dataDir} && chown -R squeezelite ${cfg.dataDir}";
+      serviceConfig = {
+        ExecStart = "${pkgs.squeezelite}/bin/squeezelite -N ${cfg.dataDir}/player-name ${cfg.extraArguments}";
+        User = "squeezelite";
+        PermissionsStartOnly = true;
+      };
+    };
+
+    users.extraUsers.squeezelite= {
+      inherit uid;
+      group = "nogroup";
+      extraGroups = [ "audio" ];
+      description = "Squeezelite user";
+      home = "${cfg.dataDir}";
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/mail/opendkim.nix b/nixos/modules/services/mail/opendkim.nix
index af996758f41f..f065208ddfc1 100644
--- a/nixos/modules/services/mail/opendkim.nix
+++ b/nixos/modules/services/mail/opendkim.nix
@@ -101,7 +101,7 @@ in {
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.opendkim}/bin/opendkim ${concatMapStringsSep " " escapeShellArg args}";
+        ExecStart = "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}";
         User = cfg.user;
         Group = cfg.group;
         RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 0ae0516769c0..1a95e2d9367d 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -5,17 +5,31 @@ with lib;
 let
   cfg = config.services.matrix-synapse;
   logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
+  mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${if r.compress then "true" else "false"}}'';
+  mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${if l.tls then "true" else "false"}, x_forwarded: ${if l.x_forwarded then "true" else "false"}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}'';
   configFile = pkgs.writeText "homeserver.yaml" ''
 tls_certificate_path: "${cfg.tls_certificate_path}"
+${optionalString (cfg.tls_private_key_path != null) ''
 tls_private_key_path: "${cfg.tls_private_key_path}"
+''}
 tls_dh_params_path: "${cfg.tls_dh_params_path}"
 no_tls: ${if cfg.no_tls then "true" else "false"}
+${optionalString (cfg.bind_port != null) ''
 bind_port: ${toString cfg.bind_port}
+''}
+${optionalString (cfg.unsecure_port != null) ''
 unsecure_port: ${toString cfg.unsecure_port}
+''}
+${optionalString (cfg.bind_host != null) ''
 bind_host: "${cfg.bind_host}"
+''}
 server_name: "${cfg.server_name}"
 pid_file: "/var/run/matrix-synapse.pid"
 web_client: ${if cfg.web_client then "true" else "false"}
+${optionalString (cfg.public_baseurl != null) ''
+public_baseurl: "${cfg.public_baseurl}"
+''}
+listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}]
 database: {
   name: "${cfg.database_type}",
   args: {
@@ -24,21 +38,41 @@ database: {
     )}
   }
 }
+event_cache_size: "${cfg.event_cache_size}"
+verbose: ${cfg.verbose}
 log_file: "/var/log/matrix-synapse/homeserver.log"
 log_config: "${logConfigFile}"
+rc_messages_per_second: ${cfg.rc_messages_per_second}
+rc_message_burst_count: ${cfg.rc_message_burst_count}
+federation_rc_window_size: ${cfg.federation_rc_window_size}
+federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit}
+federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay}
+federation_rc_reject_limit: ${cfg.federation_rc_reject_limit}
+federation_rc_concurrent: ${cfg.federation_rc_concurrent}
 media_store_path: "/var/lib/matrix-synapse/media"
+uploads_path: "/var/lib/matrix-synapse/uploads"
+max_upload_size: "${cfg.max_upload_size}"
+max_image_pixels: "${cfg.max_image_pixels}"
+dynamic_thumbnails: ${if cfg.dynamic_thumbnails then "true" else "false"}
+url_preview_enabled: False
 recaptcha_private_key: "${cfg.recaptcha_private_key}"
 recaptcha_public_key: "${cfg.recaptcha_public_key}"
 enable_registration_captcha: ${if cfg.enable_registration_captcha then "true" else "false"}
-turn_uris: ${if (length cfg.turn_uris) == 0 then "[]" else ("\n" + (concatStringsSep "\n" (map (s: "- " + s) cfg.turn_uris)))}
+turn_uris: ${builtins.toJSON cfg.turn_uris}
 turn_shared_secret: "${cfg.turn_shared_secret}"
 enable_registration: ${if cfg.enable_registration then "true" else "false"}
-${optionalString (cfg.registration_shared_secret != "") ''
+${optionalString (cfg.registration_shared_secret != null) ''
 registration_shared_secret: "${cfg.registration_shared_secret}"
 ''}
+recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
+turn_user_lifetime: "${cfg.turn_user_lifetime}"
+user_creation_max_duration: ${cfg.user_creation_max_duration}
+bcrypt_rounds: ${cfg.bcrypt_rounds}
+allow_guest_access: {if cfg.allow_guest_access then "true" else "false"}
 enable_metrics: ${if cfg.enable_metrics then "true" else "false"}
 report_stats: ${if cfg.report_stats then "true" else "false"}
 signing_key_path: "/var/lib/matrix-synapse/homeserver.signing.key"
+key_refresh_interval: "${cfg.key_refresh_interval}"
 perspectives:
   servers: {
     ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
@@ -52,6 +86,8 @@ perspectives:
     '') cfg.servers)}
     }
   }
+app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
+
 ${cfg.extraConfig}
 '';
 in {
@@ -73,53 +109,65 @@ in {
           Don't bind to the https port
         '';
       };
-      tls_certificate_path = mkOption {
-        type = types.path;
-        default = "/var/lib/matrix-synapse/homeserver.tls.crt";
+      bind_port = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 8448;
         description = ''
-          PEM encoded X509 certificate for TLS
+          DEPRECATED: Use listeners instead.
+          The port to listen for HTTPS requests on.
+          For when matrix traffic is sent directly to synapse.
         '';
       };
-      tls_private_key_path = mkOption {
-        type = types.path;
-        default = "/var/lib/matrix-synapse/homeserver.tls.key";
+      unsecure_port = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 8008;
         description = ''
-          PEM encoded private key for TLS
+          DEPRECATED: Use listeners instead.
+          The port to listen for HTTP requests on.
+          For when matrix traffic passes through loadbalancer that unwraps TLS.
         '';
       };
-      tls_dh_params_path = mkOption {
-        type = types.path;
-        default = "/var/lib/matrix-synapse/homeserver.tls.dh";
+      bind_host = mkOption {
+        type = types.nullOr types.str;
+        default = null;
         description = ''
-          PEM dh parameters for ephemeral keys
+          DEPRECATED: Use listeners instead.
+          Local interface to listen on.
+          The empty string will cause synapse to listen on all interfaces.
         '';
       };
-      bind_port = mkOption {
-        type = types.int;
-        default = 8448;
+      tls_certificate_path = mkOption {
+        type = types.str;
+        default = "/var/lib/matrix-synapse/homeserver.tls.crt";
         description = ''
-          The port to listen for HTTPS requests on.
-          For when matrix traffic is sent directly to synapse.
+          PEM encoded X509 certificate for TLS.
+          You can replace the self-signed certificate that synapse
+          autogenerates on launch with your own SSL certificate + key pair
+          if you like.  Any required intermediary certificates can be
+          appended after the primary certificate in hierarchical order.
         '';
       };
-      unsecure_port = mkOption {
-        type = types.int;
-        default = 8008;
+      tls_private_key_path = mkOption {
+        type = types.nullOr types.str;
+        default = "/var/lib/matrix-synapse/homeserver.tls.key";
+        example = null;
         description = ''
-          The port to listen for HTTP requests on.
-          For when matrix traffic passes through loadbalancer that unwraps TLS.
+          PEM encoded private key for TLS. Specify null if synapse is not
+          speaking TLS directly.
         '';
       };
-      bind_host = mkOption {
+      tls_dh_params_path = mkOption {
         type = types.str;
-        default = "";
+        default = "/var/lib/matrix-synapse/homeserver.tls.dh";
         description = ''
-          Local interface to listen on.
-          The empty string will cause synapse to listen on all interfaces.
+          PEM dh parameters for ephemeral keys
         '';
       };
       server_name = mkOption {
         type = types.str;
+        example = "example.com";
         description = ''
           The domain name of the server, with optional explicit port.
           This is used by remote servers to connect to this server,
@@ -134,6 +182,145 @@ in {
           Whether to serve a web client from the HTTP/HTTPS root resource.
         '';
       };
+      public_baseurl = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "https://example.com:8448/";
+        description = ''
+          The public-facing base URL for the client API (not including _matrix/...)
+        '';
+      };
+      listeners = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            port = mkOption {
+              type = types.int;
+              example = 8448;
+              description = ''
+                The port to listen for HTTP(S) requests on.
+              '';
+            };
+            bind_address = mkOption {
+              type = types.str;
+              default = "";
+              example = "203.0.113.42";
+              description = ''
+                Local interface to listen on.
+                The empty string will cause synapse to listen on all interfaces.
+              '';
+            };
+            type = mkOption {
+              type = types.str;
+              default = "http";
+              description = ''
+                Type of listener.
+              '';
+            };
+            tls = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to listen for HTTPS connections rather than HTTP.
+              '';
+            };
+            x_forwarded = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Use the X-Forwarded-For (XFF) header as the client IP and not the
+                actual client IP.
+              '';
+            };
+            resources = mkOption {
+              type = types.listOf (types.submodule {
+                options = {
+                  names = mkOption {
+                    type = types.listOf types.str;
+                    description = ''
+                      List of resources to host on this listener.
+                    '';
+                    example = ["client" "webclient" "federation"];
+                  };
+                  compress = mkOption {
+                    type = types.bool;
+                    description = ''
+                      Should synapse compress HTTP responses to clients that support it?
+                      This should be disabled if running synapse behind a load balancer
+                      that can do automatic compression.
+                    '';
+                  };
+                };
+              });
+              description = ''
+                List of HTTP resources to serve on this listener.
+              '';
+            };
+          };
+        });
+        default = [{
+          port = 8448;
+          bind_address = "";
+          type = "http";
+          tls = true;
+          x_forwarded = false;
+          resources = [
+            { names = ["client" "webclient"]; compress = true; }
+            { names = ["federation"]; compress = false; }
+          ];
+        }];
+        description = ''
+          List of ports that Synapse should listen on, their purpose and their configuration.
+        '';
+      };
+      verbose = mkOption {
+        type = types.str;
+        default = "0";
+        description = "Logging verbosity level.";
+      };
+      rc_messages_per_second = mkOption {
+        type = types.str;
+        default = "0.2";
+        description = "Number of messages a client can send per second";
+      };
+      rc_message_burst_count = mkOption {
+        type = types.str;
+        default = "10.0";
+        description = "Number of message a client can send before being throttled";
+      };
+      federation_rc_window_size = mkOption {
+        type = types.str;
+        default = "1000";
+        description = "The federation window size in milliseconds";
+      };
+      federation_rc_sleep_limit = mkOption {
+        type = types.str;
+        default = "10";
+        description = ''
+          The number of federation requests from a single server in a window
+          before the server will delay processing the request.
+        '';
+      };
+      federation_rc_sleep_delay = mkOption {
+        type = types.str;
+        default = "500";
+        description = ''
+          The duration in milliseconds to delay processing events from
+          remote servers by if they go over the sleep limit.
+        '';
+      };
+      federation_rc_reject_limit = mkOption {
+        type = types.str;
+        default = "50";
+        description = ''
+          The maximum number of concurrent federation requests allowed
+          from a single server
+        '';
+      };
+      federation_rc_concurrent = mkOption {
+        type = types.str;
+        default = "3";
+        description = "The number of federation requests to concurrently process from a single server";
+      };
       database_type = mkOption {
         type = types.enum [ "sqlite3" "psycopg2" ];
         default = "sqlite3";
@@ -150,6 +337,11 @@ in {
           Arguments to pass to the engine.
         '';
       };
+      event_cache_size = mkOption {
+        type = types.str;
+        default = "10K";
+        description = "Number of events to cache in memory.";
+      };
       recaptcha_private_key = mkOption {
         type = types.str;
         default = "";
@@ -187,6 +379,11 @@ in {
           The shared secret used to compute passwords for the TURN server
         '';
       };
+      turn_user_lifetime = mkOption {
+        type = types.str;
+        default = "1h";
+        description = "How long generated TURN credentials last";
+      };
       enable_registration = mkOption {
         type = types.bool;
         default = false;
@@ -195,8 +392,8 @@ in {
         '';
       };
       registration_shared_secret = mkOption {
-        type = types.str;
-        default = "";
+        type = types.nullOr types.str;
+        default = null;
         description = ''
           If set, allows registration by anyone who also has the shared
           secret, even if registration is otherwise disabled.
@@ -216,7 +413,7 @@ in {
         '';
       };
       servers = mkOption {
-        type = types.attrs;
+        type = types.attrsOf (types.attrsOf types.str);
         default = {
           "matrix.org" = {
             "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
@@ -226,6 +423,69 @@ in {
           The trusted servers to download signing keys from.
         '';
       };
+      max_upload_size = mkOption {
+        type = types.str;
+        default = "10M";
+        description = "The largest allowed upload size in bytes";
+      };
+      max_image_pixels = mkOption {
+        type = types.str;
+        default = "32M";
+        description = "Maximum number of pixels that will be thumbnailed";
+      };
+      dynamic_thumbnails = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to generate new thumbnails on the fly to precisely match
+          the resolution requested by the client. If true then whenever
+          a new resolution is requested by the client the server will
+          generate a new thumbnail. If false the server will pick a thumbnail
+          from a precalculated list.
+        '';
+      };
+      user_creation_max_duration = mkOption {
+        type = types.str;
+        default = "1209600000";
+        description = ''
+          Sets the expiry for the short term user creation in
+          milliseconds. The default value is two weeks.
+        '';
+      };
+      bcrypt_rounds = mkOption {
+        type = types.str;
+        default = "12";
+        description = ''
+          Set the number of bcrypt rounds used to generate password hash.
+          Larger numbers increase the work factor needed to generate the hash.
+        '';
+      };
+      allow_guest_access = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allows users to register as guests without a password/email/etc, and
+          participate in rooms hosted on this server which have been made
+          accessible to anonymous users.
+        '';
+      };
+      key_refresh_interval = mkOption {
+        type = types.str;
+        default = "1d";
+        description = ''
+          How long key response published by this server is valid for.
+          Used to set the valid_until_ts in /key/v2 APIs.
+          Determines how quickly servers will query to check which keys
+          are still valid.
+        '';
+      };
+      app_service_config_files = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          A list of application service config file to use
+        '';
+      };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
@@ -265,7 +525,7 @@ in {
         mkdir -p /var/lib/matrix-synapse
         chmod 700 /var/lib/matrix-synapse
         chown -R matrix-synapse:matrix-synapse /var/lib/matrix-synapse
-        ${cfg.package}/bin/homeserver --config-path ${configFile} --generate-keys
+        ${cfg.package}/bin/homeserver --config-path ${configFile} --keys-directory /var/lib/matrix-synapse/ --generate-keys
       '';
       serviceConfig = {
         Type = "simple";
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index b7d14e90a2b7..c846ffd04551 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -152,8 +152,6 @@ let
     };
   };
 
-  mkShellStr = val: "'${replaceStrings ["'"] ["'\\''"] val}'";
-
   certtool = "${pkgs.gnutls.bin}/bin/certtool";
 
   nixos-taskserver = pkgs.buildPythonPackage {
diff --git a/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix
new file mode 100644
index 000000000000..14e6932d868b
--- /dev/null
+++ b/nixos/modules/services/networking/coturn.nix
@@ -0,0 +1,327 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.coturn;
+  pidfile = "/run/turnserver/turnserver.pid";
+  configFile = pkgs.writeText "turnserver.conf" ''
+listening-port=${toString cfg.listening-port}
+tls-listening-port=${toString cfg.tls-listening-port}
+alt-listening-port=${toString cfg.alt-listening-port}
+alt-tls-listening-port=${toString cfg.alt-tls-listening-port}
+${concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)}
+${concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)}
+min-port=${toString cfg.min-port}
+max-port=${toString cfg.max-port}
+${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"}
+${lib.optionalString cfg.no-auth "no-auth"}
+${lib.optionalString cfg.use-auth-secret "use-auth-secret"}
+${lib.optionalString (cfg.static-auth-secret != null) ("static-auth-secret=${cfg.static-auth-secret}")}
+realm=${cfg.realm}
+${lib.optionalString cfg.no-udp "no-udp"}
+${lib.optionalString cfg.no-tcp "no-tcp"}
+${lib.optionalString cfg.no-tls "no-tls"}
+${lib.optionalString cfg.no-dtls "no-dtls"}
+${lib.optionalString cfg.no-udp-relay "no-udp-relay"}
+${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"}
+${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"}
+${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"}
+${lib.optionalString (cfg.dh-file != null) ("dh-file=${cfg.dh-file}")}
+no-stdout-log
+syslog
+pidfile=${pidfile}
+${lib.optionalString cfg.secure-stun "secure-stun"}
+${lib.optionalString cfg.no-cli "no-cli"}
+cli-ip=${cfg.cli-ip}
+cli-port=${toString cfg.cli-port}
+${lib.optionalString (cfg.cli-password != null) ("cli-password=${cfg.cli-password}")}
+${cfg.extraConfig}
+'';
+in {
+  options = {
+    services.coturn = {
+      enable = mkEnableOption "coturn TURN server";
+      listening-port = mkOption {
+        type = types.int;
+        default = 3478;
+        description = ''
+          TURN listener port for UDP and TCP.
+          Note: actually, TLS and DTLS sessions can connect to the
+          "plain" TCP and UDP port(s), too - if allowed by configuration.
+        '';
+      };
+      tls-listening-port = mkOption {
+        type = types.int;
+        default = 5349;
+        description = ''
+          TURN listener port for TLS.
+          Note: actually, "plain" TCP and UDP sessions can connect to the TLS and
+          DTLS port(s), too - if allowed by configuration. The TURN server
+          "automatically" recognizes the type of traffic. Actually, two listening
+          endpoints (the "plain" one and the "tls" one) are equivalent in terms of
+          functionality; but we keep both endpoints to satisfy the RFC 5766 specs.
+          For secure TCP connections, we currently support SSL version 3 and
+          TLS version 1.0, 1.1 and 1.2.
+          For secure UDP connections, we support DTLS version 1.
+        '';
+      };
+      alt-listening-port = mkOption {
+        type = types.int;
+        default = cfg.listening-port + 1;
+        defaultText = "listening-port + 1";
+        description = ''
+          Alternative listening port for UDP and TCP listeners;
+          default (or zero) value means "listening port plus one".
+          This is needed for RFC 5780 support
+          (STUN extension specs, NAT behavior discovery). The TURN Server
+          supports RFC 5780 only if it is started with more than one
+          listening IP address of the same family (IPv4 or IPv6).
+          RFC 5780 is supported only by UDP protocol, other protocols
+          are listening to that endpoint only for "symmetry".
+        '';
+      };
+      alt-tls-listening-port = mkOption {
+        type = types.int;
+        default = cfg.tls-listening-port + 1;
+        defaultText = "tls-listening-port + 1";
+        description = ''
+          Alternative listening port for TLS and DTLS protocols.
+        '';
+      };
+      listening-ips = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "203.0.113.42" "2001:DB8::42" ];
+        description = ''
+          Listener IP addresses of relay server.
+          If no IP(s) specified in the config file or in the command line options,
+          then all IPv4 and IPv6 system IPs will be used for listening.
+        '';
+      };
+      relay-ips = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "203.0.113.42" "2001:DB8::42" ];
+        description = ''
+          Relay address (the local IP address that will be used to relay the
+          packets to the peer).
+          Multiple relay addresses may be used.
+          The same IP(s) can be used as both listening IP(s) and relay IP(s).
+
+          If no relay IP(s) specified, then the turnserver will apply the default
+          policy: it will decide itself which relay addresses to be used, and it
+          will always be using the client socket IP address as the relay IP address
+          of the TURN session (if the requested relay address family is the same
+          as the family of the client socket).
+        '';
+      };
+      min-port = mkOption {
+        type = types.int;
+        default = 49152;
+        description = ''
+          Lower bound of UDP relay endpoints
+        '';
+      };
+      max-port = mkOption {
+        type = types.int;
+        default = 65535;
+        description = ''
+          Upper bound of UDP relay endpoints
+        '';
+      };
+      lt-cred-mech = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Use long-term credential mechanism.
+        '';
+      };
+      no-auth = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          This option is opposite to lt-cred-mech.
+          (TURN Server with no-auth option allows anonymous access).
+          If neither option is defined, and no users are defined,
+          then no-auth is default. If at least one user is defined,
+          in this file or in command line or in usersdb file, then
+          lt-cred-mech is default.
+        '';
+      };
+      use-auth-secret = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          TURN REST API flag.
+          Flag that sets a special authorization option that is based upon authentication secret.
+          This feature can be used with the long-term authentication mechanism, only.
+          This feature purpose is to support "TURN Server REST API", see
+          "TURN REST API" link in the project's page
+          https://github.com/coturn/coturn/
+
+          This option is used with timestamp:
+
+          usercombo -> "timestamp:userid"
+          turn user -> usercombo
+          turn password -> base64(hmac(secret key, usercombo))
+
+          This allows TURN credentials to be accounted for a specific user id.
+          If you don't have a suitable id, the timestamp alone can be used.
+          This option is just turning on secret-based authentication.
+          The actual value of the secret is defined either by option static-auth-secret,
+          or can be found in the turn_secret table in the database.
+        '';
+      };
+      static-auth-secret = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          'Static' authentication secret value (a string) for TURN REST API only.
+          If not set, then the turn server
+          will try to use the 'dynamic' value in turn_secret table
+          in user database (if present). The database-stored  value can be changed on-the-fly
+          by a separate program, so this is why that other mode is 'dynamic'.
+        '';
+      };
+      realm = mkOption {
+        type = types.str;
+        default = config.networking.hostName;
+        example = "example.com";
+        description = ''
+          The default realm to be used for the users when no explicit
+          origin/realm relationship was found in the database, or if the TURN
+          server is not using any database (just the commands-line settings
+          and the userdb file). Must be used with long-term credentials
+          mechanism or with TURN REST API.
+        '';
+      };
+      cert = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/acme/example.com/fullchain.pem";
+        description = ''
+          Certificate file in PEM format.
+        '';
+      };
+      pkey = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/acme/example.com/key.pem";
+        description = ''
+          Private key file in PEM format.
+        '';
+      };
+      dh-file = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Use custom DH TLS key, stored in PEM format in the file.
+        '';
+      };
+      secure-stun = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Require authentication of the STUN Binding request.
+          By default, the clients are allowed anonymous access to the STUN Binding functionality.
+        '';
+      };
+      no-cli = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Turn OFF the CLI support.
+        '';
+      };
+      cli-ip = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Local system IP address to be used for CLI server endpoint.
+        '';
+      };
+      cli-port = mkOption {
+        type = types.int;
+        default = 5766;
+        description = ''
+          CLI server port.
+        '';
+      };
+      cli-password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          CLI access password.
+          For the security reasons, it is recommended to use the encrypted
+          for of the password (see the -P command in the turnadmin utility).
+        '';
+      };
+      no-udp = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable UDP client listener";
+      };
+      no-tcp = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable TCP client listener";
+      };
+      no-tls = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable TLS client listener";
+      };
+      no-dtls = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable DTLS client listener";
+      };
+      no-udp-relay = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable UDP relay endpoints";
+      };
+      no-tcp-relay = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable TCP relay endpoints";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional configuration options";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.extraUsers = [
+      { name = "turnserver";
+        uid = config.ids.uids.turnserver;
+        description = "coturn TURN server user";
+      } ];
+    users.extraGroups = [
+      { name = "turnserver";
+        gid = config.ids.gids.turnserver;
+        members = [ "turnserver" ];
+      } ];
+
+    systemd.services.coturn = {
+      description = "coturn TURN server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      unitConfig = {
+        Documentation = "man:coturn(1) man:turnadmin(1) man:turnserver(1)";
+      };
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.coturn}/bin/turnserver -c ${configFile}";
+        RuntimeDirectory = "turnserver";
+        User = "turnserver";
+        Group = "turnserver";
+        Restart = "on-abort";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index 9868f303ab2b..8ecc16257db8 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -13,7 +13,7 @@ let
 
   ectl = ''${cfg.package}/bin/ejabberdctl ${if cfg.configFile == null then "" else "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
 
-  dumps = lib.concatMapStringsSep " " lib.escapeShellArg cfg.loadDumps;
+  dumps = lib.escapeShellArgs cfg.loadDumps;
 
 in {
 
diff --git a/nixos/modules/services/networking/pptpd.nix b/nixos/modules/services/networking/pptpd.nix
index efed604d3dda..513e6174752c 100644
--- a/nixos/modules/services/networking/pptpd.nix
+++ b/nixos/modules/services/networking/pptpd.nix
@@ -16,7 +16,7 @@ with lib;
       clientIpRange = mkOption {
         type        = types.string;
         description = "The range from which client IPs are drawn.";
-        default     = "10.124.142.2-11";
+        default     = "10.124.124.2-11";
       };
 
       maxClients = mkOption {
diff --git a/nixos/modules/services/networking/xl2tpd.nix b/nixos/modules/services/networking/xl2tpd.nix
new file mode 100644
index 000000000000..5e006c13f0d0
--- /dev/null
+++ b/nixos/modules/services/networking/xl2tpd.nix
@@ -0,0 +1,143 @@
+{ config, stdenv, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options = {
+    services.xl2tpd = {
+      enable = mkEnableOption "Whether xl2tpd should be run on startup.";
+
+      serverIp = mkOption {
+        type        = types.string;
+        description = "The server-side IP address.";
+        default     = "10.125.125.1";
+      };
+
+      clientIpRange = mkOption {
+        type        = types.string;
+        description = "The range from which client IPs are drawn.";
+        default     = "10.125.125.2-11";
+      };
+
+      extraXl2tpOptions = mkOption {
+        type        = types.lines;
+        description = "Adds extra lines to the xl2tpd configuration file.";
+        default     = "";
+      };
+
+      extraPppdOptions = mkOption {
+        type        = types.lines;
+        description = "Adds extra lines to the pppd options file.";
+        default     = "";
+        example     = ''
+          ms-dns 8.8.8.8
+          ms-dns 8.8.4.4
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.xl2tpd.enable {
+    systemd.services.xl2tpd = let
+      cfg = config.services.xl2tpd;
+
+      # Config files from https://help.ubuntu.com/community/L2TPServer
+      xl2tpd-conf = pkgs.writeText "xl2tpd.conf" ''
+        [global]
+        ipsec saref = no
+
+        [lns default]
+        local ip = ${cfg.serverIp}
+        ip range = ${cfg.clientIpRange}
+        pppoptfile = ${pppd-options}
+        length bit = yes
+
+        ; Extra
+        ${cfg.extraXl2tpOptions}
+      '';
+
+      pppd-options = pkgs.writeText "ppp-options-xl2tpd.conf" ''
+        refuse-pap
+        refuse-chap
+        refuse-mschap
+        require-mschap-v2
+        # require-mppe-128
+        asyncmap 0
+        auth
+        crtscts
+        idle 1800
+        mtu 1200
+        mru 1200
+        lock
+        hide-password
+        local
+        # debug
+        name xl2tpd
+        # proxyarp
+        lcp-echo-interval 30
+        lcp-echo-failure 4
+
+        # Extra:
+        ${cfg.extraPppdOptions}
+      '';
+
+      xl2tpd-ppp-wrapped = pkgs.stdenv.mkDerivation {
+        name         = "xl2tpd-ppp-wrapped";
+        phases       = [ "installPhase" ];
+        buildInputs  = with pkgs; [ makeWrapper ];
+        installPhase = ''
+          mkdir -p $out/bin
+
+          makeWrapper ${pkgs.ppp}/sbin/pppd $out/bin/pppd \
+            --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
+            --set NIX_REDIRECTS "/etc/ppp=/etc/xl2tpd/ppp"
+
+          makeWrapper ${pkgs.xl2tpd}/bin/xl2tpd $out/bin/xl2tpd \
+            --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
+            --set NIX_REDIRECTS "${pkgs.ppp}/sbin/pppd=$out/bin/pppd"
+        '';
+      };
+    in {
+      description = "xl2tpd server";
+
+      requires = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -p -m 700 /etc/xl2tpd
+
+        pushd /etc/xl2tpd > /dev/null
+
+        mkdir -p -m 700 ppp
+
+        [ -f ppp/chap-secrets ] || cat > ppp/chap-secrets << EOF
+        # Secrets for authentication using CHAP
+        # client	server	secret		IP addresses
+        #username	xl2tpd	password	*
+        EOF
+
+        chown root.root ppp/chap-secrets
+        chmod 600 ppp/chap-secrets
+
+        # The documentation says this file should be present but doesn't explain why and things work even if not there:
+        [ -f l2tp-secrets ] || (echo -n "* * "; ${pkgs.apg}/bin/apg -n 1 -m 32 -x 32 -a 1 -M LCN) > l2tp-secrets
+        chown root.root l2tp-secrets
+        chmod 600 l2tp-secrets
+
+        popd > /dev/null
+
+        mkdir -p /run/xl2tpd
+        chown root.root /run/xl2tpd
+        chmod 700       /run/xl2tpd
+      '';
+
+      serviceConfig = {
+        ExecStart = "${xl2tpd-ppp-wrapped}/bin/xl2tpd -D -c ${xl2tpd-conf} -s /etc/xl2tpd/l2tp-secrets -p /run/xl2tpd/pid -C /run/xl2tpd/control";
+        KillMode  = "process";
+        Restart   = "on-success";
+        Type      = "simple";
+        PIDFile   = "/run/xl2tpd/pid";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-servers/lighttpd/inginious.nix b/nixos/modules/services/web-servers/lighttpd/inginious.nix
new file mode 100644
index 000000000000..43deccb6aef8
--- /dev/null
+++ b/nixos/modules/services/web-servers/lighttpd/inginious.nix
@@ -0,0 +1,262 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.lighttpd.inginious;
+  inginious = pkgs.inginious;
+  execName = "inginious-${if cfg.useLTI then "lti" else "webapp"}";
+
+  inginiousConfigFile = if cfg.configFile != null then cfg.configFile else pkgs.writeText "inginious.yaml" ''
+    # Backend; can be:
+    # - "local" (run containers on the same machine)
+    # - "remote" (connect to distant docker daemon and auto start agents) (choose this if you use boot2docker)
+    # - "remote_manual" (connect to distant and manually installed agents)
+    backend: "${cfg.backendType}"
+
+    ## TODO (maybe): Add an option for the "remote" backend in this NixOS module.
+    # List of remote docker daemon to which the backend will try
+    # to connect (backend: remote only)
+    #docker_daemons:
+    #  - # Host of the docker daemon *from the webapp*
+    #    remote_host: "some.remote.server"
+    #    # Port of the distant docker daemon *from the webapp*
+    #    remote_docker_port: "2375"
+    #    # A mandatory port used by the backend and the agent that will be automatically started.
+    #    # Needs to be available on the remote host, and to be open in the firewall.
+    #    remote_agent_port: "63456"
+    #    # Does the remote docker requires tls? Defaults to false.
+    #    # Parameter can be set to true or path to the certificates
+    #    #use_tls: false
+    #    # Link to the docker daemon *from the host that runs the docker daemon*. Defaults to:
+    #    #local_location: "unix:///var/run/docker.sock"
+    #    # Path to the cgroups "mount" *from the host that runs the docker daemon*. Defaults to:
+    #    #cgroups_location: "/sys/fs/cgroup"
+    #    # Name that will be used to reference the agent
+    #    #"agent_name": "inginious-agent"
+
+    # List of remote agents to which the backend will try
+    # to connect (backend: remote_manual only)
+    # Example:
+    #agents:
+    #  - host: "192.168.59.103"
+    #    port: 5001
+    agents:
+    ${lib.concatMapStrings (agent:
+      "  - host: \"${agent.host}\"\n" +
+      "    port: ${agent.port}\n"
+    ) cfg.remoteAgents}
+
+    # Location of the task directory
+    tasks_directory: "${cfg.tasksDirectory}"
+
+    # Super admins: list of user names that can do everything in the backend
+    superadmins:
+    ${lib.concatMapStrings (x: "  - \"${x}\"\n") cfg.superadmins}
+
+    # Aliases for containers
+    # Only containers listed here can be used by tasks
+    containers:
+    ${lib.concatStrings (lib.mapAttrsToList (name: fullname:
+      "  ${name}: \"${fullname}\"\n"
+    ) cfg.containers)}
+
+    # Use single minified javascript file (production) or multiple files (dev) ?
+    use_minified_js: true
+
+    ## TODO (maybe): Add NixOS options for these parameters.
+
+    # MongoDB options
+    #mongo_opt:
+    #    host: localhost
+    #    database: INGInious
+
+    # Disable INGInious?
+    #maintenance: false
+
+    #smtp:
+    #    sendername: 'INGInious <no-reply@inginious.org>'
+    #    host: 'smtp.gmail.com'
+    #    port: 587
+    #    username: 'configme@gmail.com'
+    #    password: 'secret'
+    #    starttls: True
+
+    ## NixOS extra config
+
+    ${cfg.extraConfig}
+  '';
+in
+{
+  options.services.lighttpd.inginious = {
+    enable = mkEnableOption  "INGInious, an automated code testing and grading system.";
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = literalExample ''pkgs.writeText "configuration.yaml" "# custom config options ...";'';
+      description = ''The path to an INGInious configuration file.'';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        # Load the dummy auth plugin.
+        plugins:
+          - plugin_module: inginious.frontend.webapp.plugins.auth.demo_auth
+            users:
+              # register the user "test" with the password "someverycomplexpassword"
+              test: someverycomplexpassword
+      '';
+      description = ''Extra option in YaML format, to be appended to the config file.'';
+    };
+
+    tasksDirectory = mkOption {
+      type = types.path;
+      default = "${inginious}/lib/python2.7/site-packages/inginious/tasks";
+      example = "/var/lib/INGInious/tasks";
+      description = ''
+        Path to the tasks folder.
+        Defaults to the provided test tasks folder (readonly).
+      '';
+    };
+
+    useLTI = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''Whether to start the LTI frontend in place of the webapp.'';
+    };
+
+    superadmins = mkOption {
+      type = types.uniq (types.listOf types.str);
+      default = [ "admin" ];
+      example = [ "john" "pepe" "emilia" ];
+      description = ''List of user logins allowed to administrate the whole server.'';
+    };
+
+    containers = mkOption {
+      type = types.attrsOf types.str;
+      default = {
+          default = "ingi/inginious-c-default";
+      };
+      example = {
+        default = "ingi/inginious-c-default";
+        sekexe  = "ingi/inginious-c-sekexe";
+        java    = "ingi/inginious-c-java";
+        oz      = "ingi/inginious-c-oz";
+        pythia1compat = "ingi/inginious-c-pythia1compat";
+      };
+      description = ''
+        An attrset describing the required containers
+        These containers will be available in INGInious using their short name (key)
+        and will be automatically downloaded before INGInious starts.
+      '';
+    };
+
+    hostPattern = mkOption {
+      type = types.str;
+      default = "^inginious.";
+      example = "^inginious.mydomain.xyz$";
+      description = ''
+        The domain that serves INGInious.
+        INGInious uses absolute paths which makes it difficult to relocate in its own subdir.
+        The default configuration will serve INGInious when the server is accessed with a hostname starting with "inginious.".
+        If left blank, INGInious will take the precedence over all the other lighttpd sites, which is probably not what you want.
+      '';
+    };
+
+    backendType = mkOption {
+      type = types.enum [ "local" "remote_manual" ]; # TODO: support backend "remote"
+      default = "local";
+      description = ''
+        Select how INGINious accesses to grading containers.
+        The default "local" option ensures that Docker is started and provisioned.
+        Fore more information, see http://inginious.readthedocs.io/en/latest/install_doc/config_reference.html
+        Not all backends are supported. Use services.inginious.configFile for full flexibility.
+      '';
+    };
+
+    remoteAgents = mkOption {
+      type = types.listOf (types.attrsOf types.str);
+      default = [];
+      example = [ { host = "192.0.2.25"; port = "1345"; } ];
+      description = ''A list of remote agents, used only when services.inginious.backendType is "remote_manual".'';
+    };
+  };
+
+  config = mkIf cfg.enable (
+    mkMerge [
+      # For a local install, we need docker.
+      (mkIf (cfg.backendType == "local") {
+        virtualisation.docker = {
+          enable = true;
+          # We need docker to listen on port 2375.
+          extraOptions = "-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock";
+          storageDriver = mkDefault "overlay";
+          socketActivation = false;
+        };
+
+        users.extraUsers."lighttpd".extraGroups = [ "docker" ];
+
+        # Ensure that docker has pulled the required images.
+        systemd.services.inginious-prefetch = {
+          script = let
+            images = lib.unique (
+              [ "centos" "ingi/inginious-agent" ]
+              ++ lib.mapAttrsToList (_: image: image) cfg.containers
+            );
+          in lib.concatMapStrings (image: ''
+            ${pkgs.docker}/bin/docker pull ${image}
+          '') images;
+
+          serviceConfig.Type = "oneshot";
+          wants = [ "docker.service" ];
+          after = [ "docker.service" ];
+          wantedBy = [ "lighttpd.service" ];
+          before = [ "lighttpd.service" ];
+        };
+      })
+
+      # Common
+      {
+        # To access inginous tools (like inginious-test-task)
+        environment.systemPackages = [ inginious ];
+
+        services.mongodb.enable = true;
+
+        services.lighttpd.enable = true;
+        services.lighttpd.enableModules = [ "mod_access" "mod_alias" "mod_fastcgi" "mod_redirect" "mod_rewrite" ];
+        services.lighttpd.extraConfig = ''
+          $HTTP["host"] =~ "${cfg.hostPattern}" {
+            fastcgi.server = ( "/${execName}" =>
+              ((
+                "socket" => "/run/lighttpd/inginious-fastcgi.socket",
+                "bin-path" => "${inginious}/bin/${execName} --config=${inginiousConfigFile}",
+                "max-procs" => 1,
+                "bin-environment" => ( "REAL_SCRIPT_NAME" => "" ),
+                "check-local" => "disable"
+              ))
+            )
+            url.rewrite-once = (
+              "^/.well-known/.*" => "$0",
+              "^/static/.*" => "$0",
+              "^/.*$" => "/${execName}$0",
+              "^/favicon.ico$" => "/static/common/favicon.ico",
+            )
+            alias.url += (
+              "/static/webapp/" => "${inginious}/lib/python2.7/site-packages/inginious/frontend/webapp/static/",
+              "/static/common/" => "${inginious}/lib/python2.7/site-packages/inginious/frontend/common/static/"
+            )
+          }
+        '';
+
+        systemd.services.lighttpd.preStart = ''
+          mkdir -p /run/lighttpd
+          chown lighttpd.lighttpd /run/lighttpd
+        '';
+
+        systemd.services.lighttpd.wants = [ "mongodb.service" "docker.service" ];
+        systemd.services.lighttpd.after = [ "mongodb.service" "docker.service" ];
+      }
+    ]);
+}
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 700faad0c695..68579a1af836 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -119,6 +119,7 @@ in {
     services.telepathy.enable = mkDefault true;
     networking.networkmanager.enable = mkDefault true;
     services.upower.enable = config.powerManagement.enable;
+    services.dbus.packages = mkIf config.services.printing.enable [ pkgs.system-config-printer ];
     hardware.bluetooth.enable = mkDefault true;
 
     fonts.fonts = [ pkgs.dejavu_fonts pkgs.cantarell_fonts ];
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 4c4e3d967988..634d2a39576a 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -33,6 +33,14 @@ in
         default = false;
         description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon).";
       };
+
+      extraSessionCommands = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Shell commands executed just before XFCE is started.
+        '';
+      };
     };
 
   };
@@ -45,6 +53,8 @@ in
         bgSupport = true;
         start =
           ''
+            ${cfg.extraSessionCommands}
+
             # Set GTK_PATH so that GTK+ can find the theme engines.
             export GTK_PATH="${config.system.path}/lib/gtk-2.0:${config.system.path}/lib/gtk-3.0"
 
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index aea0a8986786..cfe9439b688c 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -15,12 +15,21 @@ let
         If left at the default value, $HOME/.i3/config will be used.
       '';
     };
+    extraSessionCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Shell commands executed just before i3 is started.
+      '';
+    };
   };
 
   i3config = name: pkg: cfg: {
     services.xserver.windowManager.session = [{
       inherit name;
       start = ''
+        ${cfg.extraSessionCommands}
+
         ${pkg}/bin/i3 ${optionalString (cfg.configFile != null)
           "-c \"${cfg.configFile}\""
         } &
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 076bbca850d9..3d8f29c80f95 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -669,6 +669,7 @@ in
 
       "systemd/logind.conf".text = ''
         [Login]
+        KillUserProcesses=no
         ${config.services.logind.extraConfig}
       '';
 
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index dd351306cb63..cf8232c36154 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -111,15 +111,17 @@ in
 
     fileSystems = mkOption {
       default = {};
-      example = {
-        "/".device = "/dev/hda1";
-        "/data" = {
-          device = "/dev/hda2";
-          fsType = "ext3";
-          options = [ "data=journal" ];
-        };
-        "/bigdisk".label = "bigdisk";
-      };
+      example = literalExample ''
+        {
+          "/".device = "/dev/hda1";
+          "/data" = {
+            device = "/dev/hda2";
+            fsType = "ext3";
+            options = [ "data=journal" ];
+          };
+          "/bigdisk".label = "bigdisk";
+        }
+      '';
       type = types.loaOf types.optionSet;
       options = [ fileSystemOpts ];
       description = ''
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 845f14352f3d..9d76b890872a 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -47,7 +47,7 @@ in
         # TODO(wkennington): Add lvm2 and thin-provisioning-tools
         path = with pkgs; [ acl rsync gnutar xz btrfs-progs ];
 
-        serviceConfig.ExecStart = "@${pkgs.lxd}/bin/lxd lxd --syslog --group lxd";
+        serviceConfig.ExecStart = "@${pkgs.lxd.bin}/bin/lxd lxd --syslog --group lxd";
         serviceConfig.Type = "simple";
         serviceConfig.KillMode = "process"; # when stopping, leave the containers alone
       };