summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-1709.xml11
-rw-r--r--nixos/modules/config/networking.nix4
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/profiles/base.nix1
-rw-r--r--nixos/modules/programs/xonsh.nix2
-rw-r--r--nixos/modules/security/wrappers/default.nix5
-rw-r--r--nixos/modules/services/databases/influxdb.nix2
-rw-r--r--nixos/modules/services/databases/mysql.nix62
-rw-r--r--nixos/modules/services/mail/dovecot.nix13
-rw-r--r--nixos/modules/services/misc/gitlab.nix7
-rw-r--r--nixos/modules/services/misc/gogs.nix2
-rw-r--r--nixos/modules/services/misc/gollum.nix92
-rw-r--r--nixos/modules/services/misc/logkeys.nix23
-rw-r--r--nixos/modules/services/monitoring/prometheus/node-exporter.nix2
-rw-r--r--nixos/modules/services/network-filesystems/glusterfs.nix61
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix52
-rw-r--r--nixos/modules/services/networking/bind.nix9
-rw-r--r--nixos/modules/services/networking/coturn.nix3
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix8
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix2
-rw-r--r--nixos/modules/services/networking/matterbridge.nix96
-rw-r--r--nixos/modules/services/networking/namecoind.nix2
-rw-r--r--nixos/modules/services/networking/radicale.nix12
-rw-r--r--nixos/modules/services/networking/squid.nix169
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix13
-rw-r--r--nixos/modules/services/networking/tinc.nix6
-rw-r--r--nixos/modules/services/security/hologram-agent.nix2
-rw-r--r--nixos/modules/services/ttys/kmscon.nix1
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix27
-rw-r--r--nixos/modules/tasks/filesystems.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix7
-rw-r--r--nixos/release-combined.nix4
-rw-r--r--nixos/release.nix5
-rw-r--r--nixos/tests/dovecot.nix64
-rw-r--r--nixos/tests/grafana.nix25
-rw-r--r--nixos/tests/hardened.nix10
-rw-r--r--nixos/tests/initrd-network-ssh.nix74
-rw-r--r--nixos/tests/ipfs.nix6
-rw-r--r--nixos/tests/kernel-params.nix24
-rw-r--r--nixos/tests/misc.nix15
-rw-r--r--nixos/tests/networking.nix4
-rw-r--r--nixos/tests/radicale.nix90
-rw-r--r--nixos/tests/sysctl.nix25
-rw-r--r--nixos/tests/virtualbox.nix8
44 files changed, 919 insertions, 137 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1709.xml b/nixos/doc/manual/release-notes/rl-1709.xml
index a1e443818012..55b39209f0d5 100644
--- a/nixos/doc/manual/release-notes/rl-1709.xml
+++ b/nixos/doc/manual/release-notes/rl-1709.xml
@@ -107,7 +107,7 @@ rmdir /var/lib/ipfs/.ipfs
       The <literal>mysql</literal> default <literal>dataDir</literal> has changed from <literal>/var/mysql</literal> to <literal>/var/lib/mysql</literal>.
     </para>
     <para>
-      Radicale's default package has changed from 1.x to 2.x. Instructions to migrate can be found <link xlink:href="http://radicale.org/1to2/"> here </link>. It is also possible to use the newer version by setting the <literal>package</literal> to <literal>radicale2</literal>, which is done automatically when <literal>stateVersion</literal> is 17.09 or higher.
+            Radicale's default package has changed from 1.x to 2.x. Instructions to migrate can be found <link xlink:href="http://radicale.org/1to2/"> here </link>. It is also possible to use the newer version by setting the <literal>package</literal> to <literal>radicale2</literal>, which is done automatically when <literal>stateVersion</literal> is 17.09 or higher. The <literal>extraArgs</literal> option has been added to allow passing the data migration arguments specified in the instructions; see the <filename xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/radicale.nix">radicale.nix</filename> NixOS test for an example migration.
     </para>
   </listitem>
   <listitem>
@@ -255,6 +255,15 @@ rmdir /var/lib/ipfs/.ipfs
       See the overlays chapter of the Nixpkgs manual for more details.
     </para>
   </listitem>
+  <listitem>
+    <para>
+      <literal>sha256</literal> argument value of
+      <literal>dockerTools.pullImage</literal> expression must be
+      updated since the mechanism to download the image has been
+      changed. Skopeo is now used to pull the image instead of the
+      Docker daemon.
+    </para>
+  </listitem>
 
 </itemizedlist>
 
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 619f36cd5150..5fa91ec9cfbc 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -9,7 +9,9 @@ let
   cfg = config.networking;
   dnsmasqResolve = config.services.dnsmasq.enable &&
                    config.services.dnsmasq.resolveLocalQueries;
-  hasLocalResolver = config.services.bind.enable || dnsmasqResolve;
+  bindResolve =    config.services.bind.enable &&
+                   config.services.bind.resolveLocalQueries;
+  hasLocalResolver = bindResolve || dnsmasqResolve;
 
   resolvconfOptions = cfg.resolvconfOptions
     ++ optional cfg.dnsSingleRequest "single-request"
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 9ebac8852bb2..eb478f66d40f 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -301,10 +301,12 @@
   ./services/misc/gitlab.nix
   ./services/misc/gitolite.nix
   ./services/misc/gogs.nix
+  ./services/misc/gollum.nix
   ./services/misc/gpsd.nix
   #./services/misc/ihaskell.nix
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
+  ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
   ./services/misc/mantisbt.nix
   ./services/misc/mathics.nix
@@ -460,6 +462,7 @@
   ./services/networking/lldpd.nix
   ./services/networking/logmein-hamachi.nix
   ./services/networking/mailpile.nix
+  ./services/networking/matterbridge.nix
   ./services/networking/mjpg-streamer.nix
   ./services/networking/minidlna.nix
   ./services/networking/miniupnpd.nix
@@ -510,6 +513,7 @@
   ./services/networking/smokeping.nix
   ./services/networking/softether.nix
   ./services/networking/spiped.nix
+  ./services/networking/squid.nix
   ./services/networking/sslh.nix
   ./services/networking/ssh/lshd.nix
   ./services/networking/ssh/sshd.nix
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 687cd9d80d36..39b8553976eb 100644
--- a/nixos/modules/profiles/base.nix
+++ b/nixos/modules/profiles/base.nix
@@ -20,6 +20,7 @@
 
     # Some networking tools.
     pkgs.fuse
+    pkgs.fuse3
     pkgs.sshfs-fuse
     pkgs.socat
     pkgs.screen
diff --git a/nixos/modules/programs/xonsh.nix b/nixos/modules/programs/xonsh.nix
index c0be2d8884b3..49cc4906e038 100644
--- a/nixos/modules/programs/xonsh.nix
+++ b/nixos/modules/programs/xonsh.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         default = false;
         description = ''
