about summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/backup/crashplan.nix2
-rw-r--r--nixos/modules/services/backup/tarsnap.nix156
-rw-r--r--nixos/modules/services/computing/boinc/client.nix88
-rw-r--r--nixos/modules/services/continuous-integration/gocd-agent/default.nix1
-rw-r--r--nixos/modules/services/continuous-integration/gocd-server/default.nix1
-rw-r--r--nixos/modules/services/databases/riak-cs.nix202
-rw-r--r--nixos/modules/services/databases/riak.nix14
-rw-r--r--nixos/modules/services/databases/stanchion.nix212
-rw-r--r--nixos/modules/services/hardware/sane.nix111
-rw-r--r--nixos/modules/services/misc/dictd.nix3
-rw-r--r--nixos/modules/services/misc/disnix.nix1
-rw-r--r--nixos/modules/services/misc/gitlab.nix3
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix4
-rw-r--r--nixos/modules/services/misc/parsoid.nix37
-rw-r--r--nixos/modules/services/monitoring/collectd.nix2
-rw-r--r--nixos/modules/services/network-filesystems/tahoe.nix52
-rw-r--r--nixos/modules/services/networking/cjdns.nix51
-rw-r--r--nixos/modules/services/networking/dante.nix61
-rw-r--r--nixos/modules/services/networking/flannel.nix1
-rw-r--r--nixos/modules/services/networking/hostapd.nix5
-rw-r--r--nixos/modules/services/networking/nntp-proxy.nix4
-rw-r--r--nixos/modules/services/networking/nsd.nix4
-rw-r--r--nixos/modules/services/networking/quassel.nix3
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix2
-rw-r--r--nixos/modules/services/search/hound.nix2
-rw-r--r--nixos/modules/services/security/clamav.nix95
-rw-r--r--nixos/modules/services/torrent/opentracker.nix1
-rw-r--r--nixos/modules/services/web-apps/quassel-webserver.nix2
-rw-r--r--nixos/modules/services/x11/compton.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/kde5.nix24
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix8
-rw-r--r--nixos/modules/services/x11/window-managers/bspwm-unstable.nix48
-rw-r--r--nixos/modules/services/x11/window-managers/bspwm.nix83
-rw-r--r--nixos/modules/services/x11/window-managers/default.nix1
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix50
35 files changed, 1036 insertions, 299 deletions
diff --git a/nixos/modules/services/backup/crashplan.nix b/nixos/modules/services/backup/crashplan.nix
index 38cf8eb72fb8..d0af2e416b63 100644
--- a/nixos/modules/services/backup/crashplan.nix
+++ b/nixos/modules/services/backup/crashplan.nix
@@ -49,7 +49,7 @@ with lib;
         ensureDir ${crashplan.vardir}/backupArchives 700
         ensureDir ${crashplan.vardir}/log 777
         cp -avn ${crashplan}/conf.template/* ${crashplan.vardir}/conf
-        for x in app.asar bin EULA.txt install.vars lang lib libjniwrap64.so libjniwrap.so libjtux64.so libjtux.so libmd564.so libmd5.so share skin upgrade; do
+        for x in app.asar bin install.vars lang lib libc42archive64.so libc52archive.so libjniwrap64.so libjniwrap.so libjtux64.so libjtux.so libleveldb64.so libleveldb.so libmd564.so libmd5.so share skin upgrade; do
           rm -f ${crashplan.vardir}/$x;
           ln -sf ${crashplan}/$x ${crashplan.vardir}/$x;
         done
diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix
index 24892a2a59a1..67112343c335 100644
--- a/nixos/modules/services/backup/tarsnap.nix
+++ b/nixos/modules/services/backup/tarsnap.nix
@@ -1,25 +1,25 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
 let
-  cfg = config.services.tarsnap;
+  gcfg = config.services.tarsnap;
 
   configFile = name: cfg: ''
-    cachedir ${config.services.tarsnap.cachedir}/${name}
-    keyfile  ${cfg.keyfile}
+    keyfile ${cfg.keyfile}
+    ${optionalString (cfg.cachedir != null) "cachedir ${cfg.cachedir}"}
     ${optionalString cfg.nodump "nodump"}
     ${optionalString cfg.printStats "print-stats"}
     ${optionalString cfg.printStats "humanize-numbers"}
     ${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)}
     ${optionalString cfg.aggressiveNetworking "aggressive-networking"}
-    ${concatStringsSep "\n" (map (v: "exclude "+v) cfg.excludes)}
-    ${concatStringsSep "\n" (map (v: "include "+v) cfg.includes)}
+    ${concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)}
+    ${concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)}
     ${optionalString cfg.lowmem "lowmem"}
     ${optionalString cfg.verylowmem "verylowmem"}
-    ${optionalString (cfg.maxbw != null) ("maxbw "+toString cfg.maxbw)}
-    ${optionalString (cfg.maxbwRateUp != null) ("maxbw-rate-up "+toString cfg.maxbwRateUp)}
-    ${optionalString (cfg.maxbwRateDown != null) ("maxbw-rate-down "+toString cfg.maxbwRateDown)}
+    ${optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"}
+    ${optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"}
+    ${optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"}
   '';
 in
 {
@@ -60,34 +60,13 @@ in
         '';
       };
 
-      cachedir = mkOption {
-        type    = types.nullOr types.path;
-        default = "/var/cache/tarsnap";
-        description = ''
-          The cache allows tarsnap to identify previously stored data
-          blocks, reducing archival time and bandwidth usage.
-
-          Should the cache become desynchronized or corrupted, tarsnap
-          will refuse to run until you manually rebuild the cache with
-          <command>tarsnap --fsck</command>.
-
-          Note that each individual archive (specified below) has its own cache
-          directory specified under <literal>cachedir</literal>; this is because
-          tarsnap locks the cache during backups, meaning multiple services
-          archives cannot be backed up concurrently or overlap with a shared
-          cache.
-
-          Set to <literal>null</literal> to disable caching.
-        '';
-      };
-
       archives = mkOption {
-        type = types.attrsOf (types.submodule (
+        type = types.attrsOf (types.submodule ({ config, ... }:
           {
             options = {
               keyfile = mkOption {
                 type = types.str;
-                default = config.services.tarsnap.keyfile;
+                default = gcfg.keyfile;
                 description = ''
                   Set a specific keyfile for this archive. This defaults to
                   <literal>"/root/tarsnap.key"</literal> if left unspecified.
@@ -107,6 +86,21 @@ in
                 '';
               };
 
+              cachedir = mkOption {
+                type = types.nullOr types.path;
+                default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}";
+                description = ''
+                  The cache allows tarsnap to identify previously stored data
+                  blocks, reducing archival time and bandwidth usage.
+
+                  Should the cache become desynchronized or corrupted, tarsnap
+                  will refuse to run until you manually rebuild the cache with
+                  <command>tarsnap --fsck</command>.
+
+                  Set to <literal>null</literal> to disable caching.
+                '';
+              };
+
               nodump = mkOption {
                 type = types.bool;
                 default = true;
@@ -249,7 +243,7 @@ in
               };
 
             gamedata =
-              { directories = [ "/var/lib/minecraft "];
+              { directories = [ "/var/lib/minecraft" ];
                 period      = "*:30";
               };
           }
@@ -262,8 +256,8 @@ in
           archive names are suffixed by a 1 second resolution timestamp.
 
           For each member of the set is created a timer which triggers the
-          instanced <literal>tarsnap@</literal> service unit. You may use
-          <command>systemctl start tarsnap@archive-name</command> to
+          instanced <literal>tarsnap-archive-name</literal> service unit. You may use
+          <command>systemctl start tarsnap-archive-name</command> to
           manually trigger creation of <literal>archive-name</literal> at
           any time.
         '';
@@ -271,63 +265,73 @@ in
     };
   };
 
-  config = mkIf cfg.enable {
+  config = mkIf gcfg.enable {
     assertions =
       (mapAttrsToList (name: cfg:
         { assertion = cfg.directories != [];
           message = "Must specify paths for tarsnap to back up";
-        }) cfg.archives) ++
+        }) gcfg.archives) ++
       (mapAttrsToList (name: cfg:
         { assertion = !(cfg.lowmem && cfg.verylowmem);
           message = "You cannot set both lowmem and verylowmem";
-        }) cfg.archives);
-
-    systemd.services."tarsnap@" = {
-      description = "Tarsnap archive '%i'";
-      requires    = [ "network-online.target" ];
-      after       = [ "network-online.target" ];
-
-      path = [ pkgs.iputils pkgs.tarsnap pkgs.coreutils ];
-
-      # In order for the persistent tarsnap timer to work reliably, we have to
-      # make sure that the tarsnap server is reachable after systemd starts up
-      # the service - therefore we sleep in a loop until we can ping the
-      # endpoint.
-      preStart = "while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done";
-      scriptArgs = "%i";
-      script = ''
-        mkdir -p -m 0755 ${dirOf cfg.cachedir}
-        mkdir -p -m 0700 ${cfg.cachedir}
-        chown root:root ${cfg.cachedir}
-        chmod 0700 ${cfg.cachedir}
-        mkdir -p -m 0700 ${cfg.cachedir}/$1
-        DIRS=`cat /etc/tarsnap/$1.dirs`
-        exec tarsnap --configfile /etc/tarsnap/$1.conf -c -f $1-$(date +"%Y%m%d%H%M%S") $DIRS
-      '';
-
-      serviceConfig = {
-        IOSchedulingClass = "idle";
-        NoNewPrivileges = "true";
-        CapabilityBoundingSet = "CAP_DAC_READ_SEARCH";
-        PermissionsStartOnly = "true";
-      };
-    };
+        }) gcfg.archives);
+
+    systemd.services =
+      mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" {
+        description = "Tarsnap archive '${name}'";
+        requires    = [ "network-online.target" ];
+        after       = [ "network-online.target" ];
+
+        path = [ pkgs.iputils pkgs.tarsnap pkgs.utillinux ];
+
+        # In order for the persistent tarsnap timer to work reliably, we have to
+        # make sure that the tarsnap server is reachable after systemd starts up
+        # the service - therefore we sleep in a loop until we can ping the
+        # endpoint.
+        preStart = ''
+          while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
+        '';
+
+        script =
+          let run = ''tarsnap --configfile "/etc/tarsnap/${name}.conf" -c -f "${name}-$(date +"%Y%m%d%H%M%S")" ${concatStringsSep " " cfg.directories}'';
+          in if (cfg.cachedir != null) then ''
+            mkdir -p ${cfg.cachedir}
+            chmod 0700 ${cfg.cachedir}
+
+            ( flock 9
+              if [ ! -e ${cfg.cachedir}/firstrun ]; then
+                ( flock 10
+                  flock -u 9
+                  tarsnap --configfile "/etc/tarsnap/${name}.conf" --fsck
+                  flock 9
+                ) 10>${cfg.cachedir}/firstrun
+              fi
+            ) 9>${cfg.cachedir}/lockf
+
+             exec flock ${cfg.cachedir}/firstrun ${run}
+          '' else "exec ${run}";
+
+        serviceConfig = {
+          Type = "oneshot";
+          IOSchedulingClass = "idle";
+          NoNewPrivileges = "true";
+          CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
+          PermissionsStartOnly = "true";
+        };
+      }) gcfg.archives;
 
     # Note: the timer must be Persistent=true, so that systemd will start it even
     # if e.g. your laptop was asleep while the latest interval occurred.
-    systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap@${name}"
+    systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}"
       { timerConfig.OnCalendar = cfg.period;
         timerConfig.Persistent = "true";
         wantedBy = [ "timers.target" ];
-      }) cfg.archives;
+      }) gcfg.archives;
 
     environment.etc =
-      (mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
+      mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
         { text = configFile name cfg;
-        }) cfg.archives) //
-      (mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.dirs"
-        { text = concatStringsSep " " cfg.directories;
-        }) cfg.archives);
+        }) gcfg.archives;
 
     environment.systemPackages = [ pkgs.tarsnap ];
   };
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
new file mode 100644
index 000000000000..5e73638913de
--- /dev/null
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -0,0 +1,88 @@
+{config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.boinc;
+  allowRemoteGuiRpcFlag = optionalString cfg.allowRemoteGuiRpc "--allow_remote_gui_rpc";
+
+in
+  {
+    options.services.boinc = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description = ''
+          Whether to enable the BOINC distributed computing client. If this
+          option is set to true, the boinc_client daemon will be run as a
+          background service. The boinccmd command can be used to control the
+          daemon.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.boinc;
+        defaultText = "pkgs.boinc";
+        description = ''
+          Which BOINC package to use.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/boinc";
+        description = ''
+          The directory in which to store BOINC's configuration and data files.
+        '';
+      };
+
+      allowRemoteGuiRpc = mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description = ''
+          If set to true, any remote host can connect to and control this BOINC
+          client (subject to password authentication). If instead set to false,
+          only the hosts listed in <varname>dataDir</varname>/remote_hosts.cfg will be allowed to
+          connect.
+
+          See also: <ulink url="http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access"/>
+        '';
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = [cfg.package];
+
+      users.users.boinc = {
+        createHome = false;
+        description = "BOINC Client";
+        home = cfg.dataDir;
+        isSystemUser = true;
+      };
+
+      systemd.services.boinc = {
+        description = "BOINC Client";
+        after = ["network.target" "local-fs.target"];
+        wantedBy = ["multi-user.target"];
+        preStart = ''
+          mkdir -p ${cfg.dataDir}
+          chown boinc ${cfg.dataDir}
+        '';
+        script = ''
+          ${cfg.package}/bin/boinc_client --dir ${cfg.dataDir} --redirectio ${allowRemoteGuiRpcFlag}
+        '';
+        serviceConfig = {
+          PermissionsStartOnly = true; # preStart must be run as root
+          User = "boinc";
+          Nice = 10;
+        };
+      };
+    };
+
+    meta = {
+      maintainers = with lib.maintainers; [kierdavis];
+    };
+  }
diff --git a/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
index d60b55e83d11..05adb18fbe91 100644
--- a/nixos/modules/services/continuous-integration/gocd-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -37,6 +37,7 @@ in {
 
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
+        defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
         description = ''
           Packages to add to PATH for the Go.CD agent process.
diff --git a/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixos/modules/services/continuous-integration/gocd-server/default.nix
index 4bb792055d25..07e00f17f1e8 100644
--- a/nixos/modules/services/continuous-integration/gocd-server/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -68,6 +68,7 @@ in {
 
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
+        defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
         description = ''
           Packages to add to PATH for the Go.CD server's process.
diff --git a/nixos/modules/services/databases/riak-cs.nix b/nixos/modules/services/databases/riak-cs.nix
new file mode 100644
index 000000000000..198efc29222a
--- /dev/null
+++ b/nixos/modules/services/databases/riak-cs.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.riak-cs;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.riak-cs = {
+
+      enable = mkEnableOption "riak-cs";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.riak-cs;
+        defaultText = "pkgs.riak-cs";
+        example = literalExample "pkgs.riak-cs";
+        description = ''
+          Riak package to use.
+        '';
+      };
+
+      nodeName = mkOption {
+        type = types.str;
+        default = "riak-cs@127.0.0.1";
+        description = ''
+          Name of the Erlang node.
+        '';
+      };
+      
+      anonymousUserCreation = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Anonymous user creation.
+        '';
+      };
+
+      riakHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8087";
+        description = ''
+          Name of riak hosting service.
+        '';
+      };
+
+      listener = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8080";
+        description = ''
+          Name of Riak CS listening service.
+        '';
+      };
+
+      stanchionHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8085";
+        description = ''
+          Name of stanchion hosting service.
+        '';
+      };
+
+      stanchionSsl = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Tell stanchion to use SSL.
+        '';
+      };
+
+      distributedCookie = mkOption {
+        type = types.str;
+        default = "riak";
+        description = ''
+          Cookie for distributed node communication.  All nodes in the
+          same cluster should use the same cookie or they will not be able to
+          communicate.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/riak-cs";
+        description = ''
+          Data directory for Riak CS.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/riak-cs";
+        description = ''
+          Log directory for Riak CS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>riak-cs.conf</filename>.
+        '';
+      };
+
+      extraAdvancedConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>advanced.config</filename>.
+        '';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+    environment.etc."riak-cs/riak-cs.conf".text = ''
+      nodename = ${cfg.nodeName}
+      distributed_cookie = ${cfg.distributedCookie}
+
+      platform_log_dir = ${cfg.logDir}
+
+      riak_host = ${cfg.riakHost}
+      listener = ${cfg.listener}
+      stanchion_host = ${cfg.stanchionHost}
+
+      anonymous_user_creation = ${if cfg.anonymousUserCreation then "on" else "off"}
+
+      ${cfg.extraConfig}
+    '';
+
+    environment.etc."riak-cs/advanced.config".text = ''
+      ${cfg.extraAdvancedConfig}
+    '';
+
+    users.extraUsers.riak-cs = {
+      name = "riak-cs";
+      uid = config.ids.uids.riak-cs;
+      group = "riak";
+      description = "Riak CS server user";
+    };
+
+  systemd.services.riak-cs = {
+      description = "Riak CS Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [
+        pkgs.utillinux # for `logger`
+        pkgs.bash
+      ];
+
+      environment.HOME = "${cfg.dataDir}";
+      environment.RIAK_CS_DATA_DIR = "${cfg.dataDir}";
+      environment.RIAK_CS_LOG_DIR = "${cfg.logDir}";
+      environment.RIAK_CS_ETC_DIR = "/etc/riak";
+
+      preStart = ''
+        if ! test -e ${cfg.logDir}; then
+          mkdir -m 0755 -p ${cfg.logDir}
+          chown -R riak-cs ${cfg.logDir}
+        fi
+
+        if ! test -e ${cfg.dataDir}; then
+          mkdir -m 0700 -p ${cfg.dataDir}
+          chown -R riak-cs ${cfg.dataDir}
+        fi
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/riak-cs console";
+        ExecStop = "${cfg.package}/bin/riak-cs stop";
+        StandardInput = "tty";
+        User = "riak-cs";
+        Group = "riak-cs";
+        PermissionsStartOnly = true;
+        # Give Riak a decent amount of time to clean up.
+        TimeoutStopSec = 120;
+        LimitNOFILE = 65536;
+      };
+
+      unitConfig.RequiresMountsFor = [
+        "${cfg.dataDir}"
+        "${cfg.logDir}"
+        "/etc/riak"
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/services/databases/riak.nix b/nixos/modules/services/databases/riak.nix
index 4477904f78c6..e0ebf164aef0 100644
--- a/nixos/modules/services/databases/riak.nix
+++ b/nixos/modules/services/databases/riak.nix
@@ -20,6 +20,8 @@ in
 
       package = mkOption {
         type = types.package;
+        default = pkgs.riak;
+        defaultText = "pkgs.riak";
         example = literalExample "pkgs.riak";
         description = ''
           Riak package to use.
@@ -68,6 +70,14 @@ in
         '';
       };
 
+      extraAdvancedConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>advanced.config</filename>.
+        '';
+      };
+
     };
 
   };
@@ -88,6 +98,10 @@ in
       ${cfg.extraConfig}
     '';
 
+    environment.etc."riak/advanced.config".text = ''
+      ${cfg.extraAdvancedConfig}
+    '';
+
     users.extraUsers.riak = {
       name = "riak";
       uid = config.ids.uids.riak;
diff --git a/nixos/modules/services/databases/stanchion.nix b/nixos/modules/services/databases/stanchion.nix
new file mode 100644
index 000000000000..f2dbb78b5c4b
--- /dev/null
+++ b/nixos/modules/services/databases/stanchion.nix
@@ -0,0 +1,212 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.stanchion;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.stanchion = {
+
+      enable = mkEnableOption "stanchion";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.stanchion;
+        defaultText = "pkgs.stanchion";
+        example = literalExample "pkgs.stanchion";
+        description = ''
+          Stanchion package to use.
+        '';
+      };
+
+      nodeName = mkOption {
+        type = types.str;
+        default = "stanchion@127.0.0.1";
+        description = ''
+          Name of the Erlang node.
+        '';
+      };
+
+      adminKey = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Name of admin user.
+        '';
+      };
+
+      adminSecret = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Name of admin secret
+        '';
+      };
+
+      riakHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8087";
+        description = ''
+          Name of riak hosting service.
+        '';
+      };
+
+      listener = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8085";
+        description = ''
+          Name of Riak CS listening service.
+        '';
+      };
+
+      stanchionHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8085";
+        description = ''
+          Name of stanchion hosting service.
+        '';
+      };
+
+      stanchionSsl = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Tell stanchion to use SSL.
+        '';
+      };
+
+      distributedCookie = mkOption {
+        type = types.str;
+        default = "riak";
+        description = ''
+          Cookie for distributed node communication.  All nodes in the
+          same cluster should use the same cookie or they will not be able to
+          communicate.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/stanchion";
+        description = ''
+          Data directory for Stanchion.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/stanchion";
+        description = ''
+          Log directory for Stanchino.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>stanchion.conf</filename>.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    environment.etc."stanchion/advanced.config".text = ''
+      [{stanchion, []}].
+    '';
+
+    environment.etc."stanchion/stanchion.conf".text = ''
+      listener = ${cfg.listener}
+
+      riak_host = ${cfg.riakHost}
+
+      ${optionalString (cfg.adminKey == "") "#"} admin.key=${optionalString (cfg.adminKey != "") cfg.adminKey}
+      ${optionalString (cfg.adminSecret == "") "#"} admin.secret=${optionalString (cfg.adminSecret != "") cfg.adminSecret}
+
+      platform_bin_dir = ${pkgs.stanchion}/bin
+      platform_data_dir = ${cfg.dataDir}
+      platform_etc_dir = /etc/stanchion
+      platform_lib_dir = ${pkgs.stanchion}/lib
+      platform_log_dir = ${cfg.logDir}
+
+      nodename = ${cfg.nodeName}
+
+      distributed_cookie = ${cfg.distributedCookie}
+
+      stanchion_ssl=${if cfg.stanchionSsl then "on" else "off"}
+
+      ${cfg.extraConfig}
+    '';
+
+    users.extraUsers.stanchion = {
+      name = "stanchion";
+      uid = config.ids.uids.stanchion;
+      group = "stanchion";
+      description = "Stanchion server user";
+    };
+
+    users.extraGroups.stanchion.gid = config.ids.gids.stanchion;
+
+    systemd.services.stanchion = {
+      description = "Stanchion Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [
+        pkgs.utillinux # for `logger`
+        pkgs.bash
+      ];
+
+      environment.HOME = "${cfg.dataDir}";
+      environment.STANCHION_DATA_DIR = "${cfg.dataDir}";
+      environment.STANCHION_LOG_DIR = "${cfg.logDir}";
+      environment.STANCHION_ETC_DIR = "/etc/stanchion";
+
+      preStart = ''
+        if ! test -e ${cfg.logDir}; then
+          mkdir -m 0755 -p ${cfg.logDir}
+          chown -R stanchion:stanchion ${cfg.logDir}
+        fi
+
+        if ! test -e ${cfg.dataDir}; then
+          mkdir -m 0700 -p ${cfg.dataDir}
+          chown -R stanchion:stanchion ${cfg.dataDir}
+        fi
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/stanchion console";
+        ExecStop = "${cfg.package}/bin/stanchion stop";
+        StandardInput = "tty";
+        User = "stanchion";
+        Group = "stanchion";
+        PermissionsStartOnly = true;
+        # Give Stanchion a decent amount of time to clean up.
+        TimeoutStopSec = 120;
+        LimitNOFILE = 65536;
+      };
+
+      unitConfig.RequiresMountsFor = [
+        "${cfg.dataDir}"
+        "${cfg.logDir}"
+        "/etc/stanchion"
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index a34037403123..8ddb9ef9c53b 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/nixos/modules/services/hardware/sane.nix
@@ -7,9 +7,35 @@ let
   pkg = if config.hardware.sane.snapshot
     then pkgs.sane-backends-git
     else pkgs.sane-backends;
-  backends = [ pkg ] ++ config.hardware.sane.extraBackends;
+
+  sanedConf = pkgs.writeTextFile {
+    name = "saned.conf";
+    destination = "/etc/sane.d/saned.conf";
+    text = ''
+      localhost
+      ${config.services.saned.extraConfig}
+    '';
+  };
+
+  netConf = pkgs.writeTextFile {
+    name = "net.conf";
+    destination = "/etc/sane.d/net.conf";
+    text = ''
+      ${lib.optionalString config.services.saned.enable "localhost"}
+      ${config.hardware.sane.netConf}
+    '';
+  };
+
+  env = {
+    SANE_CONFIG_DIR = config.hardware.sane.configDir;
+    LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
+  };
+
+  backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends;
   saneConfig = pkgs.mkSaneConfig { paths = backends; };
 
+  enabled = config.hardware.sane.enable || config.services.saned.enable;
+
 in
 
 {
@@ -51,27 +77,86 @@ in
 
     hardware.sane.configDir = mkOption {
       type = types.string;
+      internal = true;
       description = "The value of SANE_CONFIG_DIR.";
     };
 
-  };
+    hardware.sane.netConf = mkOption {
+      type = types.lines;
+      default = "";
+      example = "192.168.0.16";
+      description = ''
+        Network hosts that should be probed for remote scanners.
+      '';
+    };
 
+    services.saned.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable saned network daemon for remote connection to scanners.
 
-  ###### implementation
+        saned would be runned from <literal>scanner</literal> user; to allow
+        access to hardware that doesn't have <literal>scanner</literal> group
+        you should add needed groups to this user.
+      '';
+    };
 
-  config = mkIf config.hardware.sane.enable {
+    services.saned.extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = "192.168.0.0/24";
+      description = ''
+        Extra saned configuration lines.
+      '';
+    };
 
-    hardware.sane.configDir = mkDefault "${saneConfig}/etc/sane.d";
+  };
 
-    environment.systemPackages = backends;
-    environment.sessionVariables = {
-      SANE_CONFIG_DIR = config.hardware.sane.configDir;
-      LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
-    };
-    services.udev.packages = backends;
 
-    users.extraGroups."scanner".gid = config.ids.gids.scanner;
+  ###### implementation
 
-  };
+  config = mkMerge [
+    (mkIf enabled {
+      hardware.sane.configDir = mkDefault "${saneConfig}/etc/sane.d";
+
+      environment.systemPackages = backends;
+      environment.sessionVariables = env;
+      services.udev.packages = backends;
+
+      users.extraGroups."scanner".gid = config.ids.gids.scanner;
+    })
+
+    (mkIf config.services.saned.enable {
+      networking.firewall.connectionTrackingModules = [ "sane" ];
+
+      systemd.services."saned@" = {
+        description = "Scanner Service";
+        environment = mapAttrs (name: val: toString val) env;
+        serviceConfig = {
+          User = "scanner";
+          Group = "scanner";
+          ExecStart = "${pkg}/bin/saned";
+        };
+      };
+
+      systemd.sockets.saned = {
+        description = "saned incoming socket";
+        wantedBy = [ "sockets.target" ];
+        listenStreams = [ "0.0.0.0:6566" "[::]:6566" ];
+        socketConfig = {
+          # saned needs to distinguish between IPv4 and IPv6 to open matching data sockets.
+          BindIPv6Only = "ipv6-only";
+          Accept = true;
+          MaxConnections = 1;
+        };
+      };
+
+      users.extraUsers."scanner" = {
+        uid = config.ids.uids.scanner;
+        group = "scanner";
+      };
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 24dca15dd913..7e3b6431a133 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -25,7 +25,8 @@ in
       DBs = mkOption {
         type = types.listOf types.package;
         default = with pkgs.dictdDBs; [ wiktionary wordnet ];
-        example = [ pkgs.dictdDBs.nld2eng ];
+        defaultText = "with pkgs.dictdDBs; [ wiktionary wordnet ]";
+        example = literalExample "[ pkgs.dictdDBs.nld2eng ]";
         description = ''List of databases to make available.'';
       };
 
diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix
index e5a125ad3245..e96645c79c77 100644
--- a/nixos/modules/services/misc/disnix.nix
+++ b/nixos/modules/services/misc/disnix.nix
@@ -41,6 +41,7 @@ in
         type = types.path;
         description = "The Disnix package";
         default = pkgs.disnix;
+        defaultText = "pkgs.disnix";
       };
 
     };
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 3e4584c7a512..cb8fa901bbd2 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -164,18 +164,21 @@ in {
       packages.gitlab = mkOption {
         type = types.package;
         default = pkgs.gitlab;
+        defaultText = "pkgs.gitlab";
         description = "Reference to the gitlab package";
       };
 
       packages.gitlab-shell = mkOption {
         type = types.package;
         default = pkgs.gitlab-shell;
+        defaultText = "pkgs.gitlab-shell";
         description = "Reference to the gitlab-shell package";
       };
 
       packages.gitlab-workhorse = mkOption {
         type = types.package;
         default = pkgs.gitlab-workhorse;
+        defaultText = "pkgs.gitlab-workhorse";
         description = "Reference to the gitlab-workhorse package";
       };
 
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 333782d15bcb..e2bbd4b01aa1 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -172,8 +172,8 @@ in
             sshKey = "/root/.ssh/id_buildfarm";
             system = "x86_64-linux";
             maxJobs = 2;
-            supportedFeatures = "kvm";
-            mandatoryFeatures = "perf";
+            supportedFeatures = [ "kvm" ];
+            mandatoryFeatures = [ "perf" ];
           }
         ];
         description = ''
diff --git a/nixos/modules/services/misc/parsoid.nix b/nixos/modules/services/misc/parsoid.nix
index ab1b54068772..ae3f84333d2d 100644
--- a/nixos/modules/services/misc/parsoid.nix
+++ b/nixos/modules/services/misc/parsoid.nix
@@ -6,20 +6,21 @@ let
 
   cfg = config.services.parsoid;
 
-  conf = ''
-    exports.setup = function( parsoidConfig ) {
-      ${toString (mapAttrsToList (name: str: "parsoidConfig.setInterwiki('${name}', '${str}');") cfg.interwikis)}
-
-      parsoidConfig.serverInterface = "${cfg.interface}";
-      parsoidConfig.serverPort = ${toString cfg.port};
-
-      parsoidConfig.useSelser = true;
-
-      ${cfg.extraConfig}
-    };
-  '';
+  confTree = {
+    worker_heartbeat_timeout = 300000;
+    logging = { level = "info"; };
+    services = [{
+      module = "lib/index.js";
+      entrypoint = "apiServiceWorker";
+      conf = {
+        mwApis = map (x: if isAttrs x then x else { uri = x; }) cfg.wikis;
+        serverInterface = cfg.interface;
+        serverPort = cfg.port;
+      };
+    }];
+  };
 
-  confFile = builtins.toFile "localsettings.js" conf;
+  confFile = pkgs.writeText "config.yml" (builtins.toJSON (recursiveUpdate confTree cfg.extraConfig));
 
 in
 {
@@ -38,9 +39,9 @@ in
         '';
       };
 
-      interwikis = mkOption {
-        type = types.attrsOf types.str;
-        example = { localhost = "http://localhost/api.php"; };
+      wikis = mkOption {
+        type = types.listOf (types.either types.str types.attrs);
+        example = [ "http://localhost/api.php" ];
         description = ''
           Used MediaWiki API endpoints.
         '';
@@ -71,8 +72,8 @@ in
       };
 
       extraConfig = mkOption {
-        type = types.lines;
-        default = "";
+        type = types.attrs;
+        default = {};
         description = ''
           Extra configuration to add to parsoid configuration.
         '';
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 3c3d83c66ed0..01c6fb817669 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -9,7 +9,7 @@ let
     BaseDir "${cfg.dataDir}"
     PIDFile "${cfg.pidFile}"
     AutoLoadPlugin ${if cfg.autoLoadPlugin then "true" else "false"}
-    Hostname ${config.networking.hostName}
+    Hostname "${config.networking.hostName}"
 
     LoadPlugin syslog
     <Plugin "syslog">
diff --git a/nixos/modules/services/network-filesystems/tahoe.nix b/nixos/modules/services/network-filesystems/tahoe.nix
index f1846b963252..f91827c379de 100644
--- a/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixos/modules/services/network-filesystems/tahoe.nix
@@ -138,6 +138,45 @@ in
               '';
             };
             helper.enable = mkEnableOption "helper service";
+            sftpd.enable = mkEnableOption "SFTP service";
+            sftpd.port = mkOption {
+              default = null;
+              type = types.nullOr types.int;
+              description = ''
+                The port on which the SFTP server will listen.
+
+                This is the correct setting to tweak if you want Tahoe's SFTP
+                daemon to listen on a different port.
+              '';
+            };
+            sftpd.hostPublicKeyFile = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path to the SSH host public key.
+              '';
+            };
+            sftpd.hostPrivateKeyFile = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path to the SSH host private key.
+              '';
+            };
+            sftpd.accounts.file = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path to the accounts file.
+              '';
+            };
+            sftpd.accounts.url = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                URL of the accounts server.
+              '';
+            };
             package = mkOption {
               default = pkgs.tahoelafs;
               defaultText = "pkgs.tahoelafs";
@@ -256,6 +295,19 @@ in
 
                 [helper]
                 enabled = ${if settings.helper.enable then "true" else "false"}
+
+                [sftpd]
+                enabled = ${if settings.sftpd.enable then "true" else "false"}
+                ${optionalString (settings.sftpd.port != null)
+                  "port = ${toString settings.sftpd.port}"}
+                ${optionalString (settings.sftpd.hostPublicKeyFile != null)
+                  "host_pubkey_file = ${settings.sftpd.hostPublicKeyFile}"}
+                ${optionalString (settings.sftpd.hostPrivateKeyFile != null)
+                  "host_privkey_file = ${settings.sftpd.hostPrivateKeyFile}"}
+                ${optionalString (settings.sftpd.accounts.file != null)
+                  "accounts.file = ${settings.sftpd.accounts.file}"}
+                ${optionalString (settings.sftpd.accounts.url != null)
+                  "accounts.url = ${settings.sftpd.accounts.url}"}
               '';
             });
           # Actually require Tahoe, so that we will have it installed.
diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix
index 7e981183353d..f50dae2ab7be 100644
--- a/nixos/modules/services/networking/cjdns.nix
+++ b/nixos/modules/services/networking/cjdns.nix
@@ -19,30 +19,21 @@ let
         type = types.str;
         description = "Public key at the opposite end of the tunnel.";
       };
-      hostname = mkOption {
-        default = "";
-        example = "foobar.hype";
-        type = types.str;
-        description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
-      };
     };
   };
 
-  # Additional /etc/hosts entries for peers with an associated hostname
-  cjdnsExtraHosts = import (pkgs.runCommand "cjdns-hosts" {}
-    # Generate a builder that produces an output usable as a Nix string value
-    ''
-      exec >$out
-      echo \'\'
-      ${concatStringsSep "\n" (mapAttrsToList (k: v:
-          optionalString (v.hostname != "")
-            "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}")
-          (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))}
-      echo \'\'
-    '');
-
-  parseModules = x:
-    x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; };
+  # check for the required attributes, otherwise
+  # permit attributes not undefined here
+  checkPeers = x:
+    x // {
+      connectTo = mapAttrs
+        (name: value:
+          if !hasAttr "publicKey" value then abort "cjdns peer ${name} missing a publicKey" else
+          if !hasAttr "password"  value then abort "cjdns peer ${name} missing a password"  else
+          value
+        )
+      x.connectTo;
+    };
 
   # would be nice to  merge 'cfg' with a //,
   # but the json nesting is wacky.
@@ -53,8 +44,8 @@ let
     };
     authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords;
     interfaces = {
-      ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ];
-      UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ];
+      ETHInterface = if (cfg.ETHInterface.bind != "") then [ (checkPeers cfg.ETHInterface) ] else [ ];
+      UDPInterface = if (cfg.UDPInterface.bind != "") then [ (checkPeers cfg.UDPInterface) ] else [ ];
     };
 
     privateKey = "@CJDNS_PRIVATE_KEY@";
@@ -134,12 +125,12 @@ in
           '';
          };
         connectTo = mkOption {
-          type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
+          type = types.attrsOf (types.attrsOf types.str);
           default = { };
           example = {
             "192.168.1.1:27313" = {
-              hostname = "homer.hype";
-              password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+              user      = "foobar";
+              password  = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
               publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
             };
           };
@@ -179,12 +170,12 @@ in
         };
 
         connectTo = mkOption {
-          type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
+          type = types.attrsOf (types.attrsOf types.str);
           default = { };
           example = {
             "01:02:03:04:05:06" = {
-              hostname = "homer.hype";
-              password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+              user      = "foobar";
+              password  = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
               publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
             };
           };
@@ -254,8 +245,6 @@ in
       };
     };
 
-    networking.extraHosts = cjdnsExtraHosts;
-
     assertions = [
       { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null );
         message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
diff --git a/nixos/modules/services/networking/dante.nix b/nixos/modules/services/networking/dante.nix
new file mode 100644
index 000000000000..8f4e15223ab0
--- /dev/null
+++ b/nixos/modules/services/networking/dante.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.dante;
+  confFile = pkgs.writeText "dante-sockd.conf" ''
+    user.privileged: root
+    user.unprivileged: dante
+
+    ${cfg.config}
+  '';
+in
+
+{
+  meta = {
+    maintainers = with maintainers; [ arobyn ];
+  };
+
+  options = {
+    services.dante = {
+      enable = mkEnableOption "Dante SOCKS proxy";
+
+      config = mkOption {
+        default     = null;
+        type        = types.str;
+        description = ''
+          Contents of Dante's configuration file
+          NOTE: user.privileged/user.unprivileged are set by the service
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion   = cfg.config != null;
+        message     = "please provide Dante configuration file contents";
+      }
+    ];
+
+    users.users.dante = {
+      description   = "Dante SOCKS proxy daemon user";
+      isSystemUser  = true;
+      group         = "dante";
+    };
+    users.groups.dante = {};
+
+    systemd.services.dante = {
+      description   = "Dante SOCKS v4 and v5 compatible proxy server";
+      after         = [ "network.target" ];
+      wantedBy      = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type        = "simple";
+        ExecStart   = "${pkgs.dante}/bin/sockd -f ${confFile}";
+        ExecReload  = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart     = "always";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index 28b6c4f657dd..ca47a18bc1f6 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -20,6 +20,7 @@ in {
       description = "Package to use for flannel";
       type = types.package;
       default = pkgs.flannel.bin;
+      defaultText = "pkgs.flannel.bin";
     };
 
     publicIp = mkOption {
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 51f95af48029..fd4545e88e2d 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -86,7 +86,7 @@ in
 
       hwMode = mkOption {
         default = "g";
-        type = types.string;
+        type = types.enum [ "a" "b" "g" ];
         description = ''
           Operation mode.
           (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g).
@@ -152,9 +152,6 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = (cfg.hwMode == "a" || cfg.hwMode == "b" || cfg.hwMode == "g");
-        message = "hwMode must be a/b/g";
-      }
       { assertion = (cfg.channel >= 1 && cfg.channel <= 13);
         message = "channel must be between 1 and 13";
       }];
diff --git a/nixos/modules/services/networking/nntp-proxy.nix b/nixos/modules/services/networking/nntp-proxy.nix
index dca8ccac7627..7eebecb23b00 100644
--- a/nixos/modules/services/networking/nntp-proxy.nix
+++ b/nixos/modules/services/networking/nntp-proxy.nix
@@ -148,11 +148,11 @@ in
       };
 
       verbosity = mkOption {
-        type = types.str;
+        type = types.enum [ "error" "warning" "notice" "info" "debug" ];
         default = "info";
         example = "error";
         description = ''
-          Verbosity level (error, warning, notice, info, debug)
+          Verbosity level
         '';
       };
 
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index ccfd219620cf..481e267f6c38 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -345,12 +345,10 @@ let
       };
 
       rrlWhitelist = mkOption {
-        type = types.listOf types.str;
+        type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
         default = [];
         description = ''
           Whitelists the given rrl-types.
-          The RRL classification types are:  nxdomain,  error, referral, any,
-          rrsig, wildcard, nodata, dnskey, positive, all
         '';
       };
 
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index 3f0906fdb80d..edcc12170b20 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -26,10 +26,11 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.kde4.quasselDaemon;
+        defaultText = "pkgs.kde4.quasselDaemon";
         description = ''
           The package of the quassel daemon.
         '';
-        example = pkgs.quasselDaemon;
+        example = literalExample "pkgs.quasselDaemon";
       };
 
       interfaces = mkOption {
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 81941ce1cfb6..073391ffdbbc 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -228,8 +228,6 @@ in
 
   config = mkIf cfg.enable {
 
-    programs.ssh.setXAuthLocation = mkForce cfg.forwardX11;
-
     users.extraUsers.sshd =
       { isSystemUser = true;
         description = "SSH privilege separation user";
diff --git a/nixos/modules/services/search/hound.nix b/nixos/modules/services/search/hound.nix
index 1226cba682ec..a94a851e80ec 100644
--- a/nixos/modules/services/search/hound.nix
+++ b/nixos/modules/services/search/hound.nix
@@ -50,6 +50,8 @@ in {
 
       package = mkOption {
         default = pkgs.hound;
+        defaultText = "pkgs.hound";
+        type = types.package;
         description = ''
           Package for running hound.
         '';
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index e4e5c1253b77..b045e140546d 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -3,26 +3,37 @@ with lib;
 let
   clamavUser = "clamav";
   stateDir = "/var/lib/clamav";
-  runDir = "/var/run/clamav";
-  logDir = "/var/log/clamav";
+  runDir = "/run/clamav";
   clamavGroup = clamavUser;
   cfg = config.services.clamav;
+  pkg = pkgs.clamav;
+
   clamdConfigFile = pkgs.writeText "clamd.conf" ''
     DatabaseDirectory ${stateDir}
     LocalSocket ${runDir}/clamd.ctl
-    LogFile ${logDir}/clamav.log
     PidFile ${runDir}/clamd.pid
+    TemporaryDirectory /tmp
     User clamav
+    Foreground yes
 
     ${cfg.daemon.extraConfig}
   '';
-  pkg = pkgs.clamav.override { freshclamConf = cfg.updater.config; };
+
+  freshclamConfigFile = pkgs.writeText "freshclam.conf" ''
+    DatabaseDirectory ${stateDir}
+    Foreground yes
+    Checks ${toString cfg.updater.frequency}
+
+    ${cfg.updater.extraConfig}
+
+    DatabaseMirror database.clamav.net
+  '';
 in
 {
   options = {
     services.clamav = {
       daemon = {
-        enable = mkEnableOption "clamd daemon";
+        enable = mkEnableOption "ClamAV clamd daemon";
 
         extraConfig = mkOption {
           type = types.lines;
@@ -34,16 +45,27 @@ in
         };
       };
       updater = {
-        enable = mkEnableOption "freshclam updater";
+        enable = mkEnableOption "ClamAV freshclam updater";
 
         frequency = mkOption {
+          type = types.int;
           default = 12;
           description = ''
             Number of database checks per day.
           '';
         };
 
-        config = mkOption {
+        interval = mkOption {
+          type = types.str;
+          default = "hourly";
+          description = ''
+            How often freshclam is invoked. See systemd.time(7) for more
+            information about the format.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
           default = "";
           description = ''
             Extra configuration for freshclam. Contents will be added verbatim to the
@@ -68,50 +90,53 @@ in
       gid = config.ids.gids.clamav;
     };
 
-    services.clamav.updater.config = mkIf cfg.updater.enable ''
-      DatabaseDirectory ${stateDir}
-      Foreground yes
-      Checks ${toString cfg.updater.frequency}
-      DatabaseMirror database.clamav.net
-    '';
+    environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
+    environment.etc."clamav/clamd.conf".source = clamdConfigFile;
 
-    systemd.services.clamd = mkIf cfg.daemon.enable {
+    systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
       description = "ClamAV daemon (clamd)";
-      path = [ pkg ];
-      after = [ "network.target" "freshclam.service" ];
-      requires = [ "freshclam.service" ];
+      after = mkIf cfg.updater.enable [ "clamav-freshclam.service" ];
+      requires = mkIf cfg.updater.enable [ "clamav-freshclam.service" ];
       wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ clamdConfigFile ];
+
       preStart = ''
-        mkdir -m 0755 -p ${logDir}
         mkdir -m 0755 -p ${runDir}
-        chown ${clamavUser}:${clamavGroup} ${logDir}
         chown ${clamavUser}:${clamavGroup} ${runDir}
       '';
+
       serviceConfig = {
-        ExecStart = "${pkg}/bin/clamd --config-file=${clamdConfigFile}";
-        Type = "forking";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        Restart = "on-failure";
-        RestartSec = "10s";
-        StartLimitInterval = "1min";
+        ExecStart = "${pkg}/bin/clamd";
+        ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+        PrivateNetwork = "yes";
       };
     };
 
-    systemd.services.freshclam = mkIf cfg.updater.enable {
-      description = "ClamAV updater (freshclam)";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      path = [ pkg ];
+    systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
+      description = "Timer for ClamAV virus database updater (freshclam)";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.updater.interval;
+        Unit = "clamav-freshclam.service";
+      };
+    };
+
+    systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
+      description = "ClamAV virus database updater (freshclam)";
+      restartTriggers = [ freshclamConfigFile ];
+
       preStart = ''
         mkdir -m 0755 -p ${stateDir}
         chown ${clamavUser}:${clamavGroup} ${stateDir}
       '';
+
       serviceConfig = {
-        ExecStart = "${pkg}/bin/freshclam --daemon --config-file=${pkgs.writeText "freshclam.conf" cfg.updater.config}";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        Restart = "on-failure";
-        RestartSec = "10s";
-        StartLimitInterval = "1min";
+        Type = "oneshot";
+        ExecStart = "${pkg}/bin/freshclam";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
       };
     };
   };
diff --git a/nixos/modules/services/torrent/opentracker.nix b/nixos/modules/services/torrent/opentracker.nix
index d86b9fea2d79..74f443381d92 100644
--- a/nixos/modules/services/torrent/opentracker.nix
+++ b/nixos/modules/services/torrent/opentracker.nix
@@ -13,6 +13,7 @@ in {
         opentracker package to use
       '';
       default = pkgs.opentracker;
+      defaultText = "pkgs.opentracker";
     };
 
     extraOptions = mkOption {
diff --git a/nixos/modules/services/web-apps/quassel-webserver.nix b/nixos/modules/services/web-apps/quassel-webserver.nix
index 7de9480d4c46..d19e4bc58277 100644
--- a/nixos/modules/services/web-apps/quassel-webserver.nix
+++ b/nixos/modules/services/web-apps/quassel-webserver.nix
@@ -31,6 +31,8 @@ in {
       };
       pkg = mkOption {
         default = pkgs.quassel-webserver;
+        defaultText = "pkgs.quassel-webserver";
+        type = types.package;
         description = "The quassel-webserver package";
       };
       quasselCoreHost = mkOption {
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index bda4eec01026..7cbca1dcddfd 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -188,6 +188,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.compton;
+      defaultText = "pkgs.compton";
       example = literalExample "pkgs.compton";
       description = ''
         Compton derivation to use.
diff --git a/nixos/modules/services/x11/desktop-managers/kde5.nix b/nixos/modules/services/x11/desktop-managers/kde5.nix
index bc010d1ce1cf..9b51b92faa4d 100644
--- a/nixos/modules/services/x11/desktop-managers/kde5.nix
+++ b/nixos/modules/services/x11/desktop-managers/kde5.nix
@@ -22,6 +22,15 @@ in
         description = "Enable the Plasma 5 (KDE 5) desktop environment.";
       };
 
+      enableQt4Support = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable support for Qt 4-based applications. Particularly, install the
+          Qt 4 version of the Breeze theme and a default backend for Phonon.
+        '';
+      };
+
     };
 
   };
@@ -105,7 +114,7 @@ in
         kde5.sonnet
         kde5.threadweaver
 
-        kde5.breeze
+        kde5.breeze-qt5
         kde5.kactivitymanagerd
         kde5.kde-cli-tools
         kde5.kdecoration
@@ -141,13 +150,12 @@ in
         kde5.konsole
         kde5.print-manager
 
-        # Oxygen icons moved to KDE Frameworks 5.16 and later.
-        (kde5.oxygen-icons or kde5.oxygen-icons5)
+        # Install Breeze icons if available
+        (kde5.breeze-icons or kde5.oxygen-icons5 or kde5.oxygen-icons)
         pkgs.hicolor_icon_theme
 
-        kde5.kde-gtk-config
+        kde5.kde-gtk-config kde5.breeze-gtk
 
-        pkgs.phonon-backend-gstreamer
         pkgs.qt5.phonon-backend-gstreamer
       ]
 
@@ -155,15 +163,14 @@ in
       # If it is not available, Orion is very similar to Breeze.
       ++ lib.optional (!(lib.hasAttr "breeze-gtk" kde5)) pkgs.orion
 
-      # Install Breeze icons if available
-      ++ lib.optional (lib.hasAttr "breeze-icons" kde5) kde5.breeze-icons
-
       # Install activity manager if available
       ++ lib.optional (lib.hasAttr "kactivitymanagerd" kde5) kde5.kactivitymanagerd
 
       # frameworkintegration was split with plasma-integration in Plasma 5.6
       ++ lib.optional (lib.hasAttr "plasma-integration" kde5) kde5.plasma-integration
 
+      ++ lib.optionals cfg.enableQt4Support [ kde5.breeze-qt4 pkgs.phonon-backend-gstreamer ]
+
       # Optional hardware support features
       ++ lib.optional config.hardware.bluetooth.enable kde5.bluedevil
       ++ lib.optional config.networking.networkmanager.enable kde5.plasma-nm
@@ -217,7 +224,6 @@ in
         kde5.ecm # for the setup-hook
         kde5.plasma-workspace
         kde5.breeze-icons
-        (kde5.oxygen-icons or kde5.oxygen-icons5)
       ];
     };
 
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 36daf55a36a5..dda8d0f7629e 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -27,7 +27,6 @@ let
     ${cfg.stopScript}
   '';
 
-
   cfgFile = pkgs.writeText "sddm.conf" ''
     [General]
     HaltCommand=${pkgs.systemd}/bin/systemctl poweroff
@@ -47,7 +46,7 @@ let
     HideShells=/run/current-system/sw/bin/nologin
 
     [X11]
-    MinimumVT=${toString xcfg.tty}
+    MinimumVT=${toString (if xcfg.tty != null then xcfg.tty else 7)}
     ServerPath=${xserverWrapper}
     XephyrPath=${pkgs.xorg.xorgserver.out}/bin/Xephyr
     SessionCommand=${dmcfg.session.script}
@@ -254,5 +253,10 @@ in
 
     users.extraGroups.sddm.gid = config.ids.gids.sddm;
 
+    services.dbus.packages = [ sddm.unwrapped ];
+
+    # To enable user switching, allow sddm to allocate TTYs/displays dynamically.
+    services.xserver.tty = null;
+    services.xserver.display = null;
   };
 }
diff --git a/nixos/modules/services/x11/window-managers/bspwm-unstable.nix b/nixos/modules/services/x11/window-managers/bspwm-unstable.nix
deleted file mode 100644
index 3282e0d0851f..000000000000
--- a/nixos/modules/services/x11/window-managers/bspwm-unstable.nix
+++ /dev/null
@@ -1,48 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.xserver.windowManager.bspwm-unstable;
-in
-
-{
-  options = {
-    services.xserver.windowManager.bspwm-unstable = {
-        enable = mkEnableOption "bspwm-unstable";
-        startThroughSession = mkOption {
-            type = with types; bool;
-            default = false;
-            description = "
-                Start the window manager through the script defined in 
-                sessionScript. Defaults to the the bspwm-session script
-                provided by bspwm
-            ";
-        };
-        sessionScript = mkOption {
-            default = "${pkgs.bspwm-unstable}/bin/bspwm-session";
-            defaultText = "(pkgs.bspwm-unstable)/bin/bspwm-session";
-            description = "
-                The start-session script to use. Defaults to the
-                provided bspwm-session script from the bspwm package.
-
-                Does nothing unless `bspwm.startThroughSession` is enabled
-            ";
-        };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.xserver.windowManager.session = singleton {
-      name = "bspwm-unstable";
-      start = if cfg.startThroughSession
-        then cfg.sessionScript
-        else ''
-            export _JAVA_AWT_WM_NONREPARENTING=1
-            SXHKD_SHELL=/bin/sh ${pkgs.sxhkd-unstable}/bin/sxhkd -f 100 &
-            ${pkgs.bspwm-unstable}/bin/bspwm
-        '';
-    };
-    environment.systemPackages = [ pkgs.bspwm-unstable ];
-  };
-}
diff --git a/nixos/modules/services/x11/window-managers/bspwm.nix b/nixos/modules/services/x11/window-managers/bspwm.nix
index 03a1b7a72e88..6783ac3479e6 100644
--- a/nixos/modules/services/x11/window-managers/bspwm.nix
+++ b/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -9,40 +9,69 @@ in
 {
   options = {
     services.xserver.windowManager.bspwm = {
-        enable = mkEnableOption "bspwm";
-        startThroughSession = mkOption {
-            type = with types; bool;
-            default = false;
-            description = "
-                Start the window manager through the script defined in 
-                sessionScript. Defaults to the the bspwm-session script
-                provided by bspwm
-            ";
-        };
-        sessionScript = mkOption {
-            default = "${pkgs.bspwm}/bin/bspwm-session";
-            defaultText = "(pkgs.bspwm)/bin/bspwm-session";
-            description = "
-                The start-session script to use. Defaults to the
-                provided bspwm-session script from the bspwm package.
+      enable = mkEnableOption "bspwm";
+
+      package = mkOption {
+        type        = types.package;
+        default     = pkgs.bspwm;
+        defaultText = "pkgs.bspwm";
+        example     = "pkgs.bspwm-unstable";
+        description = ''
+          bspwm package to use.
+        '';
+      };
+      configFile = mkOption {
+        type        = with types; nullOr path;
+        example     = "${pkgs.bspwm}/share/doc/bspwm/examples/bspwmrc";
+        default     = null;
+        description = ''
+          Path to the bspwm configuration file.
+          If null, $HOME/.config/bspwm/bspwmrc will be used.
+        '';
+      };
 
-                Does nothing unless `bspwm.startThroughSession` is enabled
-            ";
+      sxhkd = {
+        package = mkOption {
+          type        = types.package;
+          default     = pkgs.sxhkd;
+          defaultText = "pkgs.sxhkd";
+          example     = "pkgs.sxhkd-unstable";
+          description = ''
+            sxhkd package to use.
+          '';
         };
+        configFile = mkOption {
+          type        = with types; nullOr path;
+          example     = "${pkgs.bspwm}/share/doc/bspwm/examples/sxhkdrc";
+          default     = null;
+          description = ''
+            Path to the sxhkd configuration file.
+            If null, $HOME/.config/sxhkd/sxhkdrc will be used.
+          '';
+        };
+      };
     };
   };
 
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = singleton {
-      name = "bspwm";
-      start = if cfg.startThroughSession
-        then cfg.sessionScript
-        else ''
-            export _JAVA_AWT_WM_NONREPARENTING=1
-            SXHKD_SHELL=/bin/sh ${pkgs.sxhkd}/bin/sxhkd -f 100 &
-            ${pkgs.bspwm}/bin/bspwm
-        '';
+      name  = "bspwm";
+      start = ''
+        export _JAVA_AWT_WM_NONREPARENTING=1
+        SXHKD_SHELL=/bin/sh ${cfg.sxhkd.package}/bin/sxhkd ${optionalString (cfg.sxhkd.configFile != null) "-c \"${cfg.sxhkd.configFile}\""} &
+        ${cfg.package}/bin/bspwm ${optionalString (cfg.configFile != null) "-c \"${cfg.configFile}\""}
+        waitPID=$!
+      '';
     };
-    environment.systemPackages = [ pkgs.bspwm ];
+    environment.systemPackages = [ cfg.package ];
   };
+
+  imports = [
+   (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm-unstable" "enable" ]
+     "Use services.xserver.windowManager.bspwm.enable and set services.xserver.windowManager.bspwm.package to pkgs.bspwm-unstable to use the unstable version of bspwm.")
+   (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm" "startThroughSession" ]
+     "bspwm package does not provide bspwm-session anymore.")
+   (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm" "sessionScript" ]
+     "bspwm package does not provide bspwm-session anymore.")
+  ];
 }
diff --git a/nixos/modules/services/x11/window-managers/default.nix b/nixos/modules/services/x11/window-managers/default.nix
index dabe2c26a72f..f005decfa33c 100644
--- a/nixos/modules/services/x11/window-managers/default.nix
+++ b/nixos/modules/services/x11/window-managers/default.nix
@@ -10,7 +10,6 @@ in
   imports = [
     ./afterstep.nix
     ./bspwm.nix
-    ./bspwm-unstable.nix
     ./compiz.nix
     ./dwm.nix
     ./exwm.nix
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index cfe9439b688c..f9c75e80db41 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -3,52 +3,58 @@
 with lib;
 
 let
-  wmCfg = config.services.xserver.windowManager;
+  cfg = config.services.xserver.windowManager.i3;
+in
+
+{
+  options.services.xserver.windowManager.i3 = {
+    enable = mkEnableOption "i3 window manager";
 
-  i3option = name: {
-    enable = mkEnableOption name;
     configFile = mkOption {
-      default = null;
-      type = types.nullOr types.path;
+      default     = null;
+      type        = with types; nullOr path;
       description = ''
         Path to the i3 configuration file.
         If left at the default value, $HOME/.i3/config will be used.
       '';
     };
+
     extraSessionCommands = mkOption {
-      default = "";
-      type = types.lines;
+      default     = "";
+      type        = types.lines;
       description = ''
         Shell commands executed just before i3 is started.
       '';
     };
+
+    package = mkOption {
+      type        = types.package;
+      default     = pkgs.i3;
+      defaultText = "pkgs.i3";
+      example     = "pkgs.i3-gaps";
+      description = ''
+        i3 package to use.
+      '';
+    };
   };
 
-  i3config = name: pkg: cfg: {
+  config = mkIf cfg.enable {
     services.xserver.windowManager.session = [{
-      inherit name;
+      name  = "i3";
       start = ''
         ${cfg.extraSessionCommands}
 
-        ${pkg}/bin/i3 ${optionalString (cfg.configFile != null)
+        ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
           "-c \"${cfg.configFile}\""
         } &
         waitPID=$!
       '';
     }];
-    environment.systemPackages = [ pkg ];
-  };
-
-in
-
-{
-  options.services.xserver.windowManager = {
-    i3 = i3option "i3";
-    i3-gaps = i3option "i3-gaps";
+    environment.systemPackages = [ cfg.package ];
   };
 
-  config = mkMerge [
-    (mkIf wmCfg.i3.enable (i3config "i3" pkgs.i3 wmCfg.i3))
-    (mkIf wmCfg.i3-gaps.enable (i3config "i3-gaps" pkgs.i3-gaps wmCfg.i3-gaps))
+  imports = [
+    (mkRemovedOptionModule [ "services" "xserver" "windowManager" "i3-gaps" "enable" ]
+      "Use services.xserver.windowManager.i3.enable and set services.xserver.windowManager.i3.package to pkgs.i3-gaps to use i3-gaps.")
   ];
 }