-          Whether to configure xnosh as an interactive shell.
+          Whether to configure xonsh as an interactive shell.
         '';
         type = types.bool;
       };
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index a6dc8faaae98..1f64213accd4 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -155,7 +155,10 @@ in
   ###### implementation
   config = {
 
-    security.wrappers.fusermount.source = "${pkgs.fuse}/bin/fusermount";
+    security.wrappers = {
+      fusermount.source = "${pkgs.fuse}/bin/fusermount";
+      fusermount3.source = "${pkgs.fuse3}/bin/fusermount3";
+    };
 
     boot.specialFileSystems.${parentWrapperDir} = {
       fsType = "tmpfs";
diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix
index 9ffe9fdea2ce..eeab33309fda 100644
--- a/nixos/modules/services/databases/influxdb.nix
+++ b/nixos/modules/services/databases/influxdb.nix
@@ -171,7 +171,7 @@ in
         if [ "$(id -u)" = 0 ]; then chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}; fi
       '';
       postStart = mkBefore ''
-        until ${pkgs.curl.bin}/bin/curl -s -o /dev/null 'http://127.0.0.1${toString configOptions.http.bind-address}'/ping; do
+        until ${pkgs.curl.bin}/bin/curl -s -o /dev/null ${if configOptions.http.https-enabled then "-k https" else "http"}://127.0.0.1${toString configOptions.http.bind-address}/ping; do
           sleep 1;
         done
       '';
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 50766093307d..845e6d4c22ef 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -30,6 +30,10 @@ let
       master-password = ${cfg.replication.masterPassword}
       master-port = ${toString cfg.replication.masterPort}
     ''}
+    ${optionalString (cfg.ensureUsers != [])
+    ''
+      plugin-load-add = auth_socket.so
+    ''}
     ${cfg.extraOptions}
   '';
 
@@ -123,6 +127,46 @@ in
         description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
       };
 
+      ensureDatabases = mkOption {
+        default = [];
+        description = ''
+          Ensures that the specified databases exist.
+          This option will never delete existing databases, especially not when the value of this
+          option is changed. This means that databases created once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = [
+          "nextcloud"
+          "piwik"
+        ];
+      };
+
+      ensureUsers = mkOption {
+        default = [];
+        description = ''
+          Ensures that the specified users exist and have at least the ensured permissions.
+          The MySQL users will be identified using Unix socket authentication. This authenticates the Unix user with the
+          same name only, and that without the need for a password.
+          This option will never delete existing users or remove permissions, especially not when the value of this
+          option is changed. This means that users created and permissions assigned once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = [
+          {
+            name = "nextcloud";
+            ensurePermissions = {
+              "nextcloud.*" = "ALL PRIVILEGES";
+            };
+          }
+          {
+            name = "backup";
+            ensurePermissions = {
+              "*.*" = "SELECT, LOCK TABLES";
+            };
+          }
+        ];
+      };
+
       # FIXME: remove this option; it's a really bad idea.
       rootPassword = mkOption {
         default = null;
@@ -305,6 +349,24 @@ in
 
               rm /tmp/mysql_init
             fi
+
+            ${optionalString (cfg.ensureDatabases != []) ''
+              (
+              ${concatMapStrings (database: ''
+                echo "CREATE DATABASE IF NOT EXISTS ${database};"
+              '') cfg.ensureDatabases}
+              ) | ${mysql}/bin/mysql -u root -N
+            ''}
+
+            ${concatMapStrings (user:
+              ''
+                ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if mysql == pkgs.mariadb then "unix_socket" else "auth_socket"};"
+                  ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                  echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+                  '') user.ensurePermissions)}
+                ) | ${mysql}/bin/mysql -u root -N
+              '') cfg.ensureUsers}
+
           ''; # */
       };
 
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 135d3b277378..6057acc531a3 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -9,6 +9,8 @@ let
   baseDir = "/run/dovecot2";
   stateDir = "/var/lib/dovecot";
 
+  canCreateMailUserGroup = cfg.mailUser != null && cfg.mailGroup != null;
+
   dovecotConf = concatStrings [
     ''
       base_dir = ${baseDir}
@@ -314,17 +316,18 @@ in
            description = "Dovecot user";
            group = cfg.group;
          }
-      ++ optional cfg.createMailUser
-         { name = cfg.mailUser;
-           description = "Virtual Mail User";
+      ++ optional (cfg.createMailUser && cfg.mailUser != null)
+         ({ name = cfg.mailUser;
+            description = "Virtual Mail User";
+         } // optionalAttrs (cfg.mailGroup != null) {
            group = cfg.mailGroup;
-         };
+         });
 
     users.extraGroups = optional (cfg.group == "dovecot2")
       { name = "dovecot2";
         gid = config.ids.gids.dovecot2;
       }
-    ++ optional cfg.createMailUser
+    ++ optional (cfg.createMailUser && cfg.mailGroup != null)
       { name = cfg.mailGroup;
       };
 
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 412355fb35b5..d6c8ac547246 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -142,9 +142,9 @@ let
     GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     GITLAB_LOG_PATH = "${cfg.statePath}/log";
     GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}";
-    GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/home/config.yml";
+    GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml";
     GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret";
-    GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/home/hooks";
+    GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks";
     GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "gitlab-redis.yml" redisYml;
     prometheus_multiproc_dir = "/run/gitlab";
     RAILS_ENV = "production";
@@ -555,6 +555,7 @@ in {
         openssh
         nodejs
         procps
+        gnupg
       ];
       preStart = ''
         mkdir -p ${cfg.backupPath}
@@ -567,7 +568,7 @@ in {
         mkdir -p ${cfg.statePath}/tmp/pids
         mkdir -p ${cfg.statePath}/tmp/sockets
 
-        rm -rf ${cfg.statePath}/config ${cfg.statePath}/home/hooks
+        rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks
         mkdir -p ${cfg.statePath}/config
 
         tr -dc A-Za-z0-9 < /dev/urandom | head -c 32 > ${cfg.statePath}/config/gitlab_shell_secret
diff --git a/nixos/modules/services/misc/gogs.nix b/nixos/modules/services/misc/gogs.nix
index ad2e36d04d53..d6e827a53fdf 100644
--- a/nixos/modules/services/misc/gogs.nix
+++ b/nixos/modules/services/misc/gogs.nix
@@ -257,7 +257,7 @@ in
         in the Nix store. Use database.passwordFile instead.'';
 
     # Create database passwordFile default when password is configured.
-    services.gogs.database.passwordFile = mkIf (cfg.database.password != "")
+    services.gogs.database.passwordFile =
       (mkDefault (toString (pkgs.writeTextFile {
         name = "gogs-database-password";
         text = cfg.database.password;
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
new file mode 100644
index 000000000000..81fb024f9094
--- /dev/null
+++ b/nixos/modules/services/misc/gollum.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gollum;
+in
+
+{
+  options.services.gollum = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the Gollum service.";
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = "IP address on which the web server will listen.";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 4567;
+      description = "Port on which the web server will run.";
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Content of the configuration file";
+    };
+
+    branch = mkOption {
+      type = types.str;
+      default = "master";
+      example = "develop";
+      description = "Git branch to serve";
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var/lib/gollum";
+      description = "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.gollum = {
+      group = config.users.users.gollum.name;
+      description = "Gollum user";
+      createHome = false;
+    };
+
+    users.groups.gollum = { };
+
+    systemd.services.gollum = {
+      description = "Gollum wiki";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.git ];
+
+      preStart = let
+          userName = config.users.users.gollum.name;
+          groupName = config.users.groups.gollum.name;
+        in ''
+        # All of this is safe to be run on an existing repo
+        mkdir -p ${cfg.stateDir}
+        git init ${cfg.stateDir}
+        chmod 755 ${cfg.stateDir}
+        chown -R ${userName}:${groupName} ${cfg.stateDir}
+      '';
+
+      serviceConfig = {
+        User = config.users.extraUsers.gollum.name;
+        Group = config.users.extraGroups.gollum.name;
+        PermissionsStartOnly = true;
+        ExecStart = ''
+          ${pkgs.gollum}/bin/gollum \
+            --port ${toString cfg.port} \
+            --host ${cfg.address} \
+            --config ${builtins.toFile "gollum-config.rb" cfg.extraConfig} \
+            --ref ${cfg.branch} \
+            ${cfg.stateDir}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/logkeys.nix b/nixos/modules/services/misc/logkeys.nix
new file mode 100644
index 000000000000..6051c884465d
--- /dev/null
+++ b/nixos/modules/services/misc/logkeys.nix
@@ -0,0 +1,23 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.logkeys;
+in {
+  options.services.logkeys = {
+    enable = mkEnableOption "logkeys service";
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.logkeys = {
+      description = "LogKeys Keylogger Daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.logkeys}/bin/logkeys -s";
+        ExecStop = "${pkgs.logkeys}/bin/logkeys -k";
+        Type = "forking";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/node-exporter.nix b/nixos/modules/services/monitoring/prometheus/node-exporter.nix
index 0cf0b85afb57..b5b852438d77 100644
--- a/nixos/modules/services/monitoring/prometheus/node-exporter.nix
+++ b/nixos/modules/services/monitoring/prometheus/node-exporter.nix
@@ -33,7 +33,7 @@ in {
         default = [];
         example = ''[ "systemd" ]'';
         description = ''
-          Collectors to enable, additionally to the defaults.
+          Collectors to enable. Only collectors explicitly listed here will be enabled.
         '';
       };
 
diff --git a/nixos/modules/services/network-filesystems/glusterfs.nix b/nixos/modules/services/network-filesystems/glusterfs.nix
index 7454eeef803f..e7f52bc4a7d1 100644
--- a/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -5,6 +5,22 @@ with lib;
 let
   inherit (pkgs) glusterfs rsync;
 
+  tlsCmd = if (cfg.tlsSettings != null) then
+  ''
+    mkdir -p /var/lib/glusterd
+    touch /var/lib/glusterd/secure-access
+  ''
+  else
+  ''
+    rm -f /var/lib/glusterd/secure-access
+  '';
+
+  restartTriggers = if (cfg.tlsSettings != null) then [
+    config.environment.etc."ssl/glusterfs.pem".source
+    config.environment.etc."ssl/glusterfs.key".source
+    config.environment.etc."ssl/glusterfs.ca".source
+  ] else [];
+
   cfg = config.services.glusterfs;
 
 in
@@ -30,6 +46,41 @@ in
         description = "Extra flags passed to the GlusterFS daemon";
         default = [];
       };
+
+      tlsSettings = mkOption {
+        description = ''
+          Make the server communicate via TLS.
+          This means it will only connect to other gluster
+          servers having certificates signed by the same CA.
+
+          Enabling this will create a file <filename>/var/lib/glusterd/secure-access</filename>.
+          Disabling will delete this file again.
+
+          See also: https://gluster.readthedocs.io/en/latest/Administrator%20Guide/SSL/
+        '';
+        default = null;
+        type = types.nullOr (types.submodule {
+          options = {
+            tlsKeyPath = mkOption {
+              default = null;
+              type = types.str;
+              description = "Path to the private key used for TLS.";
+            };
+
+            tlsPem = mkOption {
+              default = null;
+              type = types.path;
+              description = "Path to the certificate used for TLS.";
+            };
+
+            caCert = mkOption {
+              default = null;
+              type = types.path;
+              description = "Path certificate authority used to sign the cluster certificates.";
+            };
+          };
+        });
+      };
     };
   };
 
@@ -40,7 +91,14 @@ in
 
     services.rpcbind.enable = true;
 
+    environment.etc = mkIf (cfg.tlsSettings != null) {
+      "ssl/glusterfs.pem".source = cfg.tlsSettings.tlsPem;
+      "ssl/glusterfs.key".source = cfg.tlsSettings.tlsKeyPath;
+      "ssl/glusterfs.ca".source = cfg.tlsSettings.caCert;
+    };
+
     systemd.services.glusterd = {
+      inherit restartTriggers;
 
       description = "GlusterFS, a clustered file-system server";
 
@@ -57,6 +115,8 @@ in
       + ''
         mkdir -p /var/lib/glusterd/hooks/
         ${rsync}/bin/rsync -a ${glusterfs}/var/lib/glusterd/hooks/ /var/lib/glusterd/hooks/
+
+        ${tlsCmd}
       ''
       # `glusterfind` needs dirs that upstream installs at `make install` phase
       # https://github.com/gluster/glusterfs/blob/v3.10.2/tools/glusterfind/Makefile.am#L16-L17
@@ -75,6 +135,7 @@ in
     };
 
     systemd.services.glustereventsd = {
+      inherit restartTriggers;
 
       description = "Gluster Events Notifier";
 
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index cd320c5c4620..36e5efecf431 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.ipfs;
 
   ipfsFlags = toString ([
-    #(optionalString  cfg.autoMount                   "--mount")
+    (optionalString  cfg.autoMount                   "--mount")
     (optionalString  cfg.autoMigrate                 "--migrate")
     (optionalString  cfg.enableGC                    "--enable-gc")
     (optionalString (cfg.serviceFdlimit != null)     "--manage-fdlimit=false")
@@ -37,9 +37,10 @@ let
   baseService = recursiveUpdate commonEnv {
     wants = [ "ipfs-init.service" ];
     preStart = ''
+      ipfs repo fsck # workaround for BUG #4212 (https://github.com/ipfs/go-ipfs/issues/4214)
       ipfs --local config Addresses.API ${cfg.apiAddress}
       ipfs --local config Addresses.Gateway ${cfg.gatewayAddress}
-    '' + optionalString false/*cfg.autoMount*/ ''
+    '' + optionalString cfg.autoMount ''
       ipfs --local config Mounts.FuseAllowOther --json true
       ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
       ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
@@ -91,24 +92,22 @@ in {
       };
 
       defaultMode = mkOption {
-        description = "systemd service that is enabled by default";
         type = types.enum [ "online" "offline" "norouting" ];
         default = "online";
+        description = "systemd service that is enabled by default";
       };
 
       autoMigrate = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether IPFS should try to migrate the file system automatically.
-        '';
+        description = "Whether IPFS should try to migrate the file system automatically";
       };
 
-      #autoMount = mkOption {
-      #  type = types.bool;
-      #  default = false;
-      #  description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
-      #};
+      autoMount = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
+      };
 
       ipfsMountDir = mkOption {
         type = types.str;
@@ -137,26 +136,22 @@ in {
       enableGC = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable automatic garbage collection.
-        '';
+        description = "Whether to enable automatic garbage collection";
       };
 
       emptyRepo = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          If set to true, the repo won't be initialized with help files
-        '';
+        description = "If set to true, the repo won't be initialized with help files";
       };
 
       extraConfig = mkOption {
         type = types.attrs;
-        description = toString [
-          "Attrset of daemon configuration to set using `ipfs config`, every time the daemon starts."
-          "These are applied last, so may override configuration set by other options in this module."
-          "Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!"
-        ];
+        description = ''
+          Attrset of daemon configuration to set using <command>ipfs config</command>, every time the daemon starts.
+          These are applied last, so may override configuration set by other options in this module.
+          Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
+        '';
         default = {};
         example = {
           Datastore.StorageMax = "100GB";
@@ -179,10 +174,8 @@ in {
       serviceFdlimit = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
-          The fdlimit for the IPFS systemd unit or `null` to have the daemon attempt to manage it.
-        '';
-        example = 256*1024;
+        description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
+        example = 64*1024;
       };
 
     };
@@ -192,6 +185,9 @@ in {
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ wrapped ];
+    environment.etc."fuse.conf" = mkIf cfg.autoMount { text = ''
+      user_allow_other
+    ''; };
 
     users.extraUsers = mkIf (cfg.user == "ipfs") {
       ipfs = {
@@ -211,11 +207,11 @@ in {
       description = "IPFS Initializer";
 
       after = [ "local-fs.target" ];
-      before = [ "ipfs.service" "ipfs-offline.service" ];
+      before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
 
       preStart = ''
         install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.dataDir}
-      '' + optionalString false/*cfg.autoMount*/ ''
+      '' + optionalString cfg.autoMount ''
         install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipfsMountDir}
         install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipnsMountDir}
       '';
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 763283dfe7a2..9f533eedf6e1 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -151,6 +151,15 @@ in
         ";
       };
 
+      resolveLocalQueries = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether bind should resolve local queries (i.e. add 127.0.0.1 to
+          /etc/resolv.conf, overriding networking.nameserver).
+        '';
+      };
+
     };
 
   };
diff --git a/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix
index 65273a4bf939..b3c64490d97e 100644
--- a/nixos/modules/services/networking/coturn.nix
+++ b/nixos/modules/services/networking/coturn.nix
@@ -307,7 +307,8 @@ in {
 
     systemd.services.coturn = {
       description = "coturn TURN server";
-      after = [ "network.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
 
       unitConfig = {
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index cdba14be21f0..d283c7624335 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -153,10 +153,14 @@ in
 
   config = mkIf enableDHCP {
 
-    systemd.services.dhcpcd =
+    systemd.services.dhcpcd = let
+      cfgN = config.networking;
+      hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "")
+                          || (cfgN.defaultGateway6 != null && cfgN.defaultGateway6.address != "");
+    in
       { description = "DHCP Client";
 
-        wantedBy = [ "network-online.target" ];
+        wantedBy = optional (!hasDefaultGatewaySet) "network-online.target";
         after = [ "network.target" ];
         wants = [ "network.target" ];
 
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index fcf5aa5f175b..3d1b931de07e 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -42,7 +42,7 @@ in
         default = true;
         description = ''
           Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
-          /etc/resolv.conf).
+          /etc/resolv.conf overriding networking.nameservers).
         '';
       };
 
diff --git a/nixos/modules/services/networking/matterbridge.nix b/nixos/modules/services/networking/matterbridge.nix
new file mode 100644
index 000000000000..5526e2ba23ac
--- /dev/null
+++ b/nixos/modules/services/networking/matterbridge.nix
@@ -0,0 +1,96 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.matterbridge;
+
+  matterbridgeConfToml = pkgs.writeText "matterbridge.toml" (cfg.configFile);
+
+in
+
+{
+  options = {
+    services.matterbridge = {
+      enable = mkEnableOption "Matterbridge chat platform bridge";
+
+      configFile = mkOption {
+        type = types.str;
+        example = ''
+          #WARNING: as this file contains credentials, be sure to set correct file permissions          [irc]
+              [irc.freenode]
+              Server="irc.freenode.net:6667"
+              Nick="matterbot"
+
+          [mattermost]
+              [mattermost.work]
+               #do not prefix it wit http:// or https://
+               Server="yourmattermostserver.domain"
+               Team="yourteam"
+               Login="yourlogin"
+               Password="yourpass"
+               PrefixMessagesWithNick=true
+
+          [[gateway]]
+          name="gateway1"
+          enable=true
+              [[gateway.inout]]
+              account="irc.freenode"
+              channel="#testing"
+
+              [[gateway.inout]]
+              account="mattermost.work"
+              channel="off-topic"
+        '';
+        description = ''
+          The matterbridge configuration file in the TOML file format.
+        '';
+      };
+      user = mkOption {
+        type = types.str;
+        default = "matterbridge";
+        description = ''
+          User which runs the matterbridge service.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "matterbridge";
+        description = ''
+          Group which runs the matterbridge service.
+        '';
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+
+      users.extraUsers = mkIf (cfg.user == "matterbridge") [
+        { name = "matterbridge";
+          group = "matterbridge";
+        } ];
+
+      users.extraGroups = mkIf (cfg.group == "matterbridge") [
+        { name = "matterbridge";
+        } ];
+
+      systemd.services.matterbridge = {
+        description = "Matterbridge chat platform bridge";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+          ExecStart = "${pkgs.matterbridge.bin}/bin/matterbridge -conf ${matterbridgeConfToml}";
+          Restart = "always";
+          RestartSec = "10";
+        };
+      };
+    })
+  ];
+}
+
diff --git a/nixos/modules/services/networking/namecoind.nix b/nixos/modules/services/networking/namecoind.nix
index 9df9f67cde83..11f7d7e5caef 100644
--- a/nixos/modules/services/networking/namecoind.nix
+++ b/nixos/modules/services/networking/namecoind.nix
@@ -173,7 +173,7 @@ in
 
       serviceConfig = {
         User  = "namecoin";
-        Griup = "namecoin";
+        Group = "namecoin";
         ExecStart  = "${pkgs.altcoins.namecoind}/bin/namecoind -conf=${configFile} -datadir=${dataDir} -printtoconsole";
         ExecStop   = "${pkgs.coreutils}/bin/kill -KILL $MAINPID";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index f1b8edf43713..56f2e976cff5 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -48,6 +48,12 @@ in
         configuration file.
       '';
     };
+
+    services.radicale.extraArgs = mkOption {
+      type = types.listOf types.string;
+      default = [];
+      description = "Extra arguments passed to the Radicale daemon.";
+    };
   };
 
   config = mkIf cfg.enable {
@@ -71,7 +77,11 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/radicale -C ${confFile} -f";
+        ExecStart = concatStringsSep " " ([
+          "${cfg.package}/bin/radicale" "-C" confFile
+        ] ++ (
+          map escapeShellArg cfg.extraArgs
+        ));
         User = "radicale";
         Group = "radicale";
       };
diff --git a/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
new file mode 100644
index 000000000000..b220c21b604f
--- /dev/null
+++ b/nixos/modules/services/networking/squid.nix
@@ -0,0 +1,169 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.squid;
+
+
+  squidConfig = pkgs.writeText "squid.conf"
+    (if cfg.configText != null then cfg.configText else
+    ''
+    #
+    # Recommended minimum configuration (3.5):
+    #
+
+    # Example rule allowing access from your local networks.
+    # Adapt to list your (internal) IP networks from where browsing
+    # should be allowed
+    acl localnet src 10.0.0.0/8     # RFC 1918 possible internal network
+    acl localnet src 172.16.0.0/12  # RFC 1918 possible internal network
+    acl localnet src 192.168.0.0/16 # RFC 1918 possible internal network
+    acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
+    acl localnet src fc00::/7       # RFC 4193 local private network range
+    acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines
+
+    acl SSL_ports port 443          # https
+    acl Safe_ports port 80          # http
+    acl Safe_ports port 21          # ftp
+    acl Safe_ports port 443         # https
+    acl Safe_ports port 70          # gopher
+    acl Safe_ports port 210         # wais
+    acl Safe_ports port 1025-65535  # unregistered ports
+    acl Safe_ports port 280         # http-mgmt
+    acl Safe_ports port 488         # gss-http
+    acl Safe_ports port 591         # filemaker
+    acl Safe_ports port 777         # multiling http
+    acl CONNECT method CONNECT
+
+    #
+    # Recommended minimum Access Permission configuration:
+    #
+    # Deny requests to certain unsafe ports
+    http_access deny !Safe_ports
+
+    # Deny CONNECT to other than secure SSL ports
+    http_access deny CONNECT !SSL_ports
+
+    # Only allow cachemgr access from localhost
+    http_access allow localhost manager
+    http_access deny manager
+
+    # We strongly recommend the following be uncommented to protect innocent
+    # web applications running on the proxy server who think the only
+    # one who can access services on "localhost" is a local user
+    http_access deny to_localhost
+
+    # Application logs to syslog, access and store logs have specific files
+    cache_log       syslog
+    access_log      stdio:/var/log/squid/access.log
+    cache_store_log stdio:/var/log/squid/store.log
+
+    # Required by systemd service
+    pid_filename    /run/squid.pid
+
+    # Run as user and group squid
+    cache_effective_user squid squid
+
+    #
+    # INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
+    #
+    ${cfg.extraConfig}
+
+    # Example rule allowing access from your local networks.
+    # Adapt localnet in the ACL section to list your (internal) IP networks
+    # from where browsing should be allowed
+    http_access allow localnet
+    http_access allow localhost
+
+    # And finally deny all other access to this proxy
+    http_access deny all
+
+    # Squid normally listens to port 3128
+    http_port ${toString cfg.proxyPort}
+
+    # Leave coredumps in the first cache dir
+    coredump_dir /var/cache/squid
+
+    #
+    # Add any of your own refresh_pattern entries above these.
+    #
+    refresh_pattern ^ftp:           1440    20%     10080
+    refresh_pattern ^gopher:        1440    0%      1440
+    refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
+    refresh_pattern .               0       20%     4320
+  '');
+
+in
+
+{
+
+  options = {
+
+    services.squid = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run squid web proxy.";
+      };
+
+      proxyPort = mkOption {
+        type = types.int;
+        default = 3128;
+        description = "TCP port on which squid will listen.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Squid configuration. Contents will be added
+          verbatim to the configuration file.
+        '';
+      };
+
+      configText = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Verbatim contents of squid.conf. If null (default), use the
+          autogenerated file from NixOS instead.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.squid = {
+      isSystemUser = true;
+      group = "squid";
+      home = "/var/cache/squid";
+      createHome = true;
+    };
+
+    users.groups.squid = {};
+
+    systemd.services.squid = {
+      description = "Squid caching web proxy";
+      after = [ "network.target" "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target"];
+      preStart = ''
+        mkdir -p "/var/log/squid"
+        chown squid:squid "/var/log/squid"
+      '';
+      serviceConfig = {
+        Type="forking";
+        PIDFile="/run/squid.pid";
+        PermissionsStartOnly = true;
+        ExecStart  = "${pkgs.squid}/bin/squid -YCs -f ${squidConfig}";
+      };
+    };
+
+  };
+
+}
\ No newline at end of file
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 0f58536b4b73..8828429a8178 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -103,6 +103,15 @@ in
         '';
       };
 
+      sftpFlags = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = [ "-f AUTHPRIV" "-l INFO" ];
+        description = ''
+          Commandline flags to add to sftp-server.
+        '';
+      };
+
       permitRootLogin = mkOption {
         default = "prohibit-password";
         type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
@@ -208,7 +217,7 @@ in
       };
 
       moduliFile = mkOption {
-        example = "services.openssh.moduliFile = /etc/my-local-ssh-moduli;";
+        example = "/etc/my-local-ssh-moduli;";
         type = types.path;
         description = ''
           Path to <literal>moduli</literal> file to install in
@@ -338,7 +347,7 @@ in
         ''}
 
         ${optionalString cfg.allowSFTP ''
-          Subsystem sftp ${cfgc.package}/libexec/sftp-server
+          Subsystem sftp ${cfgc.package}/libexec/sftp-server ${concatStringsSep " " cfg.sftpFlags}
         ''}
 
         PermitRootLogin ${cfg.permitRootLogin}
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 7a786b54ccbb..d5db328310c1 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -199,8 +199,10 @@ in
         buildInputs = [ pkgs.makeWrapper ];
         buildCommand = ''
           mkdir -p $out/bin
-          ${concatStringsSep "\n" (mapAttrsToList (network: data: ''
-              makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" --add-flags "--pidfile=/run/tinc.${network}.pid"
+          ${concatStringsSep "\n" (mapAttrsToList (network: data:
+            optionalString (versionAtLeast data.package.version "1.1pre") ''
+              makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
+                --add-flags "--pidfile=/run/tinc.${network}.pid"
             '') cfg.networks)}
         '';
       };
diff --git a/nixos/modules/services/security/hologram-agent.nix b/nixos/modules/services/security/hologram-agent.nix
index 49b5c935267b..6c53a2df6306 100644
--- a/nixos/modules/services/security/hologram-agent.nix
+++ b/nixos/modules/services/security/hologram-agent.nix
@@ -33,6 +33,8 @@ in {
   };
 
   config = mkIf cfg.enable {
+    boot.kernelModules = [ "dummy" ];
+
     networking.interfaces.dummy0 = {
       ipAddress = "169.254.169.254";
       prefixLength = 32;
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index 8bad42927e3f..88e488425bce 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -60,6 +60,7 @@ in {
       ConditionPathExists=/dev/tty0
 
       [Service]
+      ExecStart=
       ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p
       UtmpIdentifier=%I
       TTYPath=/dev/%I
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 9be10a8283ed..43a9c28bb694 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -35,21 +35,40 @@ in rec {
       description = ''
         If set to false, this unit will be a symlink to
         /dev/null. This is primarily useful to prevent specific
-        template instances (e.g. <literal>serial-getty@ttyS0</literal>)
-        from being started.
+        template instances
+        (e.g. <literal>serial-getty@ttyS0</literal>) from being
+        started. Note that <literal>enable=true</literal> does not
+        make a unit start by default at boot; if you want that, see
+        <literal>wantedBy</literal>.
       '';
     };
 
     requiredBy = mkOption {
       default = [];
       type = types.listOf types.str;
-      description = "Units that require (i.e. depend on and need to go down with) this unit.";
+      description = ''
+        Units that require (i.e. depend on and need to go down with)
+        this unit. The discussion under <literal>wantedBy</literal>
+        applies here as well: inverse <literal>.requires</literal>
+        symlinks are established.
+      '';
     };
 
     wantedBy = mkOption {
       default = [];
       type = types.listOf types.str;
-      description = "Units that want (i.e. depend on) this unit.";
+      description = ''
+        Units that want (i.e. depend on) this unit. The standard way
+        to make a unit start by default at boot is to set this option
+        to <literal>[ "multi-user.target" ]</literal>. That's despite
+        the fact that the systemd.unit(5) manpage says this option
+        goes in the <literal>[Install]</literal> section that controls
+        the behaviour of <literal>systemctl enable</literal>. Since
+        such a process is stateful and thus contrary to the design of
+        NixOS, setting this option instead causes the equivalent
+        inverse <literal>.wants</literal> symlink to be present,
+        establishing the same desired relationship in a stateless way.
+      '';
     };
 
     aliases = mkOption {
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 1922d2924bc5..6ceb36714b28 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -217,7 +217,7 @@ in
     # Add the mount helpers to the system path so that `mount' can find them.
     system.fsPackages = [ pkgs.dosfstools ];
 
-    environment.systemPackages = [ pkgs.fuse ] ++ config.system.fsPackages;
+    environment.systemPackages = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages;
 
     environment.etc.fstab.text =
       let
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index 15b36cfcb113..adc048f3ca2c 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -73,6 +73,9 @@ let
           then [ "${dev}-netdev.service" ]
           else optional (dev != null && dev != "lo" && !config.boot.isContainer) (subsystemDevice dev);
 
+        hasDefaultGatewaySet = (cfg.defaultGateway != null && cfg.defaultGateway.address != "")
+                            || (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
+
         networkLocalCommands = {
           after = [ "network-setup.service" ];
           bindsTo = [ "network-setup.service" ];
@@ -85,7 +88,7 @@ let
             before = [ "network.target" "shutdown.target" ];
             wants = [ "network.target" ];
             conflicts = [ "shutdown.target" ];
-            wantedBy = [ "multi-user.target" ];
+            wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target";
 
             unitConfig.ConditionCapability = "CAP_NET_ADMIN";
 
@@ -102,7 +105,7 @@ let
               ''
                 # Set the static DNS configuration, if given.
                 ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
-                ${optionalString (cfg.nameservers != [] && cfg.domain != null) ''
+                ${optionalString (cfg.domain != null) ''
                   domain ${cfg.domain}
                 ''}
                 ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index a7ceb104b577..f61d80f55991 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -94,9 +94,6 @@ in rec {
         (all nixos.tests.keymap.neo)
         (all nixos.tests.keymap.qwertz)
         nixos.tests.plasma5.x86_64-linux # avoid big build on i686
-        (all nixos.tests.kernel-latest)
-        (all nixos.tests.kernel-lts)
-        (all nixos.tests.kernel-params)
         #(all nixos.tests.lightdm)
         (all nixos.tests.login)
         (all nixos.tests.misc)
@@ -119,7 +116,6 @@ in rec {
         (all nixos.tests.sddm.default)
         (all nixos.tests.simple)
         (all nixos.tests.slim)
-        nixos.tests.sysctl.x86_64-linux # i686 fails
         (all nixos.tests.udisks2)
         (all nixos.tests.xfce)
 
diff --git a/nixos/release.nix b/nixos/release.nix
index 38c446c1f8a4..a200535f3f4a 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -236,6 +236,7 @@ in rec {
   tests.containers-macvlans = callTest tests/containers-macvlans.nix {};
   tests.docker = hydraJob (import tests/docker.nix { system = "x86_64-linux"; });
   tests.docker-edge = hydraJob (import tests/docker-edge.nix { system = "x86_64-linux"; });
+  tests.dovecot = callTest tests/dovecot.nix {};
   tests.dnscrypt-proxy = callTest tests/dnscrypt-proxy.nix { system = "x86_64-linux"; };
   tests.ecryptfs = callTest tests/ecryptfs.nix {};
   tests.etcd = hydraJob (import tests/etcd.nix { system = "x86_64-linux"; });
@@ -253,10 +254,12 @@ in rec {
   tests.gocd-server = callTest tests/gocd-server.nix {};
   tests.gnome3 = callTest tests/gnome3.nix {};
   tests.gnome3-gdm = callTest tests/gnome3-gdm.nix {};
+  tests.grafama = callTest tests/grafana.nix {};
   tests.hardened = callTest tests/hardened.nix { };
   tests.hibernate = callTest tests/hibernate.nix {};
   tests.hound = callTest tests/hound.nix {};
   tests.i3wm = callTest tests/i3wm.nix {};
+  tests.initrd-network-ssh = callTest tests/initrd-network-ssh.nix {};
   tests.installer = callSubTests tests/installer.nix {};
   tests.influxdb = callTest tests/influxdb.nix {};
   tests.ipv6 = callTest tests/ipv6.nix {};
@@ -267,7 +270,6 @@ in rec {
   tests.kernel-copperhead = callTest tests/kernel-copperhead.nix {};
   tests.kernel-latest = callTest tests/kernel-latest.nix {};
   tests.kernel-lts = callTest tests/kernel-lts.nix {};
-  tests.kernel-params = callTest tests/kernel-params.nix {};
   tests.keystone = callTest tests/keystone.nix {};
   tests.kubernetes = hydraJob (import tests/kubernetes.nix { system = "x86_64-linux"; });
   tests.latestKernel.login = callTest tests/login.nix { latestKernel = true; };
@@ -313,7 +315,6 @@ in rec {
   tests.slim = callTest tests/slim.nix {};
   tests.smokeping = callTest tests/smokeping.nix {};
   tests.snapper = callTest tests/snapper.nix {};
-  tests.sysctl = callTest tests/sysctl.nix {};
   tests.taskserver = callTest tests/taskserver.nix {};
   tests.tomcat = callTest tests/tomcat.nix {};
   tests.udisks2 = callTest tests/udisks2.nix {};
diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix
new file mode 100644
index 000000000000..3814855ed8e7
--- /dev/null
+++ b/nixos/tests/dovecot.nix
@@ -0,0 +1,64 @@
+import ./make-test.nix {
+  name = "dovecot";
+
+  machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix.enable = true;
+    services.dovecot2.enable = true;
+    services.dovecot2.protocols = [ "imap" "pop3" ];
+    environment.systemPackages = let
+      sendTestMail = pkgs.writeScriptBin "send-testmail" ''
+        #!${pkgs.stdenv.shell}
+        exec sendmail -vt <<MAIL
+        From: root@localhost
+        To: alice@localhost
+        Subject: Very important!
+
+        Hello world!
+        MAIL
+      '';
+
+      testImap = pkgs.writeScriptBin "test-imap" ''
+        #!${pkgs.python3.interpreter}
+        import imaplib
+
+        with imaplib.IMAP4('localhost') as imap:
+          imap.login('alice', 'foobar')
+          imap.select()
+          status, refs = imap.search(None, 'ALL')
+          assert status == 'OK'
+          assert len(refs) == 1
+          status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+          assert status == 'OK'
+          assert msg[0][1].strip() == b'Hello world!'
+      '';
+
+      testPop = pkgs.writeScriptBin "test-pop" ''
+        #!${pkgs.python3.interpreter}
+        import poplib
+
+        pop = poplib.POP3('localhost')
+        try:
+          pop.user('alice')
+          pop.pass_('foobar')
+          assert len(pop.list()[1]) == 1
+          status, fullmail, size = pop.retr(1)
+          assert status.startswith(b'+OK ')
+          body = b"".join(fullmail[fullmail.index(b""):]).strip()
+          assert body == b'Hello world!'
+        finally:
+          pop.quit()
+      '';
+
+    in [ sendTestMail testImap testPop ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit('postfix.service');
+    $machine->waitForUnit('dovecot2.service');
+    $machine->succeed('send-testmail');
+    $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
+    $machine->succeed('test-imap');
+    $machine->succeed('test-pop');
+  '';
+}
diff --git a/nixos/tests/grafana.nix b/nixos/tests/grafana.nix
new file mode 100644
index 000000000000..16b8181498a6
--- /dev/null
+++ b/nixos/tests/grafana.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ lib, ... }:
+{
+  name = "grafana";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    services.grafana = {
+      enable = true;
+      addr = "localhost";
+      analytics.reporting.enable = false;
+      domain = "localhost";
+      security.adminUser = "testusername";
+    };
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForUnit("grafana.service");
+    $machine->waitForOpenPort(3000);
+    $machine->succeed("curl -sS http://127.0.0.1:3000/");
+  '';
+})
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index 1d9a9043e03a..ee7ffe83ba34 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -32,5 +32,15 @@ import ./make-test.nix ({ pkgs, ...} : {
       subtest "userns", sub {
           $machine->fail("unshare --user");
       };
+
+      # Test dmesg restriction
+      subtest "dmesg", sub {
+          $machine->fail("su -l alice -c dmesg");
+      };
+
+      # Test access to kcore
+      subtest "kcore", sub {
+          $machine->fail("cat /proc/kcore");
+      };
     '';
 })
diff --git a/nixos/tests/initrd-network-ssh.nix b/nixos/tests/initrd-network-ssh.nix
new file mode 100644
index 000000000000..596610493921
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh.nix
@@ -0,0 +1,74 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+let
+  keys = pkgs.runCommand "gen-keys" {
+    outputs = [ "out" "dbPub" "dbPriv" "sshPub" "sshPriv" ];
+    buildInputs = with pkgs; [ dropbear openssh ];
+  }
+  ''
+    touch $out
+    dropbearkey -t rsa -f $dbPriv -s 4096 | sed -n 2p > $dbPub
+    ssh-keygen -q -t rsa -b 4096 -N "" -f client
+    mv client $sshPriv
+    mv client.pub $sshPub
+  '';
+
+in {
+  name = "initrd-network-ssh";
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = with lib; rec {
+    server =
+      { config, pkgs, ... }:
+      {
+        boot.kernelParams = [
+          "ip=${
+            (head config.networking.interfaces.eth1.ip4).address
+          }:::255.255.255.0::eth1:none"
+        ];
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ "${readFile keys.sshPub}" ];
+            port = 22;
+            hostRSAKey = keys.dbPriv;
+          };
+        };
+        boot.initrd.preLVMCommands = ''
+          while true; do
+            if [ -f fnord ]; then
+              poweroff
+            fi
+            sleep 1
+          done
+        '';
+      };
+
+    client =
+      { config, pkgs, ... }:
+      {
+        environment.etc.knownHosts = {
+          text = concatStrings [
+            "server,"
+            "${toString (head (splitString " " (
+              toString (elemAt (splitString "\n" config.networking.extraHosts) 2)
+            )))} "
+            "${readFile keys.dbPub}"
+          ];
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $client->waitForUnit("network.target");
+    $client->copyFileFromHost("${keys.sshPriv}","/etc/sshKey");
+    $client->succeed("chmod 0600 /etc/sshKey");
+    $client->waitUntilSucceeds("ping -c 1 server");
+    $client->succeed("ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'");
+    $client->shutdown;
+  '';
+})
diff --git a/nixos/tests/ipfs.nix b/nixos/tests/ipfs.nix
index a93f8f175c55..c6bc61545245 100644
--- a/nixos/tests/ipfs.nix
+++ b/nixos/tests/ipfs.nix
@@ -23,8 +23,7 @@ import ./make-test.nix ({ pkgs, ...} : {
         services.ipfs = {
           enable = true;
           defaultMode = "norouting";
-          # not yet. See #28621
-          #autoMount = true;
+          autoMount = true;
         };
         networking.firewall.allowedTCPPorts = [ 4001 ];
       };
@@ -51,7 +50,6 @@ import ./make-test.nix ({ pkgs, ...} : {
 
     $getter->mustSucceed("ipfs --api /ip4/127.0.0.1/tcp/5001 swarm connect /ip4/$addrIp/tcp/4001/ipfs/$addrId");
     $getter->mustSucceed("[ -n \"\$(ipfs --api /ip4/127.0.0.1/tcp/5001 cat /ipfs/$ipfsHash | grep fnord)\" ]");
-    # not yet. See #28621
-    # $getter->mustSucceed("[ -n \"$(cat /ipfs/$ipfsHash | grep fnord)\" ]");
+    $getter->mustSucceed("[ -n \"$(cat /ipfs/$ipfsHash | grep fnord)\" ]");
     '';
 })
diff --git a/nixos/tests/kernel-params.nix b/nixos/tests/kernel-params.nix
deleted file mode 100644
index 14a393356911..000000000000
--- a/nixos/tests/kernel-params.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "kernel-params";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { config, lib, pkgs, ... }:
-    {
-      boot.kernelPackages = pkgs.linuxPackages;
-      boot.kernelParams = [
-        "nohibernate"
-        "page_poison=1"
-        "vsyscall=none"
-      ];
-    };
-
-  testScript =
-    ''
-      $machine->fail("cat /proc/cmdline | grep page_poison=0");
-      $machine->succeed("cat /proc/cmdline | grep nohibernate");
-      $machine->succeed("cat /proc/cmdline | grep page_poison=1");
-      $machine->succeed("cat /proc/cmdline | grep vsyscall=none");
-    '';
-})
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index 1b24551009c9..79290861cb0b 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -25,6 +25,8 @@ import ./make-test.nix ({ pkgs, ...} : {
         };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
       security.sudo = { enable = true; wheelNeedsPassword = false; };
+      boot.kernel.sysctl."vm.swappiness" = 1;
+      boot.kernelParams = [ "vsyscall=emulate" ];
     };
 
   testScript =
@@ -117,5 +119,18 @@ import ./make-test.nix ({ pkgs, ...} : {
       subtest "sudo", sub {
           $machine->succeed("su - sybil -c 'sudo true'");
       };
+
+      # Test sysctl
+      subtest "sysctl", sub {
+          $machine->waitForUnit("systemd-sysctl.service");
+          $machine->succeed('[ `sysctl -ne vm.swappiness` = 1 ]');
+          $machine->execute('sysctl vm.swappiness=60');
+          $machine->succeed('[ `sysctl -ne vm.swappiness` = 60 ]');
+      };
+
+      # Test boot parameters
+      subtest "bootparam", sub {
+          $machine->succeed('grep -Fq vsyscall=emulate /proc/cmdline');
+      };
     '';
 })
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 6a7e628d8ef1..7708775f73f3 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -105,7 +105,7 @@ let
           startAll;
 
           $client->waitForUnit("network.target");
-          $router->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
 
           # Make sure dhcpcd is not started
           $client->fail("systemctl status dhcpcd.service");
@@ -157,7 +157,7 @@ let
           startAll;
 
           $client->waitForUnit("network.target");
-          $router->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
 
           # Wait until we have an ip address on each interface
           $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
index bec86d2cb284..2c888469d0a4 100644
--- a/nixos/tests/radicale.nix
+++ b/nixos/tests/radicale.nix
@@ -2,12 +2,8 @@ let
   user = "someuser";
   password = "some_password";
   port = builtins.toString 5232;
-in
-  import ./make-test.nix ({ pkgs, lib, ... }: {
-  name = "radicale";
-  meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ];
 
-  machine = {
+  common = { pkgs, ... }: {
     services.radicale = {
       enable = true;
       config = ''
@@ -29,11 +25,81 @@ in
       ${pkgs.apacheHttpd}/bin/htpasswd -bcB "$out" ${user} ${password}
     '';
   };
-  
-  # This tests whether the web interface is accessible to an authenticated user
-  testScript = ''
-    $machine->waitForUnit('radicale.service');
-    $machine->waitForOpenPort(${port});
-    $machine->succeed('curl --fail http://${user}:${password}@localhost:${port}/.web/');
-  '';
+
+in
+
+  import ./make-test.nix ({ pkgs, lib, ... }@args: {
+    name = "radicale";
+    meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ];
+
+    nodes = rec {
+      radicale = radicale1; # Make the test script read more nicely
+      radicale1 = lib.recursiveUpdate (common args) {
+        nixpkgs.overlays = [
+          (self: super: {
+            radicale1 = super.radicale1.overrideAttrs (oldAttrs: {
+              propagatedBuildInputs = with self.pythonPackages;
+                (oldAttrs.propagatedBuildInputs or []) ++ [ passlib ];
+            });
+          })
+        ];
+      };
+      radicale1_export = lib.recursiveUpdate radicale1 {
+        services.radicale.extraArgs = [
+          "--export-storage" "/tmp/collections-new"
+        ];
+      };
+      radicale2_verify = lib.recursiveUpdate radicale2 {
+        services.radicale.extraArgs = [ "--verify-storage" ];
+      };
+      radicale2 = lib.recursiveUpdate (common args) {
+        system.stateVersion = "17.09";
+      };
+    };
+
+    # This tests whether the web interface is accessible to an authenticated user
+    testScript = { nodes }: let
+      switchToConfig = nodeName: let
+        newSystem = nodes.${nodeName}.config.system.build.toplevel;
+      in "${newSystem}/bin/switch-to-configuration test";
+    in ''
+      # Check Radicale 1 functionality
+      $radicale->succeed('${switchToConfig "radicale1"} >&2');
+      $radicale->waitForUnit('radicale.service');
+      $radicale->waitForOpenPort(${port});
+      $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/');
+
+      # Export data in Radicale 2 format
+      $radicale->succeed('systemctl stop radicale');
+      $radicale->succeed('ls -al /tmp/collections');
+      $radicale->fail('ls -al /tmp/collections-new');
+      # Radicale exits immediately after exporting storage
+      $radicale->succeed('${switchToConfig "radicale1_export"} >&2');
+      $radicale->waitUntilFails('systemctl status radicale');
+      $radicale->succeed('ls -al /tmp/collections');
+      $radicale->succeed('ls -al /tmp/collections-new');
+
+      # Verify data in Radicale 2 format
+      $radicale->succeed('rm -r /tmp/collections/${user}');
+      $radicale->succeed('mv /tmp/collections-new/collection-root /tmp/collections');
+      $radicale->succeed('${switchToConfig "radicale2_verify"} >&2');
+      $radicale->waitUntilFails('systemctl status radicale');
+      my ($retcode, $logs) = $radicale->execute('journalctl -u radicale -n 5');
+      if ($retcode != 0 || index($logs, 'Verifying storage') == -1) {
+        die "Radicale 2 didn't verify storage"
+      }
+      if (index($logs, 'failed') != -1 || index($logs, 'exception') != -1) {
+        die "storage verification failed"
+      }
+
+      # Check Radicale 2 functionality
+      $radicale->succeed('${switchToConfig "radicale2"} >&2');
+      $radicale->waitForUnit('radicale.service');
+      $radicale->waitForOpenPort(${port});
+      my ($retcode, $output) = $radicale->execute('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/');
+      if ($retcode != 0 || index($output, 'VCALENDAR') == -1) {
+        die "Could not read calendar from Radicale 2"
+      }
+      $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/.web/');
+    '';
 })
diff --git a/nixos/tests/sysctl.nix b/nixos/tests/sysctl.nix
deleted file mode 100644
index d7220cabb22c..000000000000
--- a/nixos/tests/sysctl.nix
+++ /dev/null
@@ -1,25 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "sysctl";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { config, lib, pkgs, ... }:
-    {
-      boot.kernelPackages = pkgs.linuxPackages;
-      boot.kernel.sysctl = {
-        "kernel.dmesg_restrict" = true; # Restrict dmesg access
-        "net.core.bpf_jit_enable" = false; # Turn off bpf JIT
-        "user.max_user_namespaces" = 0; # Disable user namespaces
-        "vm.swappiness" = 2; # Low swap usage
-      };
-    };
-
-  testScript =
-    ''
-      $machine->succeed("sysctl kernel.dmesg_restrict | grep 'kernel.dmesg_restrict = 1'");
-      $machine->succeed("sysctl net.core.bpf_jit_enable | grep 'net.core.bpf_jit_enable = 0'");
-      $machine->succeed("sysctl user.max_user_namespaces | grep 'user.max_user_namespaces = 0'");
-      $machine->succeed("sysctl vm.swappiness | grep 'vm.swappiness = 2'");
-    '';
-})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 4f7cb176d96f..a1ab7614871a 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -461,11 +461,11 @@ in mapAttrs mkVBoxTest {
     my $test1IP = waitForIP_test1 1;
     my $test2IP = waitForIP_test2 1;
 
-    $machine->succeed("echo '$test2IP' | nc '$test1IP' 1234");
-    $machine->succeed("echo '$test1IP' | nc '$test2IP' 1234");
+    $machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234");
+    $machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234");
 
-    $machine->waitUntilSucceeds("nc '$test1IP' 5678 >&2");
-    $machine->waitUntilSucceeds("nc '$test2IP' 5678 >&2");
+    $machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2");
+    $machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2");
 
     shutdownVM_test1;
     shutdownVM_test2;