about summary refs log tree commit diff
path: root/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/tests')
-rw-r--r--nixos/tests/acme.nix64
-rw-r--r--nixos/tests/ammonite.nix20
-rw-r--r--nixos/tests/atd.nix30
-rw-r--r--nixos/tests/avahi.nix25
-rw-r--r--nixos/tests/beegfs.nix115
-rw-r--r--nixos/tests/bind.nix27
-rw-r--r--nixos/tests/bittorrent.nix10
-rw-r--r--nixos/tests/blivet.nix3
-rw-r--r--nixos/tests/boot-stage1.nix162
-rw-r--r--nixos/tests/boot.nix66
-rw-r--r--nixos/tests/borgbackup.nix162
-rw-r--r--nixos/tests/buildbot.nix111
-rw-r--r--nixos/tests/cadvisor.nix10
-rw-r--r--nixos/tests/cassandra.nix68
-rw-r--r--nixos/tests/ceph.nix140
-rw-r--r--nixos/tests/chromium.nix168
-rw-r--r--nixos/tests/cjdns.nix27
-rw-r--r--nixos/tests/cloud-init.nix46
-rw-r--r--nixos/tests/common/letsencrypt.nix466
-rw-r--r--nixos/tests/common/resolver.nix141
-rw-r--r--nixos/tests/common/user-account.nix9
-rw-r--r--nixos/tests/containers-bridge.nix87
-rw-r--r--nixos/tests/containers-extra_veth.nix103
-rw-r--r--nixos/tests/containers-hosts.nix52
-rw-r--r--nixos/tests/containers-imperative.nix (renamed from nixos/tests/containers.nix)50
-rw-r--r--nixos/tests/containers-ipv4.nix56
-rw-r--r--nixos/tests/containers-ipv6.nix61
-rw-r--r--nixos/tests/containers-macvlans.nix82
-rw-r--r--nixos/tests/containers-physical_interfaces.nix133
-rw-r--r--nixos/tests/containers-portforward.nix63
-rw-r--r--nixos/tests/containers-reloadable.nix66
-rw-r--r--nixos/tests/containers-restart_networking.nix114
-rw-r--r--nixos/tests/containers-tmpfs.nix79
-rw-r--r--nixos/tests/couchdb.nix56
-rw-r--r--nixos/tests/deluge.nix29
-rw-r--r--nixos/tests/dhparams.nix144
-rw-r--r--nixos/tests/dnscrypt-proxy.nix33
-rw-r--r--nixos/tests/docker-edge.nix47
-rw-r--r--nixos/tests/docker-registry.nix24
-rw-r--r--nixos/tests/docker-tools-overlay.nix32
-rw-r--r--nixos/tests/docker-tools.nix55
-rw-r--r--nixos/tests/docker.nix28
-rw-r--r--nixos/tests/dovecot.nix77
-rw-r--r--nixos/tests/ec2.nix49
-rw-r--r--nixos/tests/ecryptfs.nix84
-rw-r--r--nixos/tests/elk.nix107
-rw-r--r--nixos/tests/emacs-daemon.nix45
-rw-r--r--nixos/tests/env.nix35
-rw-r--r--nixos/tests/etcd-cluster.nix157
-rw-r--r--nixos/tests/etcd.nix108
-rw-r--r--nixos/tests/ferm.nix72
-rw-r--r--nixos/tests/firefox.nix10
-rw-r--r--nixos/tests/firewall.nix53
-rw-r--r--nixos/tests/flannel.nix55
-rw-r--r--nixos/tests/flatpak.nix23
-rw-r--r--nixos/tests/fleet.nix76
-rw-r--r--nixos/tests/fwupd.nix19
-rw-r--r--nixos/tests/gdk-pixbuf.nix19
-rw-r--r--nixos/tests/gitlab.nix56
-rw-r--r--nixos/tests/gitolite.nix139
-rw-r--r--nixos/tests/gjs.nix19
-rw-r--r--nixos/tests/gnome3-gdm.nix15
-rw-r--r--nixos/tests/gnome3.nix16
-rw-r--r--nixos/tests/gocd-agent.nix40
-rw-r--r--nixos/tests/gocd-server.nix28
-rw-r--r--nixos/tests/grafana.nix25
-rw-r--r--nixos/tests/graphite.nix38
-rw-r--r--nixos/tests/haka.nix24
-rw-r--r--nixos/tests/haproxy.nix41
-rw-r--r--nixos/tests/hardened.nix67
-rw-r--r--nixos/tests/hibernate.nix43
-rw-r--r--nixos/tests/hitch/default.nix33
-rw-r--r--nixos/tests/hitch/example.pem53
-rw-r--r--nixos/tests/hitch/example/index.txt1
-rw-r--r--nixos/tests/hocker-fetchdocker/default.nix15
-rw-r--r--nixos/tests/hocker-fetchdocker/hello-world-container.nix19
-rw-r--r--nixos/tests/hocker-fetchdocker/machine.nix26
-rw-r--r--nixos/tests/home-assistant.nix77
-rw-r--r--nixos/tests/hound.nix58
-rwxr-xr-xnixos/tests/hydra/create-trivial-project.sh56
-rw-r--r--nixos/tests/hydra/default.nix78
-rw-r--r--nixos/tests/i3wm.nix2
-rw-r--r--nixos/tests/iftop.nix34
-rw-r--r--nixos/tests/influxdb.nix12
-rw-r--r--nixos/tests/initrd-network-ssh/default.nix59
-rw-r--r--nixos/tests/initrd-network-ssh/dropbear.privbin0 -> 1573 bytes
-rw-r--r--nixos/tests/initrd-network-ssh/dropbear.pub1
-rw-r--r--nixos/tests/initrd-network-ssh/generate-keys.nix12
-rw-r--r--nixos/tests/initrd-network-ssh/openssh.priv51
-rw-r--r--nixos/tests/initrd-network-ssh/openssh.pub1
-rw-r--r--nixos/tests/initrd-network.nix22
-rw-r--r--nixos/tests/installer.nix342
-rw-r--r--nixos/tests/ipfs.nix55
-rw-r--r--nixos/tests/ipv6.nix14
-rw-r--r--nixos/tests/jenkins.nix7
-rw-r--r--nixos/tests/kafka.nix69
-rw-r--r--nixos/tests/kde4.nix68
-rw-r--r--nixos/tests/kernel-copperhead.nix19
-rw-r--r--nixos/tests/kernel-latest.nix17
-rw-r--r--nixos/tests/kernel-lts.nix17
-rw-r--r--nixos/tests/keymap.nix154
-rw-r--r--nixos/tests/krb5/default.nix5
-rw-r--r--nixos/tests/krb5/deprecated-config.nix48
-rw-r--r--nixos/tests/krb5/example-config.nix106
-rw-r--r--nixos/tests/kubernetes.nix182
-rw-r--r--nixos/tests/kubernetes/base.nix112
-rw-r--r--nixos/tests/kubernetes/certs.nix219
-rw-r--r--nixos/tests/kubernetes/default.nix7
-rw-r--r--nixos/tests/kubernetes/dns.nix127
-rw-r--r--nixos/tests/kubernetes/e2e.nix40
-rw-r--r--nixos/tests/kubernetes/kubernetes-common.nix58
-rw-r--r--nixos/tests/kubernetes/rbac.nix137
-rw-r--r--nixos/tests/ldap.nix119
-rw-r--r--nixos/tests/leaps.nix30
-rw-r--r--nixos/tests/lightdm.nix2
-rw-r--r--nixos/tests/login.nix5
-rw-r--r--nixos/tests/logstash.nix43
-rw-r--r--nixos/tests/make-test.nix2
-rw-r--r--nixos/tests/mathics.nix20
-rw-r--r--nixos/tests/matrix-synapse.nix30
-rw-r--r--nixos/tests/memcached.nix28
-rw-r--r--nixos/tests/mesos.nix97
-rw-r--r--nixos/tests/mesos_test.py72
-rw-r--r--nixos/tests/minio.nix34
-rw-r--r--nixos/tests/misc.nix35
-rw-r--r--nixos/tests/mongodb.nix34
-rw-r--r--nixos/tests/morty.nix32
-rw-r--r--nixos/tests/mpich.nix41
-rw-r--r--nixos/tests/mumble.nix11
-rw-r--r--nixos/tests/munin.nix3
-rw-r--r--nixos/tests/mutable-users.nix39
-rw-r--r--nixos/tests/mysql-backup.nix50
-rw-r--r--nixos/tests/mysql-replication.nix34
-rw-r--r--nixos/tests/mysql.nix2
-rw-r--r--nixos/tests/nat.nix79
-rw-r--r--nixos/tests/netdata.nix31
-rw-r--r--nixos/tests/networking.nix923
-rw-r--r--nixos/tests/nexus.nix32
-rw-r--r--nixos/tests/nfs.nix8
-rw-r--r--nixos/tests/nghttpx.nix61
-rw-r--r--nixos/tests/nginx.nix42
-rw-r--r--nixos/tests/nix-ssh-serve.nix39
-rw-r--r--nixos/tests/novacomd.nix28
-rw-r--r--nixos/tests/nsd.nix31
-rw-r--r--nixos/tests/openldap.nix35
-rw-r--r--nixos/tests/openssh.nix40
-rw-r--r--nixos/tests/osquery.nix28
-rw-r--r--nixos/tests/owncloud.nix39
-rw-r--r--nixos/tests/pam-oath-login.nix126
-rw-r--r--nixos/tests/panamax.nix21
-rw-r--r--nixos/tests/partition.nix2
-rw-r--r--nixos/tests/pgjwt.nix37
-rw-r--r--nixos/tests/pgmanage.nix39
-rw-r--r--nixos/tests/phabricator.nix2
-rw-r--r--nixos/tests/php-pcre.nix44
-rw-r--r--nixos/tests/plasma5.nix61
-rw-r--r--nixos/tests/plotinus.nix27
-rw-r--r--nixos/tests/postgis.nix26
-rw-r--r--nixos/tests/postgresql.nix54
-rw-r--r--nixos/tests/powerdns.nix12
-rw-r--r--nixos/tests/predictable-interface-names.nix24
-rw-r--r--nixos/tests/printing.nix11
-rw-r--r--nixos/tests/prometheus.nix26
-rw-r--r--nixos/tests/prosody.nix75
-rw-r--r--nixos/tests/quagga.nix97
-rw-r--r--nixos/tests/quake3.nix25
-rw-r--r--nixos/tests/radicale.nix106
-rw-r--r--nixos/tests/riak.nix2
-rw-r--r--nixos/tests/rspamd.nix140
-rw-r--r--nixos/tests/run-in-machine.nix17
-rw-r--r--nixos/tests/rxe.nix53
-rw-r--r--nixos/tests/samba.nix47
-rw-r--r--nixos/tests/sddm.nix66
-rw-r--r--nixos/tests/simple.nix4
-rw-r--r--nixos/tests/slim.nix66
-rw-r--r--nixos/tests/slurm.nix92
-rw-r--r--nixos/tests/smokeping.nix33
-rw-r--r--nixos/tests/snapper.nix43
-rw-r--r--nixos/tests/ssh-keys.nix15
-rw-r--r--nixos/tests/statsd.nix51
-rw-r--r--nixos/tests/strongswan-swanctl.nix148
-rw-r--r--nixos/tests/sudo.nix93
-rw-r--r--nixos/tests/switch-test.nix25
-rw-r--r--nixos/tests/systemd.nix68
-rw-r--r--nixos/tests/taskserver.nix289
-rwxr-xr-xnixos/tests/test-config-examples.sh14
-rw-r--r--nixos/tests/testdb.sql1
-rw-r--r--nixos/tests/timezone.nix45
-rw-r--r--nixos/tests/tomcat.nix5
-rw-r--r--nixos/tests/trac.nix4
-rw-r--r--nixos/tests/transmission.nix21
-rw-r--r--nixos/tests/udisks2.nix5
-rw-r--r--nixos/tests/vault.nix23
-rw-r--r--nixos/tests/virtualbox.nix273
-rw-r--r--nixos/tests/wordpress.nix56
-rw-r--r--nixos/tests/xautolock.nix24
-rw-r--r--nixos/tests/xdg-desktop-portal.nix17
-rw-r--r--nixos/tests/xfce.nix8
-rw-r--r--nixos/tests/xmonad.nix29
-rw-r--r--nixos/tests/xrdp.nix45
-rw-r--r--nixos/tests/xss-lock.nix25
-rw-r--r--nixos/tests/yabar.nix25
-rw-r--r--nixos/tests/zfs.nix85
-rw-r--r--nixos/tests/zookeeper.nix28
204 files changed, 10753 insertions, 1443 deletions
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
new file mode 100644
index 000000000000..21b0fedcfefe
--- /dev/null
+++ b/nixos/tests/acme.nix
@@ -0,0 +1,64 @@
+let
+  commonConfig = { config, lib, pkgs, nodes, ... }: {
+    networking.nameservers = [
+      nodes.letsencrypt.config.networking.primaryIPAddress
+    ];
+
+    nixpkgs.overlays = lib.singleton (self: super: {
+      cacert = super.cacert.overrideDerivation (drv: {
+        installPhase = (drv.installPhase or "") + ''
+          cat "${nodes.letsencrypt.config.test-support.letsencrypt.caCert}" \
+            >> "$out/etc/ssl/certs/ca-bundle.crt"
+        '';
+      });
+
+      pythonPackages = (super.python.override {
+        packageOverrides = lib.const (pysuper: {
+          certifi = pysuper.certifi.overridePythonAttrs (attrs: {
+            postPatch = (attrs.postPatch or "") + ''
+              cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \
+                > certifi/cacert.pem
+            '';
+          });
+        });
+      }).pkgs;
+    });
+  };
+
+in import ./make-test.nix {
+  name = "acme";
+
+  nodes = {
+    letsencrypt = ./common/letsencrypt.nix;
+
+    webserver = { config, pkgs, ... }: {
+      imports = [ commonConfig ];
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      networking.extraHosts = ''
+        ${config.networking.primaryIPAddress} example.com
+      '';
+
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."example.com" = {
+        enableACME = true;
+        forceSSL = true;
+        locations."/".root = pkgs.runCommand "docroot" {} ''
+          mkdir -p "$out"
+          echo hello world > "$out/index.html"
+        '';
+      };
+    };
+
+    client = commonConfig;
+  };
+
+  testScript = ''
+    $letsencrypt->waitForUnit("default.target");
+    $letsencrypt->waitForUnit("boulder.service");
+    $webserver->waitForUnit("default.target");
+    $webserver->waitForUnit("acme-certificates.target");
+    $client->waitForUnit("default.target");
+    $client->succeed('curl https://example.com/ | grep -qF "hello world"');
+  '';
+}
diff --git a/nixos/tests/ammonite.nix b/nixos/tests/ammonite.nix
new file mode 100644
index 000000000000..e1dee71fddf2
--- /dev/null
+++ b/nixos/tests/ammonite.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ammonite";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes = {
+    amm =
+      { config, pkgs, ... }:
+        {
+          environment.systemPackages = [ pkgs.ammonite ];
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $amm->succeed("amm -c 'val foo = 21; println(foo * 2)' | grep 42")
+  '';
+})
diff --git a/nixos/tests/atd.nix b/nixos/tests/atd.nix
new file mode 100644
index 000000000000..5260c8ddfb82
--- /dev/null
+++ b/nixos/tests/atd.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+{
+  name = "atd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bjornfor ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { services.atd.enable = true;
+      users.extraUsers.alice = { isNormalUser = true; };
+    };
+
+  # "at" has a resolution of 1 minute
+  testScript = ''
+    startAll;
+
+    $machine->fail("test -f ~root/at-1");
+    $machine->fail("test -f ~alice/at-1");
+
+    $machine->succeed("echo 'touch ~root/at-1' | at now+1min");
+    $machine->succeed("su - alice -c \"echo 'touch at-1' | at now+1min\"");
+
+    $machine->succeed("sleep 1.5m");
+
+    $machine->succeed("test -f ~root/at-1");
+    $machine->succeed("test -f ~alice/at-1");
+  '';
+})
diff --git a/nixos/tests/avahi.nix b/nixos/tests/avahi.nix
index a8369a6d1f88..976a770e887c 100644
--- a/nixos/tests/avahi.nix
+++ b/nixos/tests/avahi.nix
@@ -5,18 +5,21 @@ import ./make-test.nix ({ pkgs, ... } : {
     maintainers = [ eelco chaoflow ];
   };
 
-  nodes = {
-    one =
-      { config, pkgs, ... }: {
-        services.avahi.enable = true;
-        services.avahi.nssmdns = true;
-      };
-
-    two =
-      { config, pkgs, ... }: {
-        services.avahi.enable = true;
-        services.avahi.nssmdns = true;
+  nodes = let
+    cfg = { config, pkgs, ... }: {
+      services.avahi = {
+        enable = true;
+        nssmdns = true;
+        publish.addresses = true;
+        publish.domain = true;
+        publish.enable = true;
+        publish.userServices = true;
+        publish.workstation = true;
       };
+    };
+  in {
+    one = cfg;
+    two = cfg;
   };
 
   testScript =
diff --git a/nixos/tests/beegfs.nix b/nixos/tests/beegfs.nix
new file mode 100644
index 000000000000..433910feafe3
--- /dev/null
+++ b/nixos/tests/beegfs.nix
@@ -0,0 +1,115 @@
+import ./make-test.nix ({ pkgs, ... } :
+
+let
+  connAuthFile="beegfs/auth-def.key";
+
+  client = { config, pkgs, lib, ... } : {
+    networking.firewall.enable = false;
+    services.beegfsEnable = true;
+    services.beegfs.default = {
+      mgmtdHost = "mgmt";
+      connAuthFile = "/etc/${connAuthFile}";
+      client = {
+        mount = false;
+        enable = true;
+      };
+    };
+
+    fileSystems = pkgs.lib.mkVMOverride # FIXME: this should be creatd by the module
+      [ { mountPoint = "/beegfs";
+          device = "default";
+          fsType = "beegfs";
+          options = [ "cfgFile=/etc/beegfs/client-default.conf" "_netdev" ];
+        }
+      ];
+
+    environment.etc."${connAuthFile}" = {
+      enable = true;
+      text = "ThisIsALousySecret";
+      mode = "0600";
+    };
+  };
+
+
+  server = service : { config, pkgs, lib, ... } : {
+    networking.firewall.enable = false;
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 4096 ];
+
+    fileSystems = pkgs.lib.mkVMOverride
+      [ { mountPoint = "/data";
+          device = "/dev/disk/by-label/data";
+          fsType = "ext4";
+        }
+      ];
+
+    environment.systemPackages = with pkgs; [ beegfs ];
+    environment.etc."${connAuthFile}" = {
+      enable = true;
+      text = "ThisIsALousySecret";
+      mode = "0600";
+    };
+
+    services.beegfsEnable = true;
+    services.beegfs.default = {
+      mgmtdHost = "mgmt";
+      connAuthFile = "/etc/${connAuthFile}";
+      "${service}" = {
+        enable = true;
+        storeDir = "/data";
+      };
+    };
+  };
+
+in
+{
+  name = "beegfs";
+
+  nodes = {
+    meta = server "meta";
+    mgmt = server "mgmtd";
+    storage1 = server "storage";
+    storage2 = server "storage";
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    # Initalize the data directories
+    $mgmt->waitForUnit("default.target");
+    $mgmt->succeed("beegfs-setup-mgmtd -C -f -p /data");
+    $mgmt->succeed("systemctl start beegfs-mgmtd-default");
+
+    $meta->waitForUnit("default.target");
+    $meta->succeed("beegfs-setup-meta -C -f -s 1 -p /data");
+    $meta->succeed("systemctl start beegfs-meta-default");
+
+    $storage1->waitForUnit("default.target");
+    $storage1->succeed("beegfs-setup-storage -C -f -s 1 -i 1 -p /data");
+    $storage1->succeed("systemctl start beegfs-storage-default");
+
+    $storage2->waitForUnit("default.target");
+    $storage2->succeed("beegfs-setup-storage -C -f -s 2 -i 2 -p /data");
+    $storage2->succeed("systemctl start beegfs-storage-default");
+
+    #
+
+    # Basic test
+    $client1->waitForUnit("beegfs.mount");
+    $client1->succeed("beegfs-check-servers-default");
+    $client1->succeed("echo test > /beegfs/test");
+    $client2->waitForUnit("beegfs.mount");
+    $client2->succeed("test -e /beegfs/test");
+    $client2->succeed("cat /beegfs/test | grep test");
+
+    # test raid0/stripping
+    $client1->succeed("dd if=/dev/urandom bs=1M count=10 of=/beegfs/striped");
+    $client2->succeed("cat /beegfs/striped > /dev/null");
+
+    # check if fs is still healthy
+    $client1->succeed("beegfs-fsck-default --checkfs");
+  '';
+})
diff --git a/nixos/tests/bind.nix b/nixos/tests/bind.nix
new file mode 100644
index 000000000000..1f8c1dc7be40
--- /dev/null
+++ b/nixos/tests/bind.nix
@@ -0,0 +1,27 @@
+import ./make-test.nix {
+  name = "bind";
+
+  machine = { pkgs, lib, ... }: {
+    services.bind.enable = true;
+    services.bind.extraOptions = "empty-zones-enable no;";
+    services.bind.zones = lib.singleton {
+      name = ".";
+      file = pkgs.writeText "root.zone" ''
+        $TTL 3600
+        . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+        . IN NS ns.example.org.
+
+        ns.example.org. IN A    192.168.0.1
+        ns.example.org. IN AAAA abcd::1
+
+        1.0.168.192.in-addr.arpa IN PTR ns.example.org.
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit('bind.service');
+    $machine->waitForOpenPort(53);
+    $machine->succeed('host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org');
+  '';
+}
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 95bba075612d..50c98664660a 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -11,12 +11,12 @@ import ./make-test.nix ({ pkgs, ... }:
 let
 
   # Some random file to serve.
-  file = pkgs.nixUnstable.src;
+  file = pkgs.hello.src;
 
   miniupnpdConf = nodes: pkgs.writeText "miniupnpd.conf"
     ''
       ext_ifname=eth1
-      listening_ip=${(pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ip4).address}/24
+      listening_ip=${(pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address}/24
       allow 1024-65535 192.168.2.0/24 1024-65535
     '';
 
@@ -25,7 +25,7 @@ in
 {
   name = "bittorrent";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric eelco chaoflow rob wkennington ];
+    maintainers = [ domenkozar eelco chaoflow rob wkennington ];
   };
 
   nodes =
@@ -56,7 +56,7 @@ in
         { environment.systemPackages = [ pkgs.transmission ];
           virtualisation.vlans = [ 2 ];
           networking.defaultGateway =
-            (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ip4).address;
+            (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
           networking.firewall.enable = false;
         };
 
@@ -84,7 +84,7 @@ in
       # Create the torrent.
       $tracker->succeed("mkdir /tmp/data");
       $tracker->succeed("cp ${file} /tmp/data/test.tar.bz2");
-      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 -p -t http://${(pkgs.lib.head nodes.tracker.config.networking.interfaces.eth1.ip4).address}:6969/announce -o /tmp/test.torrent");
+      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 -p -t http://${(pkgs.lib.head nodes.tracker.config.networking.interfaces.eth1.ipv4.addresses).address}:6969/announce -o /tmp/test.torrent");
       $tracker->succeed("chmod 644 /tmp/test.torrent");
 
       # Start the tracker.  !!! use a less crappy tracker
diff --git a/nixos/tests/blivet.nix b/nixos/tests/blivet.nix
index 33a79e65efe1..2adc2ee1eeea 100644
--- a/nixos/tests/blivet.nix
+++ b/nixos/tests/blivet.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... }: with pkgs.pythonPackages; rec {
+import ./make-test.nix ({ pkgs, ... }: with pkgs.python2Packages; rec {
   name = "blivet";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ aszlig ];
@@ -69,6 +69,7 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.pythonPackages; rec {
     sed -i \
       -e '1i import tempfile' \
       -e 's|_STORE_FILE_PATH = .*|_STORE_FILE_PATH = tempfile.gettempdir()|' \
+      -e 's|DEFAULT_STORE_SIZE = .*|DEFAULT_STORE_SIZE = 409600|' \
       tests/loopbackedtestcase.py
 
     PYTHONPATH=".:$(< "${pkgs.stdenv.mkDerivation {
diff --git a/nixos/tests/boot-stage1.nix b/nixos/tests/boot-stage1.nix
new file mode 100644
index 000000000000..b2e74bff6fcd
--- /dev/null
+++ b/nixos/tests/boot-stage1.nix
@@ -0,0 +1,162 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "boot-stage1";
+
+  machine = { config, pkgs, lib, ... }: {
+    boot.extraModulePackages = let
+      compileKernelModule = name: source: pkgs.runCommandCC name rec {
+        inherit source;
+        kdev = config.boot.kernelPackages.kernel.dev;
+        kver = config.boot.kernelPackages.kernel.modDirVersion;
+        ksrc = "${kdev}/lib/modules/${kver}/build";
+        hardeningDisable = [ "pic" ];
+        nativeBuildInputs = kdev.moduleBuildDependencies;
+      } ''
+        echo "obj-m += $name.o" > Makefile
+        echo "$source" > "$name.c"
+        make -C "$ksrc" M=$(pwd) modules
+        install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
+      '';
+
+      # This spawns a kthread which just waits until it gets a signal and
+      # terminates if that is the case. We want to make sure that nothing during
+      # the boot process kills any kthread by accident, like what happened in
+      # issue #15226.
+      kcanary = compileKernelModule "kcanary" ''
+        #include <linux/version.h>
+        #include <linux/init.h>
+        #include <linux/module.h>
+        #include <linux/kernel.h>
+        #include <linux/kthread.h>
+        #include <linux/sched.h>
+        #include <linux/signal.h>
+        #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+        #include <linux/sched/signal.h>
+        #endif
+
+        struct task_struct *canaryTask;
+
+        static int kcanary(void *nothing)
+        {
+          allow_signal(SIGINT);
+          allow_signal(SIGTERM);
+          allow_signal(SIGKILL);
+          while (!kthread_should_stop()) {
+            set_current_state(TASK_INTERRUPTIBLE);
+            schedule_timeout_interruptible(msecs_to_jiffies(100));
+            if (signal_pending(current)) break;
+          }
+          return 0;
+        }
+
+        static int kcanaryInit(void)
+        {
+          kthread_run(&kcanary, NULL, "kcanary");
+          return 0;
+        }
+
+        static void kcanaryExit(void)
+        {
+          kthread_stop(canaryTask);
+        }
+
+        module_init(kcanaryInit);
+        module_exit(kcanaryExit);
+      '';
+
+    in lib.singleton kcanary;
+
+    boot.initrd.kernelModules = [ "kcanary" ];
+
+    boot.initrd.extraUtilsCommands = let
+      compile = name: source: pkgs.runCommandCC name { inherit source; } ''
+        mkdir -p "$out/bin"
+        echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
+      '';
+
+      daemonize = name: source: compile name ''
+        #include <stdio.h>
+        #include <unistd.h>
+
+        void runSource(void) {
+        ${source}
+        }
+
+        int main(void) {
+          if (fork() > 0) return 0;
+          setsid();
+          runSource();
+          return 1;
+        }
+      '';
+
+      mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name ''
+        char *argv[] = {"${cmdline}", NULL};
+        execvp("${name}-child", argv);
+      '') // {
+        child = compile "${name}-child" ''
+          #include <stdio.h>
+          #include <unistd.h>
+
+          int main(void) {
+            ${source}
+            while (1) sleep(1);
+            return 1;
+          }
+        '';
+      };
+
+      copyCanaries = with lib; concatMapStrings (canary: ''
+        ${optionalString (canary ? child) ''
+          copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
+        ''}
+        copy_bin_and_libs "${canary}/bin/${canary.name}"
+      '');
+
+    in copyCanaries [
+      # Simple canary process which just sleeps forever and should be killed by
+      # stage 2.
+      (daemonize "canary1" "while (1) sleep(1);")
+
+      # We want this canary process to try mimicking a kthread using a cmdline
+      # with a zero length so we can make sure that the process is properly
+      # killed in stage 1.
+      (mkCmdlineCanary {
+        name = "canary2";
+        source = ''
+          FILE *f;
+          f = fopen("/run/canary2.pid", "w");
+          fprintf(f, "%d\n", getpid());
+          fclose(f);
+        '';
+      })
+
+      # This canary process mimicks a storage daemon, which we do NOT want to be
+      # killed before going into stage 2. For more on root storage daemons, see:
+      # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
+      (mkCmdlineCanary {
+        name = "canary3";
+        cmdline = "@canary3";
+      })
+    ];
+
+    boot.initrd.postMountCommands = ''
+      canary1
+      canary2
+      canary3
+      # Make sure the pidfile of canary 2 is created so that we still can get
+      # its former pid after the killing spree starts next within stage 1.
+      while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
+    '';
+  };
+
+  testScript = ''
+    $machine->waitForUnit("multi-user.target");
+    $machine->succeed('test -s /run/canary2.pid');
+    $machine->fail('pgrep -a canary1');
+    $machine->fail('kill -0 $(< /run/canary2.pid)');
+    $machine->succeed('pgrep -a -f \'^@canary3$\''');
+    $machine->succeed('pgrep -a -f \'^kcanary$\''');
+  '';
+
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ aszlig ];
+})
diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix
index 6a1d330155e1..301d9d0f817f 100644
--- a/nixos/tests/boot.nix
+++ b/nixos/tests/boot.nix
@@ -1,7 +1,6 @@
 { system ? builtins.currentSystem }:
 
 with import ../lib/testing.nix { inherit system; };
-with import ../lib/qemu-flags.nix;
 with pkgs.lib;
 
 let
@@ -12,7 +11,6 @@ let
       modules =
         [ ../modules/installer/cd-dvd/installation-cd-minimal.nix
           ../modules/testing/test-instrumentation.nix
-          { key = "serial"; }
         ];
     }).config.system.build.isoImage;
 
@@ -26,23 +24,73 @@ let
           my $machine = createMachine({ ${machineConfig}, qemuFlags => '-m 768' });
           $machine->start;
           $machine->waitForUnit("multi-user.target");
+          $machine->succeed("nix verify -r --no-trust /run/current-system");
+
+          # Test whether the channel got installed correctly.
+          $machine->succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello");
+          $machine->succeed("nix-env --dry-run -iA nixos.procps");
+
           $machine->shutdown;
         '';
     };
 in {
-    bootBiosCdrom = makeBootTest "bios-cdrom" ''
+
+    biosCdrom = makeBootTest "bios-cdrom" ''
         cdrom => glob("${iso}/iso/*.iso")
       '';
-    bootBiosUsb = makeBootTest "bios-usb" ''
+
+    biosUsb = makeBootTest "bios-usb" ''
         usb => glob("${iso}/iso/*.iso")
       '';
-    bootUefiCdrom = makeBootTest "uefi-cdrom" ''
+
+    uefiCdrom = makeBootTest "uefi-cdrom" ''
         cdrom => glob("${iso}/iso/*.iso"),
-        bios => '${pkgs.OVMF}/FV/OVMF.fd'
+        bios => '${pkgs.OVMF.fd}/FV/OVMF.fd'
       '';
-    bootUefiUsb = makeBootTest "uefi-usb" ''
+
+    uefiUsb = makeBootTest "uefi-usb" ''
         usb => glob("${iso}/iso/*.iso"),
-        bios => '${pkgs.OVMF}/FV/OVMF.fd'
+        bios => '${pkgs.OVMF.fd}/FV/OVMF.fd'
       '';
-  }
 
+    netboot = let
+      config = (import ../lib/eval-config.nix {
+          inherit system;
+          modules =
+            [ ../modules/installer/netboot/netboot.nix
+              ../modules/testing/test-instrumentation.nix
+              { key = "serial"; }
+            ];
+        }).config;
+      ipxeScriptDir = pkgs.writeTextFile {
+        name = "ipxeScriptDir";
+        text = ''
+          #!ipxe
+          dhcp
+          kernel bzImage init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0
+          initrd initrd
+          boot
+        '';
+        destination = "/boot.ipxe";
+      };
+      ipxeBootDir = pkgs.symlinkJoin {
+        name = "ipxeBootDir";
+        paths = [
+          config.system.build.netbootRamdisk
+          config.system.build.kernel
+          ipxeScriptDir
+        ];
+      };
+    in
+      makeTest {
+        name = "boot-netboot";
+        nodes = { };
+        testScript =
+          ''
+            my $machine = createMachine({ qemuFlags => '-boot order=n -net nic,model=e1000 -net user,tftp=${ipxeBootDir}/,bootfile=boot.ipxe -m 2000M' });
+            $machine->start;
+            $machine->waitForUnit("multi-user.target");
+            $machine->shutdown;
+          '';
+      };
+}
diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix
new file mode 100644
index 000000000000..36731773de27
--- /dev/null
+++ b/nixos/tests/borgbackup.nix
@@ -0,0 +1,162 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  passphrase = "supersecret";
+  dataDir = "/ran:dom/data";
+  excludeFile = "not_this_file";
+  keepFile = "important_file";
+  keepFileData = "important_data";
+  localRepo = "/root/back:up";
+  archiveName = "my_archive";
+  remoteRepo = "borg@server:."; # No need to specify path
+  privateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+    RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+    AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+    9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+  publicKey = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
+  '';
+  privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
+    cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
+    AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
+    IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+  publicKeyAppendOnly = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
+  '';
+
+in {
+  name = "borgbackup";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ dotlambda ];
+  };
+
+  nodes = {
+    client = { config, pkgs, ... }: {
+      services.borgbackup.jobs = {
+        
+        local = rec {
+          paths = dataDir;
+          repo = localRepo;
+          preHook = ''
+            # Don't append a timestamp
+            archiveName="${archiveName}"
+          '';
+          encryption = {
+            mode = "repokey";
+            inherit passphrase;
+          };
+          compression = "auto,zlib,9";
+          prune.keep = {
+            within = "1y";
+            yearly = 5;
+          };
+          exclude = [ "*/${excludeFile}" ];
+          postHook = "echo post";
+          startAt = [ ]; # Do not run automatically
+        };
+
+        remote = {
+          paths = dataDir;
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+        remoteAppendOnly = {
+          paths = dataDir;
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
+        };
+
+      };
+    };
+
+    server = { config, pkgs, ... }: {
+      services.openssh = {
+        enable = true;
+        passwordAuthentication = false;
+        challengeResponseAuthentication = false;
+      };
+
+      services.borgbackup.repos.repo1 = {
+        authorizedKeys = [ publicKey ];
+        path = "/data/borgbackup";
+      };
+
+      # Second repo to make sure the authorizedKeys options are merged correctly
+      services.borgbackup.repos.repo2 = {
+        authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
+        path = "/data/borgbackup";
+        quota = ".5G";
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $client->fail('test -d "${remoteRepo}"');
+
+    $client->succeed("cp ${privateKey} /root/id_ed25519");
+    $client->succeed("chmod 0600 /root/id_ed25519");
+    $client->succeed("cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly");
+    $client->succeed("chmod 0600 /root/id_ed25519.appendOnly");
+
+    $client->succeed("mkdir -p ${dataDir}");
+    $client->succeed("touch ${dataDir}/${excludeFile}");
+    $client->succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}");
+
+    subtest "local", sub {
+      my $borg = "BORG_PASSPHRASE='${passphrase}' borg";
+      $client->systemctl("start --wait borgbackup-job-local");
+      $client->fail("systemctl is-failed borgbackup-job-local");
+      # Make sure exactly one archive has been created
+      $client->succeed("c=\$($borg list '${localRepo}' | wc -l) && [[ \$c == '1' ]]");
+      # Make sure excludeFile has been excluded
+      $client->fail("$borg list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'");
+      # Make sure keepFile has the correct content
+      $client->succeed("$borg extract '${localRepo}::${archiveName}'");
+      $client->succeed('c=$(cat ${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
+    };
+
+    subtest "remote", sub {
+      my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg";
+      $server->waitForUnit("sshd.service");
+      $client->waitForUnit("network.target");
+      $client->systemctl("start --wait borgbackup-job-remote");
+      $client->fail("systemctl is-failed borgbackup-job-remote");
+
+      # Make sure we can't access repos other than the specified one
+      $client->fail("$borg list borg\@server:wrong");
+
+      #TODO: Make sure that data is actually deleted
+    };
+
+    subtest "remoteAppendOnly", sub {
+      my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg";
+      $server->waitForUnit("sshd.service");
+      $client->waitForUnit("network.target");
+      $client->systemctl("start --wait borgbackup-job-remoteAppendOnly");
+      $client->fail("systemctl is-failed borgbackup-job-remoteAppendOnly");
+
+      # Make sure we can't access repos other than the specified one
+      $client->fail("$borg list borg\@server:wrong");
+
+      #TODO: Make sure that data is not actually deleted
+    };
+
+  '';
+})
diff --git a/nixos/tests/buildbot.nix b/nixos/tests/buildbot.nix
new file mode 100644
index 000000000000..828846f17c89
--- /dev/null
+++ b/nixos/tests/buildbot.nix
@@ -0,0 +1,111 @@
+# Test ensures buildbot master comes up correctly and workers can connect
+
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "buildbot";
+
+  nodes = {
+    bbmaster = { config, pkgs, ... }: {
+      services.buildbot-master = {
+        enable = true;
+        package = pkgs.buildbot-full;
+
+        # NOTE: use fake repo due to no internet in hydra ci
+        factorySteps = [
+          "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
+          "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
+        ];
+        changeSource = [
+          "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
+      environment.systemPackages = with pkgs; [ git buildbot-full ];
+    };
+
+    bbworker = { config, pkgs, ... }: {
+      services.buildbot-worker = {
+        enable = true;
+        masterUrl = "bbmaster:9989";
+      };
+      environment.systemPackages = with pkgs; [ git buildbot-worker ];
+    };
+
+    gitrepo = { config, pkgs, ... }: {
+      services.openssh.enable = true;
+      networking.firewall.allowedTCPPorts = [ 22 9418 ];
+      environment.systemPackages = with pkgs; [ git ];
+    };
+  };
+
+  testScript = ''
+    #Start up and populate fake repo
+    $gitrepo->waitForUnit("multi-user.target");
+    print($gitrepo->execute(" \
+      git config --global user.name 'Nobody Fakeuser' && \
+      git config --global user.email 'nobody\@fakerepo.com' && \
+      rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \
+      mkdir -pv /srv/repos/fakerepo ~/.ssh && \
+      ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \
+      cat ~/.ssh/known_hosts && \
+      cd /srv/repos/fakerepo && \
+      git init && \
+      echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \
+      cat fakerepo.sh && \
+      touch .git/git-daemon-export-ok && \
+      git add fakerepo.sh .git/git-daemon-export-ok && \
+      git commit -m fakerepo && \
+      git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \
+    "));
+
+    # Test gitrepo
+    $bbmaster->waitForUnit("network-online.target");
+    #$bbmaster->execute("nc -z gitrepo 9418");
+    print($bbmaster->execute(" \
+      rm -rfv /tmp/fakerepo && \
+      git clone git://gitrepo/fakerepo /tmp/fakerepo && \
+      pwd && \
+      ls -la && \
+      ls -la /tmp/fakerepo \
+    "));
+
+    # Test start master and connect worker
+    $bbmaster->waitForUnit("buildbot-master.service");
+    $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/;
+    $bbworker->waitForUnit("network-online.target");
+    $bbworker->execute("nc -z bbmaster 8010");
+    $bbworker->execute("nc -z bbmaster 9989");
+    $bbworker->waitForUnit("buildbot-worker.service");
+    print($bbworker->execute("ls -la /home/bbworker/worker"));
+
+
+    # Test stop buildbot master and worker
+    print($bbmaster->execute(" \
+      systemctl -l --no-pager status buildbot-master && \
+      systemctl stop buildbot-master \
+    "));
+    $bbworker->fail("nc -z bbmaster 8010");
+    $bbworker->fail("nc -z bbmaster 9989");
+    print($bbworker->execute(" \
+      systemctl -l --no-pager status buildbot-worker && \
+      systemctl stop buildbot-worker && \
+      ls -la /home/bbworker/worker \
+    "));
+
+
+    # Test buildbot daemon mode
+    # NOTE: daemon mode tests disabled due to broken PYTHONPATH child inheritence
+    #
+    #$bbmaster->execute("buildbot create-master /tmp");
+    #$bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg");
+    #$bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg");
+    #$bbmaster->execute("buildbot start /tmp");
+    #$bbworker->execute("nc -z bbmaster 8011");
+    #$bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/;
+    #$bbmaster->execute("buildbot stop /tmp");
+    #$bbworker->fail("nc -z bbmaster 8011");
+
+  '';
+
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ];
+
+})
diff --git a/nixos/tests/cadvisor.nix b/nixos/tests/cadvisor.nix
index 1644cb856cec..f0083ab18e45 100644
--- a/nixos/tests/cadvisor.nix
+++ b/nixos/tests/cadvisor.nix
@@ -13,10 +13,6 @@ import ./make-test.nix ({ pkgs, ... } : {
       services.cadvisor.enable = true;
       services.cadvisor.storageDriver = "influxdb";
       services.influxdb.enable = true;
-      systemd.services.influxdb.postStart = mkAfter ''
-        ${pkgs.curl}/bin/curl -X POST 'http://localhost:8086/db?u=root&p=root' \
-          -d '{"name": "root"}'
-      '';
     };
   };
 
@@ -27,6 +23,12 @@ import ./make-test.nix ({ pkgs, ... } : {
       $machine->succeed("curl http://localhost:8080/containers/");
 
       $influxdb->waitForUnit("influxdb.service");
+
+      # create influxdb database
+      $influxdb->succeed(q~
+        curl -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE root"
+      ~);
+
       $influxdb->waitForUnit("cadvisor.service");
       $influxdb->succeed("curl http://localhost:8080/containers/");
     '';
diff --git a/nixos/tests/cassandra.nix b/nixos/tests/cassandra.nix
new file mode 100644
index 000000000000..b729e6b158bc
--- /dev/null
+++ b/nixos/tests/cassandra.nix
@@ -0,0 +1,68 @@
+import ./make-test.nix ({ pkgs, ...}:
+let
+  user = "cassandra";
+  nodeCfg = nodes: selfIP: cassandraOpts:
+    {
+      services.cassandra = {
+        enable = true;
+        listenAddress = selfIP;
+        rpcAddress = "0.0.0.0";
+        seeds = [ "192.168.1.1" ];
+        package = pkgs.cassandra_2_0;
+        jre = pkgs.openjdk;
+        clusterName = "ci ahoy";
+        authenticator = "PasswordAuthenticator";
+        authorizer = "CassandraAuthorizer";
+        user = user;
+      } // cassandraOpts;
+      nixpkgs.config.allowUnfree = true;
+      virtualisation.memorySize = 1024;
+    };
+
+in
+{
+  name = "cassandra-ci";
+
+  nodes = {
+    cass0 = {pkgs, config, nodes, ...}: nodeCfg nodes "192.168.1.1" {};
+    cass1 = {pkgs, config, nodes, ...}: nodeCfg nodes "192.168.1.2" {};
+    cass2 = {pkgs, config, nodes, ...}: nodeCfg nodes "192.168.1.3" {
+      extraParams = [
+        ''JVM_OPTS="$JVM_OPTS -Dcassandra.replace_address=192.168.1.2"''
+      ];
+      listenAddress = "192.168.1.3";
+    };
+  };
+
+  testScript = ''
+    subtest "start seed", sub {
+      $cass0->waitForUnit("cassandra.service");
+      $cass0->waitForOpenPort(9160);
+      $cass0->execute("echo show version | cqlsh localhost -u cassandra -p cassandra");
+      sleep 2;
+      $cass0->succeed("echo show version | cqlsh localhost -u cassandra -p cassandra");
+      $cass1->start;
+    };
+    subtest "cassandra user/group", sub {
+      $cass0->succeed("id \"${user}\" >/dev/null");
+      $cass1->succeed("id \"${user}\" >/dev/null");
+    };
+    subtest "bring up cassandra cluster", sub {
+      $cass1->waitForUnit("cassandra.service");
+      $cass0->waitUntilSucceeds("nodetool status | grep -c UN  | grep 2");
+    };
+    subtest "break and fix node", sub {
+      $cass0->block;
+      $cass0->waitUntilSucceeds("nodetool status | grep -c DN  | grep 1");
+      $cass0->unblock;
+      $cass0->waitUntilSucceeds("nodetool status | grep -c UN  | grep 2");
+    };
+    subtest "replace crashed node", sub {
+      $cass1->crash;
+      $cass2->start;
+      $cass2->waitForUnit("cassandra.service");
+      $cass0->waitUntilFails("nodetool status | grep UN  | grep 192.168.1.2");
+      $cass0->waitUntilSucceeds("nodetool status | grep UN | grep 192.168.1.3");
+    };
+  '';
+})
diff --git a/nixos/tests/ceph.nix b/nixos/tests/ceph.nix
new file mode 100644
index 000000000000..b9993062c079
--- /dev/null
+++ b/nixos/tests/ceph.nix
@@ -0,0 +1,140 @@
+import ./make-test.nix ({pkgs, ...}: rec {
+  name = "All-in-one-basic-ceph-cluster";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lejonet ];
+  };
+
+  nodes = {
+    aio = { config, pkgs, ... }: {
+      virtualisation = {
+        emptyDiskImages = [ 20480 20480 ];
+        vlans = [ 1 ];
+      };
+      
+      networking = {
+        firewall.allowPing = true;
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+
+      environment.systemPackages = with pkgs; [
+        bash
+        sudo
+        ceph
+        xfsprogs
+      ];
+      nixpkgs.config.packageOverrides = super: {
+        ceph = super.ceph.override({ nss = super.nss; libxfs = super.libxfs; libaio = super.libaio; jemalloc = super.jemalloc; });
+      };
+
+      boot.kernelModules = [ "xfs" ];
+
+      services.ceph.enable = true;
+      services.ceph.global = {
+        fsid = "066ae264-2a5d-4729-8001-6ad265f50b03";
+        monInitialMembers = "aio";
+        monHost = "192.168.1.1";
+      };
+
+      services.ceph.mon = {
+        enable = true;
+        daemons = [ "aio" ];
+      };
+
+      services.ceph.mgr = {
+        enable = true;
+        daemons = [ "aio" ];
+      };
+
+      services.ceph.osd = {
+        enable = true;
+        daemons = [ "0" "1" ];
+      };
+    };
+  };
+  
+  testScript = { nodes, ... }: ''
+    startAll;
+
+    $aio->waitForUnit("network.target");
+
+    # Create the ceph-related directories
+    $aio->mustSucceed(
+      "mkdir -p /var/lib/ceph/mgr/ceph-aio/",
+      "mkdir -p /var/lib/ceph/mon/ceph-aio/",
+      "mkdir -p /var/lib/ceph/osd/ceph-{0..1}/",
+      "chown ceph:ceph -R /var/lib/ceph/"
+    );
+
+    # Bootstrap ceph-mon daemon
+    $aio->mustSucceed(
+      "mkdir -p /var/lib/ceph/bootstrap-osd && chown ceph:ceph /var/lib/ceph/bootstrap-osd",
+      "sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
+      "ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --set-uid=0 --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
+      "ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
+            "monmaptool --create --add aio 192.168.1.1 --fsid 066ae264-2a5d-4729-8001-6ad265f50b03 /tmp/monmap",
+      "sudo -u ceph ceph-mon --mkfs -i aio --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
+      "touch /var/lib/ceph/mon/ceph-aio/done",
+      "systemctl start ceph-mon-aio"
+    );
+    $aio->waitForUnit("ceph-mon-aio");
+
+    # Can't check ceph status until a mon is up
+    $aio->succeed("ceph -s | grep 'mon: 1 daemons'");
+          
+    # Start the ceph-mgr daemon, it has no deps and hardly any setup
+    $aio->mustSucceed(
+      "ceph auth get-or-create mgr.aio mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-aio/keyring",
+      "systemctl start ceph-mgr-aio"
+    );
+    $aio->waitForUnit("ceph-mgr-aio");
+    $aio->waitUntilSucceeds("ceph -s | grep 'quorum aio'");
+
+    # Bootstrap both OSDs
+    $aio->mustSucceed(
+      "mkfs.xfs /dev/vdb",
+      "mkfs.xfs /dev/vdc",
+      "mount /dev/vdb /var/lib/ceph/osd/ceph-0",
+      "mount /dev/vdc /var/lib/ceph/osd/ceph-1",
+      "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-0/keyring --name osd.0 --add-key AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==",
+      "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-1/keyring --name osd.1 --add-key AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==",
+      "echo '{\"cephx_secret\": \"AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==\"}' | ceph osd new 55ba2294-3e24-478f-bee0-9dca4c231dd9 -i -",
+      "echo '{\"cephx_secret\": \"AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==\"}' | ceph osd new 5e97a838-85b6-43b0-8950-cb56d554d1e5 -i -"
+    );
+
+    # Initialize the OSDs with regular filestore
+    $aio->mustSucceed(
+      "ceph-osd -i 0 --mkfs --osd-uuid 55ba2294-3e24-478f-bee0-9dca4c231dd9",
+      "ceph-osd -i 1 --mkfs --osd-uuid 5e97a838-85b6-43b0-8950-cb56d554d1e5",
+      "chown -R ceph:ceph /var/lib/ceph/osd",
+      "systemctl start ceph-osd-0",
+      "systemctl start ceph-osd-1"
+    );
+
+    $aio->waitUntilSucceeds("ceph osd stat | grep '2 osds: 2 up, 2 in'");
+    $aio->waitUntilSucceeds("ceph -s | grep 'mgr: aio(active)'");
+    $aio->waitUntilSucceeds("ceph -s | grep 'HEALTH_OK'");
+
+    $aio->mustSucceed(
+      "ceph osd pool create aio-test 100 100",
+      "ceph osd pool ls | grep 'aio-test'",
+      "ceph osd pool rename aio-test aio-other-test",
+      "ceph osd pool ls | grep 'aio-other-test'",
+      "ceph -s | grep '1 pools, 100 pgs'",
+      "ceph osd getcrushmap -o crush",
+      "crushtool -d crush -o decrushed",
+      "sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
+      "crushtool -c modcrush -o recrushed",
+      "ceph osd setcrushmap -i recrushed",
+      "ceph osd pool set aio-other-test size 2"
+    );
+    $aio->waitUntilSucceeds("ceph -s | grep 'HEALTH_OK'");
+    $aio->waitUntilSucceeds("ceph -s | grep '100 active+clean'");
+    $aio->mustFail(
+      "ceph osd pool ls | grep 'aio-test'",
+      "ceph osd pool delete aio-other-test aio-other-test --yes-i-really-really-mean-it"
+    );
+  '';
+})
diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix
index 213dd4ca43b3..c341e83961a8 100644
--- a/nixos/tests/chromium.nix
+++ b/nixos/tests/chromium.nix
@@ -1,21 +1,27 @@
-import ./make-test.nix (
-{ pkgs
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; }
 , channelMap ? {
     stable = pkgs.chromium;
     beta   = pkgs.chromiumBeta;
     dev    = pkgs.chromiumDev;
   }
-, ...
-}: rec {
-  name = "chromium";
+}:
+
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+
+mapAttrs (channel: chromiumPkg: makeTest rec {
+  name = "chromium-${channel}";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ aszlig ];
   };
 
   enableOCR = true;
 
-  machine.imports = [ ./common/x11.nix ];
+  machine.imports = [ ./common/user-account.nix ./common/x11.nix ];
   machine.virtualisation.memorySize = 2047;
+  machine.services.xserver.displayManager.auto.user = "alice";
+  machine.environment.systemPackages = [ chromiumPkg ];
 
   startupHTML = pkgs.writeText "chromium-startup.html" ''
     <!DOCTYPE html>
@@ -26,8 +32,8 @@ import ./make-test.nix (
     </head>
     <body onload="javascript:document.title='startup done'">
       <img src="file://${pkgs.fetchurl {
-        url = "http://nixos.org/logo/nixos.svg";
-        sha256 = "0p2iaqcx2cj24xqycfw1pi4i5461gnn0034lafpi99ph435x6z68";
+        url = "http://nixos.org/logo/nixos-hex.svg";
+        sha256 = "0wxpp65npdw2cg8m0cxc9qff1sb3b478cxpg1741d8951g948rg8";
       }}" />
     </body>
     </html>
@@ -38,14 +44,20 @@ import ./make-test.nix (
       xdoScript = pkgs.writeText "${name}.xdo" text;
     in "${pkgs.xdotool}/bin/xdotool '${xdoScript}'";
   in ''
+    # Run as user alice
+    sub ru ($) {
+      my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
+      return "su - alice -c '$esc'";
+    }
+
     sub createNewWin {
       $machine->nest("creating a new Chromium window", sub {
-        $machine->execute("${xdo "new-window" ''
+        $machine->execute(ru "${xdo "new-window" ''
           search --onlyvisible --name "startup done"
           windowfocus --sync
           windowactivate --sync
         ''}");
-        $machine->execute("${xdo "new-window" ''
+        $machine->execute(ru "${xdo "new-window" ''
           key Ctrl+n
         ''}");
       });
@@ -53,16 +65,16 @@ import ./make-test.nix (
 
     sub closeWin {
       Machine::retry sub {
-        $machine->execute("${xdo "close-window" ''
+        $machine->execute(ru "${xdo "close-window" ''
           search --onlyvisible --name "new tab"
           windowfocus --sync
           windowactivate --sync
         ''}");
-        $machine->execute("${xdo "close-window" ''
+        $machine->execute(ru "${xdo "close-window" ''
           key Ctrl+w
         ''}");
         for (1..20) {
-          my ($status, $out) = $machine->execute("${xdo "wait-for-close" ''
+          my ($status, $out) = $machine->execute(ru "${xdo "wait-for-close" ''
             search --onlyvisible --name "new tab"
           ''}");
           return 1 if $status != 0;
@@ -75,13 +87,18 @@ import ./make-test.nix (
       my $ret = 0;
       $machine->nest("waiting for new Chromium window to appear", sub {
         for (1..20) {
-          my ($status, $out) = $machine->execute("${xdo "wait-for-window" ''
+          my ($status, $out) = $machine->execute(ru "${xdo "wait-for-window" ''
             search --onlyvisible --name "new tab"
             windowfocus --sync
             windowactivate --sync
           ''}");
           if ($status == 0) {
             $ret = 1;
+
+            # XXX: Somehow Chromium is not accepting keystrokes for a few
+            # seconds after a new window has appeared, so let's wait a while.
+            $machine->sleep(10);
+
             last;
           }
           $machine->sleep(1);
@@ -105,74 +122,77 @@ import ./make-test.nix (
       closeWin;
     }
 
-    sub chromiumTest {
-      my ($channel, $pkg, $code) = @_;
-      $machine->waitForX;
-
-      my $url = "file://${startupHTML}";
-      my $args = "--user-data-dir=/tmp/chromium-$channel";
-      $machine->execute(
-        "ulimit -c unlimited; ".
-        "$pkg/bin/chromium $args \"$url\" & disown"
-      );
-      $machine->waitForText(qr/Type to search or enter a URL to navigate/);
-      $machine->waitUntilSucceeds("${xdo "check-startup" ''
-        search --sync --onlyvisible --name "startup done"
-        # close first start help popup
-        key -delay 1000 Escape
+    $machine->waitForX;
+
+    my $url = "file://${startupHTML}";
+    $machine->execute(ru "ulimit -c unlimited; chromium \"$url\" & disown");
+    $machine->waitForText(qr/startup done/);
+    $machine->waitUntilSucceeds(ru "${xdo "check-startup" ''
+      search --sync --onlyvisible --name "startup done"
+      # close first start help popup
+      key -delay 1000 Escape
+      windowfocus --sync
+      windowactivate --sync
+    ''}");
+
+    createAndWaitForNewWin;
+    $machine->screenshot("empty_windows");
+    closeWin;
+
+    $machine->screenshot("startup_done");
+
+    testNewWin "check sandbox", sub {
+      $machine->succeed(ru "${xdo "type-url" ''
+        search --sync --onlyvisible --name "new tab"
         windowfocus --sync
-        windowactivate --sync
+        type --delay 1000 "chrome://sandbox"
       ''}");
 
-      createAndWaitForNewWin;
-      $machine->screenshot($channel."_emptywin");
-      closeWin;
-
-      $machine->screenshot($channel."_startup_done");
+      $machine->succeed(ru "${xdo "submit-url" ''
+        search --sync --onlyvisible --name "new tab"
+        windowfocus --sync
+        key --delay 1000 Return
+      ''}");
 
-      subtest("Chromium $channel", $code);
+      $machine->screenshot("sandbox_info");
 
-      $machine->shutdown;
-    }
+      $machine->succeed(ru "${xdo "find-window" ''
+        search --sync --onlyvisible --name "sandbox status"
+        windowfocus --sync
+      ''}");
+      $machine->succeed(ru "${xdo "copy-sandbox-info" ''
+        key --delay 1000 Ctrl+a Ctrl+c
+      ''}");
 
-    for (${let
-      mkArray = name: pkg: "[\"${name}\", \"${pkg}\"]";
-      chanArrays = pkgs.lib.mapAttrsToList mkArray channelMap;
-    in pkgs.lib.concatStringsSep ", " chanArrays}) {
-      my ($channel, $pkg) = @$_;
-      chromiumTest $channel, $pkg, sub {
-        testNewWin "check sandbox", sub {
-          $machine->succeed("${xdo "type-url" ''
-            search --sync --onlyvisible --name "new tab"
-            windowfocus --sync
-            type --delay 1000 "chrome://sandbox"
-          ''}");
+      my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o");
+      die "sandbox not working properly: $clipboard"
+      unless $clipboard =~ /namespace sandbox.*yes/mi
+          && $clipboard =~ /pid namespaces.*yes/mi
+          && $clipboard =~ /network namespaces.*yes/mi
+          && $clipboard =~ /seccomp.*sandbox.*yes/mi
+          && $clipboard =~ /you are adequately sandboxed/mi;
+
+      $machine->sleep(1);
+      $machine->succeed(ru "${xdo "find-window-after-copy" ''
+        search --onlyvisible --name "sandbox status"
+      ''}");
 
-          $machine->succeed("${xdo "submit-url" ''
-            search --sync --onlyvisible --name "new tab"
-            windowfocus --sync
-            key --delay 1000 Return
-          ''}");
+      my $clipboard = $machine->succeed(ru "echo void | ${pkgs.xclip}/bin/xclip -i");
+      $machine->succeed(ru "${xdo "copy-sandbox-info" ''
+        key --delay 1000 Ctrl+a Ctrl+c
+      ''}");
 
-          $machine->screenshot($channel."_sandbox");
+      my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o");
+      die "copying twice in a row does not work properly: $clipboard"
+      unless $clipboard =~ /namespace sandbox.*yes/mi
+          && $clipboard =~ /pid namespaces.*yes/mi
+          && $clipboard =~ /network namespaces.*yes/mi
+          && $clipboard =~ /seccomp.*sandbox.*yes/mi
+          && $clipboard =~ /you are adequately sandboxed/mi;
 
-          $machine->succeed("${xdo "submit-url" ''
-            search --sync --onlyvisible --name "sandbox status"
-            windowfocus --sync
-          ''}");
-          $machine->succeed("${xdo "submit-url" ''
-            key --delay 1000 Ctrl+a Ctrl+c
-          ''}");
+      $machine->screenshot("afer_copy_from_chromium");
+    };
 
-          my $clipboard = $machine->succeed("${pkgs.xclip}/bin/xclip -o");
-          die "sandbox not working properly: $clipboard"
-          unless $clipboard =~ /namespace sandbox.*yes/mi
-              && $clipboard =~ /pid namespaces.*yes/mi
-              && $clipboard =~ /network namespaces.*yes/mi
-              && $clipboard =~ /seccomp.*sandbox.*yes/mi
-              && $clipboard =~ /you are adequately sandboxed/mi;
-        };
-      };
-    }
+    $machine->shutdown;
   '';
-})
+}) channelMap
diff --git a/nixos/tests/cjdns.nix b/nixos/tests/cjdns.nix
index 2cae63fdda44..4d3b58abc6e5 100644
--- a/nixos/tests/cjdns.nix
+++ b/nixos/tests/cjdns.nix
@@ -12,7 +12,6 @@ let
       # the sequence of address assignment less stochastic.
       networking.useDHCP = false;
 
-      networking.interfaces.eth1.prefixLength = 24;
       # CJDNS output is incompatible with the XML log.
       systemd.services.cjdns.serviceConfig.StandardOutput = "null";
       #networking.firewall.enable = true;
@@ -25,7 +24,7 @@ in
 import ./make-test.nix ({ pkgs, ...} : {
   name = "cjdns";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ emery ];
+    maintainers = [ ehmry ];
   };
 
   nodes = rec
@@ -49,12 +48,14 @@ import ./make-test.nix ({ pkgs, ...} : {
 
           { imports = [ basicConfig ];
 
-          networking.interfaces.eth1.ipAddress = "192.168.0.2";
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = "192.168.0.2"; prefixLength = 24; }
+          ];
 
           services.cjdns =
             { UDPInterface =
                 { bind = "0.0.0.0:1024";
-                  connectTo."192.168.0.1:1024}" =
+                  connectTo."192.168.0.1:1024" =
                     { password = carolPassword;
                       publicKey = carolPubKey;
                     };
@@ -76,7 +77,9 @@ import ./make-test.nix ({ pkgs, ...} : {
             CJDNS_ADMIN_PASSWORD=FOOBAR
           '';
 
-          networking.interfaces.eth1.ipAddress = "192.168.0.1";
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = "192.168.0.1"; prefixLength = 24; }
+          ];
 
           services.cjdns =
             { authorizedPasswords = [ carolPassword ];
@@ -109,17 +112,17 @@ import ./make-test.nix ({ pkgs, ...} : {
 
       # ping a few times each to let the routing table establish itself
 
-      $alice->succeed("ping6 -c 4 $carolIp6");
-      $bob->succeed("ping6 -c 4 $carolIp6");
+      $alice->succeed("ping -c 4 $carolIp6");
+      $bob->succeed("ping -c 4 $carolIp6");
 
-      $carol->succeed("ping6 -c 4 $aliceIp6");
-      $carol->succeed("ping6 -c 4 $bobIp6");
+      $carol->succeed("ping -c 4 $aliceIp6");
+      $carol->succeed("ping -c 4 $bobIp6");
 
-      $alice->succeed("ping6 -c 4 $bobIp6");
-      $bob->succeed("ping6 -c 4 $aliceIp6");
+      $alice->succeed("ping -c 4 $bobIp6");
+      $bob->succeed("ping -c 4 $aliceIp6");
 
       $alice->waitForUnit("httpd.service");
 
       $bob->succeed("curl --fail -g http://[$aliceIp6]");
     '';
-})
\ No newline at end of file
+})
diff --git a/nixos/tests/cloud-init.nix b/nixos/tests/cloud-init.nix
new file mode 100644
index 000000000000..2a258e4bff54
--- /dev/null
+++ b/nixos/tests/cloud-init.nix
@@ -0,0 +1,46 @@
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+
+let
+  metadataDrive = pkgs.stdenv.mkDerivation {
+    name = "metadata";
+    buildCommand = ''
+      mkdir -p $out/iso
+
+      cat << EOF > $out/iso/user-data
+      #cloud-config
+      write_files:
+      -   content: |
+                cloudinit
+          path: /tmp/cloudinit-write-file
+      EOF
+
+      cat << EOF > $out/iso/meta-data
+      instance-id: iid-local01
+      local-hostname: "test"
+      public-keys:
+          - "should be a key!"
+      EOF
+      ${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
+      '';
+  };
+in makeTest {
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lewo ];
+  };
+  machine =
+    { config, pkgs, ... }:
+    {
+      virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
+      services.cloud-init.enable = true;
+    };
+  testScript = ''
+     $machine->start;
+     $machine->waitForUnit("cloud-init.service");
+     $machine->succeed("cat /tmp/cloudinit-write-file | grep -q 'cloudinit'");
+
+     $machine->waitUntilSucceeds("cat /root/.ssh/authorized_keys | grep -q 'should be a key!'");
+  '';
+}
diff --git a/nixos/tests/common/letsencrypt.nix b/nixos/tests/common/letsencrypt.nix
new file mode 100644
index 000000000000..7c6b3b29e36d
--- /dev/null
+++ b/nixos/tests/common/letsencrypt.nix
@@ -0,0 +1,466 @@
+# Fully pluggable module to have Letsencrypt's Boulder ACME service running in
+# a test environment.
+#
+# The certificate for the ACME service is exported as:
+#
+#   config.test-support.letsencrypt.caCert
+#
+# This value can be used inside the configuration of other test nodes to inject
+# the snakeoil certificate into security.pki.certificateFiles or into package
+# overlays.
+#
+# Another value that's needed if you don't use a custom resolver (see below for
+# notes on that) is to add the letsencrypt node as a nameserver to every node
+# that needs to acquire certificates using ACME, because otherwise the API host
+# for letsencrypt.org can't be resolved.
+#
+# A configuration example of a full node setup using this would be this:
+#
+# {
+#   letsencrypt = import ./common/letsencrypt.nix;
+#
+#   example = { nodes, ... }: {
+#     networking.nameservers = [
+#       nodes.letsencrypt.config.networking.primaryIPAddress
+#     ];
+#     security.pki.certificateFiles = [
+#       nodes.letsencrypt.config.test-support.letsencrypt.caCert
+#     ];
+#   };
+# }
+#
+# By default, this module runs a local resolver, generated using resolver.nix
+# from the same directory to automatically discover all zones in the network.
+#
+# If you do not want this and want to use your own resolver, you can just
+# override networking.nameservers like this:
+#
+# {
+#   letsencrypt = { nodes, ... }: {
+#     imports = [ ./common/letsencrypt.nix ];
+#     networking.nameservers = [
+#       nodes.myresolver.config.networking.primaryIPAddress
+#     ];
+#   };
+#
+#   myresolver = ...;
+# }
+#
+# Keep in mind, that currently only _one_ resolver is supported, if you have
+# more than one resolver in networking.nameservers only the first one will be
+# used.
+#
+# Also make sure that whenever you use a resolver from a different test node
+# that it has to be started _before_ the ACME service.
+{ config, pkgs, lib, ... }:
+
+let
+  softhsm = pkgs.stdenv.mkDerivation rec {
+    name = "softhsm-${version}";
+    version = "1.3.8";
+
+    src = pkgs.fetchurl {
+      url = "https://dist.opendnssec.org/source/${name}.tar.gz";
+      sha256 = "0flmnpkgp65ym7w3qyg78d3fbmvq3aznmi66rgd420n33shf7aif";
+    };
+
+    configureFlags = [ "--with-botan=${pkgs.botan}" ];
+    buildInputs = [ pkgs.sqlite ];
+  };
+
+  pkcs11-proxy = pkgs.stdenv.mkDerivation {
+    name = "pkcs11-proxy";
+
+    src = pkgs.fetchFromGitHub {
+      owner = "SUNET";
+      repo = "pkcs11-proxy";
+      rev = "944684f78bca0c8da6cabe3fa273fed3db44a890";
+      sha256 = "1nxgd29y9wmifm11pjcdpd2y293p0dgi0x5ycis55miy97n0f5zy";
+    };
+
+    postPatch = "patchShebangs mksyscalls.sh";
+
+    nativeBuildInputs = [ pkgs.cmake ];
+    buildInputs = [ pkgs.openssl pkgs.libseccomp ];
+  };
+
+  mkGoDep = { goPackagePath, url ? "https://${goPackagePath}", rev, sha256 }: {
+    inherit goPackagePath;
+    src = pkgs.fetchgit { inherit url rev sha256; };
+  };
+
+  goose = let
+    owner = "liamstask";
+    repo = "goose";
+    rev = "8488cc47d90c8a502b1c41a462a6d9cc8ee0a895";
+    version = "20150116";
+
+  in pkgs.buildGoPackage rec {
+    name = "${repo}-${version}";
+
+    src = pkgs.fetchFromBitbucket {
+      name = "${name}-src";
+      inherit rev owner repo;
+      sha256 = "1jy0pscxjnxjdg3hj111w21g8079rq9ah2ix5ycxxhbbi3f0wdhs";
+    };
+
+    goPackagePath = "bitbucket.org/${owner}/${repo}";
+    subPackages = [ "cmd/goose" ];
+    extraSrcs = map mkGoDep [
+      { goPackagePath = "github.com/go-sql-driver/mysql";
+        rev = "2e00b5cd70399450106cec6431c2e2ce3cae5034";
+        sha256 = "085g48jq9hzmlcxg122n0c4pi41sc1nn2qpx1vrl2jfa8crsppa5";
+      }
+      { goPackagePath = "github.com/kylelemons/go-gypsy";
+        rev = "08cad365cd28a7fba23bb1e57aa43c5e18ad8bb8";
+        sha256 = "1djv7nii3hy451n5jlslk0dblqzb1hia1cbqpdwhnps1g8hqjy8q";
+      }
+      { goPackagePath = "github.com/lib/pq";
+        rev = "ba5d4f7a35561e22fbdf7a39aa0070f4d460cfc0";
+        sha256 = "1mfbqw9g00bk24bfmf53wri5c2wqmgl0qh4sh1qv2da13a7cwwg3";
+      }
+      { goPackagePath = "github.com/mattn/go-sqlite3";
+        rev = "2acfafad5870400156f6fceb12852c281cbba4d5";
+        sha256 = "1rpgil3w4hh1cibidskv1js898hwz83ps06gh0hm3mym7ki8d5h7";
+      }
+      { goPackagePath = "github.com/ziutek/mymysql";
+        rev = "0582bcf675f52c0c2045c027fd135bd726048f45";
+        sha256 = "0bkc9x8sgqbzgdimsmsnhb0qrzlzfv33fgajmmjxl4hcb21qz3rf";
+      }
+      { goPackagePath = "golang.org/x/net";
+        url = "https://go.googlesource.com/net";
+        rev = "10c134ea0df15f7e34d789338c7a2d76cc7a3ab9";
+        sha256 = "14cbr2shl08gyg85n5gj7nbjhrhhgrd52h073qd14j97qcxsakcz";
+      }
+    ];
+  };
+
+  boulder = let
+    owner = "letsencrypt";
+    repo = "boulder";
+    rev = "9c6a1f2adc4c26d925588f5ae366cfd4efb7813a";
+    version = "20180129";
+
+  in pkgs.buildGoPackage rec {
+    name = "${repo}-${version}";
+
+    src = pkgs.fetchFromGitHub {
+      name = "${name}-src";
+      inherit rev owner repo;
+      sha256 = "09kszswrifm9rc6idfaq0p1mz5w21as2qbc8gd5pphrq9cf9pn55";
+    };
+
+    postPatch = ''
+      # compat for go < 1.8
+      sed -i -e 's/time\.Until(\([^)]\+\))/\1.Sub(time.Now())/' \
+        test/ocsp/helper/helper.go
+
+      find test -type f -exec sed -i -e '/libpkcs11-proxy.so/ {
+        s,/usr/local,${pkcs11-proxy},
+      }' {} +
+
+      sed -i -r \
+        -e '/^def +install/a \    return True' \
+        -e 's,exec \./bin/,,' \
+        test/startservers.py
+
+      cat "${snakeOilCa}/ca.key" > test/test-ca.key
+      cat "${snakeOilCa}/ca.pem" > test/test-ca.pem
+    '';
+
+    # Until vendored pkcs11 is go 1.9 compatible
+    preBuild = ''
+      rm -r go/src/github.com/letsencrypt/boulder/vendor/github.com/miekg/pkcs11
+    '';
+
+    extraSrcs = map mkGoDep [
+      { goPackagePath = "github.com/miekg/pkcs11";
+        rev           = "6dbd569b952ec150d1425722dbbe80f2c6193f83";
+        sha256        = "1m8g6fx7df6hf6q6zsbyw1icjmm52dmsx28rgb0h930wagvngfwb";
+      }
+    ];
+
+    goPackagePath = "github.com/${owner}/${repo}";
+    buildInputs = [ pkgs.libtool ];
+  };
+
+  boulderSource = "${boulder.out}/share/go/src/${boulder.goPackagePath}";
+
+  softHsmConf = pkgs.writeText "softhsm.conf" ''
+    0:/var/lib/softhsm/slot0.db
+    1:/var/lib/softhsm/slot1.db
+  '';
+
+  snakeOilCa = pkgs.runCommand "snakeoil-ca" {
+    buildInputs = [ pkgs.openssl ];
+  } ''
+    mkdir "$out"
+    openssl req -newkey rsa:4096 -x509 -sha256 -days 36500 \
+      -subj '/CN=Snakeoil CA' -nodes \
+      -out "$out/ca.pem" -keyout "$out/ca.key"
+  '';
+
+  createAndSignCert = fqdn: let
+    snakeoilCertConf = pkgs.writeText "snakeoil.cnf" ''
+      [req]
+      default_bits = 4096
+      prompt = no
+      default_md = sha256
+      req_extensions = req_ext
+      distinguished_name = dn
+      [dn]
+      CN = ${fqdn}
+      [req_ext]
+      subjectAltName = DNS:${fqdn}
+    '';
+  in pkgs.runCommand "snakeoil-certs-${fqdn}" {
+    buildInputs = [ pkgs.openssl ];
+  } ''
+    mkdir "$out"
+    openssl genrsa -out "$out/snakeoil.key" 4096
+    openssl req -new -key "$out/snakeoil.key" \
+      -config ${lib.escapeShellArg snakeoilCertConf} \
+      -out snakeoil.csr
+    openssl x509 -req -in snakeoil.csr -sha256 -set_serial 666 \
+      -CA "${snakeOilCa}/ca.pem" -CAkey "${snakeOilCa}/ca.key" \
+      -extfile ${lib.escapeShellArg snakeoilCertConf} \
+      -out "$out/snakeoil.pem" -days 36500
+  '';
+
+  wfeCerts = createAndSignCert wfeDomain;
+  wfeDomain = "acme-v01.api.letsencrypt.org";
+  wfeCertFile = "${wfeCerts}/snakeoil.pem";
+  wfeKeyFile = "${wfeCerts}/snakeoil.key";
+
+  siteCerts = createAndSignCert siteDomain;
+  siteDomain = "letsencrypt.org";
+  siteCertFile = "${siteCerts}/snakeoil.pem";
+  siteKeyFile = "${siteCerts}/snakeoil.key";
+
+  # Retrieved via:
+  # curl -s -I https://acme-v01.api.letsencrypt.org/terms \
+  #   | sed -ne 's/^[Ll]ocation: *//p'
+  tosUrl = "https://letsencrypt.org/documents/2017.11.15-LE-SA-v1.2.pdf";
+  tosPath = builtins.head (builtins.match "https?://[^/]+(.*)" tosUrl);
+
+  tosFile = pkgs.fetchurl {
+    url = tosUrl;
+    sha256 = "0yvyckqzj0b1xi61sypcha82nanizzlm8yqy828h2jbza7cxi26c";
+  };
+
+  resolver = let
+    message = "You need to define a resolver for the letsencrypt test module.";
+    firstNS = lib.head config.networking.nameservers;
+  in if config.networking.nameservers == [] then throw message else firstNS;
+
+  cfgDir = pkgs.stdenv.mkDerivation {
+    name = "boulder-config";
+    src = "${boulderSource}/test/config";
+    nativeBuildInputs = [ pkgs.jq ];
+    phases = [ "unpackPhase" "patchPhase" "installPhase" ];
+    postPatch = ''
+      sed -i -e 's/5002/80/' -e 's/5002/443/' va.json
+      sed -i -e '/listenAddress/s/:4000/:80/' wfe.json
+      sed -i -r \
+        -e ${lib.escapeShellArg "s,http://boulder:4000/terms/v1,${tosUrl},g"} \
+        -e 's,http://(boulder|127\.0\.0\.1):4000,https://${wfeDomain},g' \
+        -e '/dnsResolver/s/127\.0\.0\.1:8053/${resolver}:53/' \
+        *.json
+      if grep 4000 *.json; then exit 1; fi
+
+      # Change all ports from 1909X to 909X, because the 1909X range of ports is
+      # allocated by startservers.py in order to intercept gRPC communication.
+      sed -i -e 's/\<1\(909[0-9]\)\>/\1/' *.json
+
+      # Patch out all additional issuer certs
+      jq '. + {ca: (.ca + {Issuers:
+        [.ca.Issuers[] | select(.CertFile == "test/test-ca.pem")]
+      })}' ca.json > tmp
+      mv tmp ca.json
+    '';
+    installPhase = "cp -r . \"$out\"";
+  };
+
+  components = {
+    gsb-test-srv.args = "-apikey my-voice-is-my-passport";
+    gsb-test-srv.waitForPort = 6000;
+    gsb-test-srv.first = true;
+    boulder-sa.args = "--config ${cfgDir}/sa.json";
+    boulder-wfe.args = "--config ${cfgDir}/wfe.json";
+    boulder-ra.args = "--config ${cfgDir}/ra.json";
+    boulder-ca.args = "--config ${cfgDir}/ca.json";
+    boulder-va.args = "--config ${cfgDir}/va.json";
+    boulder-publisher.args = "--config ${cfgDir}/publisher.json";
+    boulder-publisher.waitForPort = 9091;
+    ocsp-updater.args = "--config ${cfgDir}/ocsp-updater.json";
+    ocsp-updater.after = [ "boulder-publisher" ];
+    ocsp-responder.args = "--config ${cfgDir}/ocsp-responder.json";
+    ct-test-srv = {};
+    mail-test-srv.args = let
+      key = "${boulderSource}/test/mail-test-srv/minica-key.pem";
+      crt = "${boulderSource}/test/mail-test-srv/minica.pem";
+     in
+      "--closeFirst 5 --cert ${crt} --key ${key}";
+  };
+
+  commonPath = [ softhsm pkgs.mariadb goose boulder ];
+
+  mkServices = a: b: with lib; listToAttrs (concatLists (mapAttrsToList a b));
+
+  componentServices = mkServices (name: attrs: let
+    mkSrvName = n: "boulder-${n}.service";
+    firsts = lib.filterAttrs (lib.const (c: c.first or false)) components;
+    firstServices = map mkSrvName (lib.attrNames firsts);
+    firstServicesNoSelf = lib.remove "boulder-${name}.service" firstServices;
+    additionalAfter = firstServicesNoSelf ++ map mkSrvName (attrs.after or []);
+    needsPort = attrs ? waitForPort;
+    inits = map (n: "boulder-init-${n}.service") [ "mysql" "softhsm" ];
+    portWaiter = {
+      name = "boulder-${name}";
+      value = {
+        description = "Wait For Port ${toString attrs.waitForPort} (${name})";
+        after = [ "boulder-real-${name}.service" "bind.service" ];
+        requires = [ "boulder-real-${name}.service" ];
+        requiredBy = [ "boulder.service" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        script = let
+          netcat = "${pkgs.netcat-openbsd}/bin/nc";
+          portCheck = "${netcat} -z 127.0.0.1 ${toString attrs.waitForPort}";
+        in "while ! ${portCheck}; do :; done";
+      };
+    };
+  in lib.optional needsPort portWaiter ++ lib.singleton {
+    name = if needsPort then "boulder-real-${name}" else "boulder-${name}";
+    value = {
+      description = "Boulder ACME Component (${name})";
+      after = inits ++ additionalAfter;
+      requires = inits;
+      requiredBy = [ "boulder.service" ];
+      path = commonPath;
+      environment.GORACE = "halt_on_error=1";
+      environment.SOFTHSM_CONF = softHsmConf;
+      environment.PKCS11_PROXY_SOCKET = "tcp://127.0.0.1:5657";
+      serviceConfig.WorkingDirectory = boulderSource;
+      serviceConfig.ExecStart = "${boulder}/bin/${name} ${attrs.args or ""}";
+      serviceConfig.Restart = "on-failure";
+    };
+  }) components;
+
+in {
+  imports = [ ./resolver.nix ];
+
+  options.test-support.letsencrypt.caCert = lib.mkOption {
+    type = lib.types.path;
+    description = ''
+      A certificate file to use with the <literal>nodes</literal> attribute to
+      inject the snakeoil CA certificate used in the ACME server into
+      <option>security.pki.certificateFiles</option>.
+    '';
+  };
+
+  config = {
+    test-support = {
+      resolver.enable = let
+        isLocalResolver = config.networking.nameservers == [ "127.0.0.1" ];
+      in lib.mkOverride 900 isLocalResolver;
+      letsencrypt.caCert = "${snakeOilCa}/ca.pem";
+    };
+
+    # This has priority 140, because modules/testing/test-instrumentation.nix
+    # already overrides this with priority 150.
+    networking.nameservers = lib.mkOverride 140 [ "127.0.0.1" ];
+    networking.firewall.enable = false;
+
+    networking.extraHosts = ''
+      127.0.0.1 ${toString [
+        "sa.boulder" "ra.boulder" "wfe.boulder" "ca.boulder" "va.boulder"
+        "publisher.boulder" "ocsp-updater.boulder" "admin-revoker.boulder"
+        "boulder" "boulder-mysql" wfeDomain
+      ]}
+      ${config.networking.primaryIPAddress} ${wfeDomain} ${siteDomain}
+    '';
+
+    services.mysql.enable = true;
+    services.mysql.package = pkgs.mariadb;
+
+    services.nginx.enable = true;
+    services.nginx.recommendedProxySettings = true;
+    # This fixes the test on i686
+    services.nginx.commonHttpConfig = ''
+      server_names_hash_bucket_size 64;
+    '';
+    services.nginx.virtualHosts.${wfeDomain} = {
+      onlySSL = true;
+      enableACME = false;
+      sslCertificate = wfeCertFile;
+      sslCertificateKey = wfeKeyFile;
+      locations."/".proxyPass = "http://127.0.0.1:80";
+    };
+    services.nginx.virtualHosts.${siteDomain} = {
+      onlySSL = true;
+      enableACME = false;
+      sslCertificate = siteCertFile;
+      sslCertificateKey = siteKeyFile;
+      locations.${tosPath}.extraConfig = "alias ${tosFile};";
+    };
+
+    systemd.services = {
+      pkcs11-daemon = {
+        description = "PKCS11 Daemon";
+        after = [ "boulder-init-softhsm.service" ];
+        before = map (n: "${n}.service") (lib.attrNames componentServices);
+        wantedBy = [ "multi-user.target" ];
+        environment.SOFTHSM_CONF = softHsmConf;
+        environment.PKCS11_DAEMON_SOCKET = "tcp://127.0.0.1:5657";
+        serviceConfig.ExecStart = let
+          softhsmLib = "${softhsm}/lib/softhsm/libsofthsm.so";
+        in "${pkcs11-proxy}/bin/pkcs11-daemon ${softhsmLib}";
+      };
+
+      boulder-init-mysql = {
+        description = "Boulder ACME Init (MySQL)";
+        after = [ "mysql.service" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.WorkingDirectory = boulderSource;
+        path = commonPath;
+        script = "${pkgs.bash}/bin/sh test/create_db.sh";
+      };
+
+      boulder-init-softhsm = {
+        description = "Boulder ACME Init (SoftHSM)";
+        environment.SOFTHSM_CONF = softHsmConf;
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.WorkingDirectory = boulderSource;
+        preStart = "mkdir -p /var/lib/softhsm";
+        path = commonPath;
+        script = ''
+          softhsm --slot 0 --init-token \
+            --label intermediate --pin 5678 --so-pin 1234
+          softhsm --slot 0 --import test/test-ca.key \
+            --label intermediate_key --pin 5678 --id FB
+          softhsm --slot 1 --init-token \
+            --label root --pin 5678 --so-pin 1234
+          softhsm --slot 1 --import test/test-root.key \
+            --label root_key --pin 5678 --id FA
+        '';
+      };
+
+      boulder = {
+        description = "Boulder ACME Server";
+        after = map (n: "${n}.service") (lib.attrNames componentServices);
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        script = let
+          ports = lib.range 8000 8005 ++ lib.singleton 80;
+          netcat = "${pkgs.netcat-openbsd}/bin/nc";
+          mkPortCheck = port: "${netcat} -z 127.0.0.1 ${toString port}";
+          checks = "(${lib.concatMapStringsSep " && " mkPortCheck ports})";
+        in "while ! ${checks}; do :; done";
+      };
+    } // componentServices;
+  };
+}
diff --git a/nixos/tests/common/resolver.nix b/nixos/tests/common/resolver.nix
new file mode 100644
index 000000000000..a1901c5c8167
--- /dev/null
+++ b/nixos/tests/common/resolver.nix
@@ -0,0 +1,141 @@
+# This module automatically discovers zones in BIND and NSD NixOS
+# configurations and creates zones for all definitions of networking.extraHosts
+# (except those that point to 127.0.0.1 or ::1) within the current test network
+# and delegates these zones using a fake root zone served by a BIND recursive
+# name server.
+{ config, nodes, pkgs, lib, ... }:
+
+{
+  options.test-support.resolver.enable = lib.mkOption {
+    type = lib.types.bool;
+    default = true;
+    internal = true;
+    description = ''
+      Whether to enable the resolver that automatically discovers zone in the
+      test network.
+
+      This option is <literal>true</literal> by default, because the module
+      defining this option needs to be explicitly imported.
+
+      The reason this option exists is for the
+      <filename>nixos/tests/common/letsencrypt.nix</filename> module, which
+      needs that option to disable the resolver once the user has set its own
+      resolver.
+    '';
+  };
+
+  config = lib.mkIf config.test-support.resolver.enable {
+    networking.firewall.enable = false;
+    services.bind.enable = true;
+    services.bind.cacheNetworks = lib.mkForce [ "any" ];
+    services.bind.forwarders = lib.mkForce [];
+    services.bind.zones = lib.singleton {
+      name = ".";
+      file = let
+        addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) ".";
+        mkNsdZoneNames = zones: map addDot (lib.attrNames zones);
+        mkBindZoneNames = zones: map (zone: addDot zone.name) zones;
+        getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones
+                     ++ mkBindZoneNames cfg.services.bind.zones;
+
+        getZonesForNode = attrs: {
+          ip = attrs.config.networking.primaryIPAddress;
+          zones = lib.filter (zone: zone != ".") (getZones attrs.config);
+        };
+
+        zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes;
+
+        # A and AAAA resource records for all the definitions of
+        # networking.extraHosts except those for 127.0.0.1 or ::1.
+        #
+        # The result is an attribute set with keys being the host name and the
+        # values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is
+        # the IP address for the corresponding key.
+        recordsFromExtraHosts = let
+          getHostsForNode = lib.const (n: n.config.networking.extraHosts);
+          allHostsList = lib.mapAttrsToList getHostsForNode nodes;
+          allHosts = lib.concatStringsSep "\n" allHostsList;
+
+          reIp = "[a-fA-F0-9.:]+";
+          reHost = "[a-zA-Z0-9.-]+";
+
+          matchAliases = str: let
+            matched = builtins.match "[ \t]+(${reHost})(.*)" str;
+            continue = lib.singleton (lib.head matched)
+                    ++ matchAliases (lib.last matched);
+          in if matched == null then [] else continue;
+
+          matchLine = str: let
+            result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
+          in if result == null then null else {
+            ipAddr = lib.head result;
+            hosts = lib.singleton (lib.elemAt result 1)
+                 ++ matchAliases (lib.last result);
+          };
+
+          skipLine = str: let
+            rest = builtins.match "[^\n]*\n(.*)" str;
+          in if rest == null then "" else lib.head rest;
+
+          getEntries = str: acc: let
+            result = matchLine str;
+            next = getEntries (skipLine str);
+            newEntry = acc ++ lib.singleton result;
+            continue = if result == null then next acc else next newEntry;
+          in if str == "" then acc else continue;
+
+          isIPv6 = str: builtins.match ".*:.*" str != null;
+          loopbackIps = [ "127.0.0.1" "::1" ];
+          filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
+
+          allEntries = lib.concatMap (entry: map (host: {
+            inherit host;
+            ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
+          }) entry.hosts) (filterLoopback (getEntries (allHosts + "\n") []));
+
+          mkRecords = entry: let
+            records = lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
+                   ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
+            mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
+          in lib.concatMapStringsSep "\n" mkRecord records;
+
+        in lib.concatMapStringsSep "\n" mkRecords allEntries;
+
+        # All of the zones that are subdomains of existing zones.
+        # For example if there is only "example.com" the following zones would
+        # be 'subZones':
+        #
+        #  * foo.example.com.
+        #  * bar.example.com.
+        #
+        # While the following would *not* be 'subZones':
+        #
+        #  * example.com.
+        #  * com.
+        #
+        subZones = let
+          allZones = lib.concatMap (zi: zi.zones) zoneInfo;
+          isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
+        in lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
+
+        # All the zones without 'subZones'.
+        filteredZoneInfo = map (zi: zi // {
+          zones = lib.filter (x: !lib.elem x subZones) zi.zones;
+        }) zoneInfo;
+
+      in pkgs.writeText "fake-root.zone" ''
+        $TTL 3600
+        . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
+        ns.fakedns. IN A ${config.networking.primaryIPAddress}
+        . IN NS ns.fakedns.
+        ${lib.concatImapStrings (num: { ip, zones }: ''
+          ns${toString num}.fakedns. IN A ${ip}
+          ${lib.concatMapStrings (zone: ''
+          ${zone} IN NS ns${toString num}.fakedns.
+          '') zones}
+        '') (lib.filter (zi: zi.zones != []) filteredZoneInfo)}
+        ${recordsFromExtraHosts}
+      '';
+    };
+  };
+}
diff --git a/nixos/tests/common/user-account.nix b/nixos/tests/common/user-account.nix
index ded8275000af..93aeb60e456a 100644
--- a/nixos/tests/common/user-account.nix
+++ b/nixos/tests/common/user-account.nix
@@ -1,9 +1,14 @@
 { lib, ... }:
 
-{ users.extraUsers = lib.singleton
+{ users.extraUsers.alice =
     { isNormalUser = true;
-      name = "alice";
       description = "Alice Foobar";
       password = "foobar";
     };
+
+  users.extraUsers.bob =
+    { isNormalUser = true;
+      description = "Bob Foobar";
+      password = "foobar";
+    };
 }
diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix
new file mode 100644
index 000000000000..dfef46a2ada4
--- /dev/null
+++ b/nixos/tests/containers-bridge.nix
@@ -0,0 +1,87 @@
+# Test for NixOS' container support.
+
+let
+  hostIp = "192.168.0.1";
+  containerIp = "192.168.0.100/24";
+  hostIp6 = "fc00::1";
+  containerIp6 = "fc00::2/7";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-bridge";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      networking.bridges = {
+        br0 = {
+          interfaces = [];
+        };
+      };
+      networking.interfaces = {
+        br0 = {
+          ipv4.addresses = [{ address = hostIp; prefixLength = 24; }];
+          ipv6.addresses = [{ address = hostIp6; prefixLength = 7; }];
+        };
+      };
+
+      containers.webserver =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          localAddress = containerIp;
+          localAddress6 = containerIp6;
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+              networking.firewall.allowPing = true;
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container status webserver") =~ /up/ or die;
+
+      "${containerIp}" =~ /([^\/]+)\/([0-9+])/;
+      my $ip = $1;
+      chomp $ip;
+      $machine->succeed("ping -n -c 1 $ip");
+      $machine->succeed("curl --fail http://$ip/ > /dev/null");
+
+      "${containerIp6}" =~ /([^\/]+)\/([0-9+])/;
+      my $ip6 = $1;
+      chomp $ip6;
+      $machine->succeed("ping -n -c 1 $ip6");
+      $machine->succeed("curl --fail http://[$ip6]/ > /dev/null");
+
+      # Check that nixos-container show-ip works in case of an ipv4 address with
+      # subnetmask in CIDR notation.
+      my $result = $machine->succeed("nixos-container show-ip webserver");
+      chomp $result;
+      $result eq $ip or die;
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null");
+      $machine->fail("curl --fail --connect-timeout 2 http://[$ip6]/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix
new file mode 100644
index 000000000000..df3f3354b2d9
--- /dev/null
+++ b/nixos/tests/containers-extra_veth.nix
@@ -0,0 +1,103 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-bridge";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampfschlaefer ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+      virtualisation.vlans = [];
+
+      networking.bridges = {
+        br0 = {
+          interfaces = [];
+        };
+        br1 = { interfaces = []; };
+      };
+      networking.interfaces = {
+        br0 = {
+          ipv4.addresses = [{ address = "192.168.0.1"; prefixLength = 24; }];
+          ipv6.addresses = [{ address = "fc00::1"; prefixLength = 7; }];
+        };
+        br1 = {
+          ipv4.addresses = [{ address = "192.168.1.1"; prefixLength = 24; }];
+        };
+      };
+
+      containers.webserver =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          localAddress = "192.168.0.100/24";
+          localAddress6 = "fc00::2/7";
+          extraVeths = {
+            veth1 = { hostBridge = "br1"; localAddress = "192.168.1.100/24"; };
+            veth2 = { hostAddress = "192.168.2.1"; localAddress = "192.168.2.100"; };
+          };
+          config =
+            {
+              networking.firewall.allowedTCPPorts = [ 80 ];
+              networking.firewall.allowPing = true;
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Status of the webserver container.
+      $machine->succeed("nixos-container status webserver") =~ /up/ or die;
+
+      # Debug
+      #$machine->succeed("nixos-container run webserver -- ip link >&2");
+
+      # Ensure that the veths are inside the container
+      $machine->succeed("nixos-container run webserver -- ip link show veth1") =~ /state UP/ or die;
+      $machine->succeed("nixos-container run webserver -- ip link show veth2") =~ /state UP/ or die;
+
+      # Debug
+      #$machine->succeed("ip link >&2");
+
+      # Ensure the presence of the extra veths
+      $machine->succeed("ip link show veth1") =~ /state UP/ or die;
+      $machine->succeed("ip link show veth2") =~ /state UP/ or die;
+
+      # Ensure the veth1 is part of br1 on the host
+      $machine->succeed("ip link show veth1") =~ /master br1/ or die;
+
+      # Debug
+      #$machine->succeed("ip -4 a >&2");
+      #$machine->succeed("ip -4 r >&2");
+      #$machine->succeed("nixos-container run webserver -- ip link >&2");
+      #$machine->succeed("nixos-container run webserver -- ip -4 a >&2");
+      #$machine->succeed("nixos-container run webserver -- ip -4 r >&2");
+
+      # Ping on main veth
+      $machine->succeed("ping -n -c 1 192.168.0.100");
+      $machine->succeed("ping -n -c 1 fc00::2");
+
+      # Ping on the first extra veth
+      $machine->succeed("ping -n -c 1 192.168.1.100 >&2");
+
+      # Ping on the second extra veth
+      $machine->succeed("ping -n -c 1 192.168.2.100 >&2");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("ping -n -c 1 192.168.1.100 >&2");
+      $machine->fail("ping -n -c 1 192.168.2.100 >&2");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+})
diff --git a/nixos/tests/containers-hosts.nix b/nixos/tests/containers-hosts.nix
new file mode 100644
index 000000000000..df1ef6d14936
--- /dev/null
+++ b/nixos/tests/containers-hosts.nix
@@ -0,0 +1,52 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-hosts";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ montag451 ];
+  };
+
+  machine =
+    { config, pkgs, lib, ... }:
+    {
+      virtualisation.memorySize = 256;
+      virtualisation.vlans = [];
+
+      networking.bridges.br0.interfaces = [];
+      networking.interfaces.br0.ipv4.addresses = [
+        { address = "10.11.0.254"; prefixLength = 24; }
+      ];
+
+      # Force /etc/hosts to be the only source for host name resolution
+      environment.etc."nsswitch.conf".text = lib.mkForce ''
+        hosts: files
+      '';
+
+      containers.simple = {
+        autoStart = true;
+        privateNetwork = true;
+        localAddress = "10.10.0.1";
+        hostAddress = "10.10.0.254";
+
+        config = {};
+      };
+
+      containers.netmask = {
+        autoStart = true;
+        privateNetwork = true;
+        hostBridge = "br0";
+        localAddress = "10.11.0.1/24";
+
+        config = {};
+      };
+    };
+
+  testScript = ''
+    startAll;
+    $machine->waitForUnit("default.target");
+
+    # Ping the containers using the entries added in /etc/hosts
+    $machine->succeed("ping -n -c 1 simple.containers");
+    $machine->succeed("ping -n -c 1 netmask.containers");
+  '';
+})
diff --git a/nixos/tests/containers.nix b/nixos/tests/containers-imperative.nix
index ce36a7e0588f..b89e08f82acb 100644
--- a/nixos/tests/containers.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -1,50 +1,35 @@
 # Test for NixOS' container support.
 
 import ./make-test.nix ({ pkgs, ...} : {
-  name = "containers";
+  name = "containers-imperative";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ aristid aszlig eelco chaoflow ];
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
   };
 
   machine =
-    { config, pkgs, ... }:
+    { config, pkgs, lib, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
       virtualisation.memorySize = 768;
-
-      containers.webserver =
-        { privateNetwork = true;
-          hostAddress = "10.231.136.1";
-          localAddress = "10.231.136.2";
-          config =
-            { services.httpd.enable = true;
-              services.httpd.adminAddr = "foo@example.org";
-              networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
+      # Make sure we always have all the required dependencies for creating a
+      # container available within the VM, because we don't have network access.
+      virtualisation.pathsInNixDB = let
+        emptyContainer = import ../lib/eval-config.nix {
+          inherit (config.nixpkgs.localSystem) system;
+          modules = lib.singleton {
+            containers.foo.config = {
+              system.nixos.stateVersion = "18.03";
             };
+          };
         };
-
-      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+      in [
+        pkgs.stdenv pkgs.stdenvNoCC emptyContainer.config.containers.foo.path
+        pkgs.libxslt
+      ];
     };
 
   testScript =
     ''
-      $machine->succeed("nixos-container list") =~ /webserver/ or die;
-
-      # Start the webserver container.
-      $machine->succeed("nixos-container start webserver");
-
-      # Since "start" returns after the container has reached
-      # multi-user.target, we should now be able to access it.
-      my $ip = $machine->succeed("nixos-container show-ip webserver");
-      chomp $ip;
-      #$machine->succeed("ping -c1 $ip"); # FIXME
-      $machine->succeed("curl --fail http://$ip/ > /dev/null");
-
-      # Stop the container.
-      $machine->succeed("nixos-container stop webserver");
-      $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null");
-
       # Make sure we have a NixOS tree (required by ‘nixos-container create’).
       $machine->succeed("PAGER=cat nix-env -qa -A nixos.hello >&2");
 
@@ -111,9 +96,6 @@ import ./make-test.nix ({ pkgs, ...} : {
         # Ensure that the container path is gone
         "test ! -e /var/lib/containers/$id1"
       );
-
-      # Destroying a declarative container should fail.
-      $machine->fail("nixos-container destroy webserver");
     '';
 
 })
diff --git a/nixos/tests/containers-ipv4.nix b/nixos/tests/containers-ipv4.nix
new file mode 100644
index 000000000000..821ce1cd07d2
--- /dev/null
+++ b/nixos/tests/containers-ipv4.nix
@@ -0,0 +1,56 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-ipv4";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress = "10.231.136.1";
+          localAddress = "10.231.136.2";
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+              networking.firewall.allowPing = true;
+              system.nixos.stateVersion = "18.03";
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container start webserver");
+
+      # wait two seconds for the container to start and the network to be up
+      sleep 2;
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      my $ip = $machine->succeed("nixos-container show-ip webserver");
+      chomp $ip;
+      $machine->succeed("ping -n -c1 $ip");
+      $machine->succeed("curl --fail http://$ip/ > /dev/null");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixos/tests/containers-ipv6.nix b/nixos/tests/containers-ipv6.nix
new file mode 100644
index 000000000000..f676ed122bb3
--- /dev/null
+++ b/nixos/tests/containers-ipv6.nix
@@ -0,0 +1,61 @@
+# Test for NixOS' container support.
+
+let
+  hostIp = "fc00::2";
+  localIp = "fc00::1";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-ipv6";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress6 = hostIp;
+          localAddress6 = localIp;
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+              networking.firewall.allowPing = true;
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container start webserver");
+
+      # wait two seconds for the container to start and the network to be up
+      sleep 2;
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      my $ip = "${localIp}";
+      chomp $ip;
+      $machine->succeed("ping -n -c 1 $ip");
+      $machine->succeed("curl --fail http://[$ip]/ > /dev/null");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://[$ip]/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixos/tests/containers-macvlans.nix b/nixos/tests/containers-macvlans.nix
new file mode 100644
index 000000000000..390dc4ad2c29
--- /dev/null
+++ b/nixos/tests/containers-macvlans.nix
@@ -0,0 +1,82 @@
+# Test for NixOS' container support.
+
+let
+  # containers IP on VLAN 1
+  containerIp1 = "192.168.1.253";
+  containerIp2 = "192.168.1.254";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-macvlans";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ montag451 ];
+  };
+
+  nodes = {
+
+    machine1 =
+      { config, pkgs, lib, ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+
+        # To be able to ping containers from the host, it is necessary
+        # to create a macvlan on the host on the VLAN 1 network.
+        networking.macvlans.mv-eth1-host = {
+          interface = "eth1";
+          mode = "bridge";
+        };
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [];
+        networking.interfaces.mv-eth1-host = {
+          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+        };
+
+        containers.test1 = {
+          autoStart = true;
+          macvlans = [ "eth1" ];
+
+          config = {
+            networking.interfaces.mv-eth1 = {
+              ipv4.addresses = [ { address = containerIp1; prefixLength = 24; } ];
+            };
+          };
+        };
+
+        containers.test2 = {
+          autoStart = true;
+          macvlans = [ "eth1" ];
+
+          config = {
+            networking.interfaces.mv-eth1 = {
+              ipv4.addresses = [ { address = containerIp2; prefixLength = 24; } ];
+            };
+          };
+        };
+      };
+
+    machine2 =
+      { config, pkgs, ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+    $machine1->waitForUnit("default.target");
+    $machine2->waitForUnit("default.target");
+
+    # Ping between containers to check that macvlans are created in bridge mode
+    $machine1->succeed("nixos-container run test1 -- ping -n -c 1 ${containerIp2}");
+
+    # Ping containers from the host (machine1)
+    $machine1->succeed("ping -n -c 1 ${containerIp1}");
+    $machine1->succeed("ping -n -c 1 ${containerIp2}");
+
+    # Ping containers from the second machine to check that containers are reachable from the outside
+    $machine2->succeed("ping -n -c 1 ${containerIp1}");
+    $machine2->succeed("ping -n -c 1 ${containerIp2}");
+  '';
+})
diff --git a/nixos/tests/containers-physical_interfaces.nix b/nixos/tests/containers-physical_interfaces.nix
new file mode 100644
index 000000000000..bde8e175f953
--- /dev/null
+++ b/nixos/tests/containers-physical_interfaces.nix
@@ -0,0 +1,133 @@
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-physical_interfaces";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampfschlaefer ];
+  };
+
+  nodes = {
+    server = { config, pkgs, ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+
+        containers.server = {
+          privateNetwork = true;
+          interfaces = [ "eth1" ];
+
+          config = {
+            networking.interfaces.eth1.ipv4.addresses = [
+              { address = "10.10.0.1"; prefixLength = 24; }
+            ];
+            networking.firewall.enable = false;
+          };
+        };
+      };
+    bridged = { config, pkgs, ... }: {
+      virtualisation.memorySize = 128;
+      virtualisation.vlans = [ 1 ];
+
+      containers.bridged = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bridges.br0.interfaces = [ "eth1" ];
+          networking.interfaces.br0.ipv4.addresses = [
+            { address = "10.10.0.2"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+
+    bonded = { config, pkgs, ... }: {
+      virtualisation.memorySize = 128;
+      virtualisation.vlans = [ 1 ];
+
+      containers.bonded = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bonds.bond0 = {
+            interfaces = [ "eth1" ];
+            driverOptions.mode = "active-backup";
+          };
+          networking.interfaces.bond0.ipv4.addresses = [
+            { address = "10.10.0.3"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+
+    bridgedbond = { config, pkgs, ... }: {
+      virtualisation.memorySize = 128;
+      virtualisation.vlans = [ 1 ];
+
+      containers.bridgedbond = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bonds.bond0 = {
+            interfaces = [ "eth1" ];
+            driverOptions.mode = "active-backup";
+          };
+          networking.bridges.br0.interfaces = [ "bond0" ];
+          networking.interfaces.br0.ipv4.addresses = [
+            { address = "10.10.0.4"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    subtest "prepare server", sub {
+      $server->waitForUnit("default.target");
+      $server->succeed("ip link show dev eth1 >&2");
+    };
+
+    subtest "simple physical interface", sub {
+      $server->succeed("nixos-container start server");
+      $server->waitForUnit("container\@server");
+      $server->succeed("systemctl -M server list-dependencies network-addresses-eth1.service >&2");
+
+      # The other tests will ping this container on its ip. Here we just check
+      # that the device is present in the container.
+      $server->succeed("nixos-container run server -- ip a show dev eth1 >&2");
+    };
+
+    subtest "physical device in bridge in container", sub {
+      $bridged->waitForUnit("default.target");
+      $bridged->succeed("nixos-container start bridged");
+      $bridged->waitForUnit("container\@bridged");
+      $bridged->succeed("systemctl -M bridged list-dependencies network-addresses-br0.service >&2");
+      $bridged->succeed("systemctl -M bridged status -n 30 -l network-addresses-br0.service");
+      $bridged->succeed("nixos-container run bridged -- ping -w 10 -c 1 -n 10.10.0.1");
+    };
+
+    subtest "physical device in bond in container", sub {
+      $bonded->waitForUnit("default.target");
+      $bonded->succeed("nixos-container start bonded");
+      $bonded->waitForUnit("container\@bonded");
+      $bonded->succeed("systemctl -M bonded list-dependencies network-addresses-bond0 >&2");
+      $bonded->succeed("systemctl -M bonded status -n 30 -l network-addresses-bond0 >&2");
+      $bonded->succeed("nixos-container run bonded -- ping -w 10 -c 1 -n 10.10.0.1");
+    };
+
+    subtest "physical device in bond in bridge in container", sub {
+      $bridgedbond->waitForUnit("default.target");
+      $bridgedbond->succeed("nixos-container start bridgedbond");
+      $bridgedbond->waitForUnit("container\@bridgedbond");
+      $bridgedbond->succeed("systemctl -M bridgedbond list-dependencies network-addresses-br0.service >&2");
+      $bridgedbond->succeed("systemctl -M bridgedbond status -n 30 -l network-addresses-br0.service");
+      $bridgedbond->succeed("nixos-container run bridgedbond -- ping -w 10 -c 1 -n 10.10.0.1");
+    };
+  '';
+})
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
new file mode 100644
index 000000000000..78cc445c2dd0
--- /dev/null
+++ b/nixos/tests/containers-portforward.nix
@@ -0,0 +1,63 @@
+# Test for NixOS' container support.
+
+let
+  hostIp = "192.168.0.1";
+  hostPort = 10080;
+  containerIp = "192.168.0.100";
+  containerPort = 80;
+in 
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-portforward";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ianwookim ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress = hostIp;
+          localAddress = containerIp;
+          forwardPorts = [ { protocol = "tcp"; hostPort = hostPort; containerPort = containerPort; } ];
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+              networking.firewall.allowPing = true;
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container start webserver");
+
+      # wait two seconds for the container to start and the network to be up
+      sleep 2;
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      #my $ip = $machine->succeed("nixos-container show-ip webserver");
+      #chomp $ip;
+      $machine->succeed("ping -n -c1 ${hostIp}");
+      $machine->succeed("curl --fail http://${hostIp}:${toString hostPort}/ > /dev/null");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://${hostIp}:${toString hostPort}/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixos/tests/containers-reloadable.nix b/nixos/tests/containers-reloadable.nix
new file mode 100644
index 000000000000..5fb42f2272b3
--- /dev/null
+++ b/nixos/tests/containers-reloadable.nix
@@ -0,0 +1,66 @@
+import ./make-test.nix ({ pkgs, lib, ...} :
+let
+  client_base = rec {
+    
+    containers.test1 = {
+      autoStart = true;
+      config = {
+        environment.etc."check".text = "client_base";
+      };
+    };
+
+    # prevent make-test.nix to change IP
+    networking.interfaces = {
+      eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+    };
+  };
+in {
+  name = "cotnainers-reloadable";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ danbst ];
+  };
+
+  nodes = {
+    client = { lib, pkgs, ... }: {
+      imports = [ client_base ];
+    };
+
+    client_c1 = { lib, pkgs, ... }: {
+      imports = [ client_base ];
+
+      containers.test1.config = {
+        environment.etc."check".text = lib.mkForce "client_c1";
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "nixos@example.com";
+      };
+    };
+    client_c2 = { lib, pkgs, ... }: {
+      imports = [ client_base ];
+
+      containers.test1.config = {
+        environment.etc."check".text = lib.mkForce "client_c2";
+        services.nginx.enable = true;
+      };
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    originalSystem = nodes.client.config.system.build.toplevel;
+    c1System = nodes.client_c1.config.system.build.toplevel;
+    c2System = nodes.client_c2.config.system.build.toplevel;
+  in ''
+    $client->start();
+    $client->waitForUnit("default.target");
+    $client->succeed("[[ \$(nixos-container run test1 cat /etc/check) == client_base ]] >&2");
+
+    $client->succeed("${c1System}/bin/switch-to-configuration test >&2");
+    $client->succeed("[[ \$(nixos-container run test1 cat /etc/check) == client_c1 ]] >&2");
+    $client->succeed("systemctl status httpd -M test1 >&2");
+
+    $client->succeed("${c2System}/bin/switch-to-configuration test >&2");
+    $client->succeed("[[ \$(nixos-container run test1 cat /etc/check) == client_c2 ]] >&2");
+    $client->fail("systemctl status httpd -M test1 >&2");
+    $client->succeed("systemctl status nginx -M test1 >&2");
+  '';
+
+})
diff --git a/nixos/tests/containers-restart_networking.nix b/nixos/tests/containers-restart_networking.nix
new file mode 100644
index 000000000000..f68c9b07759b
--- /dev/null
+++ b/nixos/tests/containers-restart_networking.nix
@@ -0,0 +1,114 @@
+# Test for NixOS' container support.
+
+let
+  client_base = rec {
+    networking.firewall.enable = false;
+
+    containers.webserver = {
+      autoStart = true;
+      privateNetwork = true;
+      hostBridge = "br0";
+      config = {
+        networking.firewall.enable = false;
+        networking.firewall.allowPing = true;
+        networking.interfaces.eth0.ipv4.addresses = [
+          { address = "192.168.1.122"; prefixLength = 24; }
+        ];
+      };
+    };
+  };
+in import ./make-test.nix ({ pkgs, lib, ...} :
+{
+  name = "containers-restart_networking";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampfschlaefer ];
+  };
+
+  nodes = {
+    client = { lib, pkgs, ... }: client_base // {
+      virtualisation.vlans = [ 1 ];
+
+      networking.bridges.br0 = {
+        interfaces = [];
+        rstp = false;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+      };
+
+    };
+    client_eth1 = { lib, pkgs, ... }: client_base // {
+      networking.bridges.br0 = {
+        interfaces = [ "eth1" ];
+        rstp = false;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+    client_eth1_rstp = { lib, pkgs, ... }: client_base // {
+      networking.bridges.br0 = {
+        interfaces = [ "eth1" ];
+        rstp = true;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses =  [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    originalSystem = nodes.client.config.system.build.toplevel;
+    eth1_bridged = nodes.client_eth1.config.system.build.toplevel;
+    eth1_rstp = nodes.client_eth1_rstp.config.system.build.toplevel;
+  in ''
+    $client->start();
+
+    $client->waitForUnit("default.target");
+
+    subtest "initial state", sub {
+      $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+      $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2");
+
+      $client->fail("ip l show eth1 |grep \"master br0\" >&2");
+      $client->fail("grep eth1 /run/br0.interfaces >&2");
+    };
+
+    subtest "interfaces without stp", sub {
+      $client->succeed("${eth1_bridged}/bin/switch-to-configuration test >&2");
+
+      $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+      $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2");
+
+      $client->succeed("ip l show eth1 |grep \"master br0\" >&2");
+      $client->succeed("grep eth1 /run/br0.interfaces >&2");
+    };
+
+    # activating rstp needs another service, therefor the bridge will restart and the container will loose its connectivity
+    #subtest "interfaces with rstp", sub {
+    #  $client->succeed("${eth1_rstp}/bin/switch-to-configuration test >&2");
+    #  $client->execute("ip -4 a >&2");
+    #  $client->execute("ip l >&2");
+    #
+    #  $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+    #  $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2");
+    #
+    #  $client->succeed("ip l show eth1 |grep \"master br0\" >&2");
+    #  $client->succeed("grep eth1 /run/br0.interfaces >&2");
+    #};
+
+    subtest "back to no interfaces and no stp", sub {
+      $client->succeed("${originalSystem}/bin/switch-to-configuration test >&2");
+
+      $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+      $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2");
+
+      $client->fail("ip l show eth1 |grep \"master br0\" >&2");
+      $client->fail("grep eth1 /run/br0.interfaces >&2");
+    };
+  '';
+
+})
diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix
new file mode 100644
index 000000000000..873dd364369f
--- /dev/null
+++ b/nixos/tests/containers-tmpfs.nix
@@ -0,0 +1,79 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-tmpfs";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ckampka ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.tmpfs =
+        {
+          autoStart = true;
+          tmpfs = [
+            # Mount var as a tmpfs
+            "/var"
+
+            # Add a nested mount inside a tmpfs
+            "/var/log"
+
+            # Add a tmpfs on a path that does not exist
+            "/some/random/path"
+          ];
+          config = { };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /tmpfs/ or die;
+
+      # Start the tmpfs container.
+      #$machine->succeed("nixos-container status tmpfs") =~ /up/ or die;
+
+      # Verify that /var is mounted as a tmpfs
+      #$machine->succeed("nixos-container run tmpfs -- systemctl status var.mount --no-pager 2>/dev/null") =~ /What: tmpfs/ or die;
+      $machine->succeed("nixos-container run tmpfs -- mountpoint -q /var 2>/dev/null");
+
+      # Verify that /var/log is mounted as a tmpfs
+      $machine->succeed("nixos-container run tmpfs -- systemctl status var-log.mount --no-pager 2>/dev/null") =~ /What: tmpfs/ or die;
+      $machine->succeed("nixos-container run tmpfs -- mountpoint -q /var/log 2>/dev/null");
+
+      # Verify that /some/random/path is mounted as a tmpfs
+      $machine->succeed("nixos-container run tmpfs -- systemctl status some-random-path.mount --no-pager 2>/dev/null") =~ /What: tmpfs/ or die;
+      $machine->succeed("nixos-container run tmpfs -- mountpoint -q /some/random/path 2>/dev/null");
+
+      # Verify that files created in the container in a non-tmpfs directory are visible on the host.
+      # This establishes legitimacy for the following tests
+      $machine->succeed("nixos-container run tmpfs -- touch /root/test.file 2>/dev/null");
+      $machine->succeed("nixos-container run tmpfs -- ls -l  /root | grep -q test.file 2>/dev/null");
+      $machine->succeed("test -e /var/lib/containers/tmpfs/root/test.file");
+
+
+      # Verify that /some/random/path is writable and that files created there
+      # are not in the hosts container dir but in the tmpfs
+      $machine->succeed("nixos-container run tmpfs -- touch /some/random/path/test.file 2>/dev/null");
+      $machine->succeed("nixos-container run tmpfs -- test -e /some/random/path/test.file 2>/dev/null");
+
+      $machine->fail("test -e /var/lib/containers/tmpfs/some/random/path/test.file");
+
+      # Verify that files created in the hosts container dir in a path where a tmpfs file system has been mounted
+      # are not visible to the container as the do not exist in the tmpfs
+      $machine->succeed("touch /var/lib/containers/tmpfs/var/test.file");
+
+      $machine->succeed("test -e /var/lib/containers/tmpfs/var/test.file");
+      $machine->succeed("ls -l /var/lib/containers/tmpfs/var/ | grep -q test.file 2>/dev/null");
+
+      $machine->fail("nixos-container run tmpfs -- ls -l /var | grep -q test.file 2>/dev/null");
+
+    '';
+
+})
diff --git a/nixos/tests/couchdb.nix b/nixos/tests/couchdb.nix
new file mode 100644
index 000000000000..a3f675236bc6
--- /dev/null
+++ b/nixos/tests/couchdb.nix
@@ -0,0 +1,56 @@
+import ./make-test.nix ({ pkgs, lib, ...}:
+
+with lib;
+
+{
+  name = "couchdb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes = {
+    couchdb1 =
+      { pkgs, config, ... }:
+
+      { environment.systemPackages = with pkgs; [ jq ];
+        services.couchdb.enable = true;
+      };
+
+    couchdb2 =
+      { pkgs, config, ... }:
+
+      { environment.systemPackages = with pkgs; [ jq ];
+        services.couchdb.enable = true;
+        services.couchdb.package = pkgs.couchdb2;
+      };
+  };
+
+  testScript = let
+    curlJqCheck = action: path: jqexpr: result:
+      pkgs.writeScript "curl-jq-check-${action}-${path}.sh" ''
+        RESULT=$(curl -X ${action} http://127.0.0.1:5984/${path} | jq -r '${jqexpr}')
+        echo $RESULT >&2
+        if [ "$RESULT" != "${result}" ]; then
+          exit 1
+        fi
+      '';
+  in ''
+    startAll;
+
+    $couchdb1->waitForUnit("couchdb.service");
+    $couchdb1->waitUntilSucceeds("${curlJqCheck "GET" "" ".couchdb" "Welcome"}");
+    $couchdb1->waitUntilSucceeds("${curlJqCheck "GET" "_all_dbs" ". | length" "2"}");
+    $couchdb1->succeed("${curlJqCheck "PUT" "foo" ".ok" "true"}");
+    $couchdb1->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "3"}");
+    $couchdb1->succeed("${curlJqCheck "DELETE" "foo" ".ok" "true"}");
+    $couchdb1->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "2"}");
+
+    $couchdb2->waitForUnit("couchdb.service");
+    $couchdb2->waitUntilSucceeds("${curlJqCheck "GET" "" ".couchdb" "Welcome"}");
+    $couchdb2->waitUntilSucceeds("${curlJqCheck "GET" "_all_dbs" ". | length" "0"}");
+    $couchdb2->succeed("${curlJqCheck "PUT" "foo" ".ok" "true"}");
+    $couchdb2->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "1"}");
+    $couchdb2->succeed("${curlJqCheck "DELETE" "foo" ".ok" "true"}");
+    $couchdb2->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "0"}");
+  '';
+})
diff --git a/nixos/tests/deluge.nix b/nixos/tests/deluge.nix
new file mode 100644
index 000000000000..6119fd58447c
--- /dev/null
+++ b/nixos/tests/deluge.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "deluge";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ flokli ];
+  };
+
+  nodes = {
+    server =
+      { pkgs, config, ... }:
+
+      { services.deluge = {
+          enable = true;
+          web.enable = true;
+        };
+        networking.firewall.allowedTCPPorts = [ 8112 ];
+      };
+
+    client = { };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("deluged");
+    $server->waitForUnit("delugeweb");
+    $client->waitForUnit("network.target");
+    $client->waitUntilSucceeds("curl --fail http://server:8112");
+  '';
+})
diff --git a/nixos/tests/dhparams.nix b/nixos/tests/dhparams.nix
new file mode 100644
index 000000000000..d11dfeec5d0c
--- /dev/null
+++ b/nixos/tests/dhparams.nix
@@ -0,0 +1,144 @@
+let
+  common = { pkgs, ... }: {
+    security.dhparams.enable = true;
+    environment.systemPackages = [ pkgs.openssl ];
+  };
+
+in import ./make-test.nix {
+  name = "dhparams";
+
+  nodes.generation1 = { pkgs, config, ... }: {
+    imports = [ common ];
+    security.dhparams.params = {
+      # Use low values here because we don't want the test to run for ages.
+      foo.bits = 16;
+      # Also use the old format to make sure the type is coerced in the right
+      # way.
+      bar = 17;
+    };
+
+    systemd.services.foo = {
+      description = "Check systemd Ordering";
+      wantedBy = [ "multi-user.target" ];
+      unitConfig = {
+        # This is to make sure that the dhparams generation of foo occurs
+        # before this service so we need this service to start as early as
+        # possible to provoke a race condition.
+        DefaultDependencies = false;
+
+        # We check later whether the service has been started or not.
+        ConditionPathExists = config.security.dhparams.params.foo.path;
+      };
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      # The reason we only provide an ExecStop here is to ensure that we don't
+      # accidentally trigger an error because a file system is not yet ready
+      # during very early startup (we might not even have the Nix store
+      # available, for example if future changes in NixOS use systemd mount
+      # units to do early file system initialisation).
+      serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true";
+    };
+  };
+
+  nodes.generation2 = {
+    imports = [ common ];
+    security.dhparams.params.foo.bits = 18;
+  };
+
+  nodes.generation3 = common;
+
+  nodes.generation4 = {
+    imports = [ common ];
+    security.dhparams.stateful = false;
+    security.dhparams.params.foo2.bits = 18;
+    security.dhparams.params.bar2.bits = 19;
+  };
+
+  nodes.generation5 = {
+    imports = [ common ];
+    security.dhparams.defaultBitSize = 30;
+    security.dhparams.params.foo3 = {};
+    security.dhparams.params.bar3 = {};
+  };
+
+  testScript = { nodes, ... }: let
+    getParamPath = gen: name: let
+      node = "generation${toString gen}";
+    in nodes.${node}.config.security.dhparams.params.${name}.path;
+
+    assertParamBits = gen: name: bits: let
+      path = getParamPath gen name;
+    in ''
+      $machine->nest('check bit size of ${path}', sub {
+        my $out = $machine->succeed('openssl dhparam -in ${path} -text');
+        $out =~ /^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$/m;
+        die "bit size should be ${toString bits} but it is $1 instead."
+          if $1 != ${toString bits};
+      });
+    '';
+
+    switchToGeneration = gen: let
+      node = "generation${toString gen}";
+      inherit (nodes.${node}.config.system.build) toplevel;
+      switchCmd = "${toplevel}/bin/switch-to-configuration test";
+    in ''
+      $machine->nest('switch to generation ${toString gen}', sub {
+        $machine->succeed('${switchCmd}');
+        $main::machine = ''$${node};
+      });
+    '';
+
+  in ''
+    my $machine = $generation1;
+
+    $machine->waitForUnit('multi-user.target');
+
+    subtest "verify startup order", sub {
+      $machine->succeed('systemctl is-active foo.service');
+    };
+
+    subtest "check bit sizes of dhparam files", sub {
+      ${assertParamBits 1 "foo" 16}
+      ${assertParamBits 1 "bar" 17}
+    };
+
+    ${switchToGeneration 2}
+
+    subtest "check whether bit size has changed", sub {
+      ${assertParamBits 2 "foo" 18}
+    };
+
+    subtest "ensure that dhparams file for 'bar' was deleted", sub {
+      $machine->fail('test -e ${getParamPath 1 "bar"}');
+    };
+
+    ${switchToGeneration 3}
+
+    subtest "ensure that 'security.dhparams.path' has been deleted", sub {
+      $machine->fail(
+        'test -e ${nodes.generation3.config.security.dhparams.path}'
+      );
+    };
+
+    ${switchToGeneration 4}
+
+    subtest "check bit sizes dhparam files", sub {
+      ${assertParamBits 4 "foo2" 18}
+      ${assertParamBits 4 "bar2" 19}
+    };
+
+    subtest "check whether dhparam files are in the Nix store", sub {
+      $machine->succeed(
+        'expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}',
+        'expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}',
+      );
+    };
+
+    ${switchToGeneration 5}
+
+    subtest "check whether defaultBitSize works as intended", sub {
+      ${assertParamBits 5 "foo3" 30}
+      ${assertParamBits 5 "bar3" 30}
+    };
+  '';
+}
diff --git a/nixos/tests/dnscrypt-proxy.nix b/nixos/tests/dnscrypt-proxy.nix
new file mode 100644
index 000000000000..1fcf3903b13e
--- /dev/null
+++ b/nixos/tests/dnscrypt-proxy.nix
@@ -0,0 +1,33 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "dnscrypt-proxy";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ joachifm ];
+  };
+
+  nodes = {
+    # A client running the recommended setup: DNSCrypt proxy as a forwarder
+    # for a caching DNS client.
+    client =
+    { config, pkgs, ... }:
+    let localProxyPort = 43; in
+    {
+      security.apparmor.enable = true;
+
+      services.dnscrypt-proxy.enable = true;
+      services.dnscrypt-proxy.localPort = localProxyPort;
+      services.dnscrypt-proxy.extraArgs = [ "-X libdcplugin_example.so" ];
+
+      services.dnsmasq.enable = true;
+      services.dnsmasq.servers = [ "127.0.0.1#${toString localProxyPort}" ];
+    };
+  };
+
+  testScript = ''
+    $client->waitForUnit("dnsmasq");
+
+    # The daemon is socket activated; sending a single ping should activate it.
+    $client->fail("systemctl is-active dnscrypt-proxy");
+    $client->execute("${pkgs.iputils}/bin/ping -c1 example.com");
+    $client->waitUntilSucceeds("systemctl is-active dnscrypt-proxy");
+  '';
+})
diff --git a/nixos/tests/docker-edge.nix b/nixos/tests/docker-edge.nix
new file mode 100644
index 000000000000..38d25daff194
--- /dev/null
+++ b/nixos/tests/docker-edge.nix
@@ -0,0 +1,47 @@
+# This test runs docker and checks if simple container starts
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "docker";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus offline ];
+  };
+
+  nodes = {
+    docker =
+      { config, pkgs, ... }:
+        {
+          virtualisation.docker.enable = true;
+          virtualisation.docker.package = pkgs.docker-edge;
+
+          users.users = {
+            noprivs = {
+              isNormalUser = true;
+              description = "Can't access the docker daemon";
+              password = "foobar";
+            };
+
+            hasprivs = {
+              isNormalUser = true;
+              description = "Can access the docker daemon";
+              password = "foobar";
+              extraGroups = [ "docker" ];
+            };
+          };
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $docker->waitForUnit("sockets.target");
+    $docker->succeed("tar cv --files-from /dev/null | docker import - scratchimg");
+    $docker->succeed("docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10");
+    $docker->succeed("docker ps | grep sleeping");
+    $docker->succeed("sudo -u hasprivs docker ps");
+    $docker->fail("sudo -u noprivs docker ps");
+    $docker->succeed("docker stop sleeping");
+
+    # Must match version twice to ensure client and server versions are correct
+    $docker->succeed('[ $(docker version | grep ${pkgs.docker-edge.version} | wc -l) = "2" ]');
+  '';
+})
diff --git a/nixos/tests/docker-registry.nix b/nixos/tests/docker-registry.nix
index eed3284202f5..1fbd199c7bc4 100644
--- a/nixos/tests/docker-registry.nix
+++ b/nixos/tests/docker-registry.nix
@@ -3,14 +3,16 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "docker-registry";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline ];
+    maintainers = [ globin ma27 ironpinguin ];
   };
 
   nodes = {
     registry = { config, pkgs, ... }: {
       services.dockerRegistry.enable = true;
+      services.dockerRegistry.enableDelete = true;
       services.dockerRegistry.port = 8080;
-      services.dockerRegistry.host = "0.0.0.0";
+      services.dockerRegistry.listenAddress = "0.0.0.0";
+      services.dockerRegistry.enableGarbageCollect = true;
       networking.firewall.allowedTCPPorts = [ 8080 ];
     };
 
@@ -33,11 +35,29 @@ import ./make-test.nix ({ pkgs, ...} : {
 
     $registry->start();
     $registry->waitForUnit("docker-registry.service");
+    $registry->waitForOpenPort("8080");
     $client1->succeed("docker push registry:8080/scratch");
 
     $client2->start();
     $client2->waitForUnit("docker.service");
     $client2->succeed("docker pull registry:8080/scratch");
     $client2->succeed("docker images | grep scratch");
+
+    $client2->succeed(
+      'curl -fsS -X DELETE registry:8080/v2/scratch/manifests/$(curl -fsS -I -H"Accept: application/vnd.docker.distribution.manifest.v2+json" registry:8080/v2/scratch/manifests/latest | grep Docker-Content-Digest | sed -e \'s/Docker-Content-Digest: //\' | tr -d \'\r\')'
+    );
+
+    $registry->systemctl("start docker-registry-garbage-collect.service");
+    $registry->waitUntilFails("systemctl status docker-registry-garbage-collect.service");
+    $registry->waitForUnit("docker-registry.service");
+
+    $registry->fail(
+      'ls -l /var/lib/docker-registry/docker/registry/v2/blobs/sha256/*/*/data'
+    );
+
+    $client1->succeed("docker push registry:8080/scratch");
+    $registry->succeed(
+      'ls -l /var/lib/docker-registry/docker/registry/v2/blobs/sha256/*/*/data'
+    );
   '';
 })
diff --git a/nixos/tests/docker-tools-overlay.nix b/nixos/tests/docker-tools-overlay.nix
new file mode 100644
index 000000000000..9d7fa3e7a8c5
--- /dev/null
+++ b/nixos/tests/docker-tools-overlay.nix
@@ -0,0 +1,32 @@
+# this test creates a simple GNU image with docker tools and sees if it executes
+
+import ./make-test.nix ({ pkgs, ... }:
+{
+  name = "docker-tools-overlay";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+
+  nodes = {
+    docker =
+      { config, pkgs, ... }:
+      {
+        virtualisation.docker.enable = true;
+        virtualisation.docker.storageDriver = "overlay";  # defaults to overlay2
+      };
+  };
+
+  testScript =
+    ''
+      $docker->waitForUnit("sockets.target");
+
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version");
+
+      # Check if the nix store has correct user permissions depending on what
+      # storage driver is used, incorrectly built images can show up as readonly.
+      # drw-------  3 0 0   3 Apr 14 11:36 /nix
+      # drw------- 99 0 0 100 Apr 14 11:36 /nix/store
+      $docker->succeed("docker run --rm -u 1000:1000 ${pkgs.dockerTools.examples.bash.imageName} bash --version");
+    '';
+})
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
new file mode 100644
index 000000000000..e2bcfbbd1f96
--- /dev/null
+++ b/nixos/tests/docker-tools.nix
@@ -0,0 +1,55 @@
+# this test creates a simple GNU image with docker tools and sees if it executes
+
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "docker-tools";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+
+  nodes = {
+    docker =
+      { config, pkgs, ... }: {
+        virtualisation = {
+          diskSize = 2048;
+          docker.enable = true;
+        };
+      };
+  };
+
+  testScript =
+    ''
+      $docker->waitForUnit("sockets.target");
+
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version");
+      $docker->succeed("docker rmi ${pkgs.dockerTools.examples.bash.imageName}");
+
+      # Check if the nix store is correctly initialized by listing dependencies of the installed Nix binary
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nix}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.nix.imageName} nix-store -qR ${pkgs.nix}");
+      $docker->succeed("docker rmi ${pkgs.dockerTools.examples.nix.imageName}");
+
+      # To test the pullImage tool
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nixFromDockerHub}'");
+      $docker->succeed("docker run --rm nixos/nix:1.11 nix-store --version");
+      $docker->succeed("docker rmi nixos/nix:1.11");
+
+      # To test runAsRoot and entry point
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nginx}'");
+      $docker->succeed("docker run --name nginx -d -p 8000:80 ${pkgs.dockerTools.examples.nginx.imageName}");
+      $docker->waitUntilSucceeds('curl http://localhost:8000/');
+      $docker->succeed("docker rm --force nginx");
+      $docker->succeed("docker rmi '${pkgs.dockerTools.examples.nginx.imageName}'");
+
+      # An pulled image can be used as base image
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.onTopOfPulledImage}'");
+      $docker->succeed("docker run --rm ontopofpulledimage hello");
+      $docker->succeed("docker rmi ontopofpulledimage");
+
+      # Regression test for issue #34779
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.runAsRootExtraCommands}'");
+      $docker->succeed("docker run --rm runasrootextracommands cat extraCommands");
+      $docker->succeed("docker run --rm runasrootextracommands cat runAsRoot");
+      $docker->succeed("docker rmi '${pkgs.dockerTools.examples.runAsRootExtraCommands.imageName}'");
+    '';
+})
diff --git a/nixos/tests/docker.nix b/nixos/tests/docker.nix
index 635a97e2ce09..c6c8f4cdb5fb 100644
--- a/nixos/tests/docker.nix
+++ b/nixos/tests/docker.nix
@@ -3,7 +3,7 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "docker";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline ];
+    maintainers = [ nequissimus offline ];
   };
 
   nodes = {
@@ -11,19 +11,37 @@ import ./make-test.nix ({ pkgs, ...} : {
       { config, pkgs, ... }:
         {
           virtualisation.docker.enable = true;
-          # FIXME: The default "devicemapper" storageDriver fails in NixOS VM
-          # tests.
-          virtualisation.docker.storageDriver = "overlay";
+          virtualisation.docker.package = pkgs.docker;
+
+          users.users = {
+            noprivs = {
+              isNormalUser = true;
+              description = "Can't access the docker daemon";
+              password = "foobar";
+            };
+
+            hasprivs = {
+              isNormalUser = true;
+              description = "Can access the docker daemon";
+              password = "foobar";
+              extraGroups = [ "docker" ];
+            };
+          };
         };
     };
 
   testScript = ''
     startAll;
 
-    $docker->waitForUnit("docker.service");
+    $docker->waitForUnit("sockets.target");
     $docker->succeed("tar cv --files-from /dev/null | docker import - scratchimg");
     $docker->succeed("docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10");
     $docker->succeed("docker ps | grep sleeping");
+    $docker->succeed("sudo -u hasprivs docker ps");
+    $docker->fail("sudo -u noprivs docker ps");
     $docker->succeed("docker stop sleeping");
+
+    # Must match version twice to ensure client and server versions are correct
+    $docker->succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "2" ]');
   '';
 })
diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix
new file mode 100644
index 000000000000..156079d1d585
--- /dev/null
+++ b/nixos/tests/dovecot.nix
@@ -0,0 +1,77 @@
+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
+      '';
+
+      sendTestMailViaDeliveryAgent = pkgs.writeScriptBin "send-lda" ''
+        #!${pkgs.stdenv.shell}
+
+        exec ${pkgs.dovecot}/libexec/dovecot/deliver -d bob <<MAIL
+        From: root@localhost
+        To: bob@localhost
+        Subject: Something else...
+
+        I'm running short of ideas!
+        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('bob')
+          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"I'm running short of ideas!"
+        finally:
+          pop.quit()
+      '';
+
+    in [ sendTestMail sendTestMailViaDeliveryAgent testImap testPop ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit('postfix.service');
+    $machine->waitForUnit('dovecot2.service');
+    $machine->succeed('send-testmail');
+    $machine->succeed('send-lda');
+    $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
+    $machine->succeed('test-imap');
+    $machine->succeed('test-pop');
+  '';
+}
diff --git a/nixos/tests/ec2.nix b/nixos/tests/ec2.nix
index b12d498e3a09..f585fa2ec237 100644
--- a/nixos/tests/ec2.nix
+++ b/nixos/tests/ec2.nix
@@ -1,7 +1,6 @@
 { system ? builtins.currentSystem }:
 
 with import ../lib/testing.nix { inherit system; };
-with import ../lib/qemu-flags.nix;
 with pkgs.lib;
 
 let
@@ -10,9 +9,9 @@ let
       inherit system;
       modules = [
         ../maintainers/scripts/ec2/amazon-image.nix
-        ../../nixos/modules/testing/test-instrumentation.nix
-        { boot.initrd.kernelModules = [ "virtio" "virtio_blk" "virtio_pci" "virtio_ring" ];
-          ec2.hvm = true;
+        ../modules/testing/test-instrumentation.nix
+        ../modules/profiles/qemu-guest.nix
+        { ec2.hvm = true;
 
           # Hack to make the partition resizing work in QEMU.
           boot.initrd.postDeviceCommands = mkBefore
@@ -20,6 +19,19 @@ let
               ln -s vda /dev/xvda
               ln -s vda1 /dev/xvda1
             '';
+
+          # Needed by nixos-rebuild due to the lack of network
+          # access. Mostly copied from
+          # modules/profiles/installation-device.nix.
+          system.extraDependencies =
+            with pkgs; [
+              stdenv busybox perlPackages.ArchiveCpio unionfs-fuse mkinitcpio-nfs-utils
+
+              # These are used in the configure-from-userdata tests for EC2. Httpd and valgrind are requested
+              # directly by the configuration we set, and libxslt.bin is used indirectly as a build dependency
+              # of the derivation for dbus configuration files.
+              apacheHttpd valgrind.doc libxslt.bin
+            ];
         }
       ];
     }).config.system.build.amazonImage;
@@ -29,10 +41,10 @@ let
       metaData = pkgs.stdenv.mkDerivation {
         name = "metadata";
         buildCommand = ''
-          mkdir -p $out/2011-01-01
-          ln -s ${pkgs.writeText "userData" userData} $out/2011-01-01/user-data
           mkdir -p $out/1.0/meta-data
+          ln -s ${pkgs.writeText "userData" userData} $out/1.0/user-data
           echo "${hostname}" > $out/1.0/meta-data/hostname
+          echo "(unknown)" > $out/1.0/meta-data/ami-manifest-path
         '' + optionalString (sshPublicKey != null) ''
           mkdir -p $out/1.0/meta-data/public-keys/0
           ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
@@ -46,7 +58,7 @@ let
           my $imageDir = ($ENV{'TMPDIR'} // "/tmp") . "/vm-state-machine";
           mkdir $imageDir, 0700;
           my $diskImage = "$imageDir/machine.qcow2";
-          system("qemu-img create -f qcow2 -o backing_file=${image}/nixos.img $diskImage") == 0 or die;
+          system("qemu-img create -f qcow2 -o backing_file=${image}/nixos.qcow2 $diskImage") == 0 or die;
           system("qemu-img resize $diskImage 10G") == 0 or die;
 
           # Note: we use net=169.0.0.0/8 rather than
@@ -56,7 +68,7 @@ let
           # again when it deletes link-local addresses.) Ideally we'd
           # turn off the DHCP server, but qemu does not have an option
           # to do that.
-          my $startCommand = "qemu-kvm -m 768 -net nic -net 'user,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'";
+          my $startCommand = "qemu-kvm -m 768 -net nic,vlan=0,model=virtio -net 'user,vlan=0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'";
           $startCommand .= " -drive file=$diskImage,if=virtio,werror=report";
           $startCommand .= " \$QEMU_OPTS";
 
@@ -89,9 +101,11 @@ in {
     '';
     script = ''
       $machine->start;
-      $machine->waitForFile("/root/user-data");
+      $machine->waitForFile("/etc/ec2-metadata/user-data");
       $machine->waitForUnit("sshd.service");
 
+      $machine->succeed("grep unknown /etc/ec2-metadata/ami-manifest-path");
+
       # We have no keys configured on the client side yet, so this should fail
       $machine->fail("ssh -o BatchMode=yes localhost exit");
 
@@ -117,7 +131,7 @@ in {
       # Just to make sure resizing is idempotent.
       $machine->shutdown;
       $machine->start;
-      $machine->waitForFile("/root/user-data");
+      $machine->waitForFile("/etc/ec2-metadata/user-data");
     '';
   };
 
@@ -125,22 +139,35 @@ in {
     name         = "config-userdata";
     sshPublicKey = snakeOilPublicKey;
 
+    # ### http://nixos.org/channels/nixos-unstable nixos
     userData = ''
-      ### http://nixos.org/channels/nixos-unstable nixos
+      { pkgs, ... }:
+
       {
         imports = [
           <nixpkgs/nixos/modules/virtualisation/amazon-image.nix>
           <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
         ];
         environment.etc.testFile = {
           text = "whoa";
         };
+
+        services.httpd = {
+          enable = true;
+          adminAddr = "test@example.org";
+          documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
+        };
+        networking.firewall.allowedTCPPorts = [ 80 ];
       }
     '';
     script = ''
       $machine->start;
       $machine->waitForFile("/etc/testFile");
       $machine->succeed("cat /etc/testFile | grep -q 'whoa'");
+
+      $machine->waitForUnit("httpd.service");
+      $machine->succeed("curl http://localhost | grep Valgrind");
     '';
   };
 }
diff --git a/nixos/tests/ecryptfs.nix b/nixos/tests/ecryptfs.nix
new file mode 100644
index 000000000000..041be0f5a624
--- /dev/null
+++ b/nixos/tests/ecryptfs.nix
@@ -0,0 +1,84 @@
+import ./make-test.nix ({ pkgs, ... }:
+{
+  name = "ecryptfs";
+
+  machine = { config, pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    boot.kernelModules = [ "ecryptfs" ];
+    security.pam.enableEcryptfs = true;
+    environment.systemPackages = with pkgs; [ keyutils ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit("default.target");
+
+    # Set alice up with a password and a home
+    $machine->succeed("(echo foobar; echo foobar) | passwd alice");
+    $machine->succeed("chown -R alice.users ~alice");
+
+    # Migrate alice's home
+    my $out = $machine->succeed("echo foobar | ecryptfs-migrate-home -u alice");
+    $machine->log("ecryptfs-migrate-home said: $out");
+
+    # Log alice in (ecryptfs passwhrase is wrapped during first login)
+    $machine->waitUntilTTYMatches(1, "login: ");
+    $machine->sendChars("alice\n");
+    $machine->waitUntilTTYMatches(1, "Password: ");
+    $machine->sendChars("foobar\n");
+    $machine->waitUntilTTYMatches(1, "alice\@machine");
+    $machine->sendChars("logout\n");
+    $machine->waitUntilTTYMatches(1, "login: ");
+
+    # Why do I need to do this??
+    $machine->succeed("su alice -c ecryptfs-umount-private || true");
+    $machine->sleep(1);
+    $machine->fail("mount | grep ecryptfs"); # check that encrypted home is not mounted
+
+    # Show contents of the user keyring
+    my $out = $machine->succeed("su - alice -c 'keyctl list \@u'");
+    $machine->log("keyctl unlink said: " . $out);
+
+    # Log alice again
+    $machine->waitUntilTTYMatches(1, "login: ");
+    $machine->sendChars("alice\n");
+    $machine->waitUntilTTYMatches(1, "Password: ");
+    $machine->sendChars("foobar\n");
+    $machine->waitUntilTTYMatches(1, "alice\@machine");
+
+    # Create some files in encrypted home
+    $machine->succeed("su alice -c 'touch ~alice/a'");
+    $machine->succeed("su alice -c 'echo c > ~alice/b'");
+
+    # Logout
+    $machine->sendChars("logout\n");
+    $machine->waitUntilTTYMatches(1, "login: ");
+
+    # Why do I need to do this??
+    $machine->succeed("su alice -c ecryptfs-umount-private || true");
+    $machine->sleep(1);
+
+    # Check that the filesystem is not accessible
+    $machine->fail("mount | grep ecryptfs");
+    $machine->succeed("su alice -c 'test \! -f ~alice/a'");
+    $machine->succeed("su alice -c 'test \! -f ~alice/b'");
+
+    # Log alice once more
+    $machine->waitUntilTTYMatches(1, "login: ");
+    $machine->sendChars("alice\n");
+    $machine->waitUntilTTYMatches(1, "Password: ");
+    $machine->sendChars("foobar\n");
+    $machine->waitUntilTTYMatches(1, "alice\@machine");
+
+    # Check that the files are there
+    $machine->sleep(1);
+    $machine->succeed("su alice -c 'test -f ~alice/a'");
+    $machine->succeed("su alice -c 'test -f ~alice/b'");
+    $machine->succeed(qq%test "\$(cat ~alice/b)" = "c"%);
+
+    # Catch https://github.com/NixOS/nixpkgs/issues/16766
+    $machine->succeed("su alice -c 'ls -lh ~alice/'");
+
+    $machine->sendChars("logout\n");
+    $machine->waitUntilTTYMatches(1, "login: ");
+  '';
+})
diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix
new file mode 100644
index 000000000000..ed656b3628b9
--- /dev/null
+++ b/nixos/tests/elk.nix
@@ -0,0 +1,107 @@
+{ system ? builtins.currentSystem }:
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+let
+  esUrl = "http://localhost:9200";
+
+  mkElkTest = name : elk : makeTest {
+    inherit name;
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ eelco chaoflow offline basvandijk ];
+    };
+    nodes = {
+      one =
+        { config, pkgs, ... }: {
+            # Not giving the machine at least 2060MB results in elasticsearch failing with the following error:
+            #
+            #   OpenJDK 64-Bit Server VM warning:
+            #     INFO: os::commit_memory(0x0000000085330000, 2060255232, 0)
+            #     failed; error='Cannot allocate memory' (errno=12)
+            #
+            #   There is insufficient memory for the Java Runtime Environment to continue.
+            #   Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
+            #
+            # When setting this to 2500 I got "Kernel panic - not syncing: Out of
+            # memory: compulsory panic_on_oom is enabled" so lets give it even a
+            # bit more room:
+            virtualisation.memorySize = 3000;
+
+            # For querying JSON objects returned from elasticsearch and kibana.
+            environment.systemPackages = [ pkgs.jq ];
+
+            services = {
+              logstash = {
+                enable = true;
+                package = elk.logstash;
+                inputConfig = ''
+                  exec { command => "echo -n flowers" interval => 1 type => "test" }
+                  exec { command => "echo -n dragons" interval => 1 type => "test" }
+                '';
+                filterConfig = ''
+                  if [message] =~ /dragons/ {
+                    drop {}
+                  }
+                '';
+                outputConfig = ''
+                  file {
+                    path => "/tmp/logstash.out"
+                    codec => line { format => "%{message}" }
+                  }
+                  elasticsearch {
+                    hosts => [ "${esUrl}" ]
+                  }
+                '';
+              };
+
+              elasticsearch = {
+                enable = true;
+                package = elk.elasticsearch;
+              };
+
+              kibana = {
+                enable = true;
+                package = elk.kibana;
+                elasticsearch.url = esUrl;
+              };
+            };
+          };
+      };
+
+    testScript = ''
+      startAll;
+
+      $one->waitForUnit("elasticsearch.service");
+
+      # Continue as long as the status is not "red". The status is probably
+      # "yellow" instead of "green" because we are using a single elasticsearch
+      # node which elasticsearch considers risky.
+      #
+      # TODO: extend this test with multiple elasticsearch nodes and see if the status turns "green".
+      $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_cluster/health' | jq .status | grep -v red");
+
+      # Perform some simple logstash tests.
+      $one->waitForUnit("logstash.service");
+      $one->waitUntilSucceeds("cat /tmp/logstash.out | grep flowers");
+      $one->waitUntilSucceeds("cat /tmp/logstash.out | grep -v dragons");
+
+      # See if kibana is healthy.
+      $one->waitForUnit("kibana.service");
+      $one->waitUntilSucceeds("curl --silent --show-error 'http://localhost:5601/api/status' | jq .status.overall.state | grep green");
+
+      # See if logstash messages arive in elasticsearch.
+      $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' -d '{\"query\" : { \"match\" : { \"message\" : \"flowers\"}}}' | jq .hits.total | grep -v 0");
+      $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' -d '{\"query\" : { \"match\" : { \"message\" : \"dragons\"}}}' | jq .hits.total | grep 0");
+    '';
+  };
+in mapAttrs mkElkTest {
+  "ELK-5" = {
+    elasticsearch = pkgs.elasticsearch5;
+    logstash      = pkgs.logstash5;
+    kibana        = pkgs.kibana5;
+  };
+  "ELK-6" = {
+    elasticsearch = pkgs.elasticsearch6;
+    logstash      = pkgs.logstash6;
+    kibana        = pkgs.kibana6;
+  };
+}
diff --git a/nixos/tests/emacs-daemon.nix b/nixos/tests/emacs-daemon.nix
new file mode 100644
index 000000000000..466e772a881f
--- /dev/null
+++ b/nixos/tests/emacs-daemon.nix
@@ -0,0 +1,45 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "emacs-daemon";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  enableOCR = true;
+
+  machine =
+    { config, pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      services.emacs = {
+        enable = true;
+        defaultEditor = true;
+      };
+
+      # Important to get the systemd service running for root
+      environment.variables.XDG_RUNTIME_DIR = "/run/user/0";
+
+      environment.variables.TEST_SYSTEM_VARIABLE = "system variable";
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+
+      # checks that the EDITOR environment variable is set
+      $machine->succeed("test \$(basename \"\$EDITOR\") = emacseditor");
+
+      # waits for the emacs service to be ready
+      $machine->waitUntilSucceeds("systemctl --user status emacs.service | grep 'Active: active'");
+
+      # connects to the daemon
+      $machine->succeed("emacsclient --create-frame \$EDITOR &");
+
+      # checks that Emacs shows the edited filename
+      $machine->waitForText("emacseditor");
+
+      # makes sure environment variables are accessible from Emacs
+      $machine->succeed("emacsclient --eval '(getenv \"TEST_SYSTEM_VARIABLE\")'") =~ /system variable/ or die;
+
+      $machine->screenshot("emacsclient");
+    '';
+})
diff --git a/nixos/tests/env.nix b/nixos/tests/env.nix
new file mode 100644
index 000000000000..c6b0424e97b9
--- /dev/null
+++ b/nixos/tests/env.nix
@@ -0,0 +1,35 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "environment";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { config, lib, pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages;
+      environment.etc."plainFile".text = ''
+        Hello World
+      '';
+      environment.etc."folder/with/file".text = ''
+        Foo Bar!
+      '';
+
+      environment.sessionVariables = {
+        TERMINFO_DIRS = "/run/current-system/sw/share/terminfo";
+        NIXCON = "awesome";
+      };
+    };
+
+  testScript =
+    ''
+      $machine->succeed('[ -L "/etc/plainFile" ]');
+      $machine->succeed('cat "/etc/plainFile" | grep "Hello World"');
+      $machine->succeed('[ -d "/etc/folder" ]');
+      $machine->succeed('[ -d "/etc/folder/with" ]');
+      $machine->succeed('[ -L "/etc/folder/with/file" ]');
+      $machine->succeed('cat "/etc/plainFile" | grep "Hello World"');
+
+      $machine->succeed('echo ''${TERMINFO_DIRS} | grep "/run/current-system/sw/share/terminfo"');
+      $machine->succeed('echo ''${NIXCON} | grep "awesome"');
+    '';
+})
diff --git a/nixos/tests/etcd-cluster.nix b/nixos/tests/etcd-cluster.nix
new file mode 100644
index 000000000000..3971997a9bf7
--- /dev/null
+++ b/nixos/tests/etcd-cluster.nix
@@ -0,0 +1,157 @@
+# This test runs simple etcd cluster
+
+import ./make-test.nix ({ pkgs, ... } : let
+
+  runWithOpenSSL = file: cmd: pkgs.runCommand file {
+    buildInputs = [ pkgs.openssl ];
+  } cmd;
+
+  ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+  ca_pem = runWithOpenSSL "ca.pem" ''
+    openssl req \
+      -x509 -new -nodes -key ${ca_key} \
+      -days 10000 -out $out -subj "/CN=etcd-ca"
+  '';
+  etcd_key = runWithOpenSSL "etcd-key.pem" "openssl genrsa -out $out 2048";
+  etcd_csr = runWithOpenSSL "etcd.csr" ''
+    openssl req \
+       -new -key ${etcd_key} \
+       -out $out -subj "/CN=etcd" \
+       -config ${openssl_cnf}
+  '';
+  etcd_cert = runWithOpenSSL "etcd.pem" ''
+    openssl x509 \
+      -req -in ${etcd_csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} \
+      -CAcreateserial -out $out \
+      -days 365 -extensions v3_req \
+      -extfile ${openssl_cnf}
+  '';
+
+  etcd_client_key = runWithOpenSSL "etcd-client-key.pem"
+    "openssl genrsa -out $out 2048";
+
+  etcd_client_csr = runWithOpenSSL "etcd-client-key.pem" ''
+    openssl req \
+      -new -key ${etcd_client_key} \
+      -out $out -subj "/CN=etcd-client" \
+      -config ${client_openssl_cnf}
+  '';
+
+  etcd_client_cert = runWithOpenSSL "etcd-client.crt" ''
+    openssl x509 \
+      -req -in ${etcd_client_csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \
+      -out $out -days 365 -extensions v3_req \
+      -extfile ${client_openssl_cnf}
+  '';
+
+  openssl_cnf = pkgs.writeText "openssl.cnf" ''
+    ions = v3_req
+    distinguished_name = req_distinguished_name
+    [req_distinguished_name]
+    [ v3_req ]
+    basicConstraints = CA:FALSE
+    keyUsage = digitalSignature, keyEncipherment
+    extendedKeyUsage = serverAuth
+    subjectAltName = @alt_names
+    [alt_names]
+    DNS.1 = node1
+    DNS.2 = node2
+    DNS.3 = node3
+    IP.1 = 127.0.0.1
+  '';
+
+  client_openssl_cnf = pkgs.writeText "client-openssl.cnf" ''
+    ions = v3_req
+    distinguished_name = req_distinguished_name
+    [req_distinguished_name]
+    [ v3_req ]
+    basicConstraints = CA:FALSE
+    keyUsage = digitalSignature, keyEncipherment
+    extendedKeyUsage = clientAuth
+  '';
+
+  nodeConfig = {
+    services = {
+      etcd = {
+        enable = true;
+        keyFile = etcd_key;
+        certFile = etcd_cert;
+        trustedCaFile = ca_pem;
+        peerClientCertAuth = true;
+        listenClientUrls = ["https://127.0.0.1:2379"];
+        listenPeerUrls = ["https://0.0.0.0:2380"];
+      };
+    };
+
+    environment.variables = {
+      ETCDCTL_CERT_FILE = "${etcd_client_cert}";
+      ETCDCTL_KEY_FILE = "${etcd_client_key}";
+      ETCDCTL_CA_FILE = "${ca_pem}";
+      ETCDCTL_PEERS = "https://127.0.0.1:2379";
+    };
+
+    networking.firewall.allowedTCPPorts = [ 2380 ];
+  };
+in {
+  name = "etcd";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    node1 = { config, pkgs, nodes, ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
+        initialAdvertisePeerUrls = ["https://node1:2380"];
+      };
+    };
+
+    node2 = { config, pkgs, ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
+        initialAdvertisePeerUrls = ["https://node2:2380"];
+      };
+    };
+
+    node3 = { config, pkgs, ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380" "node3=https://node3:2380"];
+        initialAdvertisePeerUrls = ["https://node3:2380"];
+        initialClusterState = "existing";
+      };
+    };
+  };
+
+  testScript = ''
+    subtest "should start etcd cluster", sub {
+      $node1->start();
+      $node2->start();
+      $node1->waitForUnit("etcd.service");
+      $node2->waitForUnit("etcd.service");
+      $node2->waitUntilSucceeds("etcdctl cluster-health");
+      $node1->succeed("etcdctl set /foo/bar 'Hello world'");
+      $node2->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+    };
+
+    subtest "should add another member", sub {
+      $node1->succeed("etcdctl member add node3 https://node3:2380");
+      $node3->start();
+      $node3->waitForUnit("etcd.service");
+      $node3->waitUntilSucceeds("etcdctl member list | grep 'node3'");
+      $node3->succeed("etcdctl cluster-health");
+    };
+
+    subtest "should survive member crash", sub {
+      $node3->crash;
+      $node1->succeed("etcdctl cluster-health");
+      $node1->succeed("etcdctl set /foo/bar 'Hello degraded world'");
+      $node1->succeed("etcdctl get /foo/bar | grep 'Hello degraded world'");
+    };
+  '';
+})
diff --git a/nixos/tests/etcd.nix b/nixos/tests/etcd.nix
index bac4ec6a918b..f8a6791a834f 100644
--- a/nixos/tests/etcd.nix
+++ b/nixos/tests/etcd.nix
@@ -1,111 +1,27 @@
-# This test runs etcd as single node, multy node and using discovery
+# This test runs simple etcd node
 
 import ./make-test.nix ({ pkgs, ... } : {
   name = "etcd";
+
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ offline ];
   };
 
   nodes = {
-    simple =
-      { config, pkgs, nodes, ... }:
-        {
-          services.etcd.enable = true;
-          services.etcd.listenClientUrls = ["http://0.0.0.0:4001"];
-          environment.systemPackages = [ pkgs.curl ];
-          networking.firewall.allowedTCPPorts = [ 4001 ];
-        };
-
-
-    node1 =
-      { config, pkgs, nodes, ... }:
-        {
-          services = {
-            etcd = {
-              enable = true;
-              listenPeerUrls = ["http://0.0.0.0:7001"];
-              initialAdvertisePeerUrls = ["http://node1:7001"];
-              initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
-            };
-          };
-
-          networking.firewall.allowedTCPPorts = [ 7001 ];
-        };
-
-    node2 =
-      { config, pkgs, ... }:
-        {
-          services = {
-            etcd = {
-              enable = true;
-              listenPeerUrls = ["http://0.0.0.0:7001"];
-              initialAdvertisePeerUrls = ["http://node2:7001"];
-              initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
-            };
-          };
-
-          networking.firewall.allowedTCPPorts = [ 7001 ];
-        };
-
-    discovery1 =
-      { config, pkgs, nodes, ... }:
-        {
-          services = {
-            etcd = {
-              enable = true;
-              listenPeerUrls = ["http://0.0.0.0:7001"];
-              initialAdvertisePeerUrls = ["http://discovery1:7001"];
-              discovery = "http://simple:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/";
-            };
-          };
-
-          networking.firewall.allowedTCPPorts = [ 7001 ];
-        };
-
-    discovery2 =
-      { config, pkgs, ... }:
-        {
-          services = {
-            etcd = {
-              enable = true;
-              listenPeerUrls = ["http://0.0.0.0:7001"];
-              initialAdvertisePeerUrls = ["http://discovery2:7001"];
-              discovery = "http://simple:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/";
-            };
-          };
-
-          networking.firewall.allowedTCPPorts = [ 7001 ];
-        };
+    node = { config, pkgs, nodes, ... }: {
+      services.etcd.enable = true;
     };
+  };
 
   testScript = ''
-    subtest "single node", sub {
-      $simple->start();
-      $simple->waitForUnit("etcd.service");
-      $simple->waitUntilSucceeds("etcdctl set /foo/bar 'Hello world'");
-      $simple->waitUntilSucceeds("etcdctl get /foo/bar | grep 'Hello world'");
+    subtest "should start etcd node", sub {
+      $node->start();
+      $node->waitForUnit("etcd.service");
     };
 
-    subtest "multy node", sub {
-      $node1->start();
-      $node2->start();
-      $node1->waitForUnit("etcd.service");
-      $node2->waitForUnit("etcd.service");
-      $node1->waitUntilSucceeds("etcdctl set /foo/bar 'Hello world'");
-      $node2->waitUntilSucceeds("etcdctl get /foo/bar | grep 'Hello world'");
-      $node1->shutdown();
-      $node2->shutdown();
-    };
-
-    subtest "discovery", sub {
-      $simple->succeed("curl -X PUT http://localhost:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=2");
-
-      $discovery1->start();
-      $discovery2->start();
-      $discovery1->waitForUnit("etcd.service");
-      $discovery2->waitForUnit("etcd.service");
-      $discovery1->waitUntilSucceeds("etcdctl set /foo/bar 'Hello world'");
-      $discovery2->waitUntilSucceeds("etcdctl get /foo/bar | grep 'Hello world'");
-    };
+    subtest "should write and read some values to etcd", sub {
+      $node->succeed("etcdctl set /foo/bar 'Hello world'");
+      $node->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+    }
   '';
 })
diff --git a/nixos/tests/ferm.nix b/nixos/tests/ferm.nix
new file mode 100644
index 000000000000..bb7daae118c0
--- /dev/null
+++ b/nixos/tests/ferm.nix
@@ -0,0 +1,72 @@
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ferm";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mic92 ];
+  };
+
+  nodes =
+    { client =
+        { config, pkgs, ... }:
+        with pkgs.lib;
+        {
+          networking = {
+            interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
+            interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          };
+      };
+      server =
+        { config, pkgs, ... }:
+        with pkgs.lib;
+        {
+          networking = {
+            interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
+            interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          };
+
+          services = {
+            ferm.enable = true;
+            ferm.config = ''
+              domain (ip ip6) table filter chain INPUT {
+                interface lo ACCEPT;
+                proto tcp dport 8080 REJECT reject-with tcp-reset;
+              }
+            '';
+            nginx.enable = true;
+            nginx.httpConfig = ''
+              server {
+                listen 80;
+                listen [::]:80;
+                listen 8080;
+                listen [::]:8080;
+
+                location /status { stub_status on; }
+              }
+            '';
+          };
+        };
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $client->waitForUnit("network.target");
+      $server->waitForUnit("ferm.service");
+      $server->waitForUnit("nginx.service");
+      $server->waitUntilSucceeds("ss -ntl | grep -q 80");
+
+      subtest "port 80 is allowed", sub {
+          $client->succeed("curl --fail -g http://192.168.1.1:80/status");
+          $client->succeed("curl --fail -g http://[fd00::1]:80/status");
+      };
+
+      subtest "port 8080 is not allowed", sub {
+          $server->succeed("curl --fail -g http://192.168.1.1:8080/status");
+          $server->succeed("curl --fail -g http://[fd00::1]:8080/status");
+
+          $client->fail("curl --fail -g http://192.168.1.1:8080/status");
+          $client->fail("curl --fail -g http://[fd00::1]:8080/status");
+      };
+    '';
+})
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index 1bdabe93fec1..e1b628c91445 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -8,15 +8,21 @@ import ./make-test.nix ({ pkgs, ... }: {
     { config, pkgs, ... }:
 
     { imports = [ ./common/x11.nix ];
-      environment.systemPackages = [ pkgs.firefox ];
+      environment.systemPackages = [ pkgs.firefox pkgs.xdotool ];
     };
 
   testScript =
     ''
       $machine->waitForX;
-      $machine->execute("firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html &");
+      $machine->execute("xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' &");
       $machine->waitForWindow(qr/Valgrind/);
       $machine->sleep(40); # wait until Firefox has finished loading the page
+      $machine->execute("xdotool key space"); # do I want to make Firefox the
+                             # default browser? I just want to close the dialog
+      $machine->sleep(2); # wait until Firefox hides the default browser window
+      $machine->execute("xdotool key F12");
+      $machine->sleep(10); # wait until Firefox draws the developer tool panel
+      $machine->succeed("xwininfo -root -tree | grep Valgrind");
       $machine->screenshot("screen");
     '';
 
diff --git a/nixos/tests/firewall.nix b/nixos/tests/firewall.nix
index 9faf19f0359f..1119a5312eb5 100644
--- a/nixos/tests/firewall.nix
+++ b/nixos/tests/firewall.nix
@@ -15,6 +15,16 @@ import ./make-test.nix ( { pkgs, ... } : {
           services.httpd.adminAddr = "foo@example.org";
         };
 
+      # Dummy configuration to check whether firewall.service will be honored
+      # during system activation. This only needs to be different to the
+      # original walled configuration so that there is a change in the service
+      # file.
+      walled2 =
+        { config, pkgs, nodes, ... }:
+        { networking.firewall.enable = true;
+          networking.firewall.rejectPackets = true;
+        };
+
       attacker =
         { config, pkgs, ... }:
         { services.httpd.enable = true;
@@ -23,28 +33,33 @@ import ./make-test.nix ( { pkgs, ... } : {
         };
     };
 
-  testScript =
-    { nodes, ... }:
-    ''
-      startAll;
+  testScript = { nodes, ... }: let
+    newSystem = nodes.walled2.config.system.build.toplevel;
+  in ''
+    $walled->start;
+    $attacker->start;
+
+    $walled->waitForUnit("firewall");
+    $walled->waitForUnit("httpd");
+    $attacker->waitForUnit("network.target");
 
-      $walled->waitForUnit("firewall");
-      $walled->waitForUnit("httpd");
-      $attacker->waitForUnit("network.target");
+    # Local connections should still work.
+    $walled->succeed("curl -v http://localhost/ >&2");
 
-      # Local connections should still work.
-      $walled->succeed("curl -v http://localhost/ >&2");
+    # Connections to the firewalled machine should fail, but ping should succeed.
+    $attacker->fail("curl --fail --connect-timeout 2 http://walled/ >&2");
+    $attacker->succeed("ping -c 1 walled >&2");
 
-      # Connections to the firewalled machine should fail.
-      $attacker->fail("curl --fail --connect-timeout 2 http://walled/ >&2");
-      $attacker->fail("ping -c 1 walled >&2");
+    # Outgoing connections/pings should still work.
+    $walled->succeed("curl -v http://attacker/ >&2");
+    $walled->succeed("ping -c 1 attacker >&2");
 
-      # Outgoing connections/pings should still work.
-      $walled->succeed("curl -v http://attacker/ >&2");
-      $walled->succeed("ping -c 1 attacker >&2");
+    # If we stop the firewall, then connections should succeed.
+    $walled->stopJob("firewall");
+    $attacker->succeed("curl -v http://walled/ >&2");
 
-      # If we stop the firewall, then connections should succeed.
-      $walled->stopJob("firewall");
-      $attacker->succeed("curl -v http://walled/ >&2");
-    '';
+    # Check whether activation of a new configuration reloads the firewall.
+    $walled->succeed("${newSystem}/bin/switch-to-configuration test 2>&1" .
+                     " | grep -qF firewall.service");
+  '';
 })
diff --git a/nixos/tests/flannel.nix b/nixos/tests/flannel.nix
new file mode 100644
index 000000000000..7f27903a3026
--- /dev/null
+++ b/nixos/tests/flannel.nix
@@ -0,0 +1,55 @@
+import ./make-test.nix ({ pkgs, ...} : rec {
+  name = "flannel";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = let
+    flannelConfig = {
+      services.flannel = {
+        enable = true;
+        network = "10.1.0.0/16";
+        iface = "eth1";
+        etcd.endpoints = ["http://etcd:2379"];
+      };
+
+      networking.firewall.allowedUDPPorts = [ 8472 ];
+    };
+  in {
+    etcd = { config, pkgs, ... }: {
+      services = {
+        etcd = {
+          enable = true;
+          listenClientUrls = ["http://etcd:2379"];
+          listenPeerUrls = ["http://etcd:2380"];
+          initialAdvertisePeerUrls = ["http://etcd:2379"];
+          initialCluster = ["etcd=http://etcd:2379"];
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 2379 ];
+    };
+
+    node1 = { config, ... }: {
+      require = [flannelConfig];
+    };
+
+    node2 = { config, ... }: {
+      require = [flannelConfig];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $node1->waitForUnit("flannel.service");
+    $node2->waitForUnit("flannel.service");
+
+    my $ip1 = $node1->succeed("ip -4 addr show flannel.1 | grep -oP '(?<=inet).*(?=/)'");
+    my $ip2 = $node2->succeed("ip -4 addr show flannel.1 | grep -oP '(?<=inet).*(?=/)'");
+
+    $node1->waitUntilSucceeds("ping -c 1 $ip2");
+    $node2->waitUntilSucceeds("ping -c 1 $ip1");
+  '';
+})
diff --git a/nixos/tests/flatpak.nix b/nixos/tests/flatpak.nix
new file mode 100644
index 000000000000..d1c7cf843147
--- /dev/null
+++ b/nixos/tests/flatpak.nix
@@ -0,0 +1,23 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "flatpak";
+  meta = {
+    maintainers = pkgs.flatpak.meta.maintainers;
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    services.xserver.desktopManager.gnome3.enable = true; # TODO: figure out minimal environment where the tests work
+    services.flatpak.enable = true;
+    environment.systemPackages = with pkgs; [ gnupg gnome-desktop-testing ostree python2 ];
+    virtualisation.memorySize = 2047;
+    virtualisation.diskSize = 1024;
+  };
+
+  testScript = ''
+    $machine->waitForX();
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.flatpak.installedTests}/share' --timeout 3600");
+  '';
+})
diff --git a/nixos/tests/fleet.nix b/nixos/tests/fleet.nix
deleted file mode 100644
index 67c95446526f..000000000000
--- a/nixos/tests/fleet.nix
+++ /dev/null
@@ -1,76 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : rec {
-  name = "simple";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline ];
-  };
-
-  nodes = {
-    node1 =
-      { config, pkgs, ... }:
-        {
-          services = {
-            etcd = {
-              enable = true;
-              listenPeerUrls = ["http://0.0.0.0:7001"];
-              initialAdvertisePeerUrls = ["http://node1:7001"];
-              initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
-            };
-         };
-
-          services.fleet = {
-            enable = true;
-            metadata.name = "node1";
-          };
-
-          networking.firewall.allowedTCPPorts = [ 7001 ];
-        };
-
-    node2 =
-      { config, pkgs, ... }:
-        {
-          services = {
-            etcd = {
-              enable = true;
-              listenPeerUrls = ["http://0.0.0.0:7001"];
-              initialAdvertisePeerUrls = ["http://node2:7001"];
-              initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
-            };
-           };
-
-          services.fleet = {
-            enable = true;
-            metadata.name = "node2";
-          };
-
-          networking.firewall.allowedTCPPorts = [ 7001 ];
-        };
-  };
-
-  service = builtins.toFile "hello.service" ''
-    [Unit]
-    Description=Hello World
-
-    [Service]
-    ExecStart=/bin/sh -c "while true; do echo \"Hello, world\"; /var/run/current-system/sw/bin/sleep 1; done"
-
-    [X-Fleet]
-    MachineMetadata=name=node2
-  '';
-
-  testScript =
-    ''
-      startAll;
-      $node1->waitForUnit("fleet.service");
-      $node2->waitForUnit("fleet.service");
-
-      $node2->waitUntilSucceeds("fleetctl list-machines | grep node1");
-      $node1->waitUntilSucceeds("fleetctl list-machines | grep node2");
-
-      $node1->succeed("cp ${service} hello.service && fleetctl submit hello.service");
-      $node1->succeed("fleetctl list-unit-files | grep hello");
-      $node1->succeed("fleetctl start hello.service");
-      $node1->waitUntilSucceeds("fleetctl list-units | grep running");
-      $node1->succeed("fleetctl stop hello.service");
-      $node1->succeed("fleetctl destroy hello.service");
-    '';
-})
diff --git a/nixos/tests/fwupd.nix b/nixos/tests/fwupd.nix
new file mode 100644
index 000000000000..bf4ef25130b3
--- /dev/null
+++ b/nixos/tests/fwupd.nix
@@ -0,0 +1,19 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "fwupd";
+
+  meta = {
+    maintainers = pkgs.fwupd.meta.maintainers;
+  };
+
+  machine = { config, pkgs, ... }: {
+    services.fwupd.enable = true;
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.fwupd.installedTests}/share" ];
+    virtualisation.memorySize = 768;
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner");
+  '';
+})
diff --git a/nixos/tests/gdk-pixbuf.nix b/nixos/tests/gdk-pixbuf.nix
new file mode 100644
index 000000000000..b20f61b5ffe2
--- /dev/null
+++ b/nixos/tests/gdk-pixbuf.nix
@@ -0,0 +1,19 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "gdk-pixbuf";
+
+  meta = {
+    maintainers = pkgs.gdk_pixbuf.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk_pixbuf.installedTests}/share" ];
+
+    virtualisation.memorySize = 4096; # Tests allocate a lot of memory trying to exploit a CVE
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner");
+  '';
+})
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index bce862b4ad89..7268636b62ad 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -3,15 +3,63 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "gitlab";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric offline ];
+    maintainers = [ domenkozar offline ];
   };
 
   nodes = {
     gitlab = { config, pkgs, ... }: {
       virtualisation.memorySize = 768;
-      services.gitlab.enable = true;
-      services.gitlab.databasePassword = "gitlab";
+
+      services.nginx = {
+        enable = true;
+        virtualHosts = {
+          "localhost" = {
+            locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
+          };
+        };
+      };
+
       systemd.services.gitlab.serviceConfig.TimeoutStartSec = "10min";
+      services.gitlab = {
+        enable = true;
+        databasePassword = "dbPassword";
+        secrets = {
+          secret = "secret";
+          otp = "otpsecret";
+          db = "dbsecret";
+
+          # nix-shell -p openssl --run "openssl genrsa 2048"
+          jws = ''
+            -----BEGIN RSA PRIVATE KEY-----
+            MIIEpAIBAAKCAQEA13/qEio76OWUtWO0WIz9lWnsTWOU8Esv4sQHDq9PCEFsLt21
+            PAXrlWhLjjWcxGfsrDwnh7YErGHYL62BMSxMdFJolaknlQK/O/V8UETDe45VoHM+
+            Znk270RfUcfYFgiihnXUZXVmL0om9TsQSk646wCcjCY9LxtxUyKNhvT7KjgYw2aX
+            z34aw7M+Js3T2p1TjZPSC82GtmtKkJEKFMi5EjprLTDE7EdcUzr9Xuw+kQ+gRm9k
+            7FE+JQqSoprwE3Q0v2OAn3UhLMgg0gNFRnsc5l6IAshDzV+H22RPqKKlJjVjjfPY
+            0TQSvYLVApigHbDPH0BoCXfjFfQazbbP3OUHrwIDAQABAoIBAQCMU+tkcMQaYIV5
+            qLdjgkwO467QpivyXcOM8wF1eosIYTHFQvIlZ+WEoSmyLQ8shlADyBgls01Pw1c3
+            lNAv6RzQEmmwKzpvOh61OKH+0whIiOMRXHoh2IUBQZCgfHYlwvGyhUAN4WjtGmhM
+            AG4XNTQNM5S9Xpkw97nP3Qwz+YskbbkrfqtCEVy9ro+4nhbjqPsuO3adbnkva4zR
+            cyurRhrHgHU6LPjn5NHnHH4qw2faY2oAsL8pmpkTbO5IqWDvOcbjNfjVPgVoq26O
+            bbaa1qs4nmc80qQgMjRPJef535xyf3eLsSlDvpf6O8sPrJzVR1zaqEqixpQCZDac
+            +kRiSBrhAoGBAOwHiq0PuyJh6VzBu7ybqX6+gF/wA4Jkwzx6mbfaBgurvU1aospp
+            kisIonAkxSbxllZMnjbkShZEdATYKeT9o5NEhnU4YnHfc5bJZbiWOZAzYGLcY7g8
+            vDQ31pBItyY4pFgPbSpNlbUvUsoPVJ45RasRADDTNCzMzdjFQQXst2V9AoGBAOm7
+            sSpzYfFPLEAhieAkuhtbsX58Boo46djiKVfzGftfp6F9aHTOfzGORU5jrZ16mSbS
+            qkkC6BEFrATX2051dzzXC89fWoJYALrsffE5I3KlKXsCAWSnCP1MMxOfH+Ls61Mr
+            7pK/LKfvJt53mUH4jIdbmmFUDwbg18oBEH+x9PmbAoGAS/+JqXu9N67rIxDGUE6W
+            3tacI0f2+U9Uhe67/DTZaXyc8YFTlXU0uWKIWy+bw5RaYeM9tlL/f/f+m2i25KK+
+            vrZ7zNag7CWU5GJovGyykDnauTpZaYM03mN0VPT08/uc/zXIYqyknbhlIeaZynCK
+            fDB3LUF0NVCknz20WCIGU0kCgYEAkxY0ZXx61Dp4pFr2wwEZxQGs7uXpz64FKyEX
+            12r6nMATY4Lh6y/Px0W6w5vis8lk+5Ny6cNUevHQ0LNuJS+yu6ywl+1vrbrnqroM
+            f3LvpcPeGLSoX8jl1VDQi7aFgG6LoKly1xJLbdsH4NPutB9PgBbbTghx9GgmI88L
+            rPA2M6UCgYBOmkYJocNgxg6B1/n4Tb9fN1Q/XuJrFDE6NxVUoke+IIyMPRH7FC3m
+            VMYzu+b7zTVJjaBb1cmJemxl/xajziWDofJYPefhdbOVU7HXtmJFY0IG3pVxU1zW
+            3bmDj5QAtCUDpuuNa6GEIT0YR4+D/V7o3DmlZ0tVIwKJmVJoQ2f5dw==
+            -----END RSA PRIVATE KEY-----
+          '';
+        };
+      };
     };
   };
 
@@ -19,6 +67,6 @@ import ./make-test.nix ({ pkgs, ...} : {
     $gitlab->start();
     $gitlab->waitForUnit("gitlab.service");
     $gitlab->waitForUnit("gitlab-sidekiq.service");
-    $gitlab->waitUntilSucceeds("curl http://localhost:8080/users/sign_in");
+    $gitlab->waitUntilSucceeds("curl http://localhost:80/users/sign_in");
   '';
 })
diff --git a/nixos/tests/gitolite.nix b/nixos/tests/gitolite.nix
new file mode 100644
index 000000000000..4b4e081acc5f
--- /dev/null
+++ b/nixos/tests/gitolite.nix
@@ -0,0 +1,139 @@
+import ./make-test.nix ({ pkgs, ...}:
+
+let
+  adminPrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3gAAAJBJiYxDSYmM
+    QwAAAAtzc2gtZWQyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3g
+    AAAEDE1W6vMwSEUcF1r7Hyypm/+sCOoDmKZgPxi3WOa1mD2u7urFhAA90BTpGuEHeWWTY3
+    W/g9PBxXNxfWhfbrm4LeAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  adminPublicKey = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client
+  '';
+
+  alicePrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQAAAJAwVQ5VMFUO
+    VQAAAAtzc2gtZWQyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQ
+    AAAEB7lbfkkdkJoE+4TKHPdPQWBKLSx+J54Eg8DaTr+3KoSlt5a8eH8BYZYjoQhzXGVKKH
+    Je1pw1D0p7O2Vb9VTLzBAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  alicePublicKey = pkgs.writeText "id_ed25519.pub" ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFt5a8eH8BYZYjoQhzXGVKKHJe1pw1D0p7O2Vb9VTLzB alice@client
+  '';
+
+  bobPrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMAAAAJDQBmNV0AZj
+    VQAAAAtzc2gtZWQyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMA
+    AAAEDM1IYYFUwk/IVxauha9kuR6bbRtT3gZ6ZA0GLb9txb/pZNonUP1ePHLrvn0W9D2hdN
+    6zWWZYFyJc+QR6pOKQEwAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  bobPublicKey = pkgs.writeText "id_ed25519.pub" ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZNonUP1ePHLrvn0W9D2hdN6zWWZYFyJc+QR6pOKQEw bob@client
+  '';
+
+  gitoliteAdminConfSnippet = ''
+    repo alice-project
+        RW+     =   alice
+  '';
+in
+{
+  name = "gitolite";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bjornfor ];
+  };
+
+  nodes = {
+
+    server =
+      { config, pkgs, lib, ... }:
+      {
+        services.gitolite = {
+          enable = true;
+          adminPubkey = adminPublicKey;
+        };
+        services.openssh.enable = true;
+      };
+
+    client =
+      { config, pkgs, lib, ... }:
+      {
+        environment.systemPackages = [ pkgs.git ];
+        programs.ssh.extraConfig = ''
+          Host *
+            UserKnownHostsFile /dev/null
+            StrictHostKeyChecking no
+            # there's nobody around that can input password
+            PreferredAuthentications publickey
+        '';
+        users.extraUsers.alice = { isNormalUser = true; };
+        users.extraUsers.bob = { isNormalUser = true; };
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    subtest "can setup ssh keys on system", sub {
+      $client->mustSucceed("mkdir -p ~root/.ssh");
+      $client->mustSucceed("cp ${adminPrivateKey} ~root/.ssh/id_ed25519");
+      $client->mustSucceed("chmod 600 ~root/.ssh/id_ed25519");
+
+      $client->mustSucceed("sudo -u alice mkdir -p ~alice/.ssh");
+      $client->mustSucceed("sudo -u alice cp ${alicePrivateKey} ~alice/.ssh/id_ed25519");
+      $client->mustSucceed("sudo -u alice chmod 600 ~alice/.ssh/id_ed25519");
+
+      $client->mustSucceed("sudo -u bob mkdir -p ~bob/.ssh");
+      $client->mustSucceed("sudo -u bob cp ${bobPrivateKey} ~bob/.ssh/id_ed25519");
+      $client->mustSucceed("sudo -u bob chmod 600 ~bob/.ssh/id_ed25519");
+    };
+
+    subtest "gitolite server starts", sub {
+      $server->waitForUnit("gitolite-init.service");
+      $server->waitForUnit("sshd.service");
+      $client->mustSucceed('ssh gitolite@server info');
+    };
+
+    subtest "admin can clone and configure gitolite-admin.git", sub {
+      $client->mustSucceed('git clone gitolite@server:gitolite-admin.git');
+      $client->mustSucceed("git config --global user.name 'System Administrator'");
+      $client->mustSucceed("git config --global user.email root\@domain.example");
+      $client->mustSucceed("cp ${alicePublicKey} gitolite-admin/keydir/alice.pub");
+      $client->mustSucceed("cp ${bobPublicKey} gitolite-admin/keydir/bob.pub");
+      $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add keys for alice, bob" && git push)');
+      $client->mustSucceed("printf '${gitoliteAdminConfSnippet}' >> gitolite-admin/conf/gitolite.conf");
+      $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add repo for alice" && git push)');
+    };
+
+    subtest "non-admins cannot clone gitolite-admin.git", sub {
+      $client->mustFail('sudo -i -u alice git clone gitolite@server:gitolite-admin.git');
+      $client->mustFail('sudo -i -u bob git clone gitolite@server:gitolite-admin.git');
+    };
+
+    subtest "non-admins can clone testing.git", sub {
+      $client->mustSucceed('sudo -i -u alice git clone gitolite@server:testing.git');
+      $client->mustSucceed('sudo -i -u bob git clone gitolite@server:testing.git');
+    };
+
+    subtest "alice can clone alice-project.git", sub {
+      $client->mustSucceed('sudo -i -u alice git clone gitolite@server:alice-project.git');
+    };
+
+    subtest "bob cannot clone alice-project.git", sub {
+      $client->mustFail('sudo -i -u bob git clone gitolite@server:alice-project.git');
+    };
+  '';
+})
diff --git a/nixos/tests/gjs.nix b/nixos/tests/gjs.nix
new file mode 100644
index 000000000000..e6002ef98dd0
--- /dev/null
+++ b/nixos/tests/gjs.nix
@@ -0,0 +1,19 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "gjs";
+
+  meta = {
+    maintainers = pkgs.gnome3.gjs.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.gnome3.gjs.installedTests}/share" ];
+  };
+
+  testScript = ''
+    $machine->waitForX;
+    $machine->succeed("gnome-desktop-testing-runner");
+  '';
+})
diff --git a/nixos/tests/gnome3-gdm.nix b/nixos/tests/gnome3-gdm.nix
index 1c07ddf79c2e..71ae1709d526 100644
--- a/nixos/tests/gnome3-gdm.nix
+++ b/nixos/tests/gnome3-gdm.nix
@@ -11,6 +11,7 @@ import ./make-test.nix ({ pkgs, ...} : {
 
       services.xserver.enable = true;
 
+      services.xserver.displayManager.slim.enable = false;
       services.xserver.displayManager.gdm = {
         enable = true;
         autoLogin = {
@@ -20,19 +21,27 @@ import ./make-test.nix ({ pkgs, ...} : {
       };
       services.xserver.desktopManager.gnome3.enable = true;
 
-      virtualisation.memorySize = 512;
+      virtualisation.memorySize = 1024;
     };
 
   testScript =
     ''
+      # wait for gdm to start and bring up X
+      $machine->waitForUnit("display-manager.service");
       $machine->waitForX;
-      $machine->sleep(15);
+
+      # wait for alice to be logged in
+      $machine->waitForUnit("default.target","alice");
 
       # Check that logging in has given the user ownership of devices.
       $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
 
-      $machine->succeed("su - alice -c 'DISPLAY=:0.0 gnome-terminal &'");
+      # open a terminal and check it's there
+      $machine->succeed("su - alice -c 'DISPLAY=:0.0 XAUTHORITY=/run/user/\$UID/gdm/Xauthority gnome-terminal'");
+      $machine->succeed("xauth merge /run/user/1000/gdm/Xauthority");
       $machine->waitForWindow(qr/Terminal/);
+
+      # wait to get a nice screenshot
       $machine->sleep(20);
       $machine->screenshot("screen");
     '';
diff --git a/nixos/tests/gnome3.nix b/nixos/tests/gnome3.nix
index 714b35503706..591ed8600685 100644
--- a/nixos/tests/gnome3.nix
+++ b/nixos/tests/gnome3.nix
@@ -1,7 +1,7 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "gnome3";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric eelco chaoflow lethalman ];
+    maintainers = [ domenkozar eelco chaoflow lethalman ];
   };
 
   machine =
@@ -11,24 +11,28 @@ import ./make-test.nix ({ pkgs, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.auto.enable = true;
-      services.xserver.displayManager.auto.user = "alice";
+      services.xserver.displayManager.lightdm.enable = true;
+      services.xserver.displayManager.lightdm.autoLogin.enable = true;
+      services.xserver.displayManager.lightdm.autoLogin.user = "alice";
       services.xserver.desktopManager.gnome3.enable = true;
 
-      virtualisation.memorySize = 512;
+      virtualisation.memorySize = 1024;
     };
 
   testScript =
     ''
       $machine->waitForX;
-      $machine->sleep(15);
+
+      # wait for alice to be logged in
+      $machine->waitForUnit("default.target","alice");
 
       # Check that logging in has given the user ownership of devices.
       $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
 
       $machine->succeed("su - alice -c 'DISPLAY=:0.0 gnome-terminal &'");
+      $machine->succeed("xauth merge ~alice/.Xauthority");
       $machine->waitForWindow(qr/Terminal/);
-      $machine->mustSucceed("timeout 900 bash -c 'journalctl -f|grep -m 1 \"GNOME Shell started\"'");
+      $machine->succeed("timeout 900 bash -c 'while read msg; do if [[ \$msg =~ \"GNOME Shell started\" ]]; then break; fi; done < <(journalctl -f)'");
       $machine->sleep(10);
       $machine->screenshot("screen");
     '';
diff --git a/nixos/tests/gocd-agent.nix b/nixos/tests/gocd-agent.nix
new file mode 100644
index 000000000000..5cadff089950
--- /dev/null
+++ b/nixos/tests/gocd-agent.nix
@@ -0,0 +1,40 @@
+# verifies:
+#   1. GoCD agent starts
+#   2. GoCD agent responds
+#   3. GoCD agent is available on GoCD server using GoCD API
+#     3.1. https://api.go.cd/current/#get-all-agents
+
+let
+  serverUrl = "localhost:8153/go/api/agents";
+  header = "Accept: application/vnd.go.cd.v2+json";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "gocd-agent";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ grahamc swarren83 ];
+  };
+
+  nodes = {
+    gocd_agent =
+      { config, pkgs, ... }:
+      {
+        virtualisation.memorySize = 2046;
+        services.gocd-agent = {
+          enable = true;
+        };
+        services.gocd-server = {
+          enable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $gocd_agent->waitForUnit("gocd-server");
+    $gocd_agent->waitForOpenPort("8153");
+    $gocd_agent->waitForUnit("gocd-agent");
+    $gocd_agent->waitUntilSucceeds("curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].uuid");
+    $gocd_agent->succeed("curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].agent_state | grep -q Idle");
+  '';
+})
diff --git a/nixos/tests/gocd-server.nix b/nixos/tests/gocd-server.nix
new file mode 100644
index 000000000000..b473d4ad61c7
--- /dev/null
+++ b/nixos/tests/gocd-server.nix
@@ -0,0 +1,28 @@
+# verifies:
+#   1. GoCD server starts
+#   2. GoCD server responds
+
+import ./make-test.nix ({ pkgs, ...} :
+
+{
+  name = "gocd-server";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ swarren83 ];
+  };
+
+nodes = {
+  gocd_server =
+    { config, pkgs, ... }:
+    {
+      virtualisation.memorySize = 2046;
+      services.gocd-server.enable = true;
+    };
+};
+
+  testScript = ''
+    $gocd_server->start;
+    $gocd_server->waitForUnit("gocd-server");
+    $gocd_server->waitForOpenPort("8153");
+    $gocd_server->waitUntilSucceeds("curl -s -f localhost:8153/go");
+  '';
+})
diff --git a/nixos/tests/grafana.nix b/nixos/tests/grafana.nix
new file mode 100644
index 000000000000..d45776c3ee29
--- /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 -sSfL http://127.0.0.1:3000/");
+  '';
+})
diff --git a/nixos/tests/graphite.nix b/nixos/tests/graphite.nix
new file mode 100644
index 000000000000..5a1f50bd29b1
--- /dev/null
+++ b/nixos/tests/graphite.nix
@@ -0,0 +1,38 @@
+import ./make-test.nix ({ pkgs, ...} :
+{
+  name = "graphite";
+  nodes = {
+    one =
+      { config, pkgs, ... }: {
+        virtualisation.memorySize = 1024;
+        time.timeZone = "UTC";
+        services.graphite = {
+          web.enable = true;
+          api = {
+            enable = true;
+            port = 8082;
+          };
+          carbon.enableCache = true;
+          seyren.enable = true;
+          pager.enable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("default.target");
+    $one->waitForUnit("graphiteWeb.service");
+    $one->waitForUnit("graphiteApi.service");
+    $one->waitForUnit("graphitePager.service");
+    $one->waitForUnit("carbonCache.service");
+    $one->waitForUnit("seyren.service");
+    # The services above are of type "simple". systemd considers them active immediately
+    # even if they're still in preStart (which takes quite long for graphiteWeb).
+    # Wait for ports to open so we're sure the services are up and listening.
+    $one->waitForOpenPort(8080);
+    $one->waitForOpenPort(2003);
+    $one->succeed("echo \"foo 1 `date +%s`\" | nc -N localhost 2003");
+    $one->waitUntilSucceeds("curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2");
+  '';
+})
diff --git a/nixos/tests/haka.nix b/nixos/tests/haka.nix
new file mode 100644
index 000000000000..40548f34690f
--- /dev/null
+++ b/nixos/tests/haka.nix
@@ -0,0 +1,24 @@
+# This test runs haka and probes it with hakactl
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "haka";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ tvestelind ];
+  };
+
+  nodes = {
+    haka =
+      { config, pkgs, ... }:
+        {
+          services.haka.enable = true;
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $haka->waitForUnit("haka.service");
+    $haka->succeed("hakactl status");
+    $haka->succeed("hakactl stop");
+  '';
+})
diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix
new file mode 100644
index 000000000000..ce4094237db2
--- /dev/null
+++ b/nixos/tests/haproxy.nix
@@ -0,0 +1,41 @@
+import ./make-test.nix ({ pkgs, ...}: {
+  name = "haproxy";
+  nodes = {
+    machine = { config, ...}: {
+      imports = [ ../modules/profiles/minimal.nix ];
+      services.haproxy = {
+        enable = true;
+        config = ''
+          defaults
+            timeout connect 10s
+
+          backend http_server
+            mode http
+            server httpd [::1]:8000
+
+          frontend http
+            bind *:80
+            mode http
+            use_backend http_server
+        '';
+      };
+      services.httpd = {
+        enable = true;
+        documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
+        adminAddr = "notme@yourhost.local";
+        listen = [{
+          ip = "::1";
+          port = 8000;
+        }];
+      };
+    };
+  };
+  testScript = ''
+    startAll;
+    $machine->waitForUnit('multi-user.target');
+    $machine->waitForUnit('haproxy.service');
+    $machine->waitForUnit('httpd.service');
+    $machine->succeed('curl -k http://localhost:80/index.txt | grep "We are all good!"');
+
+  '';
+})
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
new file mode 100644
index 000000000000..0a0639d62796
--- /dev/null
+++ b/nixos/tests/hardened.nix
@@ -0,0 +1,67 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "hardened";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ joachifm ];
+  };
+
+  machine =
+    { config, lib, pkgs, ... }:
+    with lib;
+    { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
+      users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      imports = [ ../modules/profiles/hardened.nix ];
+      virtualisation.emptyDiskImages = [ 4096 ];
+      boot.initrd.postDeviceCommands = ''
+        ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
+      '';
+      fileSystems = lib.mkVMOverride {
+        "/efi" = {
+          device = "/dev/disk/by-label/EFISYS";
+          fsType = "vfat";
+          options = [ "noauto" ];
+        };
+      };
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+
+      # Test hidepid
+      subtest "hidepid", sub {
+          $machine->succeed("grep -Fq hidepid=2 /proc/mounts");
+          # cannot use pgrep -u here, it segfaults when access to process info is denied
+          $machine->succeed("[ `su - sybil -c 'ps --no-headers --user root | wc -l'` = 0 ]");
+          $machine->succeed("[ `su - alice -c 'ps --no-headers --user root | wc -l'` != 0 ]");
+      };
+
+      # Test kernel module hardening
+      subtest "lock-modules", sub {
+          # note: this better a be module we normally wouldn't load ...
+          $machine->fail("modprobe dccp");
+      };
+
+      # Test userns
+      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");
+      };
+
+      # Test deferred mount
+      subtest "mount", sub {
+        $machine->fail("mountpoint -q /efi"); # was deferred
+        $machine->execute("mkdir -p /efi");
+        $machine->succeed("mount /dev/disk/by-label/EFISYS /efi");
+        $machine->succeed("mountpoint -q /efi"); # now mounted
+      };
+    '';
+})
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
new file mode 100644
index 000000000000..3ae2bdffed90
--- /dev/null
+++ b/nixos/tests/hibernate.nix
@@ -0,0 +1,43 @@
+# Test whether hibernation from partition works.
+
+import ./make-test.nix (pkgs: {
+  name = "hibernate";
+
+  nodes = {
+    machine = { config, lib, pkgs, ... }: with lib; {
+      virtualisation.emptyDiskImages = [ config.virtualisation.memorySize ];
+
+      systemd.services.backdoor.conflicts = [ "sleep.target" ];
+
+      swapDevices = mkOverride 0 [ { device = "/dev/vdb"; } ];
+
+      networking.firewall.allowedTCPPorts = [ 4444 ];
+
+      systemd.services.listener.serviceConfig.ExecStart = "${pkgs.netcat}/bin/nc -l 4444 -k";
+    };
+
+    probe = { config, lib, pkgs, ...}: {
+      environment.systemPackages = [ pkgs.netcat ];
+    };
+  };
+
+  # 9P doesn't support reconnection to virtio transport after a hibernation.
+  # Therefore, machine just hangs on any Nix store access.
+  # To work around it we run a daemon which listens to a TCP connection and
+  # try to connect to it as a test.
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+      $machine->succeed("mkswap /dev/vdb");
+      $machine->succeed("swapon -a");
+      $machine->startJob("listener");
+      $machine->waitForOpenPort(4444);
+      $machine->succeed("systemctl hibernate &");
+      $machine->waitForShutdown;
+      $machine->start;
+      $probe->waitForUnit("network.target");
+      $probe->waitUntilSucceeds("echo test | nc machine 4444 -N");
+    '';
+
+})
diff --git a/nixos/tests/hitch/default.nix b/nixos/tests/hitch/default.nix
new file mode 100644
index 000000000000..b024306cde56
--- /dev/null
+++ b/nixos/tests/hitch/default.nix
@@ -0,0 +1,33 @@
+import ../make-test.nix ({ pkgs, ... }:
+{
+  name = "hitch";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ jflanglois ];
+  };
+  machine = { config, pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.hitch = {
+      enable = true;
+      backend = "[127.0.0.1]:80";
+      pem-files = [
+        ./example.pem
+      ];
+    };
+
+    services.httpd = {
+      enable = true;
+      documentRoot = ./example;
+      adminAddr = "noone@testing.nowhere";
+    };
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitForUnit('hitch.service');
+      $machine->waitForOpenPort(443);
+      $machine->succeed('curl -k https://localhost:443/index.txt | grep "We are all good!"');
+    '';
+})
diff --git a/nixos/tests/hitch/example.pem b/nixos/tests/hitch/example.pem
new file mode 100644
index 000000000000..fde6f3cbd19a
--- /dev/null
+++ b/nixos/tests/hitch/example.pem
@@ -0,0 +1,53 @@
+-----BEGIN CERTIFICATE-----
+MIIEKTCCAxGgAwIBAgIJAIFAWQXSZ7lIMA0GCSqGSIb3DQEBCwUAMIGqMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMUmVkd29vZCBD
+aXR5MRkwFwYDVQQKDBBUZXN0aW5nIDEyMyBJbmMuMRQwEgYDVQQLDAtJVCBTZXJ2
+aWNlczEYMBYGA1UEAwwPdGVzdGluZy5ub3doZXJlMSQwIgYJKoZIhvcNAQkBFhVu
+b29uZUB0ZXN0aW5nLm5vd2hlcmUwHhcNMTgwNDIzMDcxMTI5WhcNMTkwNDIzMDcx
+MTI5WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNV
+BAcMDFJlZHdvb2QgQ2l0eTEZMBcGA1UECgwQVGVzdGluZyAxMjMgSW5jLjEUMBIG
+A1UECwwLSVQgU2VydmljZXMxGDAWBgNVBAMMD3Rlc3Rpbmcubm93aGVyZTEkMCIG
+CSqGSIb3DQEJARYVbm9vbmVAdGVzdGluZy5ub3doZXJlMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAxQq6AA9o/QErMbQwfgDF4mqXcvglRTwPr2zPE6Rv
+1g0ncRBSMM8iKbPapHM6qHNfg2e1fU2SFqzD6HkyZqHHLCgLzkdzswEcEjsMqiUP
+OR++5g4CWoQrdTi31itzYzCjnQ45BrAMrLEhBQgDTNwrEE+Tit0gpOGggtj/ktLk
+OD8BKa640lkmWEUGF18fd3rYTUC4hwM5qhAVXTe21vj9ZWsgprpQKdN61v0dCUap
+C5eAgvZ8Re+Cd0Id674hK4cJ4SekqfHKv/jLyIg3Vsdc9nkhmiC4O6KH5f1Zzq2i
+E4Kd5mnJDFxfSzIErKWmbhriLWsj3KEJ983AGLJ9hxQTAwIDAQABo1AwTjAdBgNV
+HQ4EFgQU76Mm6DP/BePJRQUNrJ9z038zjocwHwYDVR0jBBgwFoAU76Mm6DP/BePJ
+RQUNrJ9z038zjocwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAZzt
+VdPaUqrvDAh5rMYqzYMJ3tj6daNYoX6CbTFoevK5J5D4FESM0D/FMKgpNiVz39kB
+8Cjaw5rPHMHY61rHz7JRDK1sWXsonwzCF21BK7Tx0G1CIfLpYHWYb/FfdWGROx+O
+hPgKuoMRWQB+txozkZp5BqWJmk5MOyFCDEXhMOmrfsJq0IYU6QaH3Lsf1oJRy4yU
+afFrT9o3DLOyYLG/j/HXijCu8DVjZVa4aboum79ecYzPjjGF1posrFUnvQiuAeYy
+t7cuHNUB8gW9lWR5J7tP8fzFWtIcyT2oRL8u3H+fXf0i4bW73wtOBOoeULBzBNE7
+6rphcSrQunSZQIc+hg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFCroAD2j9ASsx
+tDB+AMXiapdy+CVFPA+vbM8TpG/WDSdxEFIwzyIps9qkczqoc1+DZ7V9TZIWrMPo
+eTJmoccsKAvOR3OzARwSOwyqJQ85H77mDgJahCt1OLfWK3NjMKOdDjkGsAyssSEF
+CANM3CsQT5OK3SCk4aCC2P+S0uQ4PwEprrjSWSZYRQYXXx93ethNQLiHAzmqEBVd
+N7bW+P1layCmulAp03rW/R0JRqkLl4CC9nxF74J3Qh3rviErhwnhJ6Sp8cq/+MvI
+iDdWx1z2eSGaILg7oofl/VnOraITgp3mackMXF9LMgSspaZuGuItayPcoQn3zcAY
+sn2HFBMDAgMBAAECggEAcaR8HijFHpab+PC5vxJnDuz3KEHiDQpU6ZJR5DxEnCm+
+A8GsBaaRR4gJpCspO5o/DiS0Ue55QUanPt8XqIXJv7fhBznCiw0qyYDxDviMzR94
+FGskBFySS+tIa+dnh1+4HY7kaO0Egl0udB5o+N1KoP+kUsSyXSYcUxsgW+fx5FW9
+22Ya3HNWnWxMCSfSGGlTFXGj2whf25SkL25dM9iblO4ZOx4MX8kaXij7TaYy8hMM
+Vf6/OMnXqtPKho+ctZZVKZkE9PxdS4f/pnp5EsdoOZwNBtfQ1WqVLWd3DlGWhnsH
+7L8ZSP2HkoI4Pd1wtkpOKZc+yM2bFXWa8WY4TcmpUQKBgQD33HxGdtmtZehrexSA
+/ZwWJlMslUsNz4Ivv6s7J4WCRhdh94+r9TWQP/yHdT9Ry5bvn84I5ZLUdp+aA962
+mvjz+GIglkCGpA7HU/hqurB1O63pj2cIDB8qhV21zjVIoqXcQ7IBJ+tqD79nF8vm
+h3KfuHUhuu1rayGepbtIyNhLdwKBgQDLgw4TJBg/QB8RzYECk78QnfZpCExsQA/z
+YJpc+dF2/nsid5R2u9jWzfmgHM2Jjo2/+ofRUaTqcFYU0K57CqmQkOLIzsbNQoYt
+e2NOANNVHiZLuzTZC2r3BrrkNbo3YvQzhAesUA5lS6LfrxBLUKiwo2LU9NlmJs3b
+UPVFYI0/1QKBgCswxIcS1sOcam+wNtZzWuuRKhUuvrFdY3YmlBPuwxj8Vb7AgMya
+IgdM3xhLmgkKzPZchm6OcpOLSCxyWDDBuHfq5E6BYCUWGW0qeLNAbNdA2wFD99Qz
+KIskSjwP/sD1dql3MmF5L1CABf5U6zb0i0jBv8ds50o8lNMsVgJM3UPpAoGBAL1+
+nzllb4pdi1CJWKnspoizfQCZsIdPM0r71V/jYY36MO+MBtpz2NlSWzAiAaQm74gl
+oBdgfT2qMg0Zro11BSRONEykdOolGkj5TiMQk7b65s+3VeMPRZ8UTis2d9kgs5/Q
+PVDODkl1nwfGu1ZVmW04BUujXVZHpYCkJm1eFMetAoGAImE7gWj+qRMhpbtCCGCg
+z06gDKvMrF6S+GJsvUoSyM8oUtfdPodI6gWAC65NfYkIiqbpCaEVNzfui73f5Lnz
+p5X1IbzhuH5UZs/k5A3OR2PPDbPs3lqEw7YJdBdLVRmO1o824uaXaJJwkL/1C+lq
+8dh1wV3CnynNmZApkz4vpzQ=
+-----END PRIVATE KEY-----
diff --git a/nixos/tests/hitch/example/index.txt b/nixos/tests/hitch/example/index.txt
new file mode 100644
index 000000000000..0478b1c26351
--- /dev/null
+++ b/nixos/tests/hitch/example/index.txt
@@ -0,0 +1 @@
+We are all good!
diff --git a/nixos/tests/hocker-fetchdocker/default.nix b/nixos/tests/hocker-fetchdocker/default.nix
new file mode 100644
index 000000000000..4f30f01e4032
--- /dev/null
+++ b/nixos/tests/hocker-fetchdocker/default.nix
@@ -0,0 +1,15 @@
+import ../make-test.nix ({ pkgs, ...} : {
+  name = "test-hocker-fetchdocker";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ixmatus ];
+  };
+
+  machine = import ./machine.nix;
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("sockets.target");
+    $machine->waitUntilSucceeds("docker run registry-1.docker.io/v2/library/hello-world:latest");
+  '';
+})
diff --git a/nixos/tests/hocker-fetchdocker/hello-world-container.nix b/nixos/tests/hocker-fetchdocker/hello-world-container.nix
new file mode 100644
index 000000000000..a127875264e9
--- /dev/null
+++ b/nixos/tests/hocker-fetchdocker/hello-world-container.nix
@@ -0,0 +1,19 @@
+{ fetchDockerConfig, fetchDockerLayer, fetchdocker }:
+fetchdocker rec {
+    name = "hello-world";
+    registry = "https://registry-1.docker.io/v2/";
+    repository = "library";
+    imageName = "hello-world";
+    tag = "latest";
+    imageConfig = fetchDockerConfig {
+      inherit tag registry repository imageName;
+      sha256 = "1ivbd23hyindkahzfw4kahgzi6ibzz2ablmgsz6340vc6qr1gagj";
+    };
+    imageLayers = let
+      layer0 = fetchDockerLayer {
+        inherit registry repository imageName;
+        layerDigest = "ca4f61b1923c10e9eb81228bd46bee1dfba02b9c7dac1844527a734752688ede";
+        sha256 = "1plfd194fwvsa921ib3xkhms1yqxxrmx92r2h7myj41wjaqn2kya";
+      };
+      in [ layer0 ];
+  }
diff --git a/nixos/tests/hocker-fetchdocker/machine.nix b/nixos/tests/hocker-fetchdocker/machine.nix
new file mode 100644
index 000000000000..12c58a012243
--- /dev/null
+++ b/nixos/tests/hocker-fetchdocker/machine.nix
@@ -0,0 +1,26 @@
+{ config, pkgs, ... }:
+{ nixpkgs.config.packageOverrides = pkgs': {
+    hello-world-container = pkgs'.callPackage ./hello-world-container.nix { };
+  };
+
+  virtualisation.docker = {
+    enable  = true;
+    package = pkgs.docker;
+  };
+
+  systemd.services.docker-load-fetchdocker-image = {
+    description = "Docker load hello-world-container";
+    wantedBy    = [ "multi-user.target" ];
+    wants       = [ "docker.service" "local-fs.target" ];
+    after       = [ "docker.service" "local-fs.target" ];
+
+    script = ''
+      ${pkgs.hello-world-container}/compositeImage.sh | ${pkgs.docker}/bin/docker load
+    '';
+
+    serviceConfig = {
+      Type = "oneshot";
+    };
+  };
+}
+
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
new file mode 100644
index 000000000000..797706a062ca
--- /dev/null
+++ b/nixos/tests/home-assistant.nix
@@ -0,0 +1,77 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  configDir = "/var/lib/foobar";
+  apiPassword = "secret";
+
+in {
+  name = "home-assistant";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ dotlambda ];
+  };
+
+  nodes = {
+    hass =
+      { config, pkgs, ... }:
+      {
+        environment.systemPackages = with pkgs; [
+          mosquitto
+        ];
+        services.home-assistant = {
+          inherit configDir;
+          enable = true;
+          package = pkgs.home-assistant.override {
+            extraPackages = ps: with ps; [ hbmqtt ];
+          };
+          config = {
+            homeassistant = {
+              name = "Home";
+              time_zone = "UTC";
+              latitude = "0.0";
+              longitude = "0.0";
+              elevation = 0;
+            };
+            frontend = { };
+            http.api_password = apiPassword;
+            mqtt = { }; # Use hbmqtt as broker
+            binary_sensor = [
+              {
+                platform = "mqtt";
+                state_topic = "home-assistant/test";
+                payload_on = "let_there_be_light";
+                payload_off = "off";
+              }
+            ];
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $hass->waitForUnit("home-assistant.service");
+
+    # The config is specified using a Nix attribute set,
+    # but then converted from JSON to YAML
+    $hass->succeed("test -f ${configDir}/configuration.yaml");
+
+    # Check that Home Assistant's web interface and API can be reached
+    $hass->waitForOpenPort(8123);
+    $hass->succeed("curl --fail http://localhost:8123/states");
+    $hass->succeed("curl --fail -H 'x-ha-access: ${apiPassword}' http://localhost:8123/api/ | grep -qF 'API running'");
+
+    # Toggle a binary sensor using MQTT
+    $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'");
+    $hass->waitUntilSucceeds("mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${apiPassword}' -m let_there_be_light");
+    $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"on\"'");
+
+    # Print log to ease debugging
+    my $log = $hass->succeed("cat ${configDir}/home-assistant.log");
+    print "\n### home-assistant.log ###\n";
+    print "$log\n";
+
+    # Check that no errors were logged
+    # The timer can get out of sync due to Hydra's load, so this error is ignored
+    $hass->fail("cat ${configDir}/home-assistant.log | grep -vF 'Timer got out of sync' | grep -qF ERROR");
+  '';
+})
diff --git a/nixos/tests/hound.nix b/nixos/tests/hound.nix
new file mode 100644
index 000000000000..82fd44e8e36f
--- /dev/null
+++ b/nixos/tests/hound.nix
@@ -0,0 +1,58 @@
+# Test whether `houndd` indexes nixpkgs
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "hound";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ grahamc ];
+  };
+  machine = { config, pkgs, ... }: {
+    services.hound = {
+      enable = true;
+      config = ''
+        {
+          "max-concurrent-indexers": 1,
+          "dbpath": "/var/lib/hound/data",
+          "repos": {
+            "nix": {
+              "url": "file:///var/lib/hound/my-git"
+            }
+          }
+        }
+      '';
+    };
+
+    systemd.services.houndseed = {
+      description = "seed hound with a git repo";
+      requiredBy = [ "hound.service" ];
+      before = [ "hound.service" ];
+
+      serviceConfig = {
+        User = "hound";
+        Group = "hound";
+        WorkingDirectory = "/var/lib/hound";
+      };
+      path = [ pkgs.git ];
+      script = ''
+        git config --global user.email "you@example.com"
+        git config --global user.name "Your Name"
+        git init my-git --bare
+        git init my-git-clone
+        cd my-git-clone
+        echo 'hi nix!' > hello
+        git add hello
+        git commit -m "hello there :)"
+        git remote add origin /var/lib/hound/my-git
+        git push origin master
+      '';
+    };
+  };
+
+  testScript =
+    '' startAll;
+
+       $machine->waitForUnit("network.target");
+       $machine->waitForUnit("hound.service");
+       $machine->waitForOpenPort(6080);
+       $machine->succeed('curl http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep "Filename" | grep "hello"');
+
+    '';
+})
diff --git a/nixos/tests/hydra/create-trivial-project.sh b/nixos/tests/hydra/create-trivial-project.sh
new file mode 100755
index 000000000000..3cca5665acc5
--- /dev/null
+++ b/nixos/tests/hydra/create-trivial-project.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+#
+# This script creates a project, a jobset with an input of type local
+# path. This local path is a directory that contains a Nix expression
+# to define a job.
+# The EXPR-PATH environment variable must be set with the local path.
+
+set -e
+
+URL=http://localhost:3000
+USERNAME="admin"
+PASSWORD="admin"
+PROJECT_NAME="trivial"
+JOBSET_NAME="trivial"
+EXPR_PATH=${EXPR_PATH:-}
+
+if [ -z $EXPR_PATH ]; then
+   echo "Environment variable EXPR_PATH must be set"
+   exit 1
+fi
+
+mycurl() {
+  curl --referer $URL -H "Accept: application/json" -H "Content-Type: application/json" $@
+}
+
+cat >data.json <<EOF
+{ "username": "$USERNAME", "password": "$PASSWORD" }
+EOF
+mycurl -X POST -d '@data.json' $URL/login -c hydra-cookie.txt
+
+cat >data.json <<EOF
+{
+  "displayname":"Trivial",
+  "enabled":"1"
+}
+EOF
+mycurl --silent -X PUT $URL/project/$PROJECT_NAME -d @data.json -b hydra-cookie.txt
+
+cat >data.json <<EOF
+{
+  "description": "Trivial",
+  "checkinterval": "60",
+  "enabled": "1",
+  "visible": "1",
+  "keepnr": "1",
+  "nixexprinput": "trivial",
+  "nixexprpath": "trivial.nix",
+  "inputs": {
+    "trivial": {
+      "value": "$EXPR_PATH",
+      "type": "path"
+    }
+  }
+}
+EOF
+mycurl --silent -X PUT $URL/jobset/$PROJECT_NAME/$JOBSET_NAME -d @data.json -b hydra-cookie.txt
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
new file mode 100644
index 000000000000..74919444c16d
--- /dev/null
+++ b/nixos/tests/hydra/default.nix
@@ -0,0 +1,78 @@
+import ../make-test.nix ({ pkgs, ...} :
+
+let
+   trivialJob = pkgs.writeTextDir "trivial.nix" ''
+     with import <nix/config.nix>;
+
+     { trivial = builtins.derivation {
+         name = "trivial";
+         system = "x86_64-linux";
+         PATH = coreutils;
+         builder = shell;
+         args = ["-c" "touch $out; exit 0"];
+       };
+     }
+   '';
+
+    createTrivialProject = pkgs.stdenv.mkDerivation {
+      name = "create-trivial-project";
+      unpackPhase = ":";
+      buildInputs = [ pkgs.makeWrapper ];
+      installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
+      postFixup = ''
+        wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob}
+      '';
+    };
+
+in {
+  name = "hydra-init-localdb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ pstn lewo ];
+  };
+
+  machine =
+    { config, pkgs, ... }:
+
+    {
+      virtualisation.memorySize = 1024;
+      time.timeZone = "UTC";
+
+      environment.systemPackages = [ createTrivialProject pkgs.jq ];
+      services.hydra = {
+        enable = true;
+
+        #Hydra needs those settings to start up, so we add something not harmfull.
+        hydraURL = "example.com";
+        notificationSender = "example@example.com";
+      };
+      nix = {
+        buildMachines = [{
+          hostName = "localhost";
+          systems = [ "x86_64-linux" ];
+        }];
+      };
+    };
+
+  testScript =
+    ''
+      # let the system boot up
+      $machine->waitForUnit("multi-user.target");
+      # test whether the database is running
+      $machine->succeed("systemctl status postgresql.service");
+      # test whether the actual hydra daemons are running
+      $machine->succeed("systemctl status hydra-queue-runner.service");
+      $machine->succeed("systemctl status hydra-init.service");
+      $machine->succeed("systemctl status hydra-evaluator.service");
+      $machine->succeed("systemctl status hydra-send-stats.service");
+
+      $machine->succeed("hydra-create-user admin --role admin --password admin");
+
+      # create a project with a trivial job
+      $machine->waitForOpenPort(3000);
+
+      # make sure the build as been successfully built
+      $machine->succeed("create-trivial-project.sh");
+
+      $machine->waitUntilSucceeds('curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq');
+     '';
+})
diff --git a/nixos/tests/i3wm.nix b/nixos/tests/i3wm.nix
index 627a150f641b..4685992d7a05 100644
--- a/nixos/tests/i3wm.nix
+++ b/nixos/tests/i3wm.nix
@@ -13,6 +13,8 @@ import ./make-test.nix ({ pkgs, ...} : {
 
   testScript = { nodes, ... }: ''
     $machine->waitForX;
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
     $machine->waitForWindow(qr/first configuration/);
     $machine->sleep(1);
     $machine->screenshot("started");
diff --git a/nixos/tests/iftop.nix b/nixos/tests/iftop.nix
new file mode 100644
index 000000000000..a4f524ceb27b
--- /dev/null
+++ b/nixos/tests/iftop.nix
@@ -0,0 +1,34 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "iftop";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  nodes = {
+    withIftop = {
+      imports = [ ./common/user-account.nix ];
+      programs.iftop.enable = true;
+    };
+    withoutIftop = {
+      imports = [ ./common/user-account.nix ];
+      environment.systemPackages = [ pkgs.iftop ];
+    };
+  };
+
+  testScript = ''
+    subtest "machine with iftop enabled", sub {
+      $withIftop->waitForUnit("default.target");
+      # limit to eth1 (eth0 is the test driver's control interface)
+      # and don't try name lookups
+      $withIftop->succeed("su -l alice -c 'iftop -t -s 1 -n -i eth1'");
+    };
+    subtest "machine without iftop", sub {
+      $withoutIftop->waitForUnit("default.target");
+      # check that iftop is there but user alice lacks capabilities
+      $withoutIftop->succeed("iftop -t -s 1 -n -i eth1");
+      $withoutIftop->fail("su -l alice -c 'iftop -t -s 1 -n -i eth1'");
+    };
+  '';
+})
diff --git a/nixos/tests/influxdb.nix b/nixos/tests/influxdb.nix
index 0408d8983ade..ee126091667a 100644
--- a/nixos/tests/influxdb.nix
+++ b/nixos/tests/influxdb.nix
@@ -17,23 +17,17 @@ import ./make-test.nix ({ pkgs, ...} : {
   
     $one->waitForUnit("influxdb.service");
 
-    # Check if admin interface is avalible
-    $one->waitUntilSucceeds("curl -f 127.0.0.1:8083");
-
     # create database
     $one->succeed(q~
-      curl -X POST 'http://localhost:8086/db?u=root&p=root' \
-        -d '{"name": "test"}'
+      curl -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE test"
     ~);
 
     # write some points and run simple query
     $one->succeed(q~
-      curl -X POST 'http://localhost:8086/db/test/series?u=root&p=root' \
-        -d '[{"name":"foo","columns":["val"],"points":[[6666]]}]'
+      curl -XPOST 'http://localhost:8086/write?db=test' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'
     ~);
     $one->succeed(q~
-      curl -G 'http://localhost:8086/db/test/series?u=root&p=root' \
-        --data-urlencode 'q=select * from foo limit 1' | grep 6666
+      curl -GET 'http://localhost:8086/query' --data-urlencode "db=test" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"  | grep "0\.64"
     ~);
   '';
 })
diff --git a/nixos/tests/initrd-network-ssh/default.nix b/nixos/tests/initrd-network-ssh/default.nix
new file mode 100644
index 000000000000..b1f3d147e862
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh/default.nix
@@ -0,0 +1,59 @@
+import ../make-test.nix ({ pkgs, lib, ... }:
+
+{
+  name = "initrd-network-ssh";
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = with lib; rec {
+    server =
+      { config, pkgs, ... }:
+      {
+        boot.kernelParams = [
+          "ip=${config.networking.primaryIPAddress}:::255.255.255.0::eth1:none"
+        ];
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ "${readFile ./openssh.pub}" ];
+            port = 22;
+            hostRSAKey = ./dropbear.priv;
+          };
+        };
+        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 ./dropbear.pub}"
+          ];
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $client->waitForUnit("network.target");
+    $client->copyFileFromHost("${./openssh.priv}","/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/initrd-network-ssh/dropbear.priv b/nixos/tests/initrd-network-ssh/dropbear.priv
new file mode 100644
index 000000000000..af340535f0a3
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh/dropbear.priv
Binary files differdiff --git a/nixos/tests/initrd-network-ssh/dropbear.pub b/nixos/tests/initrd-network-ssh/dropbear.pub
new file mode 100644
index 000000000000..385c625522aa
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh/dropbear.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCzJ0OniLB91MpPC86I1m3wwJeAc+Gme7bAuaLIU/cSfPwxT5NO7MfCp0Pu94gYDKtDXMs/wXg0bTAVDeAFFkdIj6kBBumEmQLCTL48q2UxDIXVLT/E/AAgj6q7WwgCg7fwm4Vjn4z7aUyBx8EfRy+5/SQyeYla3D/lFYgMi5x4D6J+yeR+JPAptDE/IR5IizNV7mY0ZcoXYyHrrehI1tTYEEqjX13ZqS4OCBFWwHe1QHhRNM+jHhcATbgikjAj8FyFPtLvc+dSVtkuhQktQl36Bi8zMUQcV6+mM7Ln6DBcDlM9urHKLYPTWmUAyhxM955iglOn5z0RaAIcyNMT6hz0rHaNf0BIlmbXoTC0XGjHh/OnoOEC/zg0JqgQTnPiU45K4TnRSSXp2GfiDfiQAK0+HaXACkjuFR68u7WCZpB1Bse1OgKNClFqtRhIr5DilUb2/e5DCCmFkddMUcjmYqzZdbXNt7fo8CFULe+mbiCp8+tMg4aRTaDZ/Hk93nCvGE5TP2ypEMbfL6nRVKvXOjhdvSQQgKwx+O003FDEHCSG0Bpageh7yVpna+SPrbGklce7MjTpbx3iIwmvKpQ6asnK1L3KkahpY1S3NhQ+/S3Gs8KWQ5LAU+d3xiPX3jfIVHsCIIyxHDbwcJvxM4MFBFQpqRMD6E+LoM9RHjl4C9k2iQ== tmtynkky@duuni
diff --git a/nixos/tests/initrd-network-ssh/generate-keys.nix b/nixos/tests/initrd-network-ssh/generate-keys.nix
new file mode 100644
index 000000000000..0183e12d7a88
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh/generate-keys.nix
@@ -0,0 +1,12 @@
+with import ../../.. {};
+
+runCommand "gen-keys" {
+    buildInputs = [ dropbear openssh ];
+  }
+  ''
+    mkdir $out
+    dropbearkey -t rsa -f $out/dropbear.priv -s 4096 | sed -n 2p > $out/dropbear.pub
+    ssh-keygen -q -t rsa -b 4096 -N "" -f client
+    mv client $out/openssh.priv
+    mv client.pub $out/openssh.pub
+  ''
diff --git a/nixos/tests/initrd-network-ssh/openssh.priv b/nixos/tests/initrd-network-ssh/openssh.priv
new file mode 100644
index 000000000000..816d65435fd7
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh/openssh.priv
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEA7+9A2PCPOTAlFmrablrUWA+VZdAuLfM6JXeHsOF7ZbC2F6lv
+WmvDM925DQqhiAjcgWnt5WHWS5Y+b7lGnuzT7fyKegXd80nCRmqlpSG3srX0/lxR
+aQAJLzfoDjcsF+ceswQo6GSsYnCHVxMNs007gbbVY3f7o+sWZtLdxJPD2iHvl5Zr
+LK0d1RLMmU6cfIhIABlL0S8EWiv29RROepsCQnS0dnK2b+von1SCYoggvAMe2ToA
+IAJ8+uqaYfGAyn9q8fjZiRHxLmKDq90tKoCUL5r/2dmEIE+t8T/3PfHoq1QzZts9
+W9idhBdT21dEXBtGyoMtckp5njk5m82LQDYiOXkuSoIUhSOteh5g7fBv1BtVSERx
+Jg3UeJjPeGKFwdnzapmAKC2w/6V8xcIINNA+fhZA7B9fD1RAi2TECZ+gyMYDc4T+
+USlMSm9cfvSOrf2+5ngtFb84nHjqvClxCMLu+bCWK8HamqUzhE/a5LbR+48E7PyG
+s3KV+sWFN9KOnakTjj/6iQhXZRhgeAK39F2XTk5Ms5Y+BRSStnMoMZA2grIV+jHi
+1zbWokVqXPI5YRo5isR/PgtKAV6FfNWumcYoFJ9F40pMHQ6hJVEmtrCBx7EApSl3
+mSGbQJUmilLC51qNhwQRbD//ZtpIrN82HTMKzZ6kj7kDCdsff+wsnkIXmmMCAwEA
+AQKCAgA4tMINw6UF7hQF3VEsnbjr6xrzCiWv5HlMm5htPI1OdlpC81+G7ksfOfrf
+UzDkFrwOtftsqBfem268Nvyy2OQprfMIbdSMCFWrEM9/XJ2u1gRGDYmMGF8TUtI8
+cduw9oWx53zHl+uKBHBoKu+k/c7flFeQf63wisIroRCawhWau0SF/h3sXCndzuie
+Hw8q+4aQx2m80bDkotlmCNuXbIU3MZ/pEql9gDLlXTLHmMaryM0EqAmZhx0ErGe6
+WDqJIV4kPB0loSDwRoY6GzbugZ8ENUzcruTkQhCpIOYNNNw5idfwKkaxK1vm+SBv
+iYt1fVjYyfH2vhVKSNoNsaGEloa1u4Dymt/FpFztEpRzHXcw93N8BdLxJ4OUhzm2
+iAbpiyjniTIeAVVi7BUwLXh5WAx8nT0eeb1zKoZg1p1ciK5cYl1Uel7j8xRycsSW
+3YgmtuPqY4Agbc9v3eXbQZNDk48JFMEqpIxk97FAkRYpzfxg5Qq14WJCp60CkdRt
+T60hXy8lT/BcI8OWLfGJuBbsVLNRiC7PpwqRKQAinXSv134FpP7jrhpkMybs2oIS
+5obRG7J5OfOTp925erG5mrpwqa3BPkgqx347Wj9z8quOZyuhi+XaPvqmPtvs5JOl
+4RCqjt6RQlHm7xos9ZZGI4jDAIFaFWgyVZrYplOgwxWma4DTgQKCAQEA9+tizQRU
+lF0lxNcEPvsFnYJo80Y+MQK9VdtlhR19YuSfwP1NCaMG1MhQ+PVBVmepOwJMRJR7
+9PLfOouNMfixKBGP12dtStMuh7jowq/BxhRI6JWp3RhTZ1yJ9ouzHze7IDrEBa6w
+p0hUu9H0Sbt51LXbC3JmTyhbdhfry559DfyGW1Ma/bv/pihL9B5Y7sNf1thNp1gi
+GbQ9B+o2Yyw8ZD8zY+sl+aYDSWyCtcBV/KXEF74Bkfs/a5ExJ00X0jYj/TAp2ray
+T4PY0FR8wN/O10bFLP9j+Xa/ywbcPhoj8nvVRIg9VfWT/QaEd+KR0EZVxdjCCqne
+enbSQksTpAZNwQKCAQEA98E+BMmS+yHUVUhNZABtQ5avwuV4+DoSN8KTp3xwQ0CH
+m9fWxSDs12FdyMhDxrJPeywvHtZ18/7cl3dr8wnFVE0s4ongnRDXsNk5xN6J3AaO
+KqW4HF9cbwZqzLILy8TrO+EK/EQV9FypbrxqvxAlP1kezIA2CJNzVRAgimSuV/H7
+05HTnp5W06fjtEf8U1CUrdNetoSROUo1j/IMGPYGlsBFYAGrj5y/BlKd+3T3kjRp
+Xje7HpiykjrZHn0WDp04Ln+u9nveEewXmHKch313emt7HpW0xspp8JM8OZtEKozk
+D5PfYdBfMJJOUlqovCCzTTJ6kNOahknKXFeO/qs5IwKCAQEAjF0/zhWikXF/fcfD
+Bql2z2vTYdEmSvdjHSYff1Nn90K71DdVk5wytOxJM/sfp/z+yoMNjVKIL/IGQw5Z
+va4xFx+CUhGjxlZ0pLEjT37U9gHsGYsK5jvslLvG/MixfH5AOwoqi5ERQVTpbIF9
+jvVPEAh6YSu/ExglWGJIxTsRUIblxvTxdjEnl/p+rlM0RNJnA6vpo1J51BXA7CdF
+7bZQ5u0Feo/bK1I70ClYg/DGfkmYEV0pZG5cxNkqfDbgwsqWa7YGLGd94xkh+ymq
+jETqxeWyozxhbQ83nYpfzeVc7t//qlJ8b5uf0wUKoRmtNr9rtp13lzP/21REzPXW
+w+oxwQKCAQAoAf2Y2lAw25KlPuq4ZlU+n9u8FkBFnWMJvBMJ7c9XHNmJMf6NkLaO
+RTvWy3geYvbwxf7J9QnRH+vRTciR05cY+Olxn6A03N5nwXxRrToH3MsiWeZ0NnX/
+u8KNUYcUHbV60ulqOThuYHQ/3I9EUUAijaqqjV2sXts19ke68W0x6HKpBJhuudT9
+ktPzbdhyP8Xyl/pocNnerXwexZBsi3Ye6+eIDFz+8OnsBHVcgNPluS72tvsxgqj7
+ciNTiBGCxKKo55eCWBhRPpXE2WUrf/hGPYsBMl2h6FfZMH1+M/N7B4tgdJmS+woU
+Ftws8lTjJEiwA6HFN1ZxrwLNjJobx9yPAoIBAE0igsBuWWn6rXeOPylYg4264XOq
+8gb94pte2n9amDgCzyCn8m6AL3snLC/AoCD19DK+gyK0ukoesXPa3iX6w2xv69ZC
+urDx36Jhd4zrJb4QsFPoeKfDP+UvNVZaS41vipRRzY/y11em15prUZ4U8FA/UT1Y
+FzkBo9r6iUZRnyBLppMuEfWASDtuRNmeIHynoT1AcQOH3l9vR210iEpmAuJr0CYA
+bvTuz3UzzGGEAuIUvuaiRtkfKY52jBmiEr7SSPCr1HvLj3Ccz8bgjgR2kiXmcU50
+1zLnaPAD44LZ/0Fjqj+PimQGT6K7CNXPllmYh7MvoU52g3SVPf6rHlIR0Nc=
+-----END RSA PRIVATE KEY-----
diff --git a/nixos/tests/initrd-network-ssh/openssh.pub b/nixos/tests/initrd-network-ssh/openssh.pub
new file mode 100644
index 000000000000..5b72b8085f27
--- /dev/null
+++ b/nixos/tests/initrd-network-ssh/openssh.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDv70DY8I85MCUWatpuWtRYD5Vl0C4t8zold4ew4XtlsLYXqW9aa8Mz3bkNCqGICNyBae3lYdZLlj5vuUae7NPt/Ip6Bd3zScJGaqWlIbeytfT+XFFpAAkvN+gONywX5x6zBCjoZKxicIdXEw2zTTuBttVjd/uj6xZm0t3Ek8PaIe+XlmssrR3VEsyZTpx8iEgAGUvRLwRaK/b1FE56mwJCdLR2crZv6+ifVIJiiCC8Ax7ZOgAgAnz66pph8YDKf2rx+NmJEfEuYoOr3S0qgJQvmv/Z2YQgT63xP/c98eirVDNm2z1b2J2EF1PbV0RcG0bKgy1ySnmeOTmbzYtANiI5eS5KghSFI616HmDt8G/UG1VIRHEmDdR4mM94YoXB2fNqmYAoLbD/pXzFwgg00D5+FkDsH18PVECLZMQJn6DIxgNzhP5RKUxKb1x+9I6t/b7meC0VvziceOq8KXEIwu75sJYrwdqapTOET9rkttH7jwTs/IazcpX6xYU30o6dqROOP/qJCFdlGGB4Arf0XZdOTkyzlj4FFJK2cygxkDaCshX6MeLXNtaiRWpc8jlhGjmKxH8+C0oBXoV81a6ZxigUn0XjSkwdDqElUSa2sIHHsQClKXeZIZtAlSaKUsLnWo2HBBFsP/9m2kis3zYdMwrNnqSPuQMJ2x9/7CyeQheaYw== tmtynkky@duuni
diff --git a/nixos/tests/initrd-network.nix b/nixos/tests/initrd-network.nix
new file mode 100644
index 000000000000..db9f572d3c2f
--- /dev/null
+++ b/nixos/tests/initrd-network.nix
@@ -0,0 +1,22 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "initrd-network";
+
+  meta.maintainers = [ pkgs.stdenv.lib.maintainers.eelco ];
+
+  machine = { config, pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    boot.initrd.network.enable = true;
+    boot.initrd.network.postCommands =
+      ''
+        ip addr | grep 10.0.2.15 || exit 1
+        ping -c1 10.0.2.2 || exit 1
+      '';
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("multi-user.target");
+      $machine->succeed("ip link >&2");
+    '';
+})
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index b2e1abc26eec..7da02d9c204a 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -1,13 +1,12 @@
 { system ? builtins.currentSystem }:
 
 with import ../lib/testing.nix { inherit system; };
-with import ../lib/qemu-flags.nix;
 with pkgs.lib;
 
 let
 
   # The configuration to install.
-  makeConfig = { grubVersion, grubDevice, grubIdentifier
+  makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier, grubUseEfi
                , extraConfig, forceGrubReinstallCount ? 0
                }:
     pkgs.writeText "configuration.nix" ''
@@ -18,15 +17,37 @@ let
             <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
           ];
 
-        boot.loader.grub.version = ${toString grubVersion};
-        ${optionalString (grubVersion == 1) ''
-          boot.loader.grub.splashImage = null;
+        # To ensure that we can rebuild the grub configuration on the nixos-rebuild
+        system.extraDependencies = with pkgs; [ stdenvNoCC ];
+
+        ${optionalString (bootLoader == "grub") ''
+          boot.loader.grub.version = ${toString grubVersion};
+          ${optionalString (grubVersion == 1) ''
+            boot.loader.grub.splashImage = null;
+          ''}
+
+          boot.loader.grub.extraConfig = "serial; terminal_output.serial";
+          ${if grubUseEfi then ''
+            boot.loader.grub.device = "nodev";
+            boot.loader.grub.efiSupport = true;
+            boot.loader.grub.efiInstallAsRemovable = true; # XXX: needed for OVMF?
+          '' else ''
+            boot.loader.grub.device = "${grubDevice}";
+            boot.loader.grub.fsIdentifier = "${grubIdentifier}";
+          ''}
+
+          boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
         ''}
-        boot.loader.grub.device = "${grubDevice}";
-        boot.loader.grub.extraConfig = "serial; terminal_output.serial";
-        boot.loader.grub.fsIdentifier = "${grubIdentifier}";
 
-        boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
+        ${optionalString (bootLoader == "systemd-boot") ''
+          boot.loader.systemd-boot.enable = true;
+        ''}
+
+        users.extraUsers.alice = {
+          isNormalUser = true;
+          home = "/home/alice";
+          description = "Alice Foobar";
+        };
 
         hardware.enableAllFirmware = lib.mkForce false;
 
@@ -42,23 +63,32 @@ let
   # disk, and then reboot from the hard disk.  It's parameterized with
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
-  testScriptFun = { createPartitions, grubVersion, grubDevice
+  testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
                   , grubIdentifier, preBootCommands, extraConfig
                   }:
     let
-      iface = if grubVersion == 1 then "scsi" else "virtio";
+      iface = if grubVersion == 1 then "ide" else "virtio";
+      isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
+
+      # FIXME don't duplicate the -enable-kvm etc. flags here yet again!
       qemuFlags =
         (if system == "x86_64-linux" then "-m 768 " else "-m 512 ") +
-        (optionalString (system == "x86_64-linux") "-cpu kvm64 ");
-      hdFlags = ''hda => "vm-state-machine/machine.qcow2", hdaInterface => "${iface}", '';
-    in
-    ''
+        (optionalString (system == "x86_64-linux") "-cpu kvm64 ") +
+        (optionalString (system == "aarch64-linux") "-enable-kvm -machine virt,gic-version=host -cpu host ");
+
+      hdFlags = ''hda => "vm-state-machine/machine.qcow2", hdaInterface => "${iface}", ''
+        + optionalString isEfi (if pkgs.stdenv.isAarch64
+            then ''bios => "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd", ''
+            else ''bios => "${pkgs.OVMF.fd}/FV/OVMF.fd", '');
+    in if !isEfi && !(pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then
+      throw "Non-EFI boot methods are only supported on i686 / x86_64"
+    else ''
       $machine->start;
 
       # Make sure that we get a login prompt etc.
       $machine->succeed("echo hello");
       #$machine->waitForUnit('getty@tty2');
-      $machine->waitForUnit("rogue");
+      #$machine->waitForUnit("rogue");
       $machine->waitForUnit("nixos-manual");
 
       # Wait for hard disks to appear in /dev
@@ -73,7 +103,7 @@ let
       $machine->succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2");
 
       $machine->copyFileFromHost(
-          "${ makeConfig { inherit grubVersion grubDevice grubIdentifier extraConfig; } }",
+          "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; } }",
           "/mnt/etc/nixos/configuration.nix");
 
       # Perform the installation.
@@ -89,7 +119,7 @@ let
       $machine->shutdown;
 
       # Now see if we can boot the installation.
-      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}" });
+      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "boot-after-install" });
 
       # For example to enter LUKS passphrase.
       ${preBootCommands}
@@ -97,24 +127,34 @@ let
       # Did /boot get mounted?
       $machine->waitForUnit("local-fs.target");
 
-      $machine->succeed("test -e /boot/grub");
+      ${if bootLoader == "grub" then
+          ''$machine->succeed("test -e /boot/grub");''
+        else
+          ''$machine->succeed("test -e /boot/loader/loader.conf");''
+      }
 
       # Check whether /root has correct permissions.
       $machine->succeed("stat -c '%a' /root") =~ /700/ or die;
 
       # Did the swap device get activated?
       # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved
-      #$machine->waitForUnit("swap.target");
-      $machine->waitUntilSucceeds("cat /proc/swaps | grep -q /dev");
+      $machine->waitForUnit("swap.target");
+      $machine->succeed("cat /proc/swaps | grep -q /dev");
+
+      # Check that the store is in good shape
+      $machine->succeed("nix-store --verify --check-contents >&2");
 
       # Check whether the channel works.
-      $machine->succeed("nix-env -i coreutils >&2");
-      $machine->succeed("type -tP ls | tee /dev/stderr") =~ /.nix-profile/
+      $machine->succeed("nix-env -iA nixos.procps >&2");
+      $machine->succeed("type -tP ps | tee /dev/stderr") =~ /.nix-profile/
           or die "nix-env failed";
 
-      # We need to a writable nix-store on next boot.
+      # Check that the daemon works, and that non-root users can run builds (this will build a new profile generation through the daemon)
+      $machine->succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2");
+
+      # We need a writable Nix store on next boot.
       $machine->copyFileFromHost(
-          "${ makeConfig { inherit grubVersion grubDevice grubIdentifier extraConfig; forceGrubReinstallCount = 1; } }",
+          "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 1; } }",
           "/etc/nixos/configuration.nix");
 
       # Check whether nixos-rebuild works.
@@ -128,18 +168,18 @@ let
       $machine->shutdown;
 
       # Check whether a writable store build works
-      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}" });
+      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "rebuild-switch" });
       ${preBootCommands}
       $machine->waitForUnit("multi-user.target");
       $machine->copyFileFromHost(
-          "${ makeConfig { inherit grubVersion grubDevice grubIdentifier extraConfig; forceGrubReinstallCount = 2; } }",
+          "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 2; } }",
           "/etc/nixos/configuration.nix");
       $machine->succeed("nixos-rebuild boot >&2");
       $machine->shutdown;
 
       # And just to be sure, check that the machine still boots after
       # "nixos-rebuild switch".
-      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}" });
+      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", "boot-after-rebuild-switch" });
       ${preBootCommands}
       $machine->waitForUnit("network.target");
       $machine->shutdown;
@@ -148,8 +188,10 @@ let
 
   makeInstallerTest = name:
     { createPartitions, preBootCommands ? "", extraConfig ? ""
-    , grubVersion ? 2, grubDevice ? "/dev/vda"
-    , grubIdentifier ? "uuid", enableOCR ? false, meta ? {}
+    , extraInstallerConfig ? {}
+    , bootLoader ? "grub" # either "grub" or "systemd-boot"
+    , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
+    , enableOCR ? false, meta ? {}
     }:
     makeTest {
       inherit enableOCR;
@@ -160,19 +202,18 @@ let
       };
       nodes = {
 
-        # The configuration of the machine used to run "nixos-install". It
-        # also has a web server that simulates cache.nixos.org.
+        # The configuration of the machine used to run "nixos-install".
         machine =
           { config, lib, pkgs, ... }:
 
           { imports =
               [ ../modules/profiles/installation-device.nix
                 ../modules/profiles/base.nix
+                extraInstallerConfig
               ];
 
             virtualisation.diskSize = 8 * 1024;
-            virtualisation.memorySize = 768;
-            virtualisation.writableStore = true;
+            virtualisation.memorySize = 1024;
 
             # Use a small /dev/vdb as the root disk for the
             # installer. This ensures the target disk (/dev/vda) is
@@ -183,32 +224,45 @@ let
             virtualisation.qemu.diskInterface =
               if grubVersion == 1 then "scsi" else "virtio";
 
+            boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true;
+
             hardware.enableAllFirmware = mkForce false;
 
             # The test cannot access the network, so any packages we
             # need must be included in the VM.
-            system.extraDependencies =
-              [ pkgs.sudo
-                pkgs.docbook5
-                pkgs.docbook5_xsl
-                pkgs.unionfs-fuse
-                pkgs.ntp
-                pkgs.nixos-artwork
-                pkgs.gummiboot
-                pkgs.perlPackages.XMLLibXML
-                pkgs.perlPackages.ListCompare
+            system.extraDependencies = with pkgs;
+              [ sudo
+                libxml2.bin
+                libxslt.bin
+                docbook5
+                docbook5_xsl
+                unionfs-fuse
+                ntp
+                nixos-artwork.wallpapers.gnome-dark
+                perlPackages.XMLLibXML
+                perlPackages.ListCompare
+                xorg.lndir
+
+                # add curl so that rather than seeing the test attempt to download
+                # curl's tarball, we see what it's trying to download
+                curl
               ]
-              ++ optional (grubVersion == 1) pkgs.grub
-              ++ optionals (grubVersion == 2) [ pkgs.grub2 pkgs.grub2_efi ];
+              ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub
+              ++ optionals (bootLoader == "grub" && grubVersion == 2) [ pkgs.grub2 pkgs.grub2_efi ];
 
             nix.binaryCaches = mkForce [ ];
+            nix.extraOptions =
+              ''
+                hashed-mirrors =
+                connect-timeout = 1
+              '';
           };
 
       };
 
       testScript = testScriptFun {
-        inherit createPartitions preBootCommands grubVersion
-                grubDevice grubIdentifier extraConfig;
+        inherit bootLoader createPartitions preBootCommands
+                grubVersion grubDevice grubIdentifier grubUseEfi extraConfig;
       };
     };
 
@@ -224,9 +278,9 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted /dev/vda mklabel msdos",
-              "parted /dev/vda -- mkpart primary linux-swap 1M 1024M",
-              "parted /dev/vda -- mkpart primary ext2 1024M -1s",
+              "parted --script /dev/vda mklabel msdos",
+              "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M",
+              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s",
               "udevadm settle",
               "mkswap /dev/vda1 -L swap",
               "swapon -L swap",
@@ -236,15 +290,61 @@ in {
         '';
     };
 
+  # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
+  simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted --script /dev/vda mklabel gpt",
+              "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
+              "parted --script /dev/vda -- set 1 boot on",
+              "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
+              "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
+              "udevadm settle",
+              "mkswap /dev/vda2 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/vda3",
+              "mount LABEL=nixos /mnt",
+              "mkfs.vfat -n BOOT /dev/vda1",
+              "mkdir -p /mnt/boot",
+              "mount LABEL=BOOT /mnt/boot",
+          );
+        '';
+        bootLoader = "systemd-boot";
+    };
+
+  simpleUefiGrub = makeInstallerTest "simpleUefiGrub"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted --script /dev/vda mklabel gpt",
+              "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
+              "parted --script /dev/vda -- set 1 boot on",
+              "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
+              "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
+              "udevadm settle",
+              "mkswap /dev/vda2 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/vda3",
+              "mount LABEL=nixos /mnt",
+              "mkfs.vfat -n BOOT /dev/vda1",
+              "mkdir -p /mnt/boot",
+              "mount LABEL=BOOT /mnt/boot",
+          );
+        '';
+        bootLoader = "grub";
+        grubUseEfi = true;
+    };
+
   # Same as the previous, but now with a separate /boot partition.
   separateBoot = makeInstallerTest "separateBoot"
     { createPartitions =
         ''
           $machine->succeed(
-              "parted /dev/vda mklabel msdos",
-              "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-              "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M",
-              "parted /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "parted --script /dev/vda mklabel msdos",
+              "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
+              "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M",
+              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -262,10 +362,10 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted /dev/vda mklabel msdos",
-              "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-              "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M",
-              "parted /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "parted --script /dev/vda mklabel msdos",
+              "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
+              "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M",
+              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -278,17 +378,54 @@ in {
         '';
     };
 
+  # zfs on / with swap
+  zfsroot = makeInstallerTest "zfs-root"
+    {
+      extraInstallerConfig = {
+        boot.supportedFilesystems = [ "zfs" ];
+      };
+
+      extraConfig = ''
+        boot.supportedFilesystems = [ "zfs" ];
+
+        # Using by-uuid overrides the default of by-id, and is unique
+        # to the qemu disks, as they don't produce by-id paths for
+        # some reason.
+        boot.zfs.devNodes = "/dev/disk/by-uuid/";
+        networking.hostId = "00000000";
+      '';
+
+      createPartitions =
+        ''
+          $machine->succeed(
+              "parted --script /dev/vda mklabel msdos",
+              "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M",
+              "parted --script /dev/vda -- mkpart primary 1024M -1s",
+              "udevadm settle",
+
+              "mkswap /dev/vda1 -L swap",
+              "swapon -L swap",
+
+              "zpool create rpool /dev/vda2",
+              "zfs create -o mountpoint=legacy rpool/root",
+              "mount -t zfs rpool/root /mnt",
+
+              "udevadm settle"
+          );
+        '';
+    };
+
   # Create two physical LVM partitions combined into one volume group
   # that contains the logical swap and root partitions.
   lvm = makeInstallerTest "lvm"
     { createPartitions =
         ''
           $machine->succeed(
-              "parted /dev/vda mklabel msdos",
-              "parted /dev/vda -- mkpart primary 1M 2048M", # PV1
-              "parted /dev/vda -- set 1 lvm on",
-              "parted /dev/vda -- mkpart primary 2048M -1s", # PV2
-              "parted /dev/vda -- set 2 lvm on",
+              "parted --script /dev/vda mklabel msdos",
+              "parted --script /dev/vda -- mkpart primary 1M 2048M", # PV1
+              "parted --script /dev/vda -- set 1 lvm on",
+              "parted --script /dev/vda -- mkpart primary 2048M -1s", # PV2
+              "parted --script /dev/vda -- set 2 lvm on",
               "udevadm settle",
               "pvcreate /dev/vda1 /dev/vda2",
               "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
@@ -306,10 +443,10 @@ in {
   luksroot = makeInstallerTest "luksroot"
     { createPartitions = ''
         $machine->succeed(
-          "parted /dev/vda mklabel msdos",
-          "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-          "parted /dev/vda -- mkpart primary linux-swap 50M 1024M",
-          "parted /dev/vda -- mkpart primary 1024M -1s", # LUKS
+          "parted --script /dev/vda mklabel msdos",
+          "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
+          "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M",
+          "parted --script /dev/vda -- mkpart primary 1024M -1s", # LUKS
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
           "swapon -L swap",
@@ -323,14 +460,8 @@ in {
           "mount LABEL=boot /mnt/boot",
         );
       '';
-      # XXX: Currently, generate-config doesn't detect LUKS yet.
       extraConfig = ''
         boot.kernelParams = lib.mkAfter [ "console=tty0" ];
-        boot.initrd.luks.devices = lib.singleton {
-          name = "cryptroot";
-          device = "/dev/vda3";
-          preLVM = true;
-        };
       '';
       enableOCR = true;
       preBootCommands = ''
@@ -340,18 +471,59 @@ in {
       '';
     };
 
+  # Test whether opening encrypted filesystem with keyfile
+  # Checks for regression of missing cryptsetup, when no luks device without
+  # keyfile is configured
+  filesystemEncryptedWithKeyfile = makeInstallerTest "filesystemEncryptedWithKeyfile"
+    { createPartitions = ''
+       $machine->succeed(
+          "parted --script /dev/vda mklabel msdos",
+          "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
+          "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M",
+          "parted --script /dev/vda -- mkpart primary 1024M 1280M", # LUKS with keyfile
+          "parted --script /dev/vda -- mkpart primary 1280M -1s",
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda4",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+          "modprobe dm_mod dm_crypt",
+          "echo -n supersecret > /mnt/keyfile",
+          "cryptsetup luksFormat -q /dev/vda3 --key-file /mnt/keyfile",
+          "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt",
+          "mkfs.ext3 -L test /dev/mapper/crypt",
+          "cryptsetup luksClose crypt",
+          "mkdir -p /mnt/test"
+        );
+      '';
+      extraConfig = ''
+        fileSystems."/test" =
+        { device = "/dev/disk/by-label/test";
+          fsType = "ext3";
+          encrypted.enable = true;
+          encrypted.blkDev = "/dev/vda3";
+          encrypted.label = "crypt";
+          encrypted.keyFile = "/mnt-root/keyfile";
+        };
+      '';
+    };
+
+
   swraid = makeInstallerTest "swraid"
     { createPartitions =
         ''
           $machine->succeed(
-              "parted /dev/vda --"
+              "parted --script /dev/vda --"
               . " mklabel msdos"
               . " mkpart primary ext2 1M 100MB" # /boot
               . " mkpart extended 100M -1s"
-              . " mkpart logical 102M 1602M" # md0 (root), first device
-              . " mkpart logical 1603M 3103M" # md0 (root), second device
-              . " mkpart logical 3104M 3360M" # md1 (swap), first device
-              . " mkpart logical 3361M 3617M", # md1 (swap), second device
+              . " mkpart logical 102M 2102M" # md0 (root), first device
+              . " mkpart logical 2103M 4103M" # md0 (root), second device
+              . " mkpart logical 4104M 4360M" # md1 (swap), first device
+              . " mkpart logical 4361M 4617M", # md1 (swap), second device
               "udevadm settle",
               "ls -l /dev/vda* >&2",
               "cat /proc/partitions >&2",
@@ -366,10 +538,12 @@ in {
               "mkdir /mnt/boot",
               "mount LABEL=boot /mnt/boot",
               "udevadm settle",
-              "mdadm -W /dev/md0", # wait for sync to finish; booting off an unsynced device tends to fail
-              "mdadm -W /dev/md1",
           );
         '';
+      preBootCommands = ''
+        $machine->start;
+        $machine->fail("dmesg | grep 'immediate safe mode'");
+      '';
     };
 
   # Test a basic install using GRUB 1.
@@ -377,9 +551,9 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted /dev/sda mklabel msdos",
-              "parted /dev/sda -- mkpart primary linux-swap 1M 1024M",
-              "parted /dev/sda -- mkpart primary ext2 1024M -1s",
+              "parted --script /dev/sda mklabel msdos",
+              "parted --script /dev/sda -- mkpart primary linux-swap 1M 1024M",
+              "parted --script /dev/sda -- mkpart primary ext2 1024M -1s",
               "udevadm settle",
               "mkswap /dev/sda1 -L swap",
               "swapon -L swap",
diff --git a/nixos/tests/ipfs.nix b/nixos/tests/ipfs.nix
new file mode 100644
index 000000000000..c6bc61545245
--- /dev/null
+++ b/nixos/tests/ipfs.nix
@@ -0,0 +1,55 @@
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ipfs";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mguentner ];
+  };
+
+  nodes = {
+    adder =
+      { config, pkgs, ... }:
+      {
+        services.ipfs = {
+          enable = true;
+          defaultMode = "norouting";
+          gatewayAddress = "/ip4/127.0.0.1/tcp/2323";
+          apiAddress = "/ip4/127.0.0.1/tcp/2324";
+        };
+        networking.firewall.allowedTCPPorts = [ 4001 ];
+      };
+    getter =
+      { config, pkgs, ... }:
+      {
+        services.ipfs = {
+          enable = true;
+          defaultMode = "norouting";
+          autoMount = true;
+        };
+        networking.firewall.allowedTCPPorts = [ 4001 ];
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $adder->waitForUnit("ipfs-norouting");
+    $getter->waitForUnit("ipfs-norouting");
+
+    # wait until api is available
+    $adder->waitUntilSucceeds("ipfs --api /ip4/127.0.0.1/tcp/2324 id");
+    my $addrId = $adder->succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id -f=\"<id>\"");
+    my $addrIp = (split /[ \/]+/, $adder->succeed("ip -o -4 addr show dev eth1"))[3];
+
+    $adder->mustSucceed("[ -n \"\$(ipfs --api /ip4/127.0.0.1/tcp/2324 config Addresses.Gateway | grep /ip4/127.0.0.1/tcp/2323)\" ]");
+
+    # wait until api is available
+    $getter->waitUntilSucceeds("ipfs --api /ip4/127.0.0.1/tcp/5001 id");
+    my $ipfsHash = $adder->mustSucceed("echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | cut -d' ' -f2");
+    chomp($ipfsHash);
+
+    $adder->mustSucceed("[ -n \"\$(echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | grep added)\" ]");
+
+    $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)\" ]");
+    $getter->mustSucceed("[ -n \"$(cat /ipfs/$ipfsHash | grep fnord)\" ]");
+    '';
+})
diff --git a/nixos/tests/ipv6.nix b/nixos/tests/ipv6.nix
index 4e2e6379cad3..7a98fd85cfda 100644
--- a/nixos/tests/ipv6.nix
+++ b/nixos/tests/ipv6.nix
@@ -47,29 +47,29 @@ import ./make-test.nix ({ pkgs, ...} : {
       # Detection).
       sub waitForAddress {
           my ($machine, $iface, $scope) = @_;
-          $machine->waitUntilSucceeds("[ `ip -o -6 addr show dev $iface scope $scope | grep -v tentative | wc -l` -eq 1 ]");
+          $machine->waitUntilSucceeds("[ `ip -o -6 addr show dev $iface scope $scope | grep -v tentative | wc -l` -ge 1 ]");
           my $ip = (split /[ \/]+/, $machine->succeed("ip -o -6 addr show dev $iface scope $scope"))[3];
           $machine->log("$scope address on $iface is $ip");
           return $ip;
       }
 
       subtest "loopback address", sub {
-          $client->succeed("ping6 -c 1 ::1 >&2");
-          $client->fail("ping6 -c 1 ::2 >&2");
+          $client->succeed("ping -c 1 ::1 >&2");
+          $client->fail("ping -c 1 ::2 >&2");
       };
 
       subtest "local link addressing", sub {
           my $clientIp = waitForAddress $client, "eth1", "link";
           my $serverIp = waitForAddress $server, "eth1", "link";
-          $client->succeed("ping6 -c 1 -I eth1 $clientIp >&2");
-          $client->succeed("ping6 -c 1 -I eth1 $serverIp >&2");
+          $client->succeed("ping -c 1 $clientIp%eth1 >&2");
+          $client->succeed("ping -c 1 $serverIp%eth1 >&2");
       };
 
       subtest "global addressing", sub {
           my $clientIp = waitForAddress $client, "eth1", "global";
           my $serverIp = waitForAddress $server, "eth1", "global";
-          $client->succeed("ping6 -c 1 $clientIp >&2");
-          $client->succeed("ping6 -c 1 $serverIp >&2");
+          $client->succeed("ping -c 1 $clientIp >&2");
+          $client->succeed("ping -c 1 $serverIp >&2");
           $client->succeed("curl --fail -g http://[$serverIp]");
           $client->fail("curl --fail -g http://[$clientIp]");
       };
diff --git a/nixos/tests/jenkins.nix b/nixos/tests/jenkins.nix
index 804cecd700c1..ed55b2ff5871 100644
--- a/nixos/tests/jenkins.nix
+++ b/nixos/tests/jenkins.nix
@@ -6,7 +6,7 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "jenkins";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ bjornfor coconnor iElectric eelco chaoflow ];
+    maintainers = [ bjornfor coconnor domenkozar eelco chaoflow ];
   };
 
   nodes = {
@@ -36,6 +36,9 @@ import ./make-test.nix ({ pkgs, ...} : {
     startAll;
 
     $master->waitForUnit("jenkins");
+
+    $master->mustSucceed("curl http://localhost:8080 | grep 'Authentication required'");
+
     print $master->execute("sudo -u jenkins groups");
     $master->mustSucceed("sudo -u jenkins groups | grep jenkins | grep users");
 
@@ -44,4 +47,4 @@ import ./make-test.nix ({ pkgs, ...} : {
 
     $slave->mustFail("systemctl is-enabled jenkins.service");
   '';
-})
\ No newline at end of file
+})
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
new file mode 100644
index 000000000000..e48b25d67df3
--- /dev/null
+++ b/nixos/tests/kafka.nix
@@ -0,0 +1,69 @@
+{ system ? builtins.currentSystem }:
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+
+let
+  makeKafkaTest = name: kafkaPackage: (makeTest {
+    inherit name;
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ nequissimus ];
+    };
+
+    nodes = {
+      zookeeper1 = { config, ... }: {
+        services.zookeeper = {
+          enable = true;
+        };
+
+        networking.firewall.allowedTCPPorts = [ 2181 ];
+        virtualisation.memorySize = 1024;
+      };
+      kafka = { config, ... }: {
+        services.apache-kafka = {
+          enable = true;
+          extraProperties = ''
+            offsets.topic.replication.factor = 1
+            zookeeper.session.timeout.ms = 600000
+          '';
+          package = kafkaPackage;
+          zookeeper = "zookeeper1:2181";
+          # These are the default options, but UseCompressedOops doesn't work with 32bit JVM
+          jvmOptions = [
+            "-server" "-Xmx1G" "-Xms1G" "-XX:+UseParNewGC" "-XX:+UseConcMarkSweepGC" "-XX:+CMSClassUnloadingEnabled"
+            "-XX:+CMSScavengeBeforeRemark" "-XX:+DisableExplicitGC" "-Djava.awt.headless=true" "-Djava.net.preferIPv4Stack=true"
+          ] ++ optionals (! pkgs.stdenv.isi686 ) [ "-XX:+UseCompressedOops" ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 9092 ];
+        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
+        virtualisation.memorySize = 2047; 
+      };
+    };
+
+    testScript = ''
+      startAll;
+
+      $zookeeper1->waitForUnit("default.target");
+      $zookeeper1->waitForUnit("zookeeper.service");
+      $zookeeper1->waitForOpenPort(2181);
+
+      $kafka->waitForUnit("default.target");
+      $kafka->waitForUnit("apache-kafka.service");
+      $kafka->waitForOpenPort(9092);
+
+      $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic");
+      $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic");
+    '' + (if name == "kafka_0_9" then ''
+      $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --zookeeper zookeeper1:2181 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'");
+    '' else ''
+      $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'");
+    '');
+  });
+
+in with pkgs; {
+  kafka_0_9  = makeKafkaTest "kafka_0_9"  apacheKafka_0_9;
+  kafka_0_10 = makeKafkaTest "kafka_0_10" apacheKafka_0_10;
+  kafka_0_11 = makeKafkaTest "kafka_0_11" apacheKafka_0_11;
+  kafka_1_0  = makeKafkaTest "kafka_1_0"  apacheKafka_1_0;
+  kafka_1_1  = makeKafkaTest "kafka_1_1"  apacheKafka_1_1;
+}
diff --git a/nixos/tests/kde4.nix b/nixos/tests/kde4.nix
deleted file mode 100644
index dc61658cd1c4..000000000000
--- a/nixos/tests/kde4.nix
+++ /dev/null
@@ -1,68 +0,0 @@
-import ./make-test.nix ({ pkgs, ... }: {
-  name = "kde4";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric eelco chaoflow ];
-  };
-
-  machine =
-    { config, pkgs, ... }:
-
-    { imports = [ ./common/user-account.nix ];
-
-      virtualisation.memorySize = 1024;
-
-      services.xserver.enable = true;
-
-      services.httpd.enable = true;
-      services.httpd.adminAddr = "foo@example.org";
-      services.httpd.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
-
-      services.xserver.displayManager.kdm.enable = true;
-      services.xserver.displayManager.kdm.extraConfig =
-        ''
-          [X-:0-Core]
-          AutoLoginEnable=true
-          AutoLoginUser=alice
-          AutoLoginPass=foobar
-        '';
-
-      services.xserver.desktopManager.kde4.enable = true;
-
-      # Include most of KDE. We don't really test these here, but at
-      # least they should build.
-      environment.systemPackages =
-        [ pkgs.kde4.kdemultimedia
-          pkgs.kde4.kdegraphics
-          pkgs.kde4.kdeutils
-          pkgs.kde4.kdegames
-          #pkgs.kde4.kdeedu
-          pkgs.kde4.kdeaccessibility
-          pkgs.kde4.kdeadmin
-          pkgs.kde4.kdenetwork
-          pkgs.kde4.kdetoys
-          pkgs.kde4.kdewebdev
-        ];
-    };
-
-  testScript = '' 
-      $machine->waitUntilSucceeds("pgrep plasma-desktop");
-      $machine->waitForWindow(qr/plasma-desktop/);
-
-      # Check that logging in has given the user ownership of devices.
-      $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
-
-      $machine->execute("su - alice -c 'DISPLAY=:0.0 kwrite /var/log/messages &'");
-      $machine->waitForWindow(qr/messages.*KWrite/);
-
-      $machine->execute("su - alice -c 'DISPLAY=:0.0 konqueror http://localhost/ &'");
-      $machine->waitForWindow(qr/Valgrind.*Konqueror/);
-
-      $machine->execute("su - alice -c 'DISPLAY=:0.0 gwenview ${pkgs.kde4.kde_wallpapers}/share/wallpapers/Hanami/contents/images/1280x1024.jpg &'");
-      $machine->waitForWindow(qr/Gwenview/);
-
-      $machine->sleep(10);
-
-      $machine->screenshot("screen"); 
-    '';
-
-})
diff --git a/nixos/tests/kernel-copperhead.nix b/nixos/tests/kernel-copperhead.nix
new file mode 100644
index 000000000000..aa133c9b0aa7
--- /dev/null
+++ b/nixos/tests/kernel-copperhead.nix
@@ -0,0 +1,19 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "kernel-copperhead";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { config, lib, pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages_copperhead_lts;
+    };
+
+  testScript =
+    ''
+      $machine->succeed("uname -a");
+      $machine->succeed("uname -s | grep 'Linux'");
+      $machine->succeed("uname -a | grep '${pkgs.linuxPackages_copperhead_lts.kernel.modDirVersion}'");
+      $machine->succeed("uname -a | grep 'hardened'");
+    '';
+})
diff --git a/nixos/tests/kernel-latest.nix b/nixos/tests/kernel-latest.nix
new file mode 100644
index 000000000000..1350426654d7
--- /dev/null
+++ b/nixos/tests/kernel-latest.nix
@@ -0,0 +1,17 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "kernel-latest";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { config, lib, pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages_latest;
+    };
+
+  testScript =
+    ''
+      $machine->succeed("uname -s | grep 'Linux'");
+      $machine->succeed("uname -a | grep '${pkgs.linuxPackages_latest.kernel.version}'");
+    '';
+})
diff --git a/nixos/tests/kernel-lts.nix b/nixos/tests/kernel-lts.nix
new file mode 100644
index 000000000000..2aab4ce0b49e
--- /dev/null
+++ b/nixos/tests/kernel-lts.nix
@@ -0,0 +1,17 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "kernel-lts";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { config, lib, pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages;
+    };
+
+  testScript =
+    ''
+      $machine->succeed("uname -s | grep 'Linux'");
+      $machine->succeed("uname -a | grep '${pkgs.linuxPackages.kernel.version}'");
+    '';
+})
diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix
new file mode 100644
index 000000000000..be880388314c
--- /dev/null
+++ b/nixos/tests/keymap.nix
@@ -0,0 +1,154 @@
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+
+let
+  readyFile  = "/tmp/readerReady";
+  resultFile = "/tmp/readerResult";
+
+  testReader = pkgs.writeScript "test-input-reader" ''
+    #!${pkgs.stdenv.shell}
+    rm -f ${resultFile} ${resultFile}.tmp
+    logger "testReader: START: Waiting for $1 characters, expecting '$2'."
+    touch ${readyFile}
+    read -r -N $1 chars
+    rm -f ${readyFile}
+
+    if [ "$chars" == "$2" ]; then
+      logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
+    else
+      logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
+    fi
+    # rename after the file is written to prevent a race condition
+    mv  ${resultFile}.tmp ${resultFile}
+  '';
+
+
+  mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; let
+    combinedTests = foldAttrs (acc: val: acc ++ val) [] (builtins.attrValues tests);
+    perlStr = val: "'${escape ["'" "\\"] val}'";
+    lq = length combinedTests.qwerty;
+    le = length combinedTests.expect;
+    msg = "length mismatch between qwerty (${toString lq}) and expect (${toString le}) lists!";
+    send   = concatMapStringsSep ", " perlStr combinedTests.qwerty;
+    expect = if (lq == le) then concatStrings combinedTests.expect else throw msg;
+
+  in makeTest {
+    name = "keymap-${layout}";
+
+    machine.services.xserver.desktopManager.xterm.enable = false;
+    machine.i18n.consoleKeyMap = mkOverride 900 layout;
+    machine.services.xserver.layout = mkOverride 900 layout;
+    machine.imports = [ ./common/x11.nix extraConfig ];
+
+    testScript = ''
+
+      sub mkTest ($$) {
+        my ($desc, $cmd) = @_;
+
+        subtest $desc, sub {
+          # prepare and start testReader
+          $machine->execute("rm -f ${readyFile} ${resultFile}");
+          $machine->succeed("$cmd ${testReader} ${toString le} ".q(${escapeShellArg expect} & ));
+
+          if ($desc eq "Xorg keymap") {
+            # make sure the xterm window is open and has focus
+            $machine->waitForWindow(qr/testterm/);
+            $machine->waitUntilSucceeds("${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible --class testterm windowfocus --sync");
+          }
+
+          # wait for reader to be ready
+          $machine->waitForFile("${readyFile}");
+          $machine->sleep(1);
+
+          # send all keys
+          foreach ((${send})) { $machine->sendKeys($_); };
+
+          # wait for result and check
+          $machine->waitForFile("${resultFile}");
+          $machine->succeed("grep -q 'PASS:' ${resultFile}");
+        };
+      };
+
+      $machine->waitForX;
+
+      mkTest "VT keymap", "openvt -sw --";
+      mkTest "Xorg keymap", "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e";
+    '';
+  };
+
+in pkgs.lib.mapAttrs mkKeyboardTest {
+  azerty = {
+    tests = {
+      azqw.qwerty = [ "q" "w" ];
+      azqw.expect = [ "a" "z" ];
+      altgr.qwerty = [ "alt_r-2" "alt_r-3" "alt_r-4" "alt_r-5" "alt_r-6" ];
+      altgr.expect = [ "~"       "#"       "{"       "["       "|"       ];
+    };
+
+    extraConfig.i18n.consoleKeyMap = "azerty/fr";
+    extraConfig.services.xserver.layout = "fr";
+  };
+
+  colemak = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o"         ];
+    };
+
+    extraConfig.i18n.consoleKeyMap = "en-latin9";
+    extraConfig.services.xserver.layout = "us";
+    extraConfig.services.xserver.xkbVariant = "colemak";
+  };
+
+  dvorak = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s"         ];
+      symbols.qwerty = [ "q" "w" "e" "minus" "equal" ];
+      symbols.expect = [ "'" "," "." "["     "]"     ];
+    };
+  };
+
+  dvp = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s"         ];
+      numbers.qwerty = map (x: "shift-${x}")
+                       [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
+      numbers.expect = [ "%" "7" "5" "3" "1" "9" "0" "2" "4" "6" "8" ];
+      symbols.qwerty = [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
+      symbols.expect = [ "&" "[" "{" "}" "(" "=" "*" ")" "+" "]" "!" ];
+    };
+
+    extraConfig.services.xserver.layout = "us";
+    extraConfig.services.xserver.xkbVariant = "dvp";
+  };
+
+  neo = {
+    tests = {
+      layer1.qwerty = [ "f"           "j"                     ];
+      layer1.expect = [ "e"           "n"                     ];
+      layer2.qwerty = [ "shift-f"     "shift-j"     "shift-6" ];
+      layer2.expect = [ "E"           "N"           "$"       ];
+      layer3.qwerty = [ "caps_lock-d" "caps_lock-f"           ];
+      layer3.expect = [ "{"           "}"                     ];
+    };
+
+    extraConfig.services.xserver.layout = "de";
+    extraConfig.services.xserver.xkbVariant = "neo";
+  };
+
+  qwertz = {
+    tests = {
+      zy.qwerty = [ "z" "y" ];
+      zy.expect = [ "y" "z" ];
+      altgr.qwerty = map (x: "alt_r-${x}")
+                     [ "q" "less" "7" "8" "9" "0" ];
+      altgr.expect = [ "@" "|"    "{" "[" "]" "}" ];
+    };
+
+    extraConfig.i18n.consoleKeyMap = "de";
+    extraConfig.services.xserver.layout = "de";
+  };
+}
diff --git a/nixos/tests/krb5/default.nix b/nixos/tests/krb5/default.nix
new file mode 100644
index 000000000000..dd5b2f37202e
--- /dev/null
+++ b/nixos/tests/krb5/default.nix
@@ -0,0 +1,5 @@
+{ system ? builtins.currentSystem }:
+{
+  example-config = import ./example-config.nix { inherit system; };
+  deprecated-config = import ./deprecated-config.nix { inherit system; };
+}
diff --git a/nixos/tests/krb5/deprecated-config.nix b/nixos/tests/krb5/deprecated-config.nix
new file mode 100644
index 000000000000..980b3e762dc6
--- /dev/null
+++ b/nixos/tests/krb5/deprecated-config.nix
@@ -0,0 +1,48 @@
+# Verifies that the configuration suggested in deprecated example values
+# will result in the expected output.
+
+import ../make-test.nix ({ pkgs, ...} : {
+  name = "krb5-with-deprecated-config";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  machine =
+    { config, pkgs, ... }: {
+      krb5 = {
+        enable = true;
+        defaultRealm = "ATHENA.MIT.EDU";
+        domainRealm = "athena.mit.edu";
+        kdc = "kerberos.mit.edu";
+        kerberosAdminServer = "kerberos.mit.edu";
+      };
+    };
+
+  testScript =
+    let snapshot = pkgs.writeText "krb5-with-deprecated-config.conf" ''
+      [libdefaults]
+        default_realm = ATHENA.MIT.EDU
+
+      [realms]
+        ATHENA.MIT.EDU = {
+          admin_server = kerberos.mit.edu
+          kdc = kerberos.mit.edu
+        }
+
+      [domain_realm]
+        .athena.mit.edu = ATHENA.MIT.EDU
+        athena.mit.edu = ATHENA.MIT.EDU
+
+      [capaths]
+
+
+      [appdefaults]
+
+
+      [plugins]
+
+    '';
+  in ''
+    $machine->succeed("diff /etc/krb5.conf ${snapshot}");
+  '';
+})
diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix
new file mode 100644
index 000000000000..d5328720931e
--- /dev/null
+++ b/nixos/tests/krb5/example-config.nix
@@ -0,0 +1,106 @@
+# Verifies that the configuration suggested in (non-deprecated) example values
+# will result in the expected output.
+
+import ../make-test.nix ({ pkgs, ...} : {
+  name = "krb5-with-example-config";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  machine =
+    { config, pkgs, ... }: {
+      krb5 = {
+        enable = true;
+        kerberos = pkgs.krb5Full;
+        libdefaults = {
+          default_realm = "ATHENA.MIT.EDU";
+        };
+        realms = {
+          "ATHENA.MIT.EDU" = {
+            admin_server = "athena.mit.edu";
+            kdc = "athena.mit.edu";
+          };
+        };
+        domain_realm = {
+          "example.com" = "EXAMPLE.COM";
+          ".example.com" = "EXAMPLE.COM";
+        };
+        capaths = {
+          "ATHENA.MIT.EDU" = {
+            "EXAMPLE.COM" = ".";
+          };
+          "EXAMPLE.COM" = {
+            "ATHENA.MIT.EDU" = ".";
+          };
+        };
+        appdefaults = {
+          pam = {
+            debug = false;
+            ticket_lifetime = 36000;
+            renew_lifetime = 36000;
+            max_timeout = 30;
+            timeout_shift = 2;
+            initial_timeout = 1;
+          };
+        };
+        plugins = {
+          ccselect = {
+            disable = "k5identity";
+          };
+        };
+        extraConfig = ''
+          [logging]
+            kdc          = SYSLOG:NOTICE
+            admin_server = SYSLOG:NOTICE
+            default      = SYSLOG:NOTICE
+        '';
+      };
+    };
+
+  testScript =
+    let snapshot = pkgs.writeText "krb5-with-example-config.conf" ''
+      [libdefaults]
+        default_realm = ATHENA.MIT.EDU
+
+      [realms]
+        ATHENA.MIT.EDU = {
+          admin_server = athena.mit.edu
+          kdc = athena.mit.edu
+        }
+
+      [domain_realm]
+        .example.com = EXAMPLE.COM
+        example.com = EXAMPLE.COM
+
+      [capaths]
+        ATHENA.MIT.EDU = {
+          EXAMPLE.COM = .
+        }
+        EXAMPLE.COM = {
+          ATHENA.MIT.EDU = .
+        }
+
+      [appdefaults]
+        pam = {
+          debug = false
+          initial_timeout = 1
+          max_timeout = 30
+          renew_lifetime = 36000
+          ticket_lifetime = 36000
+          timeout_shift = 2
+        }
+
+      [plugins]
+        ccselect = {
+          disable = k5identity
+        }
+
+      [logging]
+        kdc          = SYSLOG:NOTICE
+        admin_server = SYSLOG:NOTICE
+        default      = SYSLOG:NOTICE
+    '';
+  in ''
+    $machine->succeed("diff /etc/krb5.conf ${snapshot}");
+  '';
+})
diff --git a/nixos/tests/kubernetes.nix b/nixos/tests/kubernetes.nix
deleted file mode 100644
index b19ea67b0baf..000000000000
--- a/nixos/tests/kubernetes.nix
+++ /dev/null
@@ -1,182 +0,0 @@
-# This test runs two node kubernetes cluster and checks if simple redis pod works
-
-import ./make-test.nix ({ pkgs, ...} : rec {
-  name = "kubernetes";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline ];
-  };
-
-  redisMaster = builtins.toFile "redis-master-pod.yaml" ''
-      id: redis-master-pod
-      kind: Pod
-      apiVersion: v1beta1
-      desiredState:
-        manifest:
-          version: v1beta1
-          id: redis-master-pod
-          containers:
-            - name: master
-              image: master:5000/nix
-              cpu: 100
-              ports:
-                - name: redis-server
-                  containerPort: 6379
-                  hostPort: 6379
-              volumeMounts:
-                - name: nix-store
-                  mountPath: /nix/store
-                  readOnly: true
-              volumeMounts:
-                - name: system-profile
-                  mountPath: /bin
-                  readOnly: true
-              command:
-                - /bin/redis-server
-          volumes:
-            - name: nix-store
-              source:
-                hostDir:
-                  path: /nix/store
-            - name: system-profile
-              source:
-                hostDir:
-                  path: /run/current-system/sw/bin
-      labels:
-        name: redis
-        role: master
-  '';
-
-  nodes = {
-    master =
-      { config, pkgs, lib, nodes, ... }:
-        {
-          virtualisation.memorySize = 768;
-          services.kubernetes = {
-            roles = ["master" "node"];
-            dockerCfg = ''{"master:5000":{}}'';
-            controllerManager.machines = ["master" "node"];
-            apiserver.address = "0.0.0.0";
-            verbose = true;
-          };
-          virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0 --insecure-registry master:5000";
-
-          services.etcd = {
-            listenPeerUrls = ["http://0.0.0.0:7001"];
-            initialAdvertisePeerUrls = ["http://master:7001"];
-            initialCluster = ["master=http://master:7001" "node=http://node:7001"];
-          };
-          services.dockerRegistry.enable = true;
-          services.dockerRegistry.host = "0.0.0.0";
-          services.dockerRegistry.port = 5000;
-
-          virtualisation.vlans = [ 1 2 ];
-          networking.bridges = {
-            cbr0.interfaces = [ "eth2" ];
-          };
-          networking.interfaces = {
-            cbr0 = {
-              ipAddress = "10.10.0.1";
-              prefixLength = 24;
-            };
-            eth2.ip4 = lib.mkOverride 0 [ ];
-          };
-          networking.localCommands = ''
-            ip route add 10.10.0.0/16 dev cbr0
-            ip route flush cache
-          '';
-          networking.extraHosts = "127.0.0.1 master";
-
-          networking.firewall.enable = false;
-          #networking.firewall.allowedTCPPorts = [ 4001 7001 ];
-
-          environment.systemPackages = [ pkgs.redis ];
-        };
-
-    node =
-      { config, pkgs, lib, nodes, ... }:
-        {
-          services.kubernetes = {
-            roles = ["node"];
-            dockerCfg = ''{"master:5000":{}}'';
-            kubelet.apiServers = ["master:8080"];
-            verbose = true;
-          };
-          virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0 --insecure-registry master:5000";
-          services.etcd = {
-            listenPeerUrls = ["http://0.0.0.0:7001"];
-            initialAdvertisePeerUrls = ["http://node:7001"];
-            initialCluster = ["master=http://master:7001" "node=http://node:7001"];
-          };
-
-          virtualisation.vlans = [ 1 2 ];
-          networking.bridges = {
-            cbr0.interfaces = [ "eth2" ];
-          };
-          networking.interfaces = {
-            cbr0 = {
-              ipAddress = "10.10.1.1";
-              prefixLength = 24;
-            };
-            eth2.ip4 = lib.mkOverride 0 [ ];
-          };
-          networking.localCommands = ''
-            ip route add 10.10.0.0/16 dev cbr0
-            ip route flush cache
-          '';
-          networking.extraHosts = "127.0.0.1 node";
-
-          networking.firewall.enable = false;
-          #networking.firewall.allowedTCPPorts = [ 4001 7001 ];
-
-          environment.systemPackages = [ pkgs.redis ];
-        };
-
-    client =
-      { config, pkgs, nodes, ... }:
-        {
-          virtualisation.docker.enable = true;
-          virtualisation.docker.extraOptions = "--insecure-registry master:5000";
-          environment.systemPackages = [ pkgs.kubernetes ];
-          environment.etc."test/redis-master-pod.yaml".source = redisMaster;
-          environment.etc."test/pause".source = "${pkgs.kubernetes}/bin/kube-pause";
-          environment.etc."test/Dockerfile".source = pkgs.writeText "Dockerfile" ''
-            FROM scratch
-            ADD pause /
-            ENTRYPOINT ["/pause"]
-          '';
-        };
-  };
-
-  testScript = ''
-    startAll;
-
-    $master->waitForUnit("kubernetes-apiserver.service");
-    $master->waitForUnit("kubernetes-scheduler.service");
-    $master->waitForUnit("kubernetes-controller-manager.service");
-    $master->waitForUnit("kubernetes-kubelet.service");
-    $master->waitForUnit("kubernetes-proxy.service");
-
-    $node->waitForUnit("kubernetes-kubelet.service");
-    $node->waitForUnit("kubernetes-proxy.service");
-
-    $master->waitUntilSucceeds("kubectl get minions | grep master");
-    $master->waitUntilSucceeds("kubectl get minions | grep node");
-
-    $client->waitForUnit("docker.service");
-    $client->succeed("tar cv --files-from /dev/null | docker import - nix");
-    $client->succeed("docker tag nix master:5000/nix");
-    $master->waitForUnit("docker-registry.service");
-    $client->succeed("docker push master:5000/nix");
-    $client->succeed("mkdir -p /root/pause");
-    $client->succeed("cp /etc/test/pause /root/pause/");
-    $client->succeed("cp /etc/test/Dockerfile /root/pause/");
-    $client->succeed("cd /root/pause && docker build -t master:5000/pause .");
-    $client->succeed("docker push master:5000/pause");
-
-    subtest "simple pod", sub {
-      $client->succeed("kubectl create -f ${redisMaster} -s http://master:8080");
-      $client->waitUntilSucceeds("kubectl get pods -s http://master:8080 | grep redis-master | grep -i running");
-    }
-
-  '';
-})
diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
new file mode 100644
index 000000000000..e4bc5b326d34
--- /dev/null
+++ b/nixos/tests/kubernetes/base.nix
@@ -0,0 +1,112 @@
+{ system ? builtins.currentSystem }:
+
+with import ../../lib/testing.nix { inherit system; };
+with pkgs.lib;
+
+let
+  mkKubernetesBaseTest =
+    { name, domain ? "my.zyx", test, machines
+    , pkgs ? import <nixpkgs> { inherit system; }
+    , certs ? import ./certs.nix { inherit pkgs; externalDomain = domain; kubelets = attrNames machines; }
+    , extraConfiguration ? null }:
+    let
+      masterName = head (filter (machineName: any (role: role == "master") machines.${machineName}.roles) (attrNames machines));
+      master = machines.${masterName};
+      extraHosts = ''
+        ${master.ip}  etcd.${domain}
+        ${master.ip}  api.${domain}
+        ${concatMapStringsSep "\n" (machineName: "${machines.${machineName}.ip}  ${machineName}.${domain}") (attrNames machines)}
+      '';
+    in makeTest {
+      inherit name;
+
+      nodes = mapAttrs (machineName: machine:
+        { config, pkgs, lib, nodes, ... }:
+          mkMerge [
+            {
+              virtualisation.memorySize = mkDefault 1536;
+              virtualisation.diskSize = mkDefault 4096;
+              networking = {
+                inherit domain extraHosts;
+                primaryIPAddress = mkForce machine.ip;
+
+                firewall = {
+                  allowedTCPPorts = [
+                    10250 # kubelet
+                  ];
+                  trustedInterfaces = ["docker0"];
+
+                  extraCommands = concatMapStrings  (node: ''
+                    iptables -A INPUT -s ${node.config.networking.primaryIPAddress} -j ACCEPT
+                  '') (attrValues nodes);
+                };
+              };
+              programs.bash.enableCompletion = true;
+              environment.variables = {
+                ETCDCTL_CERT_FILE = "${certs.worker}/etcd-client.pem";
+                ETCDCTL_KEY_FILE = "${certs.worker}/etcd-client-key.pem";
+                ETCDCTL_CA_FILE = "${certs.worker}/ca.pem";
+                ETCDCTL_PEERS = "https://etcd.${domain}:2379";
+              };
+              services.flannel.iface = "eth1";
+              services.kubernetes.apiserver.advertiseAddress = master.ip;
+            }
+            (optionalAttrs (any (role: role == "master") machine.roles) {
+              networking.firewall.allowedTCPPorts = [
+                2379 2380  # etcd
+                443 # kubernetes apiserver
+              ];
+              services.etcd = {
+                enable = true;
+                certFile = "${certs.master}/etcd.pem";
+                keyFile = "${certs.master}/etcd-key.pem";
+                trustedCaFile = "${certs.master}/ca.pem";
+                peerClientCertAuth = true;
+                listenClientUrls = ["https://0.0.0.0:2379"];
+                listenPeerUrls = ["https://0.0.0.0:2380"];
+                advertiseClientUrls = ["https://etcd.${config.networking.domain}:2379"];
+                initialCluster = ["${masterName}=https://etcd.${config.networking.domain}:2380"];
+                initialAdvertisePeerUrls = ["https://etcd.${config.networking.domain}:2380"];
+              };
+            })
+            (import ./kubernetes-common.nix { inherit (machine) roles; inherit pkgs config certs; })
+            (optionalAttrs (machine ? "extraConfiguration") (machine.extraConfiguration { inherit config pkgs lib nodes; }))
+            (optionalAttrs (extraConfiguration != null) (extraConfiguration { inherit config pkgs lib nodes; }))
+          ]
+      ) machines;
+
+      testScript = ''
+        startAll;
+
+        ${test}
+      '';
+    };
+
+  mkKubernetesMultiNodeTest = attrs: mkKubernetesBaseTest ({
+    machines = {
+      machine1 = {
+        roles = ["master"];
+        ip = "192.168.1.1";
+      };
+      machine2 = {
+        roles = ["node"];
+        ip = "192.168.1.2";
+      };
+    };
+  } // attrs // {
+    name = "kubernetes-${attrs.name}-multinode";
+  });
+
+  mkKubernetesSingleNodeTest = attrs: mkKubernetesBaseTest ({
+    machines = {
+      machine1 = {
+        roles = ["master" "node"];
+        ip = "192.168.1.1";
+      };
+    };
+  } // attrs // {
+    name = "kubernetes-${attrs.name}-singlenode";
+  });
+in {
+  inherit mkKubernetesBaseTest mkKubernetesSingleNodeTest mkKubernetesMultiNodeTest;
+}
diff --git a/nixos/tests/kubernetes/certs.nix b/nixos/tests/kubernetes/certs.nix
new file mode 100644
index 000000000000..520c728b65ee
--- /dev/null
+++ b/nixos/tests/kubernetes/certs.nix
@@ -0,0 +1,219 @@
+{
+  pkgs ? import <nixpkgs> {},
+  internalDomain ? "cloud.yourdomain.net",
+  externalDomain ? "myawesomecluster.cluster.yourdomain.net",
+  serviceClusterIp ? "10.0.0.1",
+  kubelets
+}:
+let
+   runWithCFSSL = name: cmd:
+     let secrets = pkgs.runCommand "${name}-cfss.json" {
+         buildInputs = [ pkgs.cfssl pkgs.jq ];
+         outputs = [ "out" "cert" "key" "csr" ];
+       }
+       ''
+         (
+           echo "${cmd}"
+           cfssl ${cmd} > tmp
+           cat tmp | jq -r .key > $key
+           cat tmp | jq -r .cert > $cert
+           cat tmp | jq -r .csr > $csr
+
+           touch $out
+         ) 2>&1 | fold -w 80 -s
+       '';
+     in {
+       key = secrets.key;
+       cert = secrets.cert;
+       csr = secrets.csr;
+     };
+
+   writeCFSSL = content:
+     pkgs.runCommand content.name {
+      buildInputs = [ pkgs.cfssl pkgs.jq ];
+     } ''
+       mkdir -p $out
+       cd $out
+
+       json=${pkgs.lib.escapeShellArg (builtins.toJSON content)}
+
+       # for a given $field in the $json, treat the associated value as a
+       # file path and substitute the contents thereof into the $json
+       # object.
+       expandFileField() {
+         local field=$1
+         if jq -e --arg field "$field" 'has($field)'; then
+           local path="$(echo "$json" | jq -r ".$field")"
+           json="$(echo "$json" | jq --arg val "$(cat "$path")" ".$field = \$val")"
+         fi
+       }
+
+       expandFileField key
+       expandFileField ca
+       expandFileField cert
+
+       echo "$json" | cfssljson -bare ${content.name}
+     '';
+
+  noCSR = content: pkgs.lib.filterAttrs (n: v: n != "csr") content;
+  noKey = content: pkgs.lib.filterAttrs (n: v: n != "key") content;
+
+  writeFile = content:
+    if pkgs.lib.isDerivation content
+    then content
+    else pkgs.writeText "content" (builtins.toJSON content);
+
+  createServingCertKey = { ca, cn, hosts? [], size ? 2048, name ? cn }:
+    noCSR (
+      (runWithCFSSL name "gencert -ca=${writeFile ca.cert} -ca-key=${writeFile ca.key} -profile=server -config=${writeFile ca.config} ${writeFile {
+        CN = cn;
+        hosts = hosts;
+        key = { algo = "rsa"; inherit size; };
+      }}") // { inherit name; }
+    );
+
+  createClientCertKey = { ca, cn, groups ? [], size ? 2048, name ? cn }:
+    noCSR (
+      (runWithCFSSL name "gencert -ca=${writeFile ca.cert} -ca-key=${writeFile ca.key} -profile=client -config=${writeFile ca.config} ${writeFile {
+        CN = cn;
+        names = map (group: {O = group;}) groups;
+        hosts = [""];
+        key = { algo = "rsa"; inherit size; };
+      }}") // { inherit name; }
+    );
+
+  createSigningCertKey = { C ? "xx", ST ? "x", L ? "x", O ? "x", OU ? "x", CN ? "ca", emailAddress ? "x", expiry ? "43800h", size ? 2048, name ? CN }:
+    (noCSR (runWithCFSSL CN "genkey -initca ${writeFile {
+      key = { algo = "rsa"; inherit size; };
+      names = [{ inherit C ST L O OU CN emailAddress; }];
+    }}")) // {
+      inherit name;
+      config.signing = {
+        default.expiry = expiry;
+        profiles = {
+          server = {
+            inherit expiry;
+            usages = [
+              "signing"
+              "key encipherment"
+              "server auth"
+            ];
+          };
+          client = {
+            inherit expiry;
+            usages = [
+              "signing"
+              "key encipherment"
+              "client auth"
+            ];
+          };
+          peer = {
+            inherit expiry;
+            usages = [
+              "signing"
+              "key encipherment"
+              "server auth"
+              "client auth"
+            ];
+          };
+        };
+      };
+    };
+
+  ca = createSigningCertKey {};
+
+  kube-apiserver = createServingCertKey {
+    inherit ca;
+    cn = "kube-apiserver";
+    hosts = ["kubernetes.default" "kubernetes.default.svc" "localhost" "api.${externalDomain}" serviceClusterIp];
+  };
+
+  kubelet = createServingCertKey {
+    inherit ca;
+    cn = "kubelet";
+    hosts = ["*.${externalDomain}"];
+  };
+
+  service-accounts = createServingCertKey {
+    inherit ca;
+    cn = "kube-service-accounts";
+  };
+
+  etcd = createServingCertKey {
+    inherit ca;
+    cn = "etcd";
+    hosts = ["etcd.${externalDomain}"];
+  };
+
+  etcd-client = createClientCertKey {
+    inherit ca;
+    cn = "etcd-client";
+  };
+
+  kubelet-client = createClientCertKey {
+    inherit ca;
+    cn = "kubelet-client";
+    groups = ["system:masters"];
+  };
+
+  apiserver-client = {
+    kubelet = hostname: createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kubelet-${hostname}";
+      cn = "system:node:${hostname}.${externalDomain}";
+      groups = ["system:nodes"];
+    };
+
+    kube-proxy = createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kube-proxy";
+      cn = "system:kube-proxy";
+      groups = ["system:kube-proxy" "system:nodes"];
+    };
+
+    kube-controller-manager = createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kube-controller-manager";
+      cn = "system:kube-controller-manager";
+      groups = ["system:masters"];
+    };
+
+    kube-scheduler = createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kube-scheduler";
+      cn = "system:kube-scheduler";
+      groups = ["system:kube-scheduler"];
+    };
+
+    admin = createClientCertKey {
+      inherit ca;
+      cn = "admin";
+      groups = ["system:masters"];
+    };
+  };
+in {
+  master = pkgs.buildEnv {
+    name = "master-keys";
+    paths = [
+      (writeCFSSL (noKey ca))
+      (writeCFSSL kube-apiserver)
+      (writeCFSSL kubelet-client)
+      (writeCFSSL apiserver-client.kube-controller-manager)
+      (writeCFSSL apiserver-client.kube-scheduler)
+      (writeCFSSL service-accounts)
+      (writeCFSSL etcd)
+    ];
+  };
+
+  worker = pkgs.buildEnv {
+    name = "worker-keys";
+    paths = [
+      (writeCFSSL (noKey ca))
+      (writeCFSSL kubelet)
+      (writeCFSSL apiserver-client.kube-proxy)
+      (writeCFSSL etcd-client)
+    ] ++ map (hostname: writeCFSSL (apiserver-client.kubelet hostname)) kubelets;
+  };
+
+  admin = writeCFSSL apiserver-client.admin;
+}
diff --git a/nixos/tests/kubernetes/default.nix b/nixos/tests/kubernetes/default.nix
new file mode 100644
index 000000000000..a801759bf582
--- /dev/null
+++ b/nixos/tests/kubernetes/default.nix
@@ -0,0 +1,7 @@
+{ system ? builtins.currentSystem }:
+{
+  dns = import ./dns.nix { inherit system; };
+  # e2e = import ./e2e.nix { inherit system; };  # TODO: make it pass
+  # the following test(s) can be removed when e2e is working:
+  rbac = import ./rbac.nix { inherit system; };
+}
diff --git a/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix
new file mode 100644
index 000000000000..8c488d271bcd
--- /dev/null
+++ b/nixos/tests/kubernetes/dns.nix
@@ -0,0 +1,127 @@
+{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+  domain = "my.zyx";
+
+  certs = import ./certs.nix { externalDomain = domain; kubelets = [ "machine1" "machine2" ]; };
+
+  redisPod = pkgs.writeText "redis-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "redis";
+    metadata.labels.name = "redis";
+    spec.containers = [{
+      name = "redis";
+      image = "redis";
+      args = ["--bind" "0.0.0.0"];
+      imagePullPolicy = "Never";
+      ports = [{
+        name = "redis-server";
+        containerPort = 6379;
+      }];
+    }];
+  });
+
+  redisService = pkgs.writeText "redis-service.json" (builtins.toJSON {
+    kind = "Service";
+    apiVersion = "v1";
+    metadata.name = "redis";
+    spec = {
+      ports = [{port = 6379; targetPort = 6379;}];
+      selector = {name = "redis";};
+    };
+  });
+
+  redisImage = pkgs.dockerTools.buildImage {
+    name = "redis";
+    tag = "latest";
+    contents = [ pkgs.redis pkgs.bind.host ];
+    config.Entrypoint = "/bin/redis-server";
+  };
+
+  probePod = pkgs.writeText "probe-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "probe";
+    metadata.labels.name = "probe";
+    spec.containers = [{
+      name = "probe";
+      image = "probe";
+      args = [ "-f" ];
+      tty = true;
+      imagePullPolicy = "Never";
+    }];
+  });
+
+  probeImage = pkgs.dockerTools.buildImage {
+    name = "probe";
+    tag = "latest";
+    contents = [ pkgs.bind.host pkgs.busybox ];
+    config.Entrypoint = "/bin/tail";
+  };
+
+  extraConfiguration = { config, pkgs, lib, nodes, ... }: {
+    environment.systemPackages = [ pkgs.bind.host ];
+    # virtualisation.docker.extraOptions = "--dns=${config.services.kubernetes.addons.dns.clusterIp}";
+    services.dnsmasq.enable = true;
+    services.dnsmasq.servers = [
+      "/cluster.local/${config.services.kubernetes.addons.dns.clusterIp}#53"
+    ];
+  };
+
+  base = {
+    name = "dns";
+    inherit domain certs extraConfiguration;
+  };
+
+  singleNodeTest = {
+    test = ''
+      # prepare machine1 for test
+      $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready");
+      $machine1->execute("docker load < ${redisImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisService}");
+      $machine1->execute("docker load < ${probeImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${probePod}");
+
+      # check if pods are running
+      $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'kube-dns.*3/3'");
+
+      # check dns on host (dnsmasq)
+      $machine1->succeed("host redis.default.svc.cluster.local");
+
+      # check dns inside the container
+      $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local");
+    '';
+  };
+
+  multiNodeTest = {
+    test = ''
+      # prepare machines for test
+      $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready");
+      $machine1->waitUntilSucceeds("kubectl get node machine2.${domain} | grep -w Ready");
+      $machine2->execute("docker load < ${redisImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisService}");
+      $machine2->execute("docker load < ${probeImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${probePod}");
+
+      # check if pods are running
+      $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'kube-dns.*3/3'");
+
+      # check dns on hosts (dnsmasq)
+      $machine1->succeed("host redis.default.svc.cluster.local");
+      $machine2->succeed("host redis.default.svc.cluster.local");
+
+      # check dns inside the container
+      $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local");
+    '';
+  };
+in {
+  singlenode = mkKubernetesSingleNodeTest (base // singleNodeTest);
+  multinode = mkKubernetesMultiNodeTest (base // multiNodeTest);
+}
diff --git a/nixos/tests/kubernetes/e2e.nix b/nixos/tests/kubernetes/e2e.nix
new file mode 100644
index 000000000000..175d8413045e
--- /dev/null
+++ b/nixos/tests/kubernetes/e2e.nix
@@ -0,0 +1,40 @@
+{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+  domain = "my.zyx";
+  certs = import ./certs.nix { externalDomain = domain; kubelets = ["machine1" "machine2"]; };
+  kubeconfig = pkgs.writeText "kubeconfig.json" (builtins.toJSON {
+    apiVersion = "v1";
+    kind = "Config";
+    clusters = [{
+      name = "local";
+      cluster.certificate-authority = "${certs.master}/ca.pem";
+      cluster.server = "https://api.${domain}";
+    }];
+    users = [{
+      name = "kubelet";
+      user = {
+        client-certificate = "${certs.admin}/admin.pem";
+        client-key = "${certs.admin}/admin-key.pem";
+      };
+    }];
+    contexts = [{
+      context = {
+        cluster = "local";
+        user = "kubelet";
+      };
+      current-context = "kubelet-context";
+    }];
+  });
+
+  base = {
+    name = "e2e";
+    inherit domain certs;
+    test = ''
+      $machine1->succeed("e2e.test -kubeconfig ${kubeconfig} -provider local -ginkgo.focus '\\[Conformance\\]' -ginkgo.skip '\\[Flaky\\]|\\[Serial\\]'");
+    '';
+  };
+in {
+  singlenode = mkKubernetesSingleNodeTest base;
+  multinode = mkKubernetesMultiNodeTest base;
+}
diff --git a/nixos/tests/kubernetes/kubernetes-common.nix b/nixos/tests/kubernetes/kubernetes-common.nix
new file mode 100644
index 000000000000..125c176f1132
--- /dev/null
+++ b/nixos/tests/kubernetes/kubernetes-common.nix
@@ -0,0 +1,58 @@
+{ roles, config, pkgs, certs }:
+with pkgs.lib;
+let
+  base = {
+    inherit roles;
+    featureGates = ["AllAlpha"];
+    flannel.enable = true;
+    addons.dashboard.enable = true;
+
+    caFile = "${certs.master}/ca.pem";
+    apiserver = {
+      tlsCertFile = "${certs.master}/kube-apiserver.pem";
+      tlsKeyFile = "${certs.master}/kube-apiserver-key.pem";
+      kubeletClientCertFile = "${certs.master}/kubelet-client.pem";
+      kubeletClientKeyFile = "${certs.master}/kubelet-client-key.pem";
+      serviceAccountKeyFile = "${certs.master}/kube-service-accounts.pem";
+    };
+    etcd = {
+      servers = ["https://etcd.${config.networking.domain}:2379"];
+      certFile = "${certs.worker}/etcd-client.pem";
+      keyFile = "${certs.worker}/etcd-client-key.pem";
+    };
+    kubeconfig = {
+      server = "https://api.${config.networking.domain}";
+    };
+    kubelet = {
+      tlsCertFile = "${certs.worker}/kubelet.pem";
+      tlsKeyFile = "${certs.worker}/kubelet-key.pem";
+      hostname = "${config.networking.hostName}.${config.networking.domain}";
+      kubeconfig = {
+        certFile = "${certs.worker}/apiserver-client-kubelet-${config.networking.hostName}.pem";
+        keyFile = "${certs.worker}/apiserver-client-kubelet-${config.networking.hostName}-key.pem";
+      };
+    };
+    controllerManager = {
+      serviceAccountKeyFile = "${certs.master}/kube-service-accounts-key.pem";
+      kubeconfig = {
+        certFile = "${certs.master}/apiserver-client-kube-controller-manager.pem";
+        keyFile = "${certs.master}/apiserver-client-kube-controller-manager-key.pem";
+      };
+    };
+    scheduler = {
+      kubeconfig = {
+        certFile = "${certs.master}/apiserver-client-kube-scheduler.pem";
+        keyFile = "${certs.master}/apiserver-client-kube-scheduler-key.pem";
+      };
+    };
+    proxy = {
+      kubeconfig = {
+        certFile = "${certs.worker}/apiserver-client-kube-proxy.pem";
+        keyFile = "${certs.worker}//apiserver-client-kube-proxy-key.pem";
+      };
+    };
+  };
+
+in {
+  services.kubernetes = base;
+}
diff --git a/nixos/tests/kubernetes/rbac.nix b/nixos/tests/kubernetes/rbac.nix
new file mode 100644
index 000000000000..226808c4b263
--- /dev/null
+++ b/nixos/tests/kubernetes/rbac.nix
@@ -0,0 +1,137 @@
+{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+
+  roServiceAccount = pkgs.writeText "ro-service-account.json" (builtins.toJSON {
+    kind = "ServiceAccount";
+    apiVersion = "v1";
+    metadata = {
+      name = "read-only";
+      namespace = "default";
+    };
+  });
+
+  roRoleBinding = pkgs.writeText "ro-role-binding.json" (builtins.toJSON {
+    apiVersion = "rbac.authorization.k8s.io/v1";
+    kind = "RoleBinding";
+    metadata = {
+      name = "read-pods";
+      namespace = "default";
+    };
+    roleRef = {
+      apiGroup = "rbac.authorization.k8s.io";
+      kind = "Role";
+      name = "pod-reader";
+    };
+    subjects = [{
+      kind = "ServiceAccount";
+      name = "read-only";
+      namespace = "default";
+    }];
+  });
+
+  roRole = pkgs.writeText "ro-role.json" (builtins.toJSON {
+    apiVersion = "rbac.authorization.k8s.io/v1";
+    kind = "Role";
+    metadata = {
+      name = "pod-reader";
+      namespace = "default";
+    };
+    rules = [{
+      apiGroups = [""];
+      resources = ["pods"];
+      verbs = ["get" "list" "watch"];
+    }];
+  });
+
+  kubectlPod = pkgs.writeText "kubectl-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "kubectl";
+    metadata.namespace = "default";
+    metadata.labels.name = "kubectl";
+    spec.serviceAccountName = "read-only";
+    spec.containers = [{
+      name = "kubectl";
+      image = "kubectl:latest";
+      command = ["/bin/tail" "-f"];
+      imagePullPolicy = "Never";
+      tty = true;
+    }];
+  });
+
+  kubectlPod2 = pkgs.writeTextDir "kubectl-pod-2.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "kubectl-2";
+    metadata.namespace = "default";
+    metadata.labels.name = "kubectl-2";
+    spec.serviceAccountName = "read-only";
+    spec.containers = [{
+      name = "kubectl-2";
+      image = "kubectl:latest";
+      command = ["/bin/tail" "-f"];
+      imagePullPolicy = "Never";
+      tty = true;
+    }];
+  });
+
+  kubectl = pkgs.runCommand "copy-kubectl" { buildInputs = [ pkgs.kubernetes ]; } ''
+    mkdir -p $out/bin
+    cp ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl
+  '';
+
+  kubectlImage = pkgs.dockerTools.buildImage {
+    name = "kubectl";
+    tag = "latest";
+    contents = [ kubectl pkgs.busybox kubectlPod2 ];
+    config.Entrypoint = "/bin/sh";
+  };
+
+  base = {
+    name = "rbac";
+  };
+
+  singlenode = base // {
+    test = ''
+      $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready");
+
+      $machine1->execute("docker load < ${kubectlImage}");
+
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}");
+
+      $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running");
+
+      $machine1->succeed("kubectl exec -ti kubectl -- kubectl get pods");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl");
+    '';
+  };
+
+  multinode = base // {
+    test = ''
+      $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready");
+      $machine1->waitUntilSucceeds("kubectl get node machine2.my.zyx | grep -w Ready");
+
+      $machine2->execute("docker load < ${kubectlImage}");
+
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}");
+
+      $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running");
+
+      $machine1->succeed("kubectl exec -ti kubectl -- kubectl get pods");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl");
+    '';
+  };
+
+in {
+  singlenode = mkKubernetesSingleNodeTest singlenode;
+  multinode = mkKubernetesMultiNodeTest multinode;
+}
diff --git a/nixos/tests/ldap.nix b/nixos/tests/ldap.nix
new file mode 100644
index 000000000000..b39f4124c958
--- /dev/null
+++ b/nixos/tests/ldap.nix
@@ -0,0 +1,119 @@
+import ./make-test.nix ({ pkgs, lib, ...} :
+
+let
+
+  dbSuffix = "dc=example,dc=com";
+  dbPath = "/var/db/openldap";
+  dbAdminDn = "cn=admin,${dbSuffix}";
+  dbAdminPwd = "test";
+  serverUri = "ldap:///";
+  ldapUser = "test-ldap-user";
+  ldapUserId = 10000;
+  ldapUserPwd = "test";
+  ldapGroup = "test-ldap-group";
+  ldapGroupId = 10000;
+  setupLdif = pkgs.writeText "test-ldap.ldif" ''
+    dn: ${dbSuffix}
+    dc: ${with lib; let dc = head (splitString "," dbSuffix); dcName = head (tail (splitString "=" dc)); in dcName}
+    o: ${dbSuffix}
+    objectclass: top
+    objectclass: dcObject
+    objectclass: organization
+
+    dn: cn=${ldapUser},${dbSuffix}
+    sn: ${ldapUser}
+    objectClass: person
+    objectClass: posixAccount
+    uid: ${ldapUser}
+    uidNumber: ${toString ldapUserId}
+    gidNumber: ${toString ldapGroupId}
+    homeDirectory: /home/${ldapUser}
+    loginShell: /bin/sh
+    userPassword: ${ldapUserPwd}
+
+    dn: cn=${ldapGroup},${dbSuffix}
+    objectClass: posixGroup
+    gidNumber: ${toString ldapGroupId}
+    memberUid: ${ldapUser}
+  '';
+  mkClient = useDaemon:
+    { config, pkgs, lib, ... }:
+    {
+      virtualisation.memorySize = 256;
+      virtualisation.vlans = [ 1 ];
+      security.pam.services.su.rootOK = lib.mkForce false;
+      users.ldap.enable = true;
+      users.ldap.daemon.enable = useDaemon;
+      users.ldap.loginPam = true;
+      users.ldap.nsswitch = true;
+      users.ldap.server = "ldap://server";
+      users.ldap.base = "${dbSuffix}";
+    };
+
+in
+
+{
+  name = "ldap";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ montag451 ];
+  };
+
+  nodes = {
+
+    server =
+      { config, pkgs, lib, ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+        networking.firewall.allowedTCPPorts = [ 389 ];
+        services.openldap.enable = true;
+        services.openldap.dataDir = dbPath;
+        services.openldap.urlList = [
+          serverUri
+        ];
+        services.openldap.extraConfig = ''
+          include ${pkgs.openldap.out}/etc/schema/core.schema
+          include ${pkgs.openldap.out}/etc/schema/cosine.schema
+          include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
+          include ${pkgs.openldap.out}/etc/schema/nis.schema
+
+          database mdb
+          suffix ${dbSuffix}
+          rootdn ${dbAdminDn}
+          rootpw ${dbAdminPwd}
+          directory ${dbPath}
+        '';
+      };
+
+    client1 = mkClient true; # use nss_pam_ldapd
+    client2 = mkClient false; # use nss_ldap and pam_ldap
+
+  };
+
+  testScript = ''
+    startAll;
+    $server->waitForUnit("default.target");
+    $client1->waitForUnit("default.target");
+    $client2->waitForUnit("default.target");
+
+    $server->succeed("ldapadd -D '${dbAdminDn}' -w ${dbAdminPwd} -H ${serverUri} -f '${setupLdif}'");
+
+    # NSS tests
+    subtest "nss", sub {
+        $client1->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}");
+        $client1->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'");
+        $client1->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}");
+        $client1->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'");
+        $client2->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}");
+        $client2->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'");
+        $client2->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}");
+        $client2->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'");
+    };
+
+    # PAM tests
+    subtest "pam", sub {
+        $client1->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
+        $client2->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
+    };
+  '';
+})
diff --git a/nixos/tests/leaps.nix b/nixos/tests/leaps.nix
new file mode 100644
index 000000000000..6163fed56b6f
--- /dev/null
+++ b/nixos/tests/leaps.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs,  ... }:
+
+{
+  name = "leaps";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ qknight ];
+  };
+
+  nodes =
+    { 
+      client = { };
+
+      server =
+        { services.leaps = {
+            enable = true;
+            port = 6666;
+            path = "/leaps/";
+          };
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    ''
+      startAll;
+      $server->waitForOpenPort(6666);
+      $client->waitForUnit("network.target");
+      $client->succeed("${pkgs.curl}/bin/curl http://server:6666/leaps/ | grep -i 'leaps'");
+    '';
+})
diff --git a/nixos/tests/lightdm.nix b/nixos/tests/lightdm.nix
index f30f9062dcde..97ec79406b88 100644
--- a/nixos/tests/lightdm.nix
+++ b/nixos/tests/lightdm.nix
@@ -22,6 +22,8 @@ import ./make-test.nix ({ pkgs, ...} : {
     $machine->waitForText(qr/${user.description}/);
     $machine->screenshot("lightdm");
     $machine->sendChars("${user.password}\n");
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
     $machine->waitForWindow("^IceWM ");
   '';
 })
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
index e793d89567bf..a6a460fb0a7d 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -33,10 +33,11 @@ import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
 
       # Log in as alice on a virtual console.
       subtest "virtual console login", sub {
-          $machine->sleep(2); # urgh: wait for username prompt
+          $machine->waitUntilTTYMatches(2, "login: ");
           $machine->sendChars("alice\n");
+          $machine->waitUntilTTYMatches(2, "login: alice");
           $machine->waitUntilSucceeds("pgrep login");
-          $machine->sleep(2); # urgh: wait for `Password:'
+          $machine->waitUntilTTYMatches(2, "Password: ");
           $machine->sendChars("foobar\n");
           $machine->waitUntilSucceeds("pgrep -u alice bash");
           $machine->sendChars("touch done\n");
diff --git a/nixos/tests/logstash.nix b/nixos/tests/logstash.nix
deleted file mode 100644
index edece352cafe..000000000000
--- a/nixos/tests/logstash.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-# This test runs logstash and checks if messages flows and
-# elasticsearch is started.
-
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "logstash";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ eelco chaoflow offline ];
-  };
-
-  nodes = {
-    one =
-      { config, pkgs, ... }:
-        {
-          services = {
-            logstash = {
-              enable = true;
-              inputConfig = ''
-                exec { command => "echo flowers" interval => 1 type => "test" }
-                exec { command => "echo dragons" interval => 1 type => "test" }
-              '';
-              filterConfig = ''
-                if [message] =~ /dragons/ {
-                  drop {}
-                }
-              '';
-              outputConfig = ''
-                stdout { codec => rubydebug }
-                elasticsearch { embedded => true }
-              '';
-            };
-          };
-        };
-    };
-
-  testScript = ''
-    startAll;
-
-    $one->waitForUnit("logstash.service");
-    $one->waitUntilSucceeds("journalctl -n 20 _SYSTEMD_UNIT=logstash.service | grep flowers");
-    $one->fail("journalctl -n 20 _SYSTEMD_UNIT=logstash.service | grep dragons");
-    $one->waitUntilSucceeds("curl -s http://127.0.0.1:9200/_status?pretty=true | grep logstash");
-  '';
-})
diff --git a/nixos/tests/make-test.nix b/nixos/tests/make-test.nix
index f3e26aa7e74d..ee4ba310ad50 100644
--- a/nixos/tests/make-test.nix
+++ b/nixos/tests/make-test.nix
@@ -2,4 +2,4 @@ f: { system ? builtins.currentSystem, ... } @ args:
 
 with import ../lib/testing.nix { inherit system; };
 
-makeTest (if builtins.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
+makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
diff --git a/nixos/tests/mathics.nix b/nixos/tests/mathics.nix
new file mode 100644
index 000000000000..310b751b4d84
--- /dev/null
+++ b/nixos/tests/mathics.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "mathics";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ benley ];
+  };
+
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      services.mathics.enable = true;
+      services.mathics.port = 8888;
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $machine->waitForUnit("mathics.service");
+    $machine->waitForOpenPort(8888);
+    $machine->succeed("curl http://localhost:8888/");
+  '';
+})
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
new file mode 100644
index 000000000000..113fb622588b
--- /dev/null
+++ b/nixos/tests/matrix-synapse.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs, ... } : {
+
+  name = "matrix-synapse";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ corngood ];
+  };
+
+  nodes = {
+    server_postgres = args: {
+      services.matrix-synapse.enable = true;
+      services.matrix-synapse.database_type = "psycopg2";
+    };
+
+    server_sqlite = args: {
+      services.matrix-synapse.enable = true;
+      services.matrix-synapse.database_type = "sqlite3";
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $server_postgres->waitForUnit("matrix-synapse.service");
+    $server_postgres->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $server_postgres->requireActiveUnit("postgresql.service");
+    $server_sqlite->waitForUnit("matrix-synapse.service");
+    $server_sqlite->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $server_sqlite->mustSucceed("[ -e /var/lib/matrix-synapse/homeserver.db ]");
+  '';
+
+})
diff --git a/nixos/tests/memcached.nix b/nixos/tests/memcached.nix
new file mode 100644
index 000000000000..f9ef3647bd1a
--- /dev/null
+++ b/nixos/tests/memcached.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "memcached";
+
+  nodes = {
+    machine =
+      { config, pkgs, ... }:
+      {
+        imports = [ ../modules/profiles/minimal.nix ];
+        services.memcached.enable = true;
+      };
+  };
+
+  testScript = let
+    testScript = pkgs.writeScript "testScript.py" ''
+      #!${pkgs.python3.withPackages (p: [p.memcached])}/bin/python
+
+      import memcache
+      c = memcache.Client(['localhost:11211'])
+      c.set('key', 'value')
+      assert 'value' == c.get('key')
+    '';
+  in ''
+    startAll;
+    $machine->waitForUnit("memcached.service");
+    $machine->waitForOpenPort("11211");
+    $machine->succeed("${testScript}");
+  '';
+})
diff --git a/nixos/tests/mesos.nix b/nixos/tests/mesos.nix
index 3610603aeba2..007d7ac21603 100644
--- a/nixos/tests/mesos.nix
+++ b/nixos/tests/mesos.nix
@@ -1,32 +1,91 @@
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "simple";
+import ./make-test.nix ({ pkgs, ...} : rec {
+  name = "mesos";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline ];
+    maintainers = [ offline kamilchm cstrahan ];
   };
 
-  machine = { config, pkgs, ... }: {
-    services.zookeeper.enable = true;
-    virtualisation.docker.enable = true;
-    services.mesos = {
-      slave = {
-        enable = true;
-        master = "zk://localhost:2181/mesos";
-        attributes = {
-          tag1 = "foo";
-          tag2 = "bar";
-        };
+  nodes = {
+    master = { config, pkgs, ... }: {
+      networking.firewall.enable = false;
+      services.zookeeper.enable = true;
+      services.mesos.master = {
+          enable = true;
+          zk = "zk://master:2181/mesos";
       };
-      master = {
-        enable = true;
-        zk = "zk://localhost:2181/mesos";
+    };
+
+    slave = { config, pkgs, ... }: {
+      networking.firewall.enable = false;
+      networking.nat.enable = true;
+      virtualisation.docker.enable = true;
+      services.mesos = {
+        slave = {
+          enable = true;
+          master = "master:5050";
+          dockerRegistry = registry;
+          executorEnvironmentVariables = {
+            PATH = "/run/current-system/sw/bin";
+          };
+        };
       };
     };
   };
 
+  simpleDocker = pkgs.dockerTools.buildImage {
+    name = "echo";
+    contents = [ pkgs.stdenv.shellPackage pkgs.coreutils ];
+    config = {
+      Env = [
+        # When shell=true, mesos invokes "sh -c '<cmd>'", so make sure "sh" is
+        # on the PATH.
+        "PATH=${pkgs.stdenv.shellPackage}/bin:${pkgs.coreutils}/bin"
+      ];
+      Entrypoint = [ "echo" ];
+    };
+  };
+
+  registry = pkgs.runCommand "registry" { } ''
+    mkdir -p $out
+    cp ${simpleDocker} $out/echo:latest.tar
+  '';
+
+  testFramework = pkgs.pythonPackages.buildPythonPackage {
+    name = "mesos-tests";
+    propagatedBuildInputs = [ pkgs.mesos ];
+    catchConflicts = false;
+    src = ./mesos_test.py;
+    phases = [ "installPhase" "fixupPhase" ];
+    installPhase = ''
+      install -Dvm 0755 $src $out/bin/mesos_test.py
+
+      echo "done" > test.result
+      tar czf $out/test.tar.gz test.result
+    '';
+  };
+
   testScript =
     ''
       startAll;
-      $machine->waitForUnit("mesos-master.service");
-      $machine->waitForUnit("mesos-slave.service");
+      $master->waitForUnit("zookeeper.service");
+      $master->waitForUnit("mesos-master.service");
+      $slave->waitForUnit("docker.service");
+      $slave->waitForUnit("mesos-slave.service");
+      $master->waitForOpenPort(2181);
+      $master->waitForOpenPort(5050);
+      $slave->waitForOpenPort(5051);
+
+      # is slave registered?
+      $master->waitUntilSucceeds("curl -s --fail http://master:5050/master/slaves".
+                                 " | grep -q \"\\\"hostname\\\":\\\"slave\\\"\"");
+
+      # try to run docker image
+      $master->succeed("${pkgs.mesos}/bin/mesos-execute --master=master:5050".
+                       " --resources=\"cpus:0.1;mem:32\" --name=simple-docker".
+                       " --containerizer=mesos --docker_image=echo:latest".
+                       " --shell=true --command=\"echo done\" | grep -q TASK_FINISHED");
+
+      # simple command with .tar.gz uri
+      $master->succeed("${testFramework}/bin/mesos_test.py master ".
+                       "${testFramework}/test.tar.gz");
     '';
 })
diff --git a/nixos/tests/mesos_test.py b/nixos/tests/mesos_test.py
new file mode 100644
index 000000000000..be8bb32e49a7
--- /dev/null
+++ b/nixos/tests/mesos_test.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+import uuid
+import time
+import subprocess
+import os
+
+import sys
+
+from mesos.interface import Scheduler
+from mesos.native import MesosSchedulerDriver
+from mesos.interface import mesos_pb2
+
+def log(msg):
+    process = subprocess.Popen("systemd-cat", stdin=subprocess.PIPE)
+    (out,err) = process.communicate(msg)
+
+class NixosTestScheduler(Scheduler):
+    def __init__(self):
+        self.master_ip = sys.argv[1]
+        self.download_uri = sys.argv[2]
+
+    def resourceOffers(self, driver, offers):
+        log("XXX got resource offer")
+
+        offer = offers[0]
+        task = self.new_task(offer)
+        uri = task.command.uris.add()
+        uri.value = self.download_uri
+        task.command.value = "cat test.result"
+        driver.launchTasks(offer.id, [task])
+
+    def statusUpdate(self, driver, update):
+        log("XXX status update")
+        if update.state == mesos_pb2.TASK_FAILED:
+            log("XXX test task failed with message: " + update.message)
+            driver.stop()
+            sys.exit(1)
+        elif update.state == mesos_pb2.TASK_FINISHED:
+            driver.stop()
+            sys.exit(0)
+
+    def new_task(self, offer):
+        task = mesos_pb2.TaskInfo()
+        id = uuid.uuid4()
+        task.task_id.value = str(id)
+        task.slave_id.value = offer.slave_id.value
+        task.name = "task {}".format(str(id))
+
+        cpus = task.resources.add()
+        cpus.name = "cpus"
+        cpus.type = mesos_pb2.Value.SCALAR
+        cpus.scalar.value = 0.1
+
+        mem = task.resources.add()
+        mem.name = "mem"
+        mem.type = mesos_pb2.Value.SCALAR
+        mem.scalar.value = 32
+
+        return task
+
+if __name__ == '__main__':
+    log("XXX framework started")
+
+    framework = mesos_pb2.FrameworkInfo()
+    framework.user = "root"
+    framework.name = "nixos-test-framework"
+    driver = MesosSchedulerDriver(
+        NixosTestScheduler(),
+        framework,
+        sys.argv[1] + ":5050"
+    )
+    driver.run()
diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix
new file mode 100644
index 000000000000..07a292a9baa5
--- /dev/null
+++ b/nixos/tests/minio.nix
@@ -0,0 +1,34 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "minio";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bachp ];
+  };
+
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      services.minio = {
+        enable = true;
+        accessKey = "BKIKJAA5BMMU2RHO6IBB";
+        secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+      };
+      environment.systemPackages = [ pkgs.minio-client ];
+
+      # Minio requires at least 1GiB of free disk space to run.
+      virtualisation.diskSize = 4 * 1024;
+    };
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("minio.service");
+      $machine->waitForOpenPort(9000);
+
+      # Create a test bucket on the server
+      $machine->succeed("mc config host add minio http://localhost:9000 BKIKJAA5BMMU2RHO6IBB V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12 S3v4");
+      $machine->succeed("mc mb minio/test-bucket");
+      $machine->succeed("mc ls minio") =~ /test-bucket/ or die;
+      $machine->shutdown;
+
+    '';
+})
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index ecec89226d66..179c95e76436 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -1,11 +1,13 @@
 # Miscellaneous small tests that don't warrant their own VM run.
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test.nix ({ pkgs, ...} : rec {
   name = "misc";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ eelco chaoflow ];
   };
 
+  foo = pkgs.writeText "foo" "Hello World";
+
   machine =
     { config, lib, pkgs, ... }:
     with lib;
@@ -16,23 +18,34 @@ import ./make-test.nix ({ pkgs, ...} : {
       systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
       fileSystems = mkVMOverride { "/tmp2" =
         { fsType = "tmpfs";
-          options = "mode=1777,noauto";
+          options = [ "mode=1777" "noauto" ];
         };
       };
       systemd.automounts = singleton
         { wantedBy = [ "multi-user.target" ];
           where = "/tmp2";
         };
+      users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      security.sudo = { enable = true; wheelNeedsPassword = false; };
+      boot.kernel.sysctl."vm.swappiness" = 1;
+      boot.kernelParams = [ "vsyscall=emulate" ];
+      system.extraDependencies = [ foo ];
     };
 
   testScript =
     ''
+      subtest "nix-db", sub {
+          my $json = $machine->succeed("nix path-info --json ${foo}");
+          $json =~ /"narHash":"sha256:0afw0d9j1hvwiz066z93jiddc33nxg6i6qyp26vnqyglpyfivlq5"/ or die "narHash not set";
+          $json =~ /"narSize":128/ or die "narSize not set";
+      };
+
       subtest "nixos-version", sub {
           $machine->succeed("[ `nixos-version | wc -w` = 2 ]");
       };
 
       subtest "nixos-rebuild", sub {
-          $machine->succeed("nixos-rebuild --help | grep SYNOPSIS");
+          $machine->succeed("nixos-rebuild --help | grep 'NixOS module' ");
       };
 
       # Sanity check for uid/gid assignment.
@@ -80,9 +93,10 @@ import ./make-test.nix ({ pkgs, ...} : {
       };
 
       # Test whether systemd-udevd automatically loads modules for our hardware.
+      $machine->succeed("systemctl start systemd-udev-settle.service");
       subtest "udev-auto-load", sub {
           $machine->waitForUnit('systemd-udev-settle.service');
-          $machine->succeed('lsmod | grep psmouse');
+          $machine->succeed('lsmod | grep mousedev');
       };
 
       # Test whether systemd-tmpfiles-clean works.
@@ -109,5 +123,18 @@ import ./make-test.nix ({ pkgs, ...} : {
       subtest "nix-db", sub {
           $machine->succeed("nix-store -qR /run/current-system | grep nixos-");
       };
+
+      # 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/mongodb.nix b/nixos/tests/mongodb.nix
new file mode 100644
index 000000000000..18535f51af9b
--- /dev/null
+++ b/nixos/tests/mongodb.nix
@@ -0,0 +1,34 @@
+# This test start mongodb, runs a query using mongo shell
+
+import ./make-test.nix ({ pkgs, ...} : let
+  testQuery = pkgs.writeScript "nixtest.js" ''
+    db.greetings.insert({ "greeting": "hello" });
+    print(db.greetings.findOne().greeting);
+  '';
+in {
+  name = "mongodb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bluescreen303 offline wkennington cstrahan rvl ];
+  };
+
+  nodes = {
+    one =
+      { config, pkgs, ... }:
+        {
+          services = {
+           mongodb.enable = true;
+           mongodb.extraConfig = ''
+             # Allow starting engine with only a small virtual disk
+             storage.journal.enabled: false
+             storage.mmapv1.smallFiles: true
+           '';
+          };
+        };
+    };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("mongodb.service");
+    $one->succeed("mongo nixtest ${testQuery}") =~ /hello/ or die;
+  '';
+})
diff --git a/nixos/tests/morty.nix b/nixos/tests/morty.nix
new file mode 100644
index 000000000000..0a5324259ada
--- /dev/null
+++ b/nixos/tests/morty.nix
@@ -0,0 +1,32 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "morty";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ leenaars ];
+  };
+
+  nodes =
+    { mortyProxyWithKey =
+
+      { config, pkgs, ... }:
+      { services.morty = {
+        enable = true;
+	key = "78a9cd0cfee20c672f78427efb2a2a96036027f0";
+	port = 3001;
+	};
+      };
+
+    };
+
+  testScript =
+    { nodes , ... }:
+    ''
+      $mortyProxyWithKey->waitForUnit("default.target");
+
+      $mortyProxyWithKey->waitForOpenPort(3001);
+      $mortyProxyWithKey->succeed("curl -L 127.0.0.1:3001 | grep MortyProxy");
+
+    '';
+
+})
diff --git a/nixos/tests/mpich.nix b/nixos/tests/mpich.nix
deleted file mode 100644
index a28e41deb31e..000000000000
--- a/nixos/tests/mpich.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-# Simple example to showcase distributed tests using NixOS VMs.
-
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "mpich";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ eelco chaoflow ];
-  };
-
-  nodes = {
-    master =
-      { config, pkgs, ... }: {
-        environment.systemPackages = [ gcc mpich2 ];
-        #boot.kernelPackages = pkgs.kernelPackages_2_6_29;
-      };
-
-    slave =
-      { config, pkgs, ... }: {
-        environment.systemPackages = [ gcc mpich2 ];
-      };
-  };
-
-  # Start master/slave MPI daemons and compile/run a program that uses both
-  # nodes.
-  testScript =
-    ''
-       startAll;
-
-       $master->succeed("echo 'MPD_SECRETWORD=secret' > /etc/mpd.conf");
-       $master->succeed("chmod 600 /etc/mpd.conf");
-       $master->succeed("mpd --daemon --ifhn=master --listenport=4444");
-
-       $slave->succeed("echo 'MPD_SECRETWORD=secret' > /etc/mpd.conf");
-       $slave->succeed("chmod 600 /etc/mpd.conf");
-       $slave->succeed("mpd --daemon --host=master --port=4444");
-
-       $master->succeed("mpicc -o example -Wall ${./mpich-example.c}");
-       $slave->succeed("mpicc -o example -Wall ${./mpich-example.c}");
-
-       $master->succeed("mpiexec -n 2 ./example >&2");
-    '';
-})
diff --git a/nixos/tests/mumble.nix b/nixos/tests/mumble.nix
index 35f440026997..7959b85a0cf0 100644
--- a/nixos/tests/mumble.nix
+++ b/nixos/tests/mumble.nix
@@ -36,18 +36,29 @@ in
     # cancel client audio configuration
     $client1->waitForWindow(qr/Audio Tuning Wizard/);
     $client2->waitForWindow(qr/Audio Tuning Wizard/);
+    $server->sleep(5); # wait because mumble is slow to register event handlers
     $client1->sendKeys("esc");
     $client2->sendKeys("esc");
 
     # cancel client cert configuration
     $client1->waitForWindow(qr/Certificate Management/);
     $client2->waitForWindow(qr/Certificate Management/);
+    $server->sleep(5); # wait because mumble is slow to register event handlers
     $client1->sendKeys("esc");
     $client2->sendKeys("esc");
 
     # accept server certificate
     $client1->waitForWindow(qr/^Mumble$/);
     $client2->waitForWindow(qr/^Mumble$/);
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+    $client1->sendChars("y");
+    $client2->sendChars("y");
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+
+    # sometimes the wrong of the 2 windows is focused, we switch focus and try pressing "y" again
+    $client1->sendKeys("alt-tab");
+    $client2->sendKeys("alt-tab");
+    $server->sleep(5); # wait because mumble is slow to register event handlers
     $client1->sendChars("y");
     $client2->sendChars("y");
 
diff --git a/nixos/tests/munin.nix b/nixos/tests/munin.nix
index 16015d335523..40fafc625146 100644
--- a/nixos/tests/munin.nix
+++ b/nixos/tests/munin.nix
@@ -4,7 +4,7 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "munin";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric eelco chaoflow ];
+    maintainers = [ domenkozar eelco chaoflow ];
   };
 
   nodes = {
@@ -29,6 +29,7 @@ import ./make-test.nix ({ pkgs, ...} : {
     startAll;
 
     $one->waitForUnit("munin-node.service");
+    $one->succeed('systemctl start munin-cron');
     $one->waitForFile("/var/lib/munin/one/one-uptime-uptime-g.rrd");
     $one->waitForFile("/var/www/munin/one/index.html");
   '';
diff --git a/nixos/tests/mutable-users.nix b/nixos/tests/mutable-users.nix
new file mode 100644
index 000000000000..4f11a4b83669
--- /dev/null
+++ b/nixos/tests/mutable-users.nix
@@ -0,0 +1,39 @@
+# Mutable users tests.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "mutable-users";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ gleber ];
+  };
+
+  nodes = {
+    machine = { config, lib, pkgs, ... }: {
+      users.mutableUsers = false;
+    };
+    mutable = { config, lib, pkgs, ... }: {
+      users.mutableUsers = true;
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    immutableSystem = nodes.machine.config.system.build.toplevel;
+    mutableSystem = nodes.mutable.config.system.build.toplevel;
+  in ''
+    $machine->start();
+    $machine->waitForUnit("default.target");
+
+    # Machine starts in immutable mode. Add a user and test if reactivating
+    # configuration removes the user.
+    $machine->fail("cat /etc/passwd | grep ^foobar:");
+    $machine->succeed("sudo useradd foobar");
+    $machine->succeed("cat /etc/passwd | grep ^foobar:");
+    $machine->succeed("${immutableSystem}/bin/switch-to-configuration test");
+    $machine->fail("cat /etc/passwd | grep ^foobar:");
+
+    # In immutable mode passwd is not wrapped, while in mutable mode it is
+    # wrapped.
+    $machine->succeed('which passwd | grep /run/current-system/');
+    $machine->succeed("${mutableSystem}/bin/switch-to-configuration test");
+    $machine->succeed('which passwd | grep /run/wrappers/');
+  '';
+})
diff --git a/nixos/tests/mysql-backup.nix b/nixos/tests/mysql-backup.nix
new file mode 100644
index 000000000000..ff3650988836
--- /dev/null
+++ b/nixos/tests/mysql-backup.nix
@@ -0,0 +1,50 @@
+# Test whether mysqlBackup option works
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "mysql-backup";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ rvl ];
+  };
+
+  nodes = {
+    master = { config, pkgs, ... }: {
+      services.mysql = {
+        enable = true;
+        initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+        package = pkgs.mysql;
+      };
+
+      services.mysqlBackup = {
+        enable = true;
+        databases = [ "doesnotexist" "testdb" ];
+      };
+    };
+  };
+
+  testScript =
+    '' startAll;
+
+       # Delete backup file that may be left over from a previous test run.
+       # This is not needed on Hydra but useful for repeated local test runs.
+       $master->execute("rm -f /var/backup/mysql/testdb.gz");
+
+       # Need to have mysql started so that it can be populated with data.
+       $master->waitForUnit("mysql.service");
+
+       # Wait for testdb to be fully populated (5 rows).
+       $master->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+
+       # Do a backup and wait for it to start
+       $master->startJob("mysql-backup.service");
+       $master->waitForJob("mysql-backup.service");
+
+       # wait for backup to fail, because of database 'doesnotexist'
+       $master->waitUntilFails("systemctl is-active -q mysql-backup.service");
+
+       # wait for backup file and check that data appears in backup
+       $master->waitForFile("/var/backup/mysql/testdb.gz");
+       $master->succeed("${pkgs.gzip}/bin/zcat /var/backup/mysql/testdb.gz | grep hello");
+
+       # Check that a failed backup is logged
+       $master->succeed("journalctl -u mysql-backup.service | grep 'fail.*doesnotexist' > /dev/null");
+    '';
+})
diff --git a/nixos/tests/mysql-replication.nix b/nixos/tests/mysql-replication.nix
index 0cd8c4484bd7..ed09ac10b75d 100644
--- a/nixos/tests/mysql-replication.nix
+++ b/nixos/tests/mysql-replication.nix
@@ -19,12 +19,10 @@ in
         services.mysql.enable = true;
         services.mysql.package = pkgs.mysql;
         services.mysql.replication.role = "master";
+        services.mysql.replication.slaveHost = "%";
+        services.mysql.replication.masterUser = replicateUser;
+        services.mysql.replication.masterPassword = replicatePassword;
         services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
-        services.mysql.initialScript = pkgs.writeText "initmysql"
-          ''
-            create user '${replicateUser}'@'%' identified by '${replicatePassword}';
-            grant replication slave on *.* to '${replicateUser}'@'%';
-          '';
         networking.firewall.allowedTCPPorts = [ 3306 ];
       };
 
@@ -56,12 +54,28 @@ in
   };
 
   testScript = ''
-    startAll;
-
-    $master->waitForUnit("mysql");
+    $master->start;
     $master->waitForUnit("mysql");
+    $master->waitForOpenPort(3306);
+    # Wait for testdb to be fully populated (5 rows).
+    $master->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+
+    $slave1->start;
+    $slave2->start;
+    $slave1->waitForUnit("mysql");
+    $slave1->waitForOpenPort(3306);
+    $slave2->waitForUnit("mysql");
+    $slave2->waitForOpenPort(3306);
+
+    # wait for replications to finish
+    $slave1->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+    $slave2->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+
+    $slave2->succeed("systemctl stop mysql");
+    $master->succeed("echo 'insert into testdb.tests values (123, 456);' | mysql -u root -N");
+    $slave2->succeed("systemctl start mysql");
     $slave2->waitForUnit("mysql");
-    $slave2->sleep(100); # Hopefully this is long enough!!
-    $slave2->succeed("echo 'use testdb; select * from tests' | mysql -u root -N | grep 4");
+    $slave2->waitForOpenPort(3306);
+    $slave2->waitUntilSucceeds("echo 'select * from testdb.tests where Id = 123;' | mysql -u root -N | grep 456");
   '';
 })
diff --git a/nixos/tests/mysql.nix b/nixos/tests/mysql.nix
index 588411617776..c18fee6c7495 100644
--- a/nixos/tests/mysql.nix
+++ b/nixos/tests/mysql.nix
@@ -10,7 +10,6 @@ import ./make-test.nix ({ pkgs, ...} : {
 
       {
         services.mysql.enable = true;
-        services.mysql.replication.role = "master";
         services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
         services.mysql.package = pkgs.mysql;
       };
@@ -20,7 +19,6 @@ import ./make-test.nix ({ pkgs, ...} : {
     startAll;
 
     $master->waitForUnit("mysql");
-    $master->sleep(10); # Hopefully this is long enough!!
     $master->succeed("echo 'use testdb; select * from tests' | mysql -u root -N | grep 4");
   '';
 })
diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix
index 4fbf64462682..7057158a829b 100644
--- a/nixos/tests/nat.nix
+++ b/nixos/tests/nat.nix
@@ -3,34 +3,57 @@
 # client on the inside network, a server on the outside network, and a
 # router connected to both that performs Network Address Translation
 # for the client.
-import ./make-test.nix ({ pkgs, withFirewall, ... }:
+import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, ... }:
   let
     unit = if withFirewall then "firewall" else "nat";
+
+    routerBase =
+      lib.mkMerge [
+        { virtualisation.vlans = [ 2 1 ];
+          networking.firewall.enable = withFirewall;
+          networking.firewall.allowPing = true;
+          networking.nat.internalIPs = [ "192.168.1.0/24" ];
+          networking.nat.externalInterface = "eth1";
+        }
+        (lib.optionalAttrs withConntrackHelpers {
+          networking.firewall.connectionTrackingModules = [ "ftp" ];
+          networking.firewall.autoLoadConntrackHelpers = true;
+        })
+      ];
   in
   {
-    name = "nat${if withFirewall then "WithFirewall" else "Standalone"}";
-  meta = with pkgs.stdenv.lib.maintainers; {
+    name = "nat" + (if withFirewall then "WithFirewall" else "Standalone")
+                 + (lib.optionalString withConntrackHelpers "withConntrackHelpers");
+    meta = with pkgs.stdenv.lib.maintainers; {
       maintainers = [ eelco chaoflow rob wkennington ];
     };
 
     nodes =
       { client =
           { config, pkgs, nodes, ... }:
-          { virtualisation.vlans = [ 1 ];
-            networking.firewall.allowPing = true;
-            networking.defaultGateway =
-              (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ip4).address;
-          };
+          lib.mkMerge [
+            { virtualisation.vlans = [ 1 ];
+              networking.firewall.allowPing = true;
+              networking.defaultGateway =
+                (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+            }
+            (lib.optionalAttrs withConntrackHelpers {
+              networking.firewall.connectionTrackingModules = [ "ftp" ];
+              networking.firewall.autoLoadConntrackHelpers = true;
+            })
+          ];
 
         router =
-          { config, pkgs, ... }:
-          { virtualisation.vlans = [ 2 1 ];
-            networking.firewall.enable = withFirewall;
-            networking.firewall.allowPing = true;
-            networking.nat.enable = true;
-            networking.nat.internalIPs = [ "192.168.1.0/24" ];
-            networking.nat.externalInterface = "eth1";
-          };
+        { config, pkgs, ... }: lib.mkMerge [
+          routerBase
+          { networking.nat.enable = true; }
+        ];
+
+        routerDummyNoNat =
+        { config, pkgs, ... }: lib.mkMerge [
+          routerBase
+          { networking.nat.enable = false; }
+        ];
 
         server =
           { config, pkgs, ... }:
@@ -44,9 +67,13 @@ import ./make-test.nix ({ pkgs, withFirewall, ... }:
       };
 
     testScript =
-      { nodes, ... }:
-      ''
-        startAll;
+      { nodes, ... }: let
+        routerDummyNoNatClosure = nodes.routerDummyNoNat.config.system.build.toplevel;
+        routerClosure = nodes.router.config.system.build.toplevel;
+      in ''
+        $client->start;
+        $router->start;
+        $server->start;
 
         # The router should have access to the server.
         $server->waitForUnit("network.target");
@@ -66,20 +93,26 @@ import ./make-test.nix ({ pkgs, withFirewall, ... }:
         $client->succeed("curl -v ftp://server/foo.txt >&2");
 
         # Test whether active FTP works.
-        $client->succeed("curl -v -P - ftp://server/foo.txt >&2");
+        $client->${if withConntrackHelpers then "succeed" else "fail"}(
+          "curl -v -P - ftp://server/foo.txt >&2");
 
         # Test ICMP.
         $client->succeed("ping -c 1 router >&2");
         $router->succeed("ping -c 1 client >&2");
 
         # If we turn off NAT, the client shouldn't be able to reach the server.
-        $router->succeed("iptables -t nat -D PREROUTING -j nixos-nat-pre");
-        $router->succeed("iptables -t nat -D POSTROUTING -j nixos-nat-post");
+        $router->succeed("${routerDummyNoNatClosure}/bin/switch-to-configuration test 2>&1");
         $client->fail("curl --fail --connect-timeout 5 http://server/ >&2");
         $client->fail("ping -c 1 server >&2");
 
         # And make sure that reloading the NAT job works.
-        $router->succeed("systemctl restart ${unit}");
+        $router->succeed("${routerClosure}/bin/switch-to-configuration test 2>&1");
+        # FIXME: this should not be necessary, but nat.service is not started because
+        #        network.target is not triggered
+        #        (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359)
+        ${lib.optionalString (!withFirewall) ''
+          $router->succeed("systemctl start nat.service");
+        ''}
         $client->succeed("curl --fail http://server/ >&2");
         $client->succeed("ping -c 1 server >&2");
       '';
diff --git a/nixos/tests/netdata.nix b/nixos/tests/netdata.nix
new file mode 100644
index 000000000000..58733c1b3379
--- /dev/null
+++ b/nixos/tests/netdata.nix
@@ -0,0 +1,31 @@
+# This test runs netdata and checks for data via apps.plugin
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "netdata";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ cransom ];
+  };
+
+  nodes = {
+    netdata =
+      { config, pkgs, ... }:
+        {
+          environment.systemPackages = with pkgs; [ curl jq ];
+          services.netdata.enable = true;
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $netdata->waitForUnit("netdata.service");
+    # check if netdata can read disk ops for root owned processes. 
+    # if > 0, successful. verifies both netdata working and 
+    # apps.plugin has elevated capabilities.
+    my $cmd = <<'CMD';
+    curl -s http://localhost:19999/api/v1/data\?chart=users.pwrites | \
+       jq -e '[.data[range(10)][.labels | indices("root")[0]]] | add | . > 0'
+    CMD
+    $netdata->waitUntilSucceeds($cmd);
+  '';
+})
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 6a7f63702c41..5cb40af5799e 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -1,395 +1,604 @@
-import ./make-test.nix ({ pkgs, networkd, test, ... }:
-  let
-    router = { config, pkgs, ... }:
-      with pkgs.lib;
-      let
-        vlanIfs = range 1 (length config.virtualisation.vlans);
-      in {
-        virtualisation.vlans = [ 1 2 3 ];
+{ system ? builtins.currentSystem
+# bool: whether to use networkd in the tests
+, networkd }:
+
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+
+let
+  router = { config, pkgs, ... }:
+    with pkgs.lib;
+    let
+      vlanIfs = range 1 (length config.virtualisation.vlans);
+    in {
+      virtualisation.vlans = [ 1 2 3 ];
+      boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+      networking = {
+        useDHCP = false;
+        useNetworkd = networkd;
+        firewall.allowPing = true;
+        firewall.checkReversePath = true;
+        firewall.allowedUDPPorts = [ 547 ];
+        interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
+          nameValuePair "eth${toString n}" {
+            ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
+            ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
+          })));
+      };
+      services.dhcpd4 = {
+        enable = true;
+        interfaces = map (n: "eth${toString n}") vlanIfs;
+        extraConfig = ''
+          authoritative;
+        '' + flip concatMapStrings vlanIfs (n: ''
+          subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
+            option routers 192.168.${toString n}.1;
+            # XXX: technically it's _not guaranteed_ that IP addresses will be
+            # issued from the first item in range onwards! We assume that in
+            # our tests however.
+            range 192.168.${toString n}.2 192.168.${toString n}.254;
+          }
+        '');
+      };
+      services.radvd = {
+        enable = true;
+        config = flip concatMapStrings vlanIfs (n: ''
+          interface eth${toString n} {
+            AdvSendAdvert on;
+            AdvManagedFlag on;
+            AdvOtherConfigFlag on;
+
+            prefix fd00:1234:5678:${toString n}::/64 {
+              AdvAutonomous off;
+            };
+          };
+        '');
+      };
+      services.dhcpd6 = {
+        enable = true;
+        interfaces = map (n: "eth${toString n}") vlanIfs;
+        extraConfig = ''
+          authoritative;
+        '' + flip concatMapStrings vlanIfs (n: ''
+          subnet6 fd00:1234:5678:${toString n}::/64 {
+            range6 fd00:1234:5678:${toString n}::2 fd00:1234:5678:${toString n}::2;
+          }
+        '');
+      };
+    };
+
+  testCases = {
+    loopback = {
+      name = "Loopback";
+      machine.networking.useNetworkd = networkd;
+      testScript = ''
+        startAll;
+        $machine->waitForUnit("network.target");
+        $machine->succeed("ip addr show lo | grep -q 'inet 127.0.0.1/8 '");
+        $machine->succeed("ip addr show lo | grep -q 'inet6 ::1/128 '");
+      '';
+    };
+    static = {
+      name = "Static";
+      nodes.router = router;
+      nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
         networking = {
-          useDHCP = false;
           useNetworkd = networkd;
           firewall.allowPing = true;
-          interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
-            nameValuePair "eth${toString n}" {
-              ipAddress = "192.168.${toString n}.1";
-              prefixLength = 24;
-            })));
-        };
-        services.dhcpd = {
-          enable = true;
-          interfaces = map (n: "eth${toString n}") vlanIfs;
-          extraConfig = ''
-            option subnet-mask 255.255.255.0;
-          '' + flip concatMapStrings vlanIfs (n: ''
-            subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
-              option broadcast-address 192.168.${toString n}.255;
-              option routers 192.168.${toString n}.1;
-              range 192.168.${toString n}.2 192.168.${toString n}.254;
-            }
-          '');
+          useDHCP = false;
+          defaultGateway = "192.168.1.1";
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [
+            { address = "192.168.1.2"; prefixLength = 24; }
+            { address = "192.168.1.3"; prefixLength = 32; }
+            { address = "192.168.1.10"; prefixLength = 32; }
+          ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [
+            { address = "192.168.2.2"; prefixLength = 24; }
+          ];
         };
       };
-    testCases = {
-      static = {
-        name = "Static";
-        nodes.router = router;
-        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 2 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = false;
-            defaultGateway = "192.168.1.1";
-            interfaces.eth1.ip4 = mkOverride 0 [
-              { address = "192.168.1.2"; prefixLength = 24; }
-              { address = "192.168.1.3"; prefixLength = 32; }
-              { address = "192.168.1.10"; prefixLength = 32; }
-            ];
-            interfaces.eth2.ip4 = mkOverride 0 [
-              { address = "192.168.2.2"; prefixLength = 24; }
-            ];
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
+
+          # Make sure dhcpcd is not started
+          $client->fail("systemctl status dhcpcd.service");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.10");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.10");
+
+          # Test vlan 2
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
+
+          # Test default gateway
+          $router->waitUntilSucceeds("ping -c 1 192.168.3.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.3.1");
+        '';
+    };
+    dhcpSimple = {
+      name = "SimpleDHCP";
+      nodes.router = router;
+      nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = true;
+          interfaces.eth1 = {
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+          };
+          interfaces.eth2 = {
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
           };
         };
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            $client->waitForUnit("network-interfaces.target");
-            $client->waitForUnit("network.target");
-            $router->waitForUnit("network-interfaces.target");
-            $router->waitForUnit("network.target");
-
-            # Make sure dhcpcd is not started
-            $client->fail("systemctl status dhcpcd.service");
-
-            # Test vlan 1
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.10");
-
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.10");
-
-            # Test vlan 2
-            $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
-
-            $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
-
-            # Test default gateway
-            $router->waitUntilSucceeds("ping -c 1 192.168.3.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.3.1");
-          '';
       };
-      dhcpSimple = {
-        name = "SimpleDHCP";
-        nodes.router = router;
-        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 2 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          $client->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'");
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
+          $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q '192.168.2'");
+          $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2");
+
+          # Test vlan 2
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2");
+        '';
+    };
+    dhcpOneIf = {
+      name = "OneInterfaceDHCP";
+      nodes.router = router;
+      nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = false;
+          interfaces.eth1 = {
+            ipv4.addresses = mkOverride 0 [ ];
             useDHCP = true;
-            interfaces.eth1.ip4 = mkOverride 0 [ ];
-            interfaces.eth2.ip4 = mkOverride 0 [ ];
           };
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
         };
-        testScript = { nodes, ... }:
-          ''
-            startAll;
+      };
+      testScript = { nodes, ... }:
+        ''
+          startAll;
 
-            $client->waitForUnit("network-interfaces.target");
-            $client->waitForUnit("network.target");
-            $router->waitForUnit("network-interfaces.target");
-            $router->waitForUnit("network.target");
+          # Wait for networking to come up
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network.target");
 
-            # Wait until we have an ip address on each interface
-            $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
-            $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q '192.168.2'");
+          # Wait until we have an ip address on each interface
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
 
-            # Test vlan 1
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
 
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
 
-            # Test vlan 2
-            $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          # Test vlan 2
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $client->fail("ping -c 1 192.168.2.2");
 
-            $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
-          '';
-      };
-      dhcpOneIf = {
-        name = "OneInterfaceDHCP";
-        nodes.router = router;
-        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 2 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = false;
-            interfaces.eth1 = {
-              ip4 = mkOverride 0 [ ];
-              useDHCP = true;
-            };
-            interfaces.eth2.ip4 = mkOverride 0 [ ];
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $router->fail("ping -c 1 192.168.2.2");
+        '';
+    };
+    bond = let
+      node = address: { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = false;
+          bonds.bond = {
+            interfaces = [ "eth1" "eth2" ];
+            driverOptions.mode = "balance-rr";
           };
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.bond.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 30; } ];
         };
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            # Wait for networking to come up
-            $client->waitForUnit("network-interfaces.target");
-            $client->waitForUnit("network.target");
-            $router->waitForUnit("network-interfaces.target");
-            $router->waitForUnit("network.target");
-
-            # Wait until we have an ip address on each interface
-            $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
-
-            # Test vlan 1
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
-
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
-
-            # Test vlan 2
-            $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
-            $client->fail("ping -c 1 192.168.2.2");
-
-            $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
-            $router->fail("ping -c 1 192.168.2.2");
-          '';
       };
-      bond = let
-        node = address: { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 2 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = false;
-            bonds.bond = {
-              mode = "balance-rr";
-              interfaces = [ "eth1" "eth2" ];
-            };
-            interfaces.eth1.ip4 = mkOverride 0 [ ];
-            interfaces.eth2.ip4 = mkOverride 0 [ ];
-            interfaces.bond.ip4 = mkOverride 0
-              [ { inherit address; prefixLength = 30; } ];
-          };
+    in {
+      name = "Bond";
+      nodes.client1 = node "192.168.1.1";
+      nodes.client2 = node "192.168.1.2";
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+
+          # Test bonding
+          $client1->waitUntilSucceeds("ping -c 2 192.168.1.1");
+          $client1->waitUntilSucceeds("ping -c 2 192.168.1.2");
+
+          $client2->waitUntilSucceeds("ping -c 2 192.168.1.1");
+          $client2->waitUntilSucceeds("ping -c 2 192.168.1.2");
+        '';
+    };
+    bridge = let
+      node = { address, vlan }: { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ vlan ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = false;
+          interfaces.eth1.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 24; } ];
         };
-      in {
-        name = "Bond";
-        nodes.client1 = node "192.168.1.1";
-        nodes.client2 = node "192.168.1.2";
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            # Wait for networking to come up
-            $client1->waitForUnit("network-interfaces.target");
-            $client1->waitForUnit("network.target");
-            $client2->waitForUnit("network-interfaces.target");
-            $client2->waitForUnit("network.target");
-
-            # Test bonding
-            $client1->waitUntilSucceeds("ping -c 2 192.168.1.1");
-            $client1->waitUntilSucceeds("ping -c 2 192.168.1.2");
-
-            $client2->waitUntilSucceeds("ping -c 2 192.168.1.1");
-            $client2->waitUntilSucceeds("ping -c 2 192.168.1.2");
-          '';
       };
-      bridge = let
-        node = { address, vlan }: { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ vlan ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = false;
-            interfaces.eth1.ip4 = mkOverride 0
-              [ { inherit address; prefixLength = 24; } ];
-          };
+    in {
+      name = "Bridge";
+      nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
+      nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
+      nodes.router = { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = false;
+          bridges.bridge.interfaces = [ "eth1" "eth2" ];
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.bridge.ipv4.addresses = mkOverride 0
+            [ { address = "192.168.1.1"; prefixLength = 24; } ];
+        };
+      };
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+          $router->waitForUnit("network.target");
+
+          # Test bridging
+          $client1->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client1->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client1->waitUntilSucceeds("ping -c 1 192.168.1.3");
+
+          $client2->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client2->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client2->waitUntilSucceeds("ping -c 1 192.168.1.3");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
+        '';
+    };
+    macvlan = {
+      name = "MACVLAN";
+      nodes.router = router;
+      nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = true;
+          macvlans.macvlan.interface = "eth1";
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
         };
-      in {
-        name = "Bridge";
-        nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
-        nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
-        nodes.router = { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 2 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = false;
-            bridges.bridge.interfaces = [ "eth1" "eth2" ];
-            interfaces.eth1.ip4 = mkOverride 0 [ ];
-            interfaces.eth2.ip4 = mkOverride 0 [ ];
-            interfaces.bridge.ip4 = mkOverride 0
-              [ { address = "192.168.1.1"; prefixLength = 24; } ];
+      };
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network.target");
+
+          # Wait until we have an ip address on each interface
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
+          $client->waitUntilSucceeds("ip addr show dev macvlan | grep -q '192.168.1'");
+
+          # Print diagnosting information
+          $router->succeed("ip addr >&2");
+          $client->succeed("ip addr >&2");
+
+          # Test macvlan creates routable ips
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
+        '';
+    };
+    sit = let
+      node = { address4, remote, address6 }: { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.enable = false;
+          useDHCP = false;
+          sits.sit = {
+            inherit remote;
+            local = address4;
+            dev = "eth1";
           };
+          interfaces.eth1.ipv4.addresses = mkOverride 0
+            [ { address = address4; prefixLength = 24; } ];
+          interfaces.sit.ipv6.addresses = mkOverride 0
+            [ { address = address6; prefixLength = 64; } ];
         };
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            # Wait for networking to come up
-            $client1->waitForUnit("network-interfaces.target");
-            $client1->waitForUnit("network.target");
-            $client2->waitForUnit("network-interfaces.target");
-            $client2->waitForUnit("network.target");
-            $router->waitForUnit("network-interfaces.target");
-            $router->waitForUnit("network.target");
-
-            # Test bridging
-            $client1->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $client1->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $client1->waitUntilSucceeds("ping -c 1 192.168.1.3");
-
-            $client2->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $client2->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $client2->waitUntilSucceeds("ping -c 1 192.168.1.3");
-
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
-          '';
       };
-      macvlan = {
-        name = "MACVLAN";
-        nodes.router = router;
-        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = true;
-            macvlans.macvlan.interface = "eth1";
-            interfaces.eth1.ip4 = mkOverride 0 [ ];
+    in {
+      name = "Sit";
+      nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
+      nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for networking to be configured
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+
+          # Print diagnostic information
+          $client1->succeed("ip addr >&2");
+          $client2->succeed("ip addr >&2");
+
+          # Test ipv6
+          $client1->waitUntilSucceeds("ping -c 1 fc00::1");
+          $client1->waitUntilSucceeds("ping -c 1 fc00::2");
+
+          $client2->waitUntilSucceeds("ping -c 1 fc00::1");
+          $client2->waitUntilSucceeds("ping -c 1 fc00::2");
+        '';
+    };
+    vlan = let
+      node = address: { config, pkgs, ... }: with pkgs.lib; {
+        #virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          useDHCP = false;
+          vlans.vlan = {
+            id = 1;
+            interface = "eth0";
           };
+          interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.vlan.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 24; } ];
         };
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            # Wait for networking to come up
-            $client->waitForUnit("network-interfaces.target");
-            $client->waitForUnit("network.target");
-            $router->waitForUnit("network-interfaces.target");
-            $router->waitForUnit("network.target");
-
-            # Wait until we have an ip address on each interface
-            $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
-            $client->waitUntilSucceeds("ip addr show dev macvlan | grep -q '192.168.1'");
-
-            # Print diagnosting information
-            $router->succeed("ip addr >&2");
-            $client->succeed("ip addr >&2");
-
-            # Test macvlan creates routable ips
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
-
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
-            $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
-          '';
       };
-      sit = let
-        node = { address4, remote, address6 }: { config, pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.enable = false;
-            useDHCP = false;
-            sits.sit = {
-              inherit remote;
-              local = address4;
-              dev = "eth1";
-            };
-            interfaces.eth1.ip4 = mkOverride 0
-              [ { address = address4; prefixLength = 24; } ];
-            interfaces.sit.ip6 = mkOverride 0
-              [ { address = address6; prefixLength = 64; } ];
+    in {
+      name = "vlan";
+      nodes.client1 = node "192.168.1.1";
+      nodes.client2 = node "192.168.1.2";
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for networking to be configured
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+
+          # Test vlan is setup
+          $client1->succeed("ip addr show dev vlan >&2");
+          $client2->succeed("ip addr show dev vlan >&2");
+        '';
+    };
+    virtual = {
+      name = "Virtual";
+      machine = {
+        networking.interfaces."tap0" = {
+          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
+          virtual = true;
+        };
+        networking.interfaces."tun0" = {
+          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
+          virtual = true;
+        };
+      };
+
+      testScript = ''
+        my $targetList = <<'END';
+        tap0: tap UNKNOWN_FLAGS:800 user 0
+        tun0: tun UNKNOWN_FLAGS:800 user 0
+        END
+
+        # Wait for networking to come up
+        $machine->start;
+        $machine->waitForUnit("network.target");
+
+        # Test interfaces set up
+        my $list = $machine->succeed("ip tuntap list | sort");
+        "$list" eq "$targetList" or die(
+          "The list of virtual interfaces does not match the expected one:\n",
+          "Result:\n", "$list\n",
+          "Expected:\n", "$targetList\n"
+        );
+
+        # Test interfaces clean up
+        $machine->succeed("systemctl stop network-addresses-tap0");
+        $machine->succeed("systemctl stop network-addresses-tun0");
+        my $residue = $machine->succeed("ip tuntap list");
+        $residue eq "" or die(
+          "Some virtual interface has not been properly cleaned:\n",
+          "$residue\n"
+        );
+      '';
+    };
+    privacy = {
+      name = "Privacy";
+      nodes.router = { config, pkgs, ... }: {
+        virtualisation.vlans = [ 1 ];
+        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+        networking = {
+          useNetworkd = networkd;
+          interfaces.eth1.ipv6.addresses = singleton {
+            address = "fd00:1234:5678:1::1";
+            prefixLength = 64;
           };
         };
-      in {
-        name = "Sit";
-        nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
-        nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            # Wait for networking to be configured
-            $client1->waitForUnit("network-interfaces.target");
-            $client1->waitForUnit("network.target");
-            $client2->waitForUnit("network-interfaces.target");
-            $client2->waitForUnit("network.target");
-
-            # Print diagnostic information
-            $client1->succeed("ip addr >&2");
-            $client2->succeed("ip addr >&2");
-
-            # Test ipv6
-            $client1->waitUntilSucceeds("ping6 -c 1 fc00::1");
-            $client1->waitUntilSucceeds("ping6 -c 1 fc00::2");
-
-            $client2->waitUntilSucceeds("ping6 -c 1 fc00::1");
-            $client2->waitUntilSucceeds("ping6 -c 1 fc00::2");
+        services.radvd = {
+          enable = true;
+          config = ''
+            interface eth1 {
+              AdvSendAdvert on;
+              AdvManagedFlag on;
+              AdvOtherConfigFlag on;
+
+              prefix fd00:1234:5678:1::/64 {
+                AdvAutonomous on;
+                AdvOnLink on;
+              };
+            };
           '';
+        };
       };
-      vlan = let
-        node = address: { config, pkgs, ... }: with pkgs.lib; {
-          #virtualisation.vlans = [ 1 ];
-          networking = {
-            useNetworkd = networkd;
-            firewall.allowPing = true;
-            useDHCP = false;
-            vlans.vlan = {
-              id = 1;
-              interface = "eth0";
-            };
-            interfaces.eth0.ip4 = mkOverride 0 [ ];
-            interfaces.eth1.ip4 = mkOverride 0 [ ];
-            interfaces.vlan.ip4 = mkOverride 0
-              [ { inherit address; prefixLength = 24; } ];
+      nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = true;
+          interfaces.eth1 = {
+            preferTempAddress = true;
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
           };
         };
-      in {
-        name = "vlan";
-        nodes.client1 = node "192.168.1.1";
-        nodes.client2 = node "192.168.1.2";
-        testScript = { nodes, ... }:
-          ''
-            startAll;
-
-            # Wait for networking to be configured
-            $client1->waitForUnit("network-interfaces.target");
-            $client1->waitForUnit("network.target");
-            $client2->waitForUnit("network-interfaces.target");
-            $client2->waitForUnit("network.target");
-
-            # Test vlan is setup
-            $client1->succeed("ip addr show dev vlan >&2");
-            $client2->succeed("ip addr show dev vlan >&2");
-          '';
       };
+      testScript = { nodes, ... }:
+        ''
+          startAll;
+
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
+
+          # Wait until we have an ip address
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
+
+          # Test address used is temporary
+          $client->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
+        '';
     };
-    case = testCases.${test};
-  in case // {
-    name = "${case.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
-    meta = with pkgs.stdenv.lib.maintainers; {
-      maintainers = [ wkennington ];
+    routes = {
+      name = "routes";
+      machine = {
+        networking.useDHCP = false;
+        networking.interfaces."eth0" = {
+          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
+          ipv6.routes = [
+            { address = "fdfd:b3f0::"; prefixLength = 48; }
+            { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
+          ];
+          ipv4.routes = [
+            { address = "10.0.0.0"; prefixLength = 16; options = { mtu = "1500"; }; }
+            { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
+          ];
+        };
+        virtualisation.vlans = [ ];
+      };
+
+      testScript = ''
+        my $targetIPv4Table = <<'END';
+        10.0.0.0/16 scope link mtu 1500 
+        192.168.1.0/24 proto kernel scope link src 192.168.1.2 
+        192.168.2.0/24 via 192.168.1.1 
+        END
+
+        my $targetIPv6Table = <<'END';
+        2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium
+        2001:1470:fffd:2098::/64 via fdfd:b3f0::1 metric 1024 pref medium
+        fdfd:b3f0::/48 metric 1024 pref medium
+        END
+
+        $machine->start;
+        $machine->waitForUnit("network.target");
+
+        # test routing tables
+        my $ipv4Table = $machine->succeed("ip -4 route list dev eth0 | head -n3");
+        my $ipv6Table = $machine->succeed("ip -6 route list dev eth0 | head -n3");
+        "$ipv4Table" eq "$targetIPv4Table" or die(
+          "The IPv4 routing table does not match the expected one:\n",
+          "Result:\n", "$ipv4Table\n",
+          "Expected:\n", "$targetIPv4Table\n"
+        );
+        "$ipv6Table" eq "$targetIPv6Table" or die(
+          "The IPv6 routing table does not match the expected one:\n",
+          "Result:\n", "$ipv6Table\n",
+          "Expected:\n", "$targetIPv6Table\n"
+        );
+
+        # test clean-up of the tables
+        $machine->succeed("systemctl stop network-addresses-eth0");
+        my $ipv4Residue = $machine->succeed("ip -4 route list dev eth0 | head -n-3");
+        my $ipv6Residue = $machine->succeed("ip -6 route list dev eth0 | head -n-3");
+        $ipv4Residue eq "" or die(
+          "The IPv4 routing table has not been properly cleaned:\n",
+          "$ipv4Residue\n"
+        );
+        $ipv6Residue eq "" or die(
+          "The IPv6 routing table has not been properly cleaned:\n",
+          "$ipv6Residue\n"
+        );
+      '';
     };
-  })
+  };
+
+in mapAttrs (const (attrs: makeTest (attrs // {
+  name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ wkennington ];
+  };
+}))) testCases
diff --git a/nixos/tests/nexus.nix b/nixos/tests/nexus.nix
new file mode 100644
index 000000000000..be8862018777
--- /dev/null
+++ b/nixos/tests/nexus.nix
@@ -0,0 +1,32 @@
+# verifies:
+#   1. nexus service starts on server
+#   2. nexus service can startup on server (creating database and all other initial stuff)
+#   3. the web application is reachable via HTTP
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "nexus";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ironpinguin ma27 ];
+  };
+
+  nodes = {
+
+    server =
+      { config, pkgs, ... }:
+      { virtualisation.memorySize = 2047; # qemu-system-i386 has a 2047M limit
+        virtualisation.diskSize = 2048;
+
+        services.nexus.enable = true;
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("nexus");
+    $server->waitForOpenPort(8081);
+
+    $server->succeed("curl -f 127.0.0.1:8081");
+  '';
+})
diff --git a/nixos/tests/nfs.nix b/nixos/tests/nfs.nix
index 24f6e0f2ed95..6ed1995f262a 100644
--- a/nixos/tests/nfs.nix
+++ b/nixos/tests/nfs.nix
@@ -8,7 +8,7 @@ let
         [ { mountPoint = "/data";
             device = "server:/data";
             fsType = "nfs";
-            options = "vers=${toString version}";
+            options = [ "vers=${toString version}" ];
           }
         ];
       networking.firewall.enable = false; # FIXME: only open statd
@@ -40,7 +40,7 @@ in
 
   testScript =
     ''
-      $server->waitForUnit("nfsd");
+      $server->waitForUnit("nfs-server");
       $server->succeed("systemctl start network-online.target");
       $server->waitForUnit("network-online.target");
 
@@ -54,8 +54,8 @@ in
       $client2->succeed("echo bla > /data/bar");
       $server->succeed("test -e /data/bar");
 
-      # Test whether restarting ‘nfsd’ works correctly.
-      $server->succeed("systemctl restart nfsd");
+      # Test whether restarting ‘nfs-server’ works correctly.
+      $server->succeed("systemctl restart nfs-server");
       $client2->succeed("echo bla >> /data/bar"); # will take 90 seconds due to the NFS grace period
 
       # Test whether we can get a lock.
diff --git a/nixos/tests/nghttpx.nix b/nixos/tests/nghttpx.nix
new file mode 100644
index 000000000000..433562b97191
--- /dev/null
+++ b/nixos/tests/nghttpx.nix
@@ -0,0 +1,61 @@
+let
+  nginxRoot = "/var/run/nginx";
+in
+  import ./make-test.nix ({...}: {
+    name  = "nghttpx";
+    nodes = {
+      webserver = {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        systemd.services.nginx = {
+          preStart = ''
+            mkdir -p ${nginxRoot}
+            echo "Hello world!" > ${nginxRoot}/hello-world.txt
+          '';
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts."server" = {
+            locations."/".root = nginxRoot;
+          };
+        };
+      };
+
+      proxy = {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        services.nghttpx = {
+          enable = true;
+          frontends = [
+            { server = {
+                host = "*";
+                port = 80;
+              };
+
+              params = {
+                tls = "no-tls";
+              };
+            }
+          ];
+          backends = [
+            { server = {
+                host = "webserver";
+                port = 80;
+              };
+              patterns = [ "/" ];
+              params.proto = "http/1.1";
+            }
+          ];
+        };
+      };
+
+      client = {};
+    };
+
+    testScript = ''
+      startAll;
+
+      $webserver->waitForOpenPort("80");
+      $proxy->waitForOpenPort("80");
+      $client->waitUntilSucceeds("curl -s --fail http://proxy/hello-world.txt");
+    '';
+  })
diff --git a/nixos/tests/nginx.nix b/nixos/tests/nginx.nix
new file mode 100644
index 000000000000..7f7bc0f0b4fe
--- /dev/null
+++ b/nixos/tests/nginx.nix
@@ -0,0 +1,42 @@
+# verifies:
+#   1. nginx generates config file with shared http context definitions above
+#      generated virtual hosts config.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "nginx";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mbbx6spp ];
+  };
+
+  nodes = {
+    webserver =
+      { config, pkgs, ... }:
+      { services.nginx.enable = true;
+        services.nginx.commonHttpConfig = ''
+        log_format ceeformat '@cee: {"status":"$status",'
+          '"request_time":$request_time,'
+          '"upstream_response_time":$upstream_response_time,'
+          '"pipe":"$pipe","bytes_sent":$bytes_sent,'
+          '"connection":"$connection",'
+          '"remote_addr":"$remote_addr",'
+          '"host":"$host",'
+          '"timestamp":"$time_iso8601",'
+          '"request":"$request",'
+          '"http_referer":"$http_referer",'
+          '"upstream_addr":"$upstream_addr"}';
+        '';
+        services.nginx.virtualHosts."0.my.test" = {
+          extraConfig = ''
+            access_log syslog:server=unix:/dev/log,facility=user,tag=mytag,severity=info ceeformat;
+          '';
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $webserver->waitForUnit("nginx");
+    $webserver->waitForOpenPort("80");
+  '';
+})
diff --git a/nixos/tests/nix-ssh-serve.nix b/nixos/tests/nix-ssh-serve.nix
new file mode 100644
index 000000000000..aa366d8612d7
--- /dev/null
+++ b/nixos/tests/nix-ssh-serve.nix
@@ -0,0 +1,39 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+let inherit (import ./ssh-keys.nix pkgs)
+      snakeOilPrivateKey snakeOilPublicKey;
+    ssh-config = builtins.toFile "ssh.conf" ''
+      UserKnownHostsFile=/dev/null
+      StrictHostKeyChecking=no
+    '';
+in
+   { name = "nix-ssh-serve";
+     meta.maintainers = [ lib.maintainers.shlevy ];
+     nodes =
+       { server.nix.sshServe =
+           { enable = true;
+             keys = [ snakeOilPublicKey ];
+             protocol = "ssh-ng";
+           };
+         server.nix.package = pkgs.nixUnstable;
+         client.nix.package = pkgs.nixUnstable;
+       };
+     testScript = ''
+       startAll;
+
+       $client->succeed("mkdir -m 700 /root/.ssh");
+       $client->copyFileFromHost("${ssh-config}", "/root/.ssh/config");
+       $client->succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa");
+       $client->succeed("chmod 600 /root/.ssh/id_ecdsa");
+
+       $client->succeed("nix-store --add /etc/machine-id > mach-id-path");
+
+       $server->waitForUnit("sshd");
+
+       $client->fail("diff /root/other-store\$(cat mach-id-path) /etc/machine-id");
+       # Currently due to shared store this is a noop :(
+       $client->succeed("nix copy --to ssh-ng://nix-ssh\@server \$(cat mach-id-path)");
+       $client->succeed("nix-store --realise \$(cat mach-id-path) --store /root/other-store --substituters ssh-ng://nix-ssh\@server");
+       $client->succeed("diff /root/other-store\$(cat mach-id-path) /etc/machine-id");
+     '';
+   }
+)
diff --git a/nixos/tests/novacomd.nix b/nixos/tests/novacomd.nix
new file mode 100644
index 000000000000..21b86f6dae27
--- /dev/null
+++ b/nixos/tests/novacomd.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "novacomd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ dtzWill ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    services.novacomd.enable = true;
+  };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("novacomd.service");
+
+    # Check status and try connecting with novacom
+    $machine->succeed("systemctl status novacomd.service >&2");
+    $machine->succeed("novacom -l");
+
+    # Stop the daemon, double-check novacom fails if daemon isn't working
+    $machine->stopJob("novacomd");
+    $machine->fail("novacom -l");
+
+    # And back again for good measure
+    $machine->startJob("novacomd");
+    $machine->succeed("novacom -l");
+  '';
+})
diff --git a/nixos/tests/nsd.nix b/nixos/tests/nsd.nix
index 0b1082056f6f..c3c91e71b5ca 100644
--- a/nixos/tests/nsd.nix
+++ b/nixos/tests/nsd.nix
@@ -15,26 +15,33 @@ in import ./make-test.nix ({ pkgs, ...} : {
     clientv4 = { lib, nodes, ... }: {
       imports = [ common ];
       networking.nameservers = lib.mkForce [
-        nodes.server.config.networking.interfaces.eth1.ipAddress
+        (lib.head nodes.server.config.networking.interfaces.eth1.ipv4.addresses).address
+      ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.2"; prefixLength = 24; }
       ];
-      networking.interfaces.eth1.ipAddress = "192.168.0.2";
-      networking.interfaces.eth1.prefixLength = 24;
     };
 
     clientv6 = { lib, nodes, ... }: {
       imports = [ common ];
       networking.nameservers = lib.mkForce [
-        nodes.server.config.networking.interfaces.eth1.ipv6Address
+        (lib.head nodes.server.config.networking.interfaces.eth1.ipv6.addresses).address
+      ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "dead:beef::2"; prefixLength = 24; }
       ];
-      networking.interfaces.eth1.ipv6Address = "dead:beef::2";
     };
 
     server = { lib, ... }: {
       imports = [ common ];
-      networking.interfaces.eth1.ipAddress = "192.168.0.1";
-      networking.interfaces.eth1.prefixLength = 24;
-      networking.interfaces.eth1.ipv6Address = "dead:beef::1";
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.1"; prefixLength = 24; }
+      ];
+      networking.interfaces.eth1.ipv6.addresses = [
+        { address = "dead:beef::1"; prefixLength = 64; }
+      ];
       services.nsd.enable = true;
+      services.nsd.rootServer = true;
       services.nsd.interfaces = lib.mkForce [];
       services.nsd.zones."example.com.".data = ''
         @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
@@ -49,6 +56,11 @@ in import ./make-test.nix ({ pkgs, ...} : {
         @ A 9.8.7.6
         @ AAAA fedc::bbaa
       '';
+      services.nsd.zones.".".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        root A 1.8.7.4
+        root AAAA acbd::4
+      '';
     };
   };
 
@@ -80,6 +92,9 @@ in import ./make-test.nix ({ pkgs, ...} : {
 
         assertHost($_, "a", "deleg.example.com", qr/address 9.8.7.6$/);
         assertHost($_, "aaaa", "deleg.example.com", qr/address fedc::bbaa$/);
+
+        assertHost($_, "a", "root", qr/address 1.8.7.4$/);
+        assertHost($_, "aaaa", "root", qr/address acbd::4$/);
       };
     }
   '';
diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix
new file mode 100644
index 000000000000..1eaf87a8eaa8
--- /dev/null
+++ b/nixos/tests/openldap.nix
@@ -0,0 +1,35 @@
+import ./make-test.nix {
+  name = "openldap";
+
+  machine = { pkgs, ... }: {
+    services.openldap = {
+      enable = true;
+      extraConfig = ''
+        include ${pkgs.openldap}/etc/schema/core.schema
+        include ${pkgs.openldap}/etc/schema/cosine.schema
+        include ${pkgs.openldap}/etc/schema/inetorgperson.schema
+        include ${pkgs.openldap}/etc/schema/nis.schema
+        database bdb
+        suffix dc=example
+        directory /var/db/openldap
+        rootdn cn=root,dc=example
+        rootpw notapassword
+      '';
+      declarativeContents = ''
+        dn: dc=example
+        objectClass: domain
+        dc: example
+
+        dn: ou=users,dc=example
+        objectClass: organizationalUnit
+        ou: users
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit('openldap.service');
+    $machine->succeed('systemctl status openldap.service');
+    $machine->succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"');
+  '';
+}
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index 390363b88e21..b2d254e9d9d9 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -1,20 +1,7 @@
 import ./make-test.nix ({ pkgs, ... }:
 
-let
-  snakeOilPrivateKey = pkgs.writeText "privkey.snakeoil" ''
-    -----BEGIN EC PRIVATE KEY-----
-    MHcCAQEEIHQf/khLvYrQ8IOika5yqtWvI0oquHlpRLTZiJy5dRJmoAoGCCqGSM49
-    AwEHoUQDQgAEKF0DYGbBwbj06tA3fd/+yP44cvmwmHBWXZCKbS+RQlAKvLXMWkpN
-    r1lwMyJZoSGgBHoUahoYjTh9/sJL7XLJtA==
-    -----END EC PRIVATE KEY-----
-  '';
-
-  snakeOilPublicKey = pkgs.lib.concatStrings [
-    "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHA"
-    "yNTYAAABBBChdA2BmwcG49OrQN33f/sj+OHL5sJhwVl2Qim0vkUJQCry1zFpKTa"
-    "9ZcDMiWaEhoAR6FGoaGI04ff7CS+1yybQ= sakeoil"
-  ];
-
+let inherit (import ./ssh-keys.nix pkgs)
+      snakeOilPrivateKey snakeOilPublicKey;
 in {
   name = "openssh";
   meta = with pkgs.stdenv.lib.maintainers; {
@@ -35,6 +22,18 @@ in {
         ];
       };
 
+    server_lazy =
+      { config, pkgs, ... }:
+
+      {
+        services.openssh = { enable = true; startWhenNeeded = true; };
+        security.pam.services.sshd.limits =
+          [ { domain = "*"; item = "memlock"; type = "-"; value = 1024; } ];
+        users.extraUsers.root.openssh.authorizedKeys.keys = [
+          snakeOilPublicKey
+        ];
+      };
+
     client =
       { config, pkgs, ... }: { };
 
@@ -50,6 +49,8 @@ in {
     subtest "manual-authkey", sub {
       $server->succeed("mkdir -m 700 /root/.ssh");
       $server->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
+      $server_lazy->succeed("mkdir -m 700 /root/.ssh");
+      $server_lazy->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
 
       $client->succeed("mkdir -m 700 /root/.ssh");
       $client->copyFileFromHost("key", "/root/.ssh/id_ed25519");
@@ -58,6 +59,10 @@ in {
       $client->waitForUnit("network.target");
       $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2");
       $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024");
+
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2");
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024");
+
     };
 
     subtest "configured-authkey", sub {
@@ -66,6 +71,11 @@ in {
       $client->succeed("ssh -o UserKnownHostsFile=/dev/null" .
                        " -o StrictHostKeyChecking=no -i privkey.snakeoil" .
                        " server true");
+
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null" .
+                       " -o StrictHostKeyChecking=no -i privkey.snakeoil" .
+                       " server_lazy true");
+
     };
   '';
 })
diff --git a/nixos/tests/osquery.nix b/nixos/tests/osquery.nix
new file mode 100644
index 000000000000..281dbcff6643
--- /dev/null
+++ b/nixos/tests/osquery.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "osquery";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  machine = {
+    services.osquery.enable = true;
+    services.osquery.loggerPath = "/var/log/osquery/logs";
+    services.osquery.pidfile = "/var/run/osqueryd.pid";
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForUnit("osqueryd.service");
+
+    $machine->succeed("echo 'SELECT address FROM etc_hosts LIMIT 1;' | osqueryi | grep '127.0.0.1'");
+    $machine->succeed(
+      "echo 'SELECT value FROM osquery_flags WHERE name = \"logger_path\";' | osqueryi | grep /var/log/osquery/logs"
+    );
+
+    $machine->succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"pidfile\";' | osqueryi | grep /var/run/osqueryd.pid");
+  '';
+})
diff --git a/nixos/tests/owncloud.nix b/nixos/tests/owncloud.nix
new file mode 100644
index 000000000000..0dcdea40b064
--- /dev/null
+++ b/nixos/tests/owncloud.nix
@@ -0,0 +1,39 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "owncloud";
+  nodes =
+    { web =
+        { config, pkgs, ... }:
+        {
+          services.postgresql.enable = true;
+          services.httpd = {
+            enable = true;
+            logPerVirtualHost = true;
+            adminAddr = "example@example.com";
+            virtualHosts = [
+              {
+                hostName = "owncloud";
+                extraSubservices =
+                  [
+                    {
+                      serviceType   = "owncloud";
+                      adminPassword = "secret";
+                      dbPassword    = "secret";
+                    }
+                  ];
+              }
+            ];
+          };
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $web->waitForUnit("postgresql");
+    $web->waitForUnit("httpd");
+
+    $web->succeed("curl -L 127.0.0.1:80");
+  '';
+})
diff --git a/nixos/tests/pam-oath-login.nix b/nixos/tests/pam-oath-login.nix
new file mode 100644
index 000000000000..4364d6e354a6
--- /dev/null
+++ b/nixos/tests/pam-oath-login.nix
@@ -0,0 +1,126 @@
+import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
+
+let
+  oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
+
+  # With HOTP mode the password is calculated based on a counter of
+  # how many passwords have been made. In this env, we'll always be on
+  # the 0th counter, so the password is static.
+  #
+  # Generated in nix-shell -p oathToolkit
+  # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
+  # and picking a the first 4:
+  oathSnakeOilPassword1 = "143349";
+  oathSnakeOilPassword2 = "801753";
+  oathSnakeOilPassword3 = "019933";
+  oathSnakeOilPassword4 = "403895";
+
+  alicePassword = "foobar";
+  # Generated via: mkpasswd -m sha-512 and passing in "foobar"
+  hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
+
+in
+{
+  name = "pam-oath-login";
+
+  machine =
+    { config, pkgs, lib, ... }:
+    {
+      security.pam.oath = {
+        enable = true;
+      };
+
+      users.extraUsers.alice = {
+        isNormalUser = true;
+        name = "alice";
+        uid = 1000;
+        hashedPassword = hashedAlicePassword;
+        extraGroups = [ "wheel" ];
+        createHome = true;
+        home = "/home/alice";
+      };
+
+
+      systemd.services.setupOathSnakeoilFile = {
+        wantedBy = [ "default.target" ];
+        before = [ "default.target" ];
+        unitConfig = {
+          type = "oneshot";
+          RemainAfterExit = true;
+        };
+        script = ''
+          touch /etc/users.oath
+          chmod 600 /etc/users.oath
+          chown root /etc/users.oath
+          echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath
+        '';
+      };
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
+      $machine->screenshot("postboot");
+
+
+      subtest "Invalid password", sub {
+        $machine->fail("pgrep -f 'agetty.*tty2'");
+        $machine->sendKeys("alt-f2");
+        $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
+        $machine->waitForUnit('getty@tty2.service');
+        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
+
+        $machine->waitUntilTTYMatches(2, "login: ");
+        $machine->sendChars("alice\n");
+        $machine->waitUntilTTYMatches(2, "login: alice");
+        $machine->waitUntilSucceeds("pgrep login");
+
+        $machine->waitUntilTTYMatches(2, "One-time password");
+        $machine->sendChars("${oathSnakeOilPassword1}\n");
+        $machine->waitUntilTTYMatches(2, "Password: ");
+        $machine->sendChars("blorg\n");
+        $machine->waitUntilTTYMatches(2, "Login incorrect");
+      };
+
+      subtest "Invalid oath token", sub {
+        $machine->fail("pgrep -f 'agetty.*tty3'");
+        $machine->sendKeys("alt-f3");
+        $machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]");
+        $machine->waitForUnit('getty@tty3.service');
+        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'");
+
+        $machine->waitUntilTTYMatches(3, "login: ");
+        $machine->sendChars("alice\n");
+        $machine->waitUntilTTYMatches(3, "login: alice");
+        $machine->waitUntilSucceeds("pgrep login");
+        $machine->waitUntilTTYMatches(3, "One-time password");
+        $machine->sendChars("000000\n");
+        $machine->waitUntilTTYMatches(3, "Login incorrect");
+        $machine->waitUntilTTYMatches(3, "login:");
+      };
+
+      subtest "Happy path (both passwords are mandatory to get us in)", sub {
+        $machine->fail("pgrep -f 'agetty.*tty4'");
+        $machine->sendKeys("alt-f4");
+        $machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]");
+        $machine->waitForUnit('getty@tty4.service');
+        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'");
+
+        $machine->waitUntilTTYMatches(4, "login: ");
+        $machine->sendChars("alice\n");
+        $machine->waitUntilTTYMatches(4, "login: alice");
+        $machine->waitUntilSucceeds("pgrep login");
+        $machine->waitUntilTTYMatches(4, "One-time password");
+        $machine->sendChars("${oathSnakeOilPassword2}\n");
+        $machine->waitUntilTTYMatches(4, "Password: ");
+        $machine->sendChars("${alicePassword}\n");
+
+        $machine->waitUntilSucceeds("pgrep -u alice bash");
+        $machine->sendChars("touch  done4\n");
+        $machine->waitForFile("/home/alice/done4");
+      };
+
+    '';
+
+})
diff --git a/nixos/tests/panamax.nix b/nixos/tests/panamax.nix
deleted file mode 100644
index 088aa79f8c61..000000000000
--- a/nixos/tests/panamax.nix
+++ /dev/null
@@ -1,21 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "panamax";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline ];
-  };
-
-  machine = { config, pkgs, ... }: {
-    services.panamax.enable = true;
-  };
-
-  testScript =
-    ''
-      startAll;
-      $machine->waitForUnit("panamax-api.service");
-      $machine->waitForUnit("panamax-ui.service");
-      $machine->waitForOpenPort(3000);
-      $machine->waitForOpenPort(8888);
-      $machine->succeed("curl --fail http://localhost:8888/ > /dev/null");
-      $machine->shutdown;
-    '';
-})
diff --git a/nixos/tests/partition.nix b/nixos/tests/partition.nix
index 5e94b263d5b8..291d9b278d3b 100644
--- a/nixos/tests/partition.nix
+++ b/nixos/tests/partition.nix
@@ -68,7 +68,7 @@ in {
   machine = { config, pkgs, ... }: {
     environment.systemPackages = [
       pkgs.pythonPackages.nixpart0
-      pkgs.file pkgs.btrfsProgs pkgs.xfsprogs pkgs.lvm2
+      pkgs.file pkgs.btrfs-progs pkgs.xfsprogs pkgs.lvm2
     ];
     virtualisation.emptyDiskImages = [ 4096 4096 ];
   };
diff --git a/nixos/tests/pgjwt.nix b/nixos/tests/pgjwt.nix
new file mode 100644
index 000000000000..d186c42a2a98
--- /dev/null
+++ b/nixos/tests/pgjwt.nix
@@ -0,0 +1,37 @@
+import ./make-test.nix ({ pkgs, lib, ...}:
+let
+  test = with pkgs; runCommand "patch-test" {
+    nativeBuildInputs = [ pgjwt ];
+  }
+  ''
+    sed -e '12 i CREATE EXTENSION pgcrypto;\nCREATE EXTENSION pgtap;\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > $out;
+  '';
+in
+with pkgs; {
+  name = "pgjwt";
+  meta = with lib.maintainers; {
+    maintainers = [ spinus willibutz ];
+  };
+
+  nodes = {
+    master = { pkgs, config, ... }:
+    {
+      services.postgresql = {
+        enable = true;
+        extraPlugins = [ pgjwt pgtap ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+  let
+    sqlSU = "${nodes.master.config.services.postgresql.superUser}";
+    pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
+  in
+  ''
+    startAll;
+    $master->waitForUnit("postgresql");
+    $master->copyFileFromHost("${test}","/tmp/test.sql");
+    $master->succeed("${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql");
+  '';
+})
diff --git a/nixos/tests/pgmanage.nix b/nixos/tests/pgmanage.nix
new file mode 100644
index 000000000000..110cbd5c5b40
--- /dev/null
+++ b/nixos/tests/pgmanage.nix
@@ -0,0 +1,39 @@
+import ./make-test.nix ({ pkgs, ... } :
+let
+  role     = "test";
+  password = "secret";
+  conn     = "local";
+in
+{
+  name = "pgmanage";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ basvandijk ];
+  };
+  nodes = {
+    one = { config, pkgs, ... }: {
+      services = {
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script" ''
+            CREATE ROLE ${role} SUPERUSER LOGIN PASSWORD '${password}';
+          '';
+        };
+        pgmanage = {
+          enable = true;
+          connections = {
+            "${conn}" = "hostaddr=127.0.0.1 port=${toString config.services.postgresql.port} dbname=postgres";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("default.target");
+    $one->requireActiveUnit("pgmanage.service");
+
+    # Test if we can log in.
+    $one->waitUntilSucceeds("curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail");
+  '';
+})
diff --git a/nixos/tests/phabricator.nix b/nixos/tests/phabricator.nix
index 3bf83ab66655..fdc39393faea 100644
--- a/nixos/tests/phabricator.nix
+++ b/nixos/tests/phabricator.nix
@@ -54,7 +54,7 @@ import ./make-test.nix ({ pkgs, ... }: {
     client =
       { config, pkgs, ... }:
       { imports = [ ./common/x11.nix ];
-        services.xserver.desktopManager.kde4.enable = true;
+        services.xserver.desktopManager.plasma5.enable = true;
       };
   };
 
diff --git a/nixos/tests/php-pcre.nix b/nixos/tests/php-pcre.nix
new file mode 100644
index 000000000000..f618a39a2293
--- /dev/null
+++ b/nixos/tests/php-pcre.nix
@@ -0,0 +1,44 @@
+
+let testString = "can-use-subgroups"; in
+
+import ./make-test.nix ({ pkgs, ...}: {
+  name = "php-httpd-pcre-jit-test";
+  machine = { config, lib, pkgs, ... }: {
+    time.timeZone = "UTC";
+    services.httpd = {
+      enable = true;
+      adminAddr = "please@dont.contact";
+      extraSubservices = lib.singleton {
+        function = f: {
+          enablePHP = true;
+          phpOptions = "pcre.jit = true";
+
+          extraConfig =
+          let
+            testRoot = pkgs.writeText "index.php"
+            ''
+              <?php
+                preg_match('/(${testString})/', '${testString}', $result);
+                var_dump($result);
+              ?>
+            '';
+          in
+            ''
+              Alias / ${testRoot}/
+
+              <Directory ${testRoot}>
+                Require all granted
+              </Directory>
+            '';
+        };
+      };
+    };
+  };
+  testScript = { nodes, ... }:
+  ''
+    $machine->waitForUnit('httpd.service');
+    # Ensure php evaluation by matching on the var_dump syntax
+    $machine->succeed('curl -vvv -s http://127.0.0.1:80/index.php \
+      | grep "string(${toString (builtins.stringLength testString)}) \"${testString}\""');
+  '';
+})
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
new file mode 100644
index 000000000000..f3bd4c5915b0
--- /dev/null
+++ b/nixos/tests/plasma5.nix
@@ -0,0 +1,61 @@
+import ./make-test.nix ({ pkgs, ...} :
+
+{
+  name = "plasma5";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ttuegel ];
+  };
+
+  machine = { lib, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    services.xserver.desktopManager.plasma5.enable = true;
+    services.xserver.desktopManager.default = "plasma5";
+    virtualisation.memorySize = 1024;
+
+    # fontconfig-penultimate-0.3.3 -> 0.3.4 broke OCR apparently, but no idea why.
+    nixpkgs.config.packageOverrides = superPkgs: {
+      fontconfig-penultimate = superPkgs.fontconfig-penultimate.override {
+        version = "0.3.3";
+        sha256 = "1z76jbkb0nhf4w7fy647yyayqr4q02fgk6w58k0yi700p0m3h4c9";
+      };
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.extraUsers.alice;
+    xdo = "${pkgs.xdotool}/bin/xdotool";
+  in ''
+    startAll;
+
+    # Wait for display manager to start
+    $machine->waitForText(qr/${user.description}/);
+    $machine->screenshot("sddm");
+
+    # Log in
+    $machine->sendChars("${user.password}\n");
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
+
+    $machine->waitUntilSucceeds("pgrep plasmashell");
+    $machine->waitForWindow("^Desktop ");
+
+    # Check that logging in has given the user ownership of devices.
+    $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
+
+    $machine->execute("su - alice -c 'DISPLAY=:0.0 dolphin &'");
+    $machine->waitForWindow(" Dolphin");
+
+    $machine->execute("su - alice -c 'DISPLAY=:0.0 konsole &'");
+    $machine->waitForWindow("Konsole");
+
+    $machine->execute("su - alice -c 'DISPLAY=:0.0 systemsettings5 &'");
+    $machine->waitForWindow("Settings");
+
+    $machine->execute("${xdo} key Alt+F1 sleep 10");
+    $machine->screenshot("screen");
+  '';
+})
diff --git a/nixos/tests/plotinus.nix b/nixos/tests/plotinus.nix
new file mode 100644
index 000000000000..9058c59c92de
--- /dev/null
+++ b/nixos/tests/plotinus.nix
@@ -0,0 +1,27 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "plotinus";
+  meta = {
+    maintainers = pkgs.plotinus.meta.maintainers;
+  };
+
+  machine =
+    { config, pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      programs.plotinus.enable = true;
+      environment.systemPackages = [ pkgs.gnome3.gnome-calculator pkgs.xdotool ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForX;
+      $machine->succeed("gnome-calculator &");
+      $machine->waitForWindow(qr/gnome-calculator/);
+      $machine->succeed("xdotool search --sync --onlyvisible --class gnome-calculator windowfocus --sync key ctrl+shift+p");
+      $machine->sleep(5); # wait for the popup
+      $machine->succeed("xdotool key --delay 100 p r e f e r e n c e s Return");
+      $machine->waitForWindow(qr/Preferences/);
+      $machine->screenshot("screen");
+    '';
+
+})
diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix
new file mode 100644
index 000000000000..7fe905eb4254
--- /dev/null
+++ b/nixos/tests/postgis.nix
@@ -0,0 +1,26 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "postgis";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lsix ];
+  };
+
+  nodes = {
+    master =
+      { pkgs, config, ... }:
+
+      {
+        services.postgresql = let mypg = pkgs.postgresql100; in {
+            enable = true;
+            package = mypg;
+            extraPlugins = [ (pkgs.postgis.override { postgresql = mypg; }) ];
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $master->waitForUnit("postgresql");
+    $master->sleep(10); # Hopefully this is long enough!!
+    $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'");
+  '';
+})
diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix
new file mode 100644
index 000000000000..0ce37b55bb7b
--- /dev/null
+++ b/nixos/tests/postgresql.nix
@@ -0,0 +1,54 @@
+{ system ? builtins.currentSystem }:
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+let
+  postgresql-versions = pkgs.callPackages ../../pkgs/servers/sql/postgresql { };
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION pgcrypto; -- just to check if lib loading works
+    CREATE TABLE sth (
+      id int
+    );
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    CREATE TABLE xmltest ( doc xml );
+    INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ zagy ];
+    };
+
+    machine = {pkgs, config, ...}:
+      {
+        services.postgresql.package=postgresql-package;
+        services.postgresql.enable = true;
+      };
+
+    testScript = ''
+      sub check_count {
+        my ($select, $nlines) = @_;
+        return 'test $(sudo -u postgres psql postgres -tAc "' . $select . '"|wc -l) -eq ' . $nlines;
+      }
+
+      $machine->start;
+      $machine->waitForUnit("postgresql");
+      # postgresql should be available just after unit start
+      $machine->succeed("cat ${test-sql} | sudo -u postgres psql");
+      $machine->shutdown; # make sure that postgresql survive restart (bug #1735)
+      sleep(2);
+      $machine->start;
+      $machine->waitForUnit("postgresql");
+      $machine->fail(check_count("SELECT * FROM sth;", 3));
+      $machine->succeed(check_count("SELECT * FROM sth;", 5));
+      $machine->fail(check_count("SELECT * FROM sth;", 4));
+      $machine->succeed(check_count("SELECT xpath(\'/test/text()\', doc) FROM xmltest;", 1));
+      $machine->shutdown;
+    '';
+
+  };
+in
+  mapAttrs' (p-name: p-package: {name=p-name; value=make-postgresql-test p-name p-package;}) postgresql-versions
diff --git a/nixos/tests/powerdns.nix b/nixos/tests/powerdns.nix
new file mode 100644
index 000000000000..0d5b0f715f52
--- /dev/null
+++ b/nixos/tests/powerdns.nix
@@ -0,0 +1,12 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "powerdns";
+
+  nodes.server = { config, pkgs, ... }: {
+    services.powerdns.enable = true;
+  };
+
+  testScript = ''
+    $server->waitForUnit("pdns");
+    $server->succeed("${pkgs.dnsutils}/bin/dig version.bind txt chaos \@127.0.0.1");
+  '';
+})
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
new file mode 100644
index 000000000000..0b431034a7a9
--- /dev/null
+++ b/nixos/tests/predictable-interface-names.nix
@@ -0,0 +1,24 @@
+{ system ? builtins.currentSystem }:
+
+let
+  inherit (import ../lib/testing.nix { inherit system; }) makeTest pkgs;
+in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
+  name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
+       + pkgs.lib.optionalString withNetworkd "Networkd";
+  value = makeTest {
+    name = "${if predictable then "" else "un"}predictableInterfaceNames${if withNetworkd then "-with-networkd" else ""}";
+    meta = {};
+
+    machine = { config, lib, ... }: {
+      networking.usePredictableInterfaceNames = lib.mkForce predictable;
+      networking.useNetworkd = withNetworkd;
+      networking.dhcpcd.enable = !withNetworkd;
+    };
+
+    testScript = ''
+      print $machine->succeed("ip link");
+      $machine->succeed("ip link show ${if predictable then "ens3" else "eth0"}");
+      $machine->fail("ip link show ${if predictable then "eth0" else "ens3"}");
+    '';
+  };
+}) [[true false] [true false]])
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index 02980cee2fbd..989008830613 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -3,7 +3,7 @@
 import ./make-test.nix ({pkgs, ... }: {
   name = "printing";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric eelco chaoflow jgeerds ];
+    maintainers = [ domenkozar eelco chaoflow jgeerds ];
   };
 
   nodes = {
@@ -39,7 +39,9 @@ import ./make-test.nix ({pkgs, ... }: {
       $client->waitForUnit("cups.service");
       $client->sleep(10); # wait until cups is fully initialized
       $client->succeed("lpstat -r") =~ /scheduler is running/ or die;
+      # Test that UNIX socket is used for connections.
       $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die;
+      # Test that HTTP server is available too.
       $client->succeed("curl --fail http://localhost:631/");
       $client->succeed("curl --fail http://server:631/");
       $server->fail("curl --fail --connect-timeout 2  http://client:631/");
@@ -62,7 +64,7 @@ import ./make-test.nix ({pkgs, ... }: {
       # Test printing various file types.
       foreach my $file ("${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
                         "${pkgs.groff.doc}/share/doc/*/meref.ps",
-                        "${pkgs.cups}/share/doc/cups/images/cups.png",
+                        "${pkgs.cups.out}/share/doc/cups/images/cups.png",
                         "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt")
       {
           $file =~ /([^\/]*)$/; my $fn = $1;
@@ -78,7 +80,7 @@ import ./make-test.nix ({pkgs, ... }: {
               # (showing that the right filters have been applied).  Of
               # course, since there is no actual USB printer attached, the
               # file will stay in the queue forever.
-              $server->waitForFile("/var/spool/cups/d00001-001");
+              $server->waitForFile("/var/spool/cups/d*-001");
               $server->sleep(10);
               $server->succeed("lpq -a") =~ /$fn/ or die;
 
@@ -90,6 +92,9 @@ import ./make-test.nix ({pkgs, ... }: {
               Machine::retry sub {
                 return 1 if $server->succeed("lpq -a") =~ /no entries/;
               };
+              # The queue is empty already, so this should be safe.
+              # Otherwise, pairs of "c*"-"d*-001" files might persist.
+              $server->execute("rm /var/spool/cups/*");
           };
       }
     '';
diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix
new file mode 100644
index 000000000000..374fb2d634b4
--- /dev/null
+++ b/nixos/tests/prometheus.nix
@@ -0,0 +1,26 @@
+import ./make-test.nix {
+  name = "prometheus";
+
+  nodes = {
+    one = { config, pkgs, ... }: {
+      services.prometheus = {
+        enable = true;
+        scrapeConfigs = [{
+          job_name = "prometheus";
+          static_configs = [{
+            targets = [ "127.0.0.1:9090" ];
+            labels = { instance = "localhost"; };
+          }];
+        }];
+        rules = [ ''testrule = count(up{job="prometheus"})'' ];
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("prometheus.service");
+    $one->waitForOpenPort(9090);
+    $one->succeed("curl -s http://127.0.0.1:9090/metrics");
+  '';
+}
diff --git a/nixos/tests/prosody.nix b/nixos/tests/prosody.nix
new file mode 100644
index 000000000000..fcebfaf74e12
--- /dev/null
+++ b/nixos/tests/prosody.nix
@@ -0,0 +1,75 @@
+import ./make-test.nix {
+  name = "prosody";
+
+  machine = { config, pkgs, ... }: {
+    services.prosody = {
+      enable = true;
+      # TODO: use a self-signed certificate
+      c2sRequireEncryption = false;
+    };
+    environment.systemPackages = let
+      sendMessage = pkgs.writeScriptBin "send-message" ''
+        #!/usr/bin/env python3
+        # Based on the sleekxmpp send_client example, look there for more details:
+        # https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py
+        import sleekxmpp
+
+        class SendMsgBot(sleekxmpp.ClientXMPP):
+            """
+            A basic SleekXMPP bot that will log in, send a message,
+            and then log out.
+            """
+            def __init__(self, jid, password, recipient, message):
+                sleekxmpp.ClientXMPP.__init__(self, jid, password)
+
+                self.recipient = recipient
+                self.msg = message
+
+                self.add_event_handler("session_start", self.start, threaded=True)
+
+            def start(self, event):
+                self.send_presence()
+                self.get_roster()
+
+                self.send_message(mto=self.recipient,
+                                  mbody=self.msg,
+                                  mtype='chat')
+
+                self.disconnect(wait=True)
+
+
+        if __name__ == '__main__':
+            xmpp = SendMsgBot("test1@localhost", "test1", "test2@localhost", "Hello World!")
+            xmpp.register_plugin('xep_0030') # Service Discovery
+            xmpp.register_plugin('xep_0199') # XMPP Ping
+
+            # TODO: verify certificate
+            # If you want to verify the SSL certificates offered by a server:
+            # xmpp.ca_certs = "path/to/ca/cert"
+
+            if xmpp.connect(('localhost', 5222)):
+                xmpp.process(block=True)
+            else:
+                print("Unable to connect.")
+                sys.exit(1)
+      '';
+    in [ (pkgs.python3.withPackages (ps: [ ps.sleekxmpp ])) sendMessage ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit('prosody.service');
+    $machine->succeed('prosodyctl status') =~ /Prosody is running/;
+
+    # set password to 'test' (it's asked twice)
+    $machine->succeed('yes test1 | prosodyctl adduser test1@localhost');
+    # set password to 'y'
+    $machine->succeed('yes | prosodyctl adduser test2@localhost');
+    # correct password to 'test2'
+    $machine->succeed('yes test2 | prosodyctl passwd test2@localhost');
+
+    $machine->succeed("send-message");
+
+    $machine->succeed('prosodyctl deluser test1@localhost');
+    $machine->succeed('prosodyctl deluser test2@localhost');
+  '';
+}
diff --git a/nixos/tests/quagga.nix b/nixos/tests/quagga.nix
new file mode 100644
index 000000000000..613180942c41
--- /dev/null
+++ b/nixos/tests/quagga.nix
@@ -0,0 +1,97 @@
+# This test runs Quagga and checks if OSPF routing works.
+#
+# Network topology:
+#   [ client ]--net1--[ router1 ]--net2--[ router2 ]--net3--[ server ]
+#
+# All interfaces are in OSPF Area 0.
+
+import ./make-test.nix ({ pkgs, ... }:
+  let
+
+    ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address;
+
+    ospfConf = ''
+      interface eth2
+        ip ospf hello-interval 1
+        ip ospf dead-interval 5
+      !
+      router ospf
+        network 192.168.0.0/16 area 0
+    '';
+
+  in
+    {
+      name = "quagga";
+
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ tavyc ];
+      };
+
+      nodes = {
+
+        client =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 1 ];
+            networking.defaultGateway = ifAddr nodes.router1 "eth1";
+          };
+
+        router1 =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 1 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospf -j ACCEPT";
+            services.quagga.ospf = {
+              enable = true;
+              config = ospfConf;
+            };
+          };
+
+        router2 =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 3 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospf -j ACCEPT";
+            services.quagga.ospf = {
+              enable = true;
+              config = ospfConf;
+            };
+          };
+
+        server =
+          { config, pkgs, nodes, ... }:
+          {
+            virtualisation.vlans = [ 3 ];
+            networking.defaultGateway = ifAddr nodes.router2 "eth1";
+            networking.firewall.allowedTCPPorts = [ 80 ];
+            networking.firewall.allowPing = true;
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.com";
+          };
+      };
+
+      testScript =
+        { nodes, ... }:
+        ''
+          startAll;
+
+          # Wait for the networking to start on all machines
+          $_->waitForUnit("network.target") foreach values %vms;
+
+          # Wait for OSPF to form adjacencies
+          for my $gw ($router1, $router2) {
+              $gw->waitForUnit("ospfd");
+              $gw->waitUntilSucceeds("vtysh -c 'show ip ospf neighbor' | grep Full");
+              $gw->waitUntilSucceeds("vtysh -c 'show ip route' | grep '^O>'");
+          }
+
+          # Test ICMP.
+          $client->succeed("ping -c 3 server >&2");
+
+          # Test whether HTTP works.
+          $server->waitForUnit("httpd");
+          $client->succeed("curl --fail http://server/ >&2");
+        '';
+    })
diff --git a/nixos/tests/quake3.nix b/nixos/tests/quake3.nix
index c72d94e11a8d..22d71595cb48 100644
--- a/nixos/tests/quake3.nix
+++ b/nixos/tests/quake3.nix
@@ -10,12 +10,19 @@ let
       });
     };
 
+  # Only allow the demo data to be used (only if it's unfreeRedistributable).
+  unfreePredicate = pkg: with pkgs.lib; let
+    allowDrvPredicates = [ "quake3-demo" "quake3-pointrelease" ];
+    allowLicenses = [ pkgs.lib.licenses.unfreeRedistributable ];
+  in any (flip hasPrefix pkg.name) allowDrvPredicates &&
+     elem (pkg.meta.license or null) allowLicenses;
+
 in
 
 rec {
   name = "quake3";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ iElectric eelco chaoflow ];
+    maintainers = [ domenkozar eelco chaoflow ];
   };
 
   # TODO: lcov doesn't work atm
@@ -28,19 +35,21 @@ rec {
       hardware.opengl.driSupport = true;
       environment.systemPackages = [ pkgs.quake3demo ];
       nixpkgs.config.packageOverrides = overrides;
+      nixpkgs.config.allowUnfreePredicate = unfreePredicate;
     };
 
   nodes =
     { server =
         { config, pkgs, ... }:
 
-        { jobs."quake3-server" =
-            { startOn = "startup";
-              exec =
-                "${pkgs.quake3demo}/bin/quake3-server '+set g_gametype 0' " +
-                "'+map q3dm7' '+addbot grunt' '+addbot daemia' 2> /tmp/log";
+        { systemd.services."quake3-server" =
+            { wantedBy = [ "multi-user.target" ];
+              script =
+                "${pkgs.quake3demo}/bin/quake3-server +set g_gametype 0 " +
+                "+map q3dm7 +addbot grunt +addbot daemia 2> /tmp/log";
             };
           nixpkgs.config.packageOverrides = overrides;
+          nixpkgs.config.allowUnfreePredicate = unfreePredicate;
           networking.firewall.allowedUDPPorts = [ 27960 ];
         };
 
@@ -56,8 +65,8 @@ rec {
       $client1->waitForX;
       $client2->waitForX;
 
-      $client1->execute("quake3 '+set r_fullscreen 0' '+set name Foo' '+connect server' &");
-      $client2->execute("quake3 '+set r_fullscreen 0' '+set name Bar' '+connect server' &");
+      $client1->execute("quake3 +set r_fullscreen 0 +set name Foo +connect server &");
+      $client2->execute("quake3 +set r_fullscreen 0 +set name Bar +connect server &");
 
       $server->waitUntilSucceeds("grep -q 'Foo.*entered the game' /tmp/log");
       $server->waitUntilSucceeds("grep -q 'Bar.*entered the game' /tmp/log");
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
new file mode 100644
index 000000000000..e38430385415
--- /dev/null
+++ b/nixos/tests/radicale.nix
@@ -0,0 +1,106 @@
+let
+  user = "someuser";
+  password = "some_password";
+  port = builtins.toString 5232;
+
+  common = { pkgs, ... }: {
+    services.radicale = {
+      enable = true;
+      config = ''
+        [auth]
+        type = htpasswd
+        htpasswd_filename = /etc/radicale/htpasswd
+        htpasswd_encryption = bcrypt
+
+        [storage]
+        filesystem_folder = /tmp/collections
+
+        [logging]
+        debug = True
+      '';
+    };
+    # WARNING: DON'T DO THIS IN PRODUCTION!
+    # This puts unhashed secrets directly into the Nix store for ease of testing.
+    environment.etc."radicale/htpasswd".source = pkgs.runCommand "htpasswd" {} ''
+      ${pkgs.apacheHttpd}/bin/htpasswd -bcB "$out" ${user} ${password}
+    '';
+  };
+
+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 ];
+            });
+          })
+        ];
+        system.nixos.stateVersion = "17.03";
+      };
+      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.nixos.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/riak.nix b/nixos/tests/riak.nix
index f36d12bdb2cf..18d028232ac2 100644
--- a/nixos/tests/riak.nix
+++ b/nixos/tests/riak.nix
@@ -7,7 +7,7 @@ import ./make-test.nix {
 
       {
         services.riak.enable = true;
-        services.riak.package = pkgs.riak2;
+        services.riak.package = pkgs.riak;
       };
   };
 
diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix
new file mode 100644
index 000000000000..6b2e2dd3a531
--- /dev/null
+++ b/nixos/tests/rspamd.nix
@@ -0,0 +1,140 @@
+{ system ? builtins.currentSystem }:
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+let
+  initMachine = ''
+    startAll
+    $machine->waitForUnit("rspamd.service");
+    $machine->succeed("id \"rspamd\" >/dev/null");
+  '';
+  checkSocket = socket: user: group: mode: ''
+    $machine->succeed("ls ${socket} >/dev/null");
+    $machine->succeed("[[ \"\$(stat -c %U ${socket})\" == \"${user}\" ]]");
+    $machine->succeed("[[ \"\$(stat -c %G ${socket})\" == \"${group}\" ]]");
+    $machine->succeed("[[ \"\$(stat -c %a ${socket})\" == \"${mode}\" ]]");
+  '';
+  simple = name: socketActivation: enableIPv6: makeTest {
+    name = "rspamd-${name}";
+    machine = {
+      services.rspamd = {
+        enable = true;
+        socketActivation = socketActivation;
+      };
+      networking.enableIPv6 = enableIPv6;
+    };
+    testScript = ''
+      startAll
+      $machine->waitForUnit("multi-user.target");
+      $machine->waitForOpenPort(11334);
+      $machine->waitForUnit("rspamd.service");
+      $machine->succeed("id \"rspamd\" >/dev/null");
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
+      sleep 10;
+      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
+      $machine->log($machine->succeed("systemctl cat rspamd.service"));
+      ${if socketActivation then ''
+        $machine->log($machine->succeed("systemctl cat rspamd-controller-1.socket"));
+        $machine->log($machine->succeed("systemctl cat rspamd-normal-1.socket"));
+      '' else ''
+        $machine->fail("systemctl cat rspamd-controller-1.socket");
+        $machine->fail("systemctl cat rspamd-normal-1.socket");
+      ''}
+      $machine->log($machine->succeed("curl http://localhost:11334/auth"));
+      $machine->log($machine->succeed("curl http://127.0.0.1:11334/auth"));
+      ${optionalString enableIPv6 ''
+        $machine->log($machine->succeed("curl http://[::1]:11334/auth"));
+      ''}
+    '';
+  };
+in
+{
+  simple = simple "simple" false true;
+  ipv4only = simple "ipv4only" false false;
+  simple-socketActivated = simple "simple-socketActivated" true true;
+  ipv4only-socketActivated = simple "ipv4only-socketActivated" true false;
+  deprecated = makeTest {
+    name = "rspamd-deprecated";
+    machine = {
+      services.rspamd = {
+        enable = true;
+        bindSocket = [ "/run/rspamd.sock mode=0600 user=root group=root" ];
+        bindUISocket = [ "/run/rspamd-worker.sock mode=0666 user=root group=root" ];
+      };
+    };
+
+    testScript = ''
+      ${initMachine}
+      $machine->waitForFile("/run/rspamd.sock");
+      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
+      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
+      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
+      $machine->fail("systemctl cat rspamd-normal-1.socket");
+      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+    '';
+  };
+
+  bindports = makeTest {
+    name = "rspamd-bindports";
+    machine = {
+      services.rspamd = {
+        enable = true;
+        socketActivation = false;
+        workers.normal.bindSockets = [{
+          socket = "/run/rspamd.sock";
+          mode = "0600";
+          owner = "root";
+          group = "root";
+        }];
+        workers.controller.bindSockets = [{
+          socket = "/run/rspamd-worker.sock";
+          mode = "0666";
+          owner = "root";
+          group = "root";
+        }];
+      };
+    };
+
+    testScript = ''
+      ${initMachine}
+      $machine->waitForFile("/run/rspamd.sock");
+      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
+      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
+      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
+      $machine->fail("systemctl cat rspamd-normal-1.socket");
+      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+    '';
+  };
+  socketActivated = makeTest {
+    name = "rspamd-socketActivated";
+    machine = {
+      services.rspamd = {
+        enable = true;
+        workers.normal.bindSockets = [{
+          socket = "/run/rspamd.sock";
+          mode = "0600";
+          owner = "root";
+          group = "root";
+        }];
+        workers.controller.bindSockets = [{
+          socket = "/run/rspamd-worker.sock";
+          mode = "0666";
+          owner = "root";
+          group = "root";
+        }];
+      };
+    };
+
+    testScript = ''
+      startAll
+      $machine->waitForFile("/run/rspamd.sock");
+      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
+      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
+      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
+      $machine->log($machine->succeed("systemctl cat rspamd-normal-1.socket"));
+      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+    '';
+  };
+}
diff --git a/nixos/tests/run-in-machine.nix b/nixos/tests/run-in-machine.nix
index d1102f8d4073..a6dfece44a92 100644
--- a/nixos/tests/run-in-machine.nix
+++ b/nixos/tests/run-in-machine.nix
@@ -2,7 +2,16 @@
 
 with import ../lib/testing.nix { inherit system; };
 
-runInMachine {
-  drv = pkgs.hello;
-  machine = { config, pkgs, ... }: { /* services.sshd.enable = true; */ };
-}
+let
+  output = runInMachine {
+    drv = pkgs.hello;
+    machine = { config, pkgs, ... }: { /* services.sshd.enable = true; */ };
+  };
+in pkgs.runCommand "verify-output" { inherit output; } ''
+  if [ ! -e "$output/bin/hello" ]; then
+    echo "Derivation built using runInMachine produced incorrect output:" >&2
+    ls -laR "$output" >&2
+    exit 1
+  fi
+  "$output/bin/hello" > "$out"
+''
diff --git a/nixos/tests/rxe.nix b/nixos/tests/rxe.nix
new file mode 100644
index 000000000000..cfe64a75a635
--- /dev/null
+++ b/nixos/tests/rxe.nix
@@ -0,0 +1,53 @@
+import ./make-test.nix ({ pkgs, ... } :
+
+let
+  node = { config, pkgs, lib, ... } : {
+    networking = {
+      firewall = {
+        allowedUDPPorts = [ 4791 ]; # open RoCE port
+        allowedTCPPorts = [ 4800 ]; # port for test utils
+      };
+      rxe = {
+        enable = true;
+        interfaces = [ "eth1" ];
+      };
+    };
+
+    environment.systemPackages = with pkgs; [ rdma-core screen ];
+  };
+
+in {
+  name = "rxe";
+
+  nodes = {
+    server = node;
+    client = node;
+  };
+
+  testScript = ''
+    # Test if rxe interface comes up
+    $server->waitForUnit("default.target");
+    $server->succeed("systemctl status rxe.service");
+    $server->succeed("ibv_devices | grep rxe0");
+
+    $client->waitForUnit("default.target");
+
+    # ping pong test
+    $server->succeed("screen -dmS rc_pingpong ibv_rc_pingpong -p 4800 -g0");
+    $client->succeed("sleep 2; ibv_rc_pingpong -p 4800 -g0 server");
+
+    $server->succeed("screen -dmS uc_pingpong ibv_uc_pingpong -p 4800 -g0");
+    $client->succeed("sleep 2; ibv_uc_pingpong -p 4800 -g0 server");
+
+    $server->succeed("screen -dmS ud_pingpong ibv_ud_pingpong -p 4800 -s 1024 -g0");
+    $client->succeed("sleep 2; ibv_ud_pingpong -p 4800 -s 1024 -g0 server");
+
+    $server->succeed("screen -dmS srq_pingpong ibv_srq_pingpong -p 4800 -g0");
+    $client->succeed("sleep 2; ibv_srq_pingpong -p 4800 -g0 server");
+
+    $server->succeed("screen -dmS rping rping -s -a server -C 10");
+    $client->succeed("sleep 2; rping -c -a server -C 10");
+  '';
+})
+
+
diff --git a/nixos/tests/samba.nix b/nixos/tests/samba.nix
new file mode 100644
index 000000000000..e446284fc0ef
--- /dev/null
+++ b/nixos/tests/samba.nix
@@ -0,0 +1,47 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "samba";
+
+  meta.maintainers = [ pkgs.lib.maintainers.eelco ];
+
+  nodes =
+    { client =
+        { config, pkgs, ... }:
+        { fileSystems = pkgs.lib.mkVMOverride
+            { "/public" = {
+                fsType = "cifs";
+                device = "//server/public";
+                options = [ "guest" ];
+              };
+            };
+        };
+
+      server =
+        { config, pkgs, ... }:
+        { services.samba.enable = true;
+          services.samba.shares.public =
+            { path = "/public";
+              "read only" = true;
+              browseable = "yes";
+              "guest ok" = "yes";
+              comment = "Public samba share.";
+            };
+          networking.firewall.allowedTCPPorts = [ 139 445 ];
+          networking.firewall.allowedUDPPorts = [ 137 138 ];
+        };
+    };
+
+  # client# [    4.542997] mount[777]: sh: systemd-ask-password: command not found
+
+  testScript =
+    ''
+      $server->start;
+      $server->waitForUnit("samba.target");
+      $server->succeed("mkdir -p /public; echo bar > /public/foo");
+
+      $client->start;
+      $client->waitForUnit("remote-fs.target");
+      $client->succeed("[[ \$(cat /public/foo) = bar ]]");
+    '';
+})
diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
new file mode 100644
index 000000000000..1ce2b8157842
--- /dev/null
+++ b/nixos/tests/sddm.nix
@@ -0,0 +1,66 @@
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+
+let
+  inherit (pkgs) lib;
+
+  tests = {
+    default = {
+      name = "sddm";
+
+      machine = { lib, ... }: {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sddm.enable = true;
+        services.xserver.windowManager.default = "icewm";
+        services.xserver.windowManager.icewm.enable = true;
+        services.xserver.desktopManager.default = "none";
+      };
+
+      enableOCR = true;
+
+      testScript = { nodes, ... }: let
+        user = nodes.machine.config.users.extraUsers.alice;
+      in ''
+        startAll;
+        $machine->waitForText(qr/select your user/i);
+        $machine->screenshot("sddm");
+        $machine->sendChars("${user.password}\n");
+        $machine->waitForFile("/home/alice/.Xauthority");
+        $machine->succeed("xauth merge ~alice/.Xauthority");
+        $machine->waitForWindow("^IceWM ");
+      '';
+    };
+
+    autoLogin = {
+      name = "sddm-autologin";
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ ttuegel ];
+      };
+
+      machine = { lib, ... }: {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sddm = {
+          enable = true;
+          autoLogin = {
+            enable = true;
+            user = "alice";
+          };
+        };
+        services.xserver.windowManager.default = "icewm";
+        services.xserver.windowManager.icewm.enable = true;
+        services.xserver.desktopManager.default = "none";
+      };
+
+      testScript = { nodes, ... }: ''
+        startAll;
+        $machine->waitForFile("/home/alice/.Xauthority");
+        $machine->succeed("xauth merge ~alice/.Xauthority");
+        $machine->waitForWindow("^IceWM ");
+      '';
+    };
+  };
+in
+  lib.mapAttrs (lib.const makeTest) tests
diff --git a/nixos/tests/simple.nix b/nixos/tests/simple.nix
index 287712be316d..04d624adcfe9 100644
--- a/nixos/tests/simple.nix
+++ b/nixos/tests/simple.nix
@@ -4,7 +4,9 @@ import ./make-test.nix ({ pkgs, ...} : {
     maintainers = [ eelco ];
   };
 
-  machine = { config, pkgs, ... }: { };
+  machine = { config, pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+  };
 
   testScript =
     ''
diff --git a/nixos/tests/slim.nix b/nixos/tests/slim.nix
new file mode 100644
index 000000000000..7b939d836381
--- /dev/null
+++ b/nixos/tests/slim.nix
@@ -0,0 +1,66 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "slim";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  machine = { pkgs, lib, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.windowManager.default = "icewm";
+    services.xserver.windowManager.icewm.enable = true;
+    services.xserver.desktopManager.default = "none";
+    services.xserver.displayManager.slim = {
+      enable = true;
+
+      # Use a custom theme in order to get best OCR results
+      theme = pkgs.runCommand "slim-theme-ocr" {
+        nativeBuildInputs = [ pkgs.imagemagick ];
+      } ''
+        mkdir "$out"
+        convert -size 1x1 xc:white "$out/background.jpg"
+        convert -size 200x100 xc:white "$out/panel.jpg"
+        cat > "$out/slim.theme" <<EOF
+        background_color #ffffff
+        background_style tile
+
+        input_fgcolor #000000
+        msg_color #000000
+
+        session_color #000000
+        session_font Verdana:size=16:bold
+
+        username_msg Username:
+        username_font Verdana:size=16:bold
+        username_color #000000
+        username_x 50%
+        username_y 40%
+
+        password_msg Password:
+        password_x 50%
+        password_y 40%
+        EOF
+      '';
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.extraUsers.alice;
+  in ''
+    startAll;
+    $machine->waitForText(qr/Username:/);
+    $machine->sendChars("${user.name}\n");
+    $machine->waitForText(qr/Password:/);
+    $machine->sendChars("${user.password}\n");
+
+    $machine->waitForFile('${user.home}/.Xauthority');
+    $machine->succeed('xauth merge ${user.home}/.Xauthority');
+    $machine->waitForWindow('^IceWM ');
+
+    # Make sure SLiM doesn't create a log file
+    $machine->fail('test -e /var/log/slim.log');
+  '';
+})
diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix
new file mode 100644
index 000000000000..ec67ea092874
--- /dev/null
+++ b/nixos/tests/slurm.nix
@@ -0,0 +1,92 @@
+import ./make-test.nix ({ pkgs, ... }:
+let mungekey = "mungeverryweakkeybuteasytointegratoinatest";
+    slurmconfig = {
+      controlMachine = "control";
+      nodeName = ''
+        control
+        NodeName=node[1-3] CPUs=1 State=UNKNOWN
+      '';
+      partitionName = "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP";
+    };
+in {
+  name = "slurm";
+
+  nodes =
+    let
+    computeNode =
+      { config, pkgs, ...}:
+      {
+        # TODO slrumd port and slurmctld port should be configurations and
+        # automatically allowed by the  firewall.
+        networking.firewall.enable = false;
+        services.slurm = {
+          client.enable = true;
+        } // slurmconfig;
+      };
+    in {
+
+    control =
+      { config, pkgs, ...}:
+      {
+        networking.firewall.enable = false;
+        services.slurm = {
+          server.enable = true;
+        } // slurmconfig;
+      };
+
+    submit =
+      { config, pkgs, ...}:
+      {
+        networking.firewall.enable = false;
+        services.slurm = {
+          enableStools = true;
+        } // slurmconfig;
+      };
+
+    node1 = computeNode;
+    node2 = computeNode;
+    node3 = computeNode;
+  };
+
+
+  testScript =
+  ''
+  startAll;
+
+  # Set up authentification across the cluster
+  foreach my $node (($submit,$control,$node1,$node2,$node3))
+  {
+    $node->waitForUnit("default.target");
+
+    $node->succeed("mkdir /etc/munge");
+    $node->succeed("echo '${mungekey}' > /etc/munge/munge.key");
+    $node->succeed("chmod 0400 /etc/munge/munge.key");
+    $node->succeed("chown munge:munge /etc/munge/munge.key");
+    $node->succeed("systemctl restart munged");
+  }
+
+  # Restart the services since they have probably failed due to the munge init
+  # failure
+
+  subtest "can_start_slurmctld", sub {
+    $control->succeed("systemctl restart slurmctld");
+    $control->waitForUnit("slurmctld.service");
+  };
+
+  subtest "can_start_slurmd", sub {
+    foreach my $node (($node1,$node2,$node3))
+    {
+      $node->succeed("systemctl restart slurmd.service");
+      $node->waitForUnit("slurmd");
+    }
+  };
+
+  # Test that the cluster work and can distribute jobs;
+
+  subtest "run_distributed_command", sub {
+    # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes).
+    # The output must contain the 3 different names
+    $submit->succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq");
+  };
+  '';
+})
diff --git a/nixos/tests/smokeping.nix b/nixos/tests/smokeping.nix
new file mode 100644
index 000000000000..4c77e4b78613
--- /dev/null
+++ b/nixos/tests/smokeping.nix
@@ -0,0 +1,33 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "smokeping";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ cransom ];
+  };
+
+  nodes = {
+    sm =
+      { pkgs, config, ... }:
+      {
+        services.smokeping = {
+          enable = true;
+          port = 8081;
+          mailHost = "127.0.0.2";
+          probeConfig = ''
+            + FPing
+            binary = /run/wrappers/bin/fping
+            offset = 0%
+          '';
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $sm->waitForUnit("smokeping");
+    $sm->waitForUnit("thttpd");
+    $sm->waitForFile("/var/lib/smokeping/data/Local/LocalMachine.rrd");
+    $sm->succeed("curl -s -f localhost:8081/smokeping.fcgi?target=Local");
+    $sm->succeed("ls /var/lib/smokeping/cache/Local/LocalMachine_mini.png");
+    $sm->succeed("ls /var/lib/smokeping/cache/index.html");
+  '';
+})
diff --git a/nixos/tests/snapper.nix b/nixos/tests/snapper.nix
new file mode 100644
index 000000000000..74ec22fd3499
--- /dev/null
+++ b/nixos/tests/snapper.nix
@@ -0,0 +1,43 @@
+import ./make-test.nix ({ ... }:
+{
+  name = "snapper";
+
+  machine = { pkgs, lib, ... }: {
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 4096 ];
+
+    fileSystems = lib.mkVMOverride {
+      "/home" = {
+        device = "/dev/disk/by-label/aux";
+        fsType = "btrfs";
+      };
+    };
+    services.snapper.configs.home.subvolume = "/home";
+    services.snapper.filters = "/nix";
+  };
+
+  testScript = ''
+    $machine->succeed("btrfs subvolume create /home/.snapshots");
+
+    $machine->succeed("snapper -c home list");
+
+    $machine->succeed("snapper -c home create --description empty");
+
+    $machine->succeed("echo test > /home/file");
+    $machine->succeed("snapper -c home create --description file");
+
+    $machine->succeed("snapper -c home status 1..2");
+
+    $machine->succeed("snapper -c home undochange 1..2");
+    $machine->fail("ls /home/file");
+
+    $machine->succeed("snapper -c home delete 2");
+
+    $machine->succeed("systemctl --wait start snapper-timeline.service");
+
+    $machine->succeed("systemctl --wait start snapper-cleanup.service");
+  '';
+})
diff --git a/nixos/tests/ssh-keys.nix b/nixos/tests/ssh-keys.nix
new file mode 100644
index 000000000000..07d422196efa
--- /dev/null
+++ b/nixos/tests/ssh-keys.nix
@@ -0,0 +1,15 @@
+pkgs:
+{ snakeOilPrivateKey = pkgs.writeText "privkey.snakeoil" ''
+    -----BEGIN EC PRIVATE KEY-----
+    MHcCAQEEIHQf/khLvYrQ8IOika5yqtWvI0oquHlpRLTZiJy5dRJmoAoGCCqGSM49
+    AwEHoUQDQgAEKF0DYGbBwbj06tA3fd/+yP44cvmwmHBWXZCKbS+RQlAKvLXMWkpN
+    r1lwMyJZoSGgBHoUahoYjTh9/sJL7XLJtA==
+    -----END EC PRIVATE KEY-----
+  '';
+
+  snakeOilPublicKey = pkgs.lib.concatStrings [
+    "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHA"
+    "yNTYAAABBBChdA2BmwcG49OrQN33f/sj+OHL5sJhwVl2Qim0vkUJQCry1zFpKTa"
+    "9ZcDMiWaEhoAR6FGoaGI04ff7CS+1yybQ= sakeoil"
+  ];
+}
diff --git a/nixos/tests/statsd.nix b/nixos/tests/statsd.nix
new file mode 100644
index 000000000000..666961249ced
--- /dev/null
+++ b/nixos/tests/statsd.nix
@@ -0,0 +1,51 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "statsd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  machine = {
+    services.statsd.enable = true;
+    services.statsd.backends = [ "statsd-influxdb-backend" "console" ];
+    services.statsd.extraConfig = ''
+      influxdb: {
+        username: "root",
+        password: "root",
+        database: "statsd"
+      }
+    '';
+
+    services.influxdb.enable = true;
+
+    systemd.services.influx-init = {
+      description = "Setup Influx Test Base";
+      after = [ "influxdb.service" ];
+      before = [ "statsd.service" ];
+
+      script = ''
+        echo "CREATE DATABASE statsd" | ${pkgs.influxdb}/bin/influx
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->start();
+    $machine->waitForUnit("statsd.service");
+    $machine->waitForOpenPort(8126);
+
+    # check state of the `statsd` server
+    $machine->succeed('[ "health: up" = "$(echo health | nc 127.0.0.1 8126 -w 120 -N)" ];');
+
+    # confirm basic examples for metrics derived from docs:
+    # https://github.com/etsy/statsd/blob/v0.8.0/README.md#usage and
+    # https://github.com/etsy/statsd/blob/v0.8.0/docs/admin_interface.md
+    $machine->succeed("echo 'foo:1|c' | nc -u -w 0  127.0.0.1 8125");
+    $machine->succeed("echo counters | nc -w 120 127.0.0.1 8126 -N | grep foo");
+    $machine->succeed("echo 'delcounters foo' | nc -w 120 127.0.0.1 8126 -N");
+    $machine->fail("echo counters | nc -w 120 127.0.0.1 8126 -N | grep foo");
+  '';
+})
diff --git a/nixos/tests/strongswan-swanctl.nix b/nixos/tests/strongswan-swanctl.nix
new file mode 100644
index 000000000000..021743021b40
--- /dev/null
+++ b/nixos/tests/strongswan-swanctl.nix
@@ -0,0 +1,148 @@
+# This strongswan-swanctl test is based on:
+# https://www.strongswan.org/testing/testresults/swanctl/rw-psk-ipv4/index.html
+# https://github.com/strongswan/strongswan/tree/master/testing/tests/swanctl/rw-psk-ipv4
+#
+# The roadwarrior carol sets up a connection to gateway moon. The authentication
+# is based on pre-shared keys and IPv4 addresses. Upon the successful
+# establishment of the IPsec tunnels, the specified updown script automatically
+# inserts iptables-based firewall rules that let pass the tunneled traffic. In
+# order to test both tunnel and firewall, carol pings the client alice behind
+# the gateway moon.
+#
+#     alice                       moon                        carol
+#      eth1------vlan_0------eth1        eth2------vlan_1------eth1
+#   192.168.0.1         192.168.0.3  192.168.1.3           192.168.1.2
+#
+# See the NixOS manual for how to run this test:
+# https://nixos.org/nixos/manual/index.html#sec-running-nixos-tests-interactively
+
+import ./make-test.nix ({ pkgs, ...} :
+
+let
+  allowESP = "iptables --insert INPUT --protocol ESP --jump ACCEPT";
+
+  # Shared VPN settings:
+  vlan0         = "192.168.0.0/24";
+  carolIp       = "192.168.1.2";
+  moonIp        = "192.168.1.3";
+  version       = 2;
+  secret        = "0sFpZAZqEN6Ti9sqt4ZP5EWcqx";
+  esp_proposals = [ "aes128gcm128-x25519" ];
+  proposals     = [ "aes128-sha256-x25519" ];
+in {
+  name = "strongswan-swanctl";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ basvandijk ];
+  nodes = {
+
+    alice = { nodes, ... } : {
+      virtualisation.vlans = [ 0 ];
+      networking = {
+        dhcpcd.enable = false;
+        defaultGateway = "192.168.0.3";
+      };
+    };
+
+    moon = {pkgs, config, nodes, ...} :
+      let strongswan = config.services.strongswan-swanctl.package;
+      in {
+        virtualisation.vlans = [ 0 1 ];
+        networking = {
+          dhcpcd.enable = false;
+          firewall = {
+            allowedUDPPorts = [ 4500 500 ];
+            extraCommands = allowESP;
+          };
+          nat = {
+            enable             = true;
+            internalIPs        = [ vlan0 ];
+            internalInterfaces = [ "eth1" ];
+            externalIP         = moonIp;
+            externalInterface  = "eth2";
+          };
+        };
+        environment.systemPackages = [ strongswan ];
+        services.strongswan-swanctl = {
+          enable = true;
+          swanctl = {
+            connections = {
+              "rw" = {
+                local_addrs = [ moonIp ];
+                local."main" = {
+                  auth = "psk";
+                };
+                remote."main" = {
+                  auth = "psk";
+                };
+                children = {
+                  "net" = {
+                    local_ts = [ vlan0 ];
+                    updown = "${strongswan}/libexec/ipsec/_updown iptables";
+                    inherit esp_proposals;
+                  };
+                };
+                inherit version;
+                inherit proposals;
+              };
+            };
+            secrets = {
+              ike."carol" = {
+                id."main" = carolIp;
+                inherit secret;
+              };
+            };
+          };
+        };
+      };
+
+    carol = {pkgs, config, nodes, ...} :
+      let strongswan = config.services.strongswan-swanctl.package;
+      in {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          dhcpcd.enable = false;
+          firewall.extraCommands = allowESP;
+        };
+        environment.systemPackages = [ strongswan ];
+        services.strongswan-swanctl = {
+          enable = true;
+          swanctl = {
+            connections = {
+              "home" = {
+                local_addrs = [ carolIp ];
+                remote_addrs = [ moonIp ];
+                local."main" = {
+                  auth = "psk";
+                  id = carolIp;
+                };
+                remote."main" = {
+                  auth = "psk";
+                  id = moonIp;
+                };
+                children = {
+                  "home" = {
+                    remote_ts = [ vlan0 ];
+                    start_action = "trap";
+                    updown = "${strongswan}/libexec/ipsec/_updown iptables";
+                    inherit esp_proposals;
+                  };
+                };
+                inherit version;
+                inherit proposals;
+              };
+            };
+            secrets = {
+              ike."moon" = {
+                id."main" = moonIp;
+                inherit secret;
+              };
+            };
+          };
+        };
+      };
+
+  };
+  testScript = ''
+    startAll();
+    $carol->waitUntilSucceeds("ping -c 1 alice");
+  '';
+})
diff --git a/nixos/tests/sudo.nix b/nixos/tests/sudo.nix
new file mode 100644
index 000000000000..35addb0ee805
--- /dev/null
+++ b/nixos/tests/sudo.nix
@@ -0,0 +1,93 @@
+# Some tests to ensure sudo is working properly.
+
+let
+  password = "helloworld";
+
+in
+  import ./make-test.nix ({ pkgs, ...} : {
+    name = "sudo";
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ lschuermann ];
+    };
+
+    machine =
+      { config, lib, pkgs, ... }:
+      with lib;
+      {
+        users.extraGroups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
+        users.users = {
+          test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+          test1 = { isNormalUser = true; password = password; };
+          test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; password = password; };
+          test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
+          test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
+          test5 = { isNormalUser = true; };
+        };
+
+        security.sudo = {
+          enable = true;
+          wheelNeedsPassword = false;
+
+          extraRules = [
+            # SUDOERS SYNTAX CHECK (Test whether the module produces a valid output;
+            # errors being detected by the visudo checks.
+
+            # These should not create any entries
+            { users = [ "notest1" ]; commands = [ ]; }
+            { commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+            # Test defining commands with the options syntax, though not setting any options
+            { users = [ "notest2" ]; commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+
+            # CONFIGURATION FOR TEST CASES
+            { users = [ "test1" ]; groups = [ "foobar" ]; commands = [ "ALL" ]; }
+            { groups = [ "barfoo" 1337 ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" "NOSETENV" ]; } ]; }
+            { users = [ "test5" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" "SETENV" ]; } ]; runAs = "test1:barfoo"; }
+          ];
+        };
+      };
+
+    testScript =
+      ''
+        subtest "users in wheel group should have passwordless sudo", sub {
+            $machine->succeed("su - test0 -c \"sudo -u root true\"");
+        };
+
+        subtest "test1 user should have sudo with password", sub {
+            $machine->succeed("su - test1 -c \"echo ${password} | sudo -S -u root true\"");
+        };
+
+        subtest "test1 user should not be able to use sudo without password", sub {
+            $machine->fail("su - test1 -c \"sudo -n -u root true\"");
+        };
+
+        subtest "users in group 'foobar' should be able to use sudo with password", sub {
+            $machine->succeed("sudo -u test2 echo ${password} | sudo -S -u root true");
+        };
+
+        subtest "users in group 'barfoo' should be able to use sudo without password", sub {
+            $machine->succeed("sudo -u test3 sudo -n -u root true");
+        };
+
+        subtest "users in group 'baz' (GID 1337) should be able to use sudo without password", sub {
+            $machine->succeed("sudo -u test4 sudo -n -u root echo true");
+        };
+
+        subtest "test5 user should be able to run commands under test1", sub {
+            $machine->succeed("sudo -u test5 sudo -n -u test1 true");
+        };
+
+        subtest "test5 user should not be able to run commands under root", sub {
+            $machine->fail("sudo -u test5 sudo -n -u root true");
+        };
+
+        subtest "test5 user should be able to keep his environment", sub {
+            $machine->succeed("sudo -u test5 sudo -n -E -u test1 true");
+        };
+
+        subtest "users in group 'barfoo' should not be able to keep their environment", sub {
+            $machine->fail("sudo -u test3 sudo -n -E -u root true");
+        };
+      '';
+  })
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
new file mode 100644
index 000000000000..46f2563af8d9
--- /dev/null
+++ b/nixos/tests/switch-test.nix
@@ -0,0 +1,25 @@
+# Test configuration switching.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "switch-test";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ gleber ];
+  };
+
+  nodes = {
+    machine = { config, lib, pkgs, ... }: {
+      users.mutableUsers = false;
+    };
+    other = { config, lib, pkgs, ... }: {
+      users.mutableUsers = true;
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    originalSystem = nodes.machine.config.system.build.toplevel;
+    otherSystem = nodes.other.config.system.build.toplevel;
+  in ''
+    $machine->succeed("env -i ${originalSystem}/bin/switch-to-configuration test | tee /dev/stderr");
+    $machine->succeed("env -i ${otherSystem}/bin/switch-to-configuration test | tee /dev/stderr");
+  '';
+})
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
new file mode 100644
index 000000000000..65aa553b3148
--- /dev/null
+++ b/nixos/tests/systemd.nix
@@ -0,0 +1,68 @@
+import ./make-test.nix {
+  name = "systemd";
+
+  machine = { lib, ... }: {
+    imports = [ common/user-account.nix common/x11.nix ];
+
+    virtualisation.emptyDiskImages = [ 512 ];
+
+    fileSystems = lib.mkVMOverride {
+      "/test-x-initrd-mount" = {
+        device = "/dev/vdb";
+        fsType = "ext2";
+        autoFormat = true;
+        noCheck = true;
+        options = [ "x-initrd.mount" ];
+      };
+    };
+
+    systemd.extraConfig = "DefaultEnvironment=\"XXX_SYSTEM=foo\"";
+    systemd.user.extraConfig = "DefaultEnvironment=\"XXX_USER=bar\"";
+    services.journald.extraConfig = "Storage=volatile";
+    services.xserver.displayManager.auto.user = "alice";
+
+    systemd.services.testservice1 = {
+      description = "Test Service 1";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        if [ "$XXX_SYSTEM" = foo ]; then
+          touch /system_conf_read
+        fi
+      '';
+    };
+
+    systemd.user.services.testservice2 = {
+      description = "Test Service 2";
+      wantedBy = [ "default.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        if [ "$XXX_USER" = bar ]; then
+          touch "$HOME/user_conf_read"
+        fi
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->waitForX;
+    # wait for user services
+    $machine->waitForUnit("default.target","alice");
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/35415
+    subtest "configuration files are recognized by systemd", sub {
+      $machine->succeed('test -e /system_conf_read');
+      $machine->succeed('test -e /home/alice/user_conf_read');
+      $machine->succeed('test -z $(ls -1 /var/log/journal)');
+    };
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/35268
+    subtest "file system with x-initrd.mount is not unmounted", sub {
+      $machine->shutdown;
+      $machine->waitForUnit('multi-user.target');
+      # If the file system was unmounted during the shutdown the file system
+      # has a last mount time, because the file system wasn't checked.
+      $machine->fail('dumpe2fs /dev/vdb | grep -q "^Last mount time: *n/a"');
+    };
+  '';
+}
diff --git a/nixos/tests/taskserver.nix b/nixos/tests/taskserver.nix
new file mode 100644
index 000000000000..75be97a507d0
--- /dev/null
+++ b/nixos/tests/taskserver.nix
@@ -0,0 +1,289 @@
+import ./make-test.nix ({ pkgs, ... }: let
+  snakeOil = pkgs.runCommand "snakeoil-certs" {
+    outputs = [ "out" "cacert" "cert" "key" "crl" ];
+    buildInputs = [ pkgs.gnutls.bin ];
+    caTemplate = pkgs.writeText "snakeoil-ca.template" ''
+      cn = server
+      expiration_days = -1
+      cert_signing_key
+      ca
+    '';
+    certTemplate = pkgs.writeText "snakeoil-cert.template" ''
+      cn = server
+      expiration_days = -1
+      tls_www_server
+      encryption_key
+      signing_key
+    '';
+    crlTemplate = pkgs.writeText "snakeoil-crl.template" ''
+      expiration_days = -1
+    '';
+    userCertTemplace = pkgs.writeText "snakoil-user-cert.template" ''
+      organization = snakeoil
+      cn = server
+      expiration_days = -1
+      tls_www_client
+      encryption_key
+      signing_key
+    '';
+  } ''
+    certtool -p --bits 4096 --outfile ca.key
+    certtool -s --template "$caTemplate" --load-privkey ca.key \
+                --outfile "$cacert"
+    certtool -p --bits 4096 --outfile "$key"
+    certtool -c --template "$certTemplate" \
+                --load-ca-privkey ca.key \
+                --load-ca-certificate "$cacert" \
+                --load-privkey "$key" \
+                --outfile "$cert"
+    certtool --generate-crl --template "$crlTemplate" \
+                            --load-ca-privkey ca.key \
+                            --load-ca-certificate "$cacert" \
+                            --outfile "$crl"
+
+    mkdir "$out"
+
+    # Stripping key information before the actual PEM-encoded values is solely
+    # to make test output a bit less verbose when copying the client key to the
+    # actual client.
+    certtool -p --bits 4096 | sed -n \
+      -e '/^----* *BEGIN/,/^----* *END/p' > "$out/alice.key"
+
+    certtool -c --template "$userCertTemplace" \
+                --load-privkey "$out/alice.key" \
+                --load-ca-privkey ca.key \
+                --load-ca-certificate "$cacert" \
+                --outfile "$out/alice.cert"
+  '';
+
+in {
+  name = "taskserver";
+
+  nodes = rec {
+    server = {
+      services.taskserver.enable = true;
+      services.taskserver.listenHost = "::";
+      services.taskserver.fqdn = "server";
+      services.taskserver.organisations = {
+        testOrganisation.users = [ "alice" "foo" ];
+        anotherOrganisation.users = [ "bob" ];
+      };
+    };
+
+    # New generation of the server with manual config
+    newServer = { lib, nodes, ... }: {
+      imports = [ server ];
+      services.taskserver.pki.manual = {
+        ca.cert = snakeOil.cacert;
+        server.cert = snakeOil.cert;
+        server.key = snakeOil.key;
+        server.crl = snakeOil.crl;
+      };
+      # This is to avoid assigning a different network address to the new
+      # generation.
+      networking = lib.mapAttrs (lib.const lib.mkForce) {
+        inherit (nodes.server.config.networking)
+          hostName interfaces primaryIPAddress extraHosts;
+      };
+    };
+
+    client1 = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.taskwarrior pkgs.gnutls ];
+      users.users.alice.isNormalUser = true;
+      users.users.bob.isNormalUser = true;
+      users.users.foo.isNormalUser = true;
+      users.users.bar.isNormalUser = true;
+    };
+
+    client2 = client1;
+  };
+
+  testScript = { nodes, ... }: let
+    cfg = nodes.server.config.services.taskserver;
+    portStr = toString cfg.listenPort;
+    newServerSystem = nodes.newServer.config.system.build.toplevel;
+    switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
+  in ''
+    sub su ($$) {
+      my ($user, $cmd) = @_;
+      my $esc = $cmd =~ s/'/'\\${"'"}'/gr;
+      return "su - $user -c '$esc'";
+    }
+
+    sub setupClientsFor ($$;$) {
+      my ($org, $user, $extraInit) = @_;
+
+      for my $client ($client1, $client2) {
+        $client->nest("initialize client for user $user", sub {
+          $client->succeed(
+            (su $user, "rm -rf /home/$user/.task"),
+            (su $user, "task rc.confirmation=no config confirmation no")
+          );
+
+          my $exportinfo = $server->succeed(
+            "nixos-taskserver user export $org $user"
+          );
+
+          $exportinfo =~ s/'/'\\'''/g;
+
+          $client->nest("importing taskwarrior configuration", sub {
+            my $cmd = su $user, "eval '$exportinfo' >&2";
+            my ($status, $out) = $client->execute_($cmd);
+            if ($status != 0) {
+              $client->log("output: $out");
+              die "command `$cmd' did not succeed (exit code $status)\n";
+            }
+          });
+
+          eval { &$extraInit($client, $org, $user) };
+
+          $client->succeed(su $user,
+            "task config taskd.server server:${portStr} >&2"
+          );
+
+          $client->succeed(su $user, "task sync init >&2");
+        });
+      }
+    }
+
+    sub restartServer {
+      $server->succeed("systemctl restart taskserver.service");
+      $server->waitForOpenPort(${portStr});
+    }
+
+    sub readdImperativeUser {
+      $server->nest("(re-)add imperative user bar", sub {
+        $server->execute("nixos-taskserver org remove imperativeOrg");
+        $server->succeed(
+          "nixos-taskserver org add imperativeOrg",
+          "nixos-taskserver user add imperativeOrg bar"
+        );
+        setupClientsFor "imperativeOrg", "bar";
+      });
+    }
+
+    sub testSync ($) {
+      my $user = $_[0];
+      subtest "sync for user $user", sub {
+        $client1->succeed(su $user, "task add foo >&2");
+        $client1->succeed(su $user, "task sync >&2");
+        $client2->fail(su $user, "task list >&2");
+        $client2->succeed(su $user, "task sync >&2");
+        $client2->succeed(su $user, "task list >&2");
+      };
+    }
+
+    sub checkClientCert ($) {
+      my $user = $_[0];
+      my $cmd = "gnutls-cli".
+        " --x509cafile=/home/$user/.task/keys/ca.cert".
+        " --x509keyfile=/home/$user/.task/keys/private.key".
+        " --x509certfile=/home/$user/.task/keys/public.cert".
+        " --port=${portStr} server < /dev/null";
+      return su $user, $cmd;
+    }
+
+    # Explicitly start the VMs so that we don't accidentally start newServer
+    $server->start;
+    $client1->start;
+    $client2->start;
+
+    $server->waitForUnit("taskserver.service");
+
+    $server->succeed(
+      "nixos-taskserver user list testOrganisation | grep -qxF alice",
+      "nixos-taskserver user list testOrganisation | grep -qxF foo",
+      "nixos-taskserver user list anotherOrganisation | grep -qxF bob"
+    );
+
+    $server->waitForOpenPort(${portStr});
+
+    $client1->waitForUnit("multi-user.target");
+    $client2->waitForUnit("multi-user.target");
+
+    setupClientsFor "testOrganisation", "alice";
+    setupClientsFor "testOrganisation", "foo";
+    setupClientsFor "anotherOrganisation", "bob";
+
+    testSync $_ for ("alice", "bob", "foo");
+
+    $server->fail("nixos-taskserver user add imperativeOrg bar");
+    readdImperativeUser;
+
+    testSync "bar";
+
+    subtest "checking certificate revocation of user bar", sub {
+      $client1->succeed(checkClientCert "bar");
+
+      $server->succeed("nixos-taskserver user remove imperativeOrg bar");
+      restartServer;
+
+      $client1->fail(checkClientCert "bar");
+
+      $client1->succeed(su "bar", "task add destroy everything >&2");
+      $client1->fail(su "bar", "task sync >&2");
+    };
+
+    readdImperativeUser;
+
+    subtest "checking certificate revocation of org imperativeOrg", sub {
+      $client1->succeed(checkClientCert "bar");
+
+      $server->succeed("nixos-taskserver org remove imperativeOrg");
+      restartServer;
+
+      $client1->fail(checkClientCert "bar");
+
+      $client1->succeed(su "bar", "task add destroy even more >&2");
+      $client1->fail(su "bar", "task sync >&2");
+    };
+
+    readdImperativeUser;
+
+    subtest "check whether declarative config overrides user bar", sub {
+      restartServer;
+      testSync "bar";
+    };
+
+    subtest "check manual configuration", sub {
+      # Remove the keys from automatic CA creation, to make sure the new
+      # generation doesn't use keys from before.
+      $server->succeed('rm -rf ${cfg.dataDir}/keys/* >&2');
+
+      $server->succeed('${switchToNewServer} >&2');
+      $server->waitForUnit("taskserver.service");
+      $server->waitForOpenPort(${portStr});
+
+      $server->succeed(
+        "nixos-taskserver org add manualOrg",
+        "nixos-taskserver user add manualOrg alice"
+      );
+
+      setupClientsFor "manualOrg", "alice", sub {
+        my ($client, $org, $user) = @_;
+        my $cfgpath = "/home/$user/.task";
+
+        $client->copyFileFromHost("${snakeOil.cacert}", "$cfgpath/ca.cert");
+        for my $file ('alice.key', 'alice.cert') {
+          $client->copyFileFromHost("${snakeOil}/$file", "$cfgpath/$file");
+        }
+
+        for my $file ("$user.key", "$user.cert") {
+          $client->copyFileFromHost(
+            "${snakeOil}/$file", "$cfgpath/$file"
+          );
+        }
+        $client->copyFileFromHost(
+          "${snakeOil.cacert}", "$cfgpath/ca.cert"
+        );
+        $client->succeed(
+          (su "alice", "task config taskd.ca $cfgpath/ca.cert"),
+          (su "alice", "task config taskd.key $cfgpath/$user.key"),
+          (su $user, "task config taskd.certificate $cfgpath/$user.cert")
+        );
+      };
+
+      testSync "alice";
+    };
+  '';
+})
diff --git a/nixos/tests/test-config-examples.sh b/nixos/tests/test-config-examples.sh
deleted file mode 100755
index 1ba2f841c41d..000000000000
--- a/nixos/tests/test-config-examples.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-
-# This script try to evaluate all configurations which are stored in
-# doc/config-examples.  This script is useful to ensure that examples are
-# working with the current system.
-
-pwd=$(pwd)
-set -xe
-for i in ../doc/config-examples/*.nix; do
-  NIXOS_CONFIG="$pwd/$i" nix-instantiate \
-      --eval-only --xml --strict > /dev/null 2>&1 \
-      ../default.nix -A system
-done
-set +xe
diff --git a/nixos/tests/testdb.sql b/nixos/tests/testdb.sql
index 4fb28fea3df9..3c68c49ae82c 100644
--- a/nixos/tests/testdb.sql
+++ b/nixos/tests/testdb.sql
@@ -8,3 +8,4 @@ insert into tests values (1, 'a');
 insert into tests values (2, 'b');
 insert into tests values (3, 'c');
 insert into tests values (4, 'd');
+insert into tests values (5, 'hello');
diff --git a/nixos/tests/timezone.nix b/nixos/tests/timezone.nix
new file mode 100644
index 000000000000..2204649a3fc4
--- /dev/null
+++ b/nixos/tests/timezone.nix
@@ -0,0 +1,45 @@
+{
+  timezone-static = import ./make-test.nix ({ pkgs, ... }: {
+    name = "timezone-static";
+    meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ];
+
+    machine.time.timeZone = "Europe/Amsterdam";
+
+    testScript = ''
+      $machine->waitForUnit("dbus.socket");
+      $machine->fail("timedatectl set-timezone Asia/Tokyo");
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      $dateResult[1] eq "1970-01-01 01:00:00\n" or die "Timezone seems to be wrong";
+    '';
+  });
+
+  timezone-imperative = import ./make-test.nix ({ pkgs, ... }: {
+    name = "timezone-imperative";
+    meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ];
+
+    machine.time.timeZone = null;
+
+    testScript = ''
+      $machine->waitForUnit("dbus.socket");
+
+      # Should default to UTC
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      print $dateResult[1];
+      $dateResult[1] eq "1970-01-01 00:00:00\n" or die "Timezone seems to be wrong";
+
+      $machine->succeed("timedatectl set-timezone Asia/Tokyo");
+
+      # Adjustment should be taken into account
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      print $dateResult[1];
+      $dateResult[1] eq "1970-01-01 09:00:00\n" or die "Timezone was not adjusted";
+
+      # Adjustment should persist across a reboot
+      $machine->shutdown;
+      $machine->waitForUnit("dbus.socket");
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      print $dateResult[1];
+      $dateResult[1] eq "1970-01-01 09:00:00\n" or die "Timezone adjustment was not persisted";
+    '';
+  });
+}
diff --git a/nixos/tests/tomcat.nix b/nixos/tests/tomcat.nix
index 92680d82ba89..475c947e72d9 100644
--- a/nixos/tests/tomcat.nix
+++ b/nixos/tests/tomcat.nix
@@ -23,9 +23,8 @@ import ./make-test.nix ({ pkgs, ...} : {
     startAll;
 
     $server->waitForUnit("tomcat");
-    $server->sleep(30); # Dirty, but it takes a while before Tomcat handles to requests properly
     $client->waitForUnit("network.target");
-    $client->succeed("curl --fail http://server/examples/servlets/servlet/HelloWorldExample");
-    $client->succeed("curl --fail http://server/examples/jsp/jsp2/simpletag/hello.jsp");
+    $client->waitUntilSucceeds("curl --fail http://server/examples/servlets/servlet/HelloWorldExample");
+    $client->waitUntilSucceeds("curl --fail http://server/examples/jsp/jsp2/simpletag/hello.jsp");
   '';
 })
diff --git a/nixos/tests/trac.nix b/nixos/tests/trac.nix
index e7d9759ae0cc..5da5717d7f86 100644
--- a/nixos/tests/trac.nix
+++ b/nixos/tests/trac.nix
@@ -17,7 +17,7 @@ import ./make-test.nix ({ pkgs, ... }: {
     postgresql =
       { config, pkgs, ... }:
       { services.postgresql.enable = true;
-        services.postgresql.package = pkgs.postgresql92;
+        services.postgresql.package = pkgs.postgresql;
         services.postgresql.enableTCPIP = true;
         services.postgresql.authentication = ''
           # Generated file; do not edit!
@@ -45,7 +45,7 @@ import ./make-test.nix ({ pkgs, ... }: {
     client =
       { config, pkgs, ... }:
       { imports = [ ./common/x11.nix ];
-        services.xserver.desktopManager.kde4.enable = true;
+        services.xserver.desktopManager.plasma5.enable = true;
       };
   };
 
diff --git a/nixos/tests/transmission.nix b/nixos/tests/transmission.nix
new file mode 100644
index 000000000000..34c49bd7f15b
--- /dev/null
+++ b/nixos/tests/transmission.nix
@@ -0,0 +1,21 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "transmission";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ coconnor ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    networking.firewall.allowedTCPPorts = [ 9091 ];
+
+    services.transmission.enable = true;
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("transmission");
+      $machine->shutdown;
+    '';
+})
diff --git a/nixos/tests/udisks2.nix b/nixos/tests/udisks2.nix
index 72d51c0051c0..70a999267a54 100644
--- a/nixos/tests/udisks2.nix
+++ b/nixos/tests/udisks2.nix
@@ -37,7 +37,8 @@ in
       $machine->fail("udisksctl info -b /dev/sda1");
 
       # Attach a USB stick and wait for it to show up.
-      $machine->sendMonitorCommand("usb_add disk:$stick");
+      $machine->sendMonitorCommand("drive_add 0 id=stick,if=none,file=$stick,format=raw");
+      $machine->sendMonitorCommand("device_add usb-storage,id=stick,drive=stick");
       $machine->waitUntilSucceeds("udisksctl info -b /dev/sda1");
       $machine->succeed("udisksctl info -b /dev/sda1 | grep 'IdLabel:.*USBSTICK'");
 
@@ -52,7 +53,7 @@ in
       $machine->fail("[ -d /run/media/alice/USBSTICK ]");
 
       # Remove the USB stick.
-      $machine->sendMonitorCommand("usb_del 0.3"); # FIXME
+      $machine->sendMonitorCommand("device_del stick");
       $machine->waitUntilFails("udisksctl info -b /dev/sda1");
       $machine->fail("[ -e /dev/sda ]");
     '';
diff --git a/nixos/tests/vault.nix b/nixos/tests/vault.nix
new file mode 100644
index 000000000000..515d5c8bac25
--- /dev/null
+++ b/nixos/tests/vault.nix
@@ -0,0 +1,23 @@
+import ./make-test.nix ({ pkgs, ... }:
+{
+  name = "vault";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+  machine = { config, pkgs, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    services.vault.enable = true;
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitForUnit('vault.service');
+      $machine->waitForOpenPort(8200);
+      $machine->succeed('vault operator init');
+      $machine->succeed('vault status | grep Sealed | grep true');
+    '';
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 01fcd15fd8bb..249571fcedec 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -1,26 +1,28 @@
-{ debug ? false, ... } @ args:
+{ system ? builtins.currentSystem, debug ? false }:
 
-import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
 
-  testVMConfig = vmName: attrs: { config, pkgs, ... }: let
+let
+  testVMConfig = vmName: attrs: { config, pkgs, lib, ... }: let
     guestAdditions = pkgs.linuxPackages.virtualboxGuestAdditions;
 
     miniInit = ''
       #!${pkgs.stdenv.shell} -xe
-      export PATH="${pkgs.coreutils}/bin:${pkgs.utillinux}/bin"
+      export PATH="${lib.makeBinPath [ pkgs.coreutils pkgs.utillinux ]}"
 
-      mkdir -p /etc/dbus-1 /var/run/dbus
+      mkdir -p /run/dbus
       cat > /etc/passwd <<EOF
       root:x:0:0::/root:/bin/false
-      messagebus:x:1:1::/var/run/dbus:/bin/false
+      messagebus:x:1:1::/run/dbus:/bin/false
       EOF
       cat > /etc/group <<EOF
       root:x:0:
       messagebus:x:1:
       EOF
-      cp -v "${pkgs.dbus.daemon}/etc/dbus-1/system.conf" \
-        /etc/dbus-1/system.conf
-      "${pkgs.dbus.daemon}/bin/dbus-daemon" --fork --system
+
+      "${pkgs.dbus.daemon}/bin/dbus-daemon" --fork \
+        --config-file="${pkgs.dbus.daemon}/share/dbus-1/system.conf"
 
       ${guestAdditions}/bin/VBoxService
       ${(attrs.vmScript or (const "")) pkgs}
@@ -41,6 +43,9 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
       "init=${pkgs.writeScript "mini-init.sh" miniInit}"
     ];
 
+    # XXX: Remove this once TSS location detection has been fixed in VirtualBox
+    boot.kernelPackages = pkgs.linuxPackages_4_9;
+
     fileSystems."/" = {
       device = "vboxshare";
       fsType = "vboxsf";
@@ -63,7 +68,7 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
       touch /mnt-root/boot-done
       hostname "${vmName}"
       mkdir -p /nix/store
-      unshare -m "@shell@" -c '
+      unshare -m ${escapeShellArg pkgs.stdenv.shell} -c '
         mount -t vboxsf nixstore /nix/store
         exec "$stage2Init"
       '
@@ -105,11 +110,8 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
 
     buildInputs = [ pkgs.utillinux pkgs.perl ];
   } ''
-    ${pkgs.parted}/sbin/parted /dev/vda mklabel msdos
-    ${pkgs.parted}/sbin/parted /dev/vda -- mkpart primary ext2 1M -1s
-    . /sys/class/block/vda1/uevent
-    mknod /dev/vda1 b $MAJOR $MINOR
-
+    ${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos
+    ${pkgs.parted}/sbin/parted --script /dev/vda -- mkpart primary ext2 1M -1s
     ${pkgs.e2fsprogs}/sbin/mkfs.ext4 /dev/vda1
     ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
     mkdir /mnt
@@ -142,6 +144,7 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
       "--uart1 0x3F8 4"
       "--uartmode1 client /run/virtualbox-log-${name}.sock"
       "--memory 768"
+      "--audio none"
     ] ++ (attrs.vmFlags or []));
 
     controllerFlags = mkFlags [
@@ -271,9 +274,12 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
 
       sub shutdownVM_${name} {
         $machine->succeed(ru "touch ${sharePath}/shutdown");
-        $machine->waitUntilSucceeds(
-          "test ! -e ${sharePath}/shutdown ".
-          "  -a ! -e ${sharePath}/boot-done"
+        $machine->execute(
+          'set -e; i=0; '.
+          'while test -e ${sharePath}/shutdown '.
+          '        -o -e ${sharePath}/boot-done; do '.
+          'sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; '.
+          'done'
         );
         waitForShutdown_${name};
       }
@@ -293,9 +299,9 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
       -pf /run/dhclient.pid \
       -v eth0 eth1
 
-    otherIP="$(${pkgs.netcat}/bin/netcat -clp 1234 || :)"
+    otherIP="$(${pkgs.netcat}/bin/nc -l 1234 || :)"
     ${pkgs.iputils}/bin/ping -I eth1 -c1 "$otherIP"
-    echo "$otherIP reachable" | ${pkgs.netcat}/bin/netcat -clp 5678 || :
+    echo "$otherIP reachable" | ${pkgs.netcat}/bin/nc -l 5678 || :
   '';
 
   sysdDetectVirt = pkgs: ''
@@ -312,140 +318,159 @@ import ./make-test.nix ({ pkgs, ... }: with pkgs.lib; let
 
     test2.vmFlags = hostonlyVMFlags;
     test2.vmScript = dhcpScript;
-  };
 
-in {
-  name = "virtualbox";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ aszlig wkennington ];
+    headless.virtualisation.virtualbox.headless = true;
+    headless.services.xserver.enable = false;
   };
 
-  machine = { pkgs, lib, config, ... }: {
-    imports = let
-      mkVMConf = name: val: val.machine // { key = "${name}-config"; };
-      vmConfigs = mapAttrsToList mkVMConf vboxVMs;
-    in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs;
-    virtualisation.memorySize = 2048;
-    virtualisation.virtualbox.host.enable = true;
-    users.extraUsers.alice.extraGroups = let
-      inherit (config.virtualisation.virtualbox.host) enableHardening;
-    in lib.mkIf enableHardening (lib.singleton "vboxusers");
-  };
+  mkVBoxTest = name: testScript: makeTest {
+    name = "virtualbox-${name}";
+
+    machine = { lib, config, ... }: {
+      imports = let
+        mkVMConf = name: val: val.machine // { key = "${name}-config"; };
+        vmConfigs = mapAttrsToList mkVMConf vboxVMs;
+      in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs;
+      virtualisation.memorySize = 2048;
+      virtualisation.virtualbox.host.enable = true;
+      services.xserver.displayManager.auto.user = "alice";
+      users.extraUsers.alice.extraGroups = let
+        inherit (config.virtualisation.virtualbox.host) enableHardening;
+      in lib.mkIf enableHardening (lib.singleton "vboxusers");
+    };
 
-  testScript = ''
-    sub ru ($) {
-      my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
-      return "su - alice -c '$esc'";
-    }
+    testScript = ''
+      sub ru ($) {
+        my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
+        return "su - alice -c '$esc'";
+      }
 
-    sub vbm {
-      $machine->succeed(ru("VBoxManage ".$_[0]));
-    };
+      sub vbm {
+        $machine->succeed(ru("VBoxManage ".$_[0]));
+      };
+
+      sub removeUUIDs {
+        return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n";
+      }
 
-    ${concatStrings (mapAttrsToList (_: getAttr "testSubs") vboxVMs)}
+      ${concatStrings (mapAttrsToList (_: getAttr "testSubs") vboxVMs)}
 
-    $machine->waitForX;
+      $machine->waitForX;
 
-    ${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
+      ${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
 
-    createVM_simple;
+      ${testScript}
+    '';
 
-    subtest "simple-gui", sub {
-      $machine->succeed(ru "VirtualBox &");
-      $machine->waitForWindow(qr/Oracle VM VirtualBox Manager/);
-      $machine->sleep(5);
-      $machine->screenshot("gui_manager_started");
-      $machine->sendKeys("ret");
-      $machine->screenshot("gui_manager_sent_startup");
-      waitForStartup_simple (sub {
-        $machine->sendKeys("ret");
-      });
-      $machine->screenshot("gui_started");
-      waitForVMBoot_simple;
-      $machine->screenshot("gui_booted");
-      shutdownVM_simple;
-      $machine->sleep(5);
-      $machine->screenshot("gui_stopped");
-      $machine->sendKeys("ctrl-q");
-      $machine->sleep(5);
-      $machine->screenshot("gui_manager_stopped");
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ aszlig wkennington ];
     };
+  };
 
-    cleanup_simple;
+in mapAttrs mkVBoxTest {
+  simple-gui = ''
+    createVM_simple;
+    $machine->succeed(ru "VirtualBox &");
+    $machine->waitUntilSucceeds(
+      ru "xprop -name 'Oracle VM VirtualBox Manager'"
+    );
+    $machine->sleep(5);
+    $machine->screenshot("gui_manager_started");
+    $machine->sendKeys("ret");
+    $machine->screenshot("gui_manager_sent_startup");
+    waitForStartup_simple (sub {
+      $machine->sendKeys("ret");
+    });
+    $machine->screenshot("gui_started");
+    waitForVMBoot_simple;
+    $machine->screenshot("gui_booted");
+    shutdownVM_simple;
+    $machine->sleep(5);
+    $machine->screenshot("gui_stopped");
+    $machine->sendKeys("ctrl-q");
+    $machine->sleep(5);
+    $machine->screenshot("gui_manager_stopped");
+    destroyVM_simple;
+  '';
 
-    subtest "simple-cli", sub {
-      vbm("startvm simple");
-      waitForStartup_simple;
-      $machine->screenshot("cli_started");
-      waitForVMBoot_simple;
-      $machine->screenshot("cli_booted");
-      shutdownVM_simple;
-    };
+  simple-cli = ''
+    createVM_simple;
+    vbm("startvm simple");
+    waitForStartup_simple;
+    $machine->screenshot("cli_started");
+    waitForVMBoot_simple;
+    $machine->screenshot("cli_booted");
 
-    subtest "privilege-escalation", sub {
+    $machine->nest("Checking for privilege escalation", sub {
       $machine->fail("test -e '/root/VirtualBox VMs'");
       $machine->fail("test -e '/root/.config/VirtualBox'");
       $machine->succeed("test -e '/home/alice/VirtualBox VMs'");
-    };
+    });
 
+    shutdownVM_simple;
     destroyVM_simple;
+  '';
 
-    sub removeUUIDs {
-      return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n";
-    }
+  headless = ''
+    createVM_headless;
+    $machine->succeed(ru("VBoxHeadless --startvm headless & disown %1"));
+    waitForStartup_headless;
+    waitForVMBoot_headless;
+    shutdownVM_headless;
+    destroyVM_headless;
+  '';
 
-    subtest "host-usb-permissions", sub {
-      my $userUSB = removeUUIDs vbm("list usbhost");
-      print STDERR $userUSB;
-      my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost");
-      print STDERR $rootUSB;
+  host-usb-permissions = ''
+    my $userUSB = removeUUIDs vbm("list usbhost");
+    print STDERR $userUSB;
+    my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost");
+    print STDERR $rootUSB;
 
-      die "USB host devices differ for root and normal user"
-        if $userUSB ne $rootUSB;
-      die "No USB host devices found" if $userUSB =~ /<none>/;
-    };
+    die "USB host devices differ for root and normal user"
+      if $userUSB ne $rootUSB;
+    die "No USB host devices found" if $userUSB =~ /<none>/;
+  '';
 
-    subtest "systemd-detect-virt", sub {
-      createVM_detectvirt;
-      vbm("startvm detectvirt");
-      waitForStartup_detectvirt;
-      waitForVMBoot_detectvirt;
-      shutdownVM_detectvirt;
-      my $result = $machine->succeed("cat '$detectvirt_sharepath/result'");
-      chomp $result;
-      destroyVM_detectvirt;
-      die "systemd-detect-virt returned \"$result\" instead of \"oracle\""
-        if $result ne "oracle";
-    };
+  systemd-detect-virt = ''
+    createVM_detectvirt;
+    vbm("startvm detectvirt");
+    waitForStartup_detectvirt;
+    waitForVMBoot_detectvirt;
+    shutdownVM_detectvirt;
+    my $result = $machine->succeed("cat '$detectvirt_sharepath/result'");
+    chomp $result;
+    destroyVM_detectvirt;
+    die "systemd-detect-virt returned \"$result\" instead of \"oracle\""
+      if $result ne "oracle";
+  '';
 
-    subtest "net-hostonlyif", sub {
-      createVM_test1;
-      createVM_test2;
+  net-hostonlyif = ''
+    createVM_test1;
+    createVM_test2;
 
-      vbm("startvm test1");
-      waitForStartup_test1;
-      waitForVMBoot_test1;
+    vbm("startvm test1");
+    waitForStartup_test1;
+    waitForVMBoot_test1;
 
-      vbm("startvm test2");
-      waitForStartup_test2;
-      waitForVMBoot_test2;
+    vbm("startvm test2");
+    waitForStartup_test2;
+    waitForVMBoot_test2;
 
-      $machine->screenshot("net_booted");
+    $machine->screenshot("net_booted");
 
-      my $test1IP = waitForIP_test1 1;
-      my $test2IP = waitForIP_test2 1;
+    my $test1IP = waitForIP_test1 1;
+    my $test2IP = waitForIP_test2 1;
 
-      $machine->succeed("echo '$test2IP' | netcat -c '$test1IP' 1234");
-      $machine->succeed("echo '$test1IP' | netcat -c '$test2IP' 1234");
+    $machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234");
+    $machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234");
 
-      $machine->waitUntilSucceeds("netcat -c '$test1IP' 5678 >&2");
-      $machine->waitUntilSucceeds("netcat -c '$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;
+    shutdownVM_test1;
+    shutdownVM_test2;
 
-      destroyVM_test1;
-      destroyVM_test2;
-    };
+    destroyVM_test1;
+    destroyVM_test2;
   '';
-}) args
+}
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
new file mode 100644
index 000000000000..c51306a8c7a0
--- /dev/null
+++ b/nixos/tests/wordpress.nix
@@ -0,0 +1,56 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "wordpress";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ grahamc ]; # under duress!
+  };
+
+  nodes =
+    { web =
+        { config, pkgs, ... }:
+        {
+          services.mysql = {
+            enable = true;
+            package = pkgs.mysql;
+          };
+          services.httpd = {
+            enable = true;
+            logPerVirtualHost = true;
+            adminAddr="js@lastlog.de";
+            extraModules = [
+              { name = "php7"; path = "${pkgs.php}/modules/libphp7.so"; }
+            ];
+
+            virtualHosts = [
+              {
+                hostName = "wordpress";
+                extraSubservices =
+                  [
+                    {
+                      serviceType = "wordpress";
+                      dbPassword = "wordpress";
+                      wordpressUploads = "/data/uploads";
+                      languages = [ "de_DE" "en_GB" ];
+                    }
+                  ];
+              }
+            ];
+          };
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      $web->waitForUnit("mysql");
+      $web->waitForUnit("httpd");
+
+      $web->succeed("curl -L 127.0.0.1:80 | grep 'Welcome to the famous'");
+
+
+    '';
+
+})
diff --git a/nixos/tests/xautolock.nix b/nixos/tests/xautolock.nix
new file mode 100644
index 000000000000..ee46d9e05b06
--- /dev/null
+++ b/nixos/tests/xautolock.nix
@@ -0,0 +1,24 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "xautolock";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    services.xserver.displayManager.auto.user = "bob";
+    services.xserver.xautolock.enable = true;
+    services.xserver.xautolock.time = 1;
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForX;
+    $machine->mustFail("pgrep xlock");
+    $machine->sleep(120);
+    $machine->mustSucceed("pgrep xlock");
+  '';
+})
diff --git a/nixos/tests/xdg-desktop-portal.nix b/nixos/tests/xdg-desktop-portal.nix
new file mode 100644
index 000000000000..d954b07f73d6
--- /dev/null
+++ b/nixos/tests/xdg-desktop-portal.nix
@@ -0,0 +1,17 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "xdg-desktop-portal";
+  meta = {
+    maintainers = pkgs.xdg-desktop-portal.meta.maintainers;
+  };
+
+  machine = { config, pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.xdg-desktop-portal.installedTests}/share'");
+  '';
+})
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index c131ef7dc8cd..c8b18f122658 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -15,11 +15,15 @@ import ./make-test.nix ({ pkgs, ...} : {
       services.xserver.displayManager.auto.user = "alice";
 
       services.xserver.desktopManager.xfce.enable = true;
+
+      environment.systemPackages = [ pkgs.xorg.xmessage ];
     };
 
   testScript =
     ''
       $machine->waitForX;
+      $machine->waitForFile("/home/alice/.Xauthority");
+      $machine->succeed("xauth merge ~alice/.Xauthority");
       $machine->waitForWindow(qr/xfce4-panel/);
       $machine->sleep(10);
 
@@ -30,5 +34,9 @@ import ./make-test.nix ({ pkgs, ...} : {
       $machine->waitForWindow(qr/Terminal/);
       $machine->sleep(10);
       $machine->screenshot("screen");
+
+      # Ensure that the X server does proper access control.
+      $machine->mustFail("su - bob -c 'DISPLAY=:0.0 xmessage Foo'");
+      $machine->mustFail("su - bob -c 'DISPLAY=:0 xmessage Foo'");
     '';
 })
diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix
new file mode 100644
index 000000000000..3ea455c393c4
--- /dev/null
+++ b/nixos/tests/xmonad.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "xmonad";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { lib, pkgs, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    services.xserver.displayManager.auto.user = "alice";
+    services.xserver.windowManager.default = lib.mkForce "xmonad";
+    services.xserver.windowManager.xmonad = {
+      enable = true;
+      enableContribAndExtras = true;
+      extraPackages = with pkgs.haskellPackages; haskellPackages: [ xmobar ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    $machine->waitForX;
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
+    $machine->waitUntilSucceeds("xmonad --restart");
+    $machine->sleep(3);
+    $machine->sendKeys("alt-shift-ret");
+    $machine->waitForWindow(qr/machine.*alice/);
+    $machine->sleep(1);
+    $machine->screenshot("terminal");
+  '';
+})
diff --git a/nixos/tests/xrdp.nix b/nixos/tests/xrdp.nix
new file mode 100644
index 000000000000..c997e36cc442
--- /dev/null
+++ b/nixos/tests/xrdp.nix
@@ -0,0 +1,45 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "xrdp";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ volth ];
+  };
+
+  nodes = {
+    server = { lib, pkgs, ... }: {
+      imports = [ ./common/user-account.nix ];
+      services.xrdp.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.xterm}/bin/xterm";
+      networking.firewall.allowedTCPPorts = [ 3389 ];
+    };
+
+    client = { lib, pkgs, ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      services.xserver.displayManager.auto.user = "alice";
+      environment.systemPackages = [ pkgs.freerdp ];
+      services.xrdp.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.icewm}/bin/icewm";
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    startAll;
+
+    $client->waitForX;
+    $client->waitForFile("/home/alice/.Xauthority");
+    $client->succeed("xauth merge ~alice/.Xauthority");
+
+    $client->sleep(5);
+
+    $client->execute("xterm &");
+    $client->sleep(1);
+    $client->sendChars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:alice /p:foobar\n");
+    $client->sleep(5);
+    $client->screenshot("localrdp");
+
+    $client->execute("xterm &");
+    $client->sleep(1);
+    $client->sendChars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:alice /p:foobar\n");
+    $client->sleep(5);
+    $client->screenshot("remoterdp");
+  '';
+})
diff --git a/nixos/tests/xss-lock.nix b/nixos/tests/xss-lock.nix
new file mode 100644
index 000000000000..045667bdcdec
--- /dev/null
+++ b/nixos/tests/xss-lock.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "xss-lock";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    programs.xss-lock.enable = true;
+    programs.xss-lock.lockerCommand = "${pkgs.xlockmore}/bin/xlock";
+    services.xserver.displayManager.auto.user = "alice";
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForX;
+    $machine->waitForUnit("xss-lock.service", "alice");
+
+    $machine->fail("pgrep xlock");
+    $machine->succeed("su -l alice -c 'xset dpms force standby'");
+    $machine->waitUntilSucceeds("pgrep xlock");
+  '';
+})
diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix
new file mode 100644
index 000000000000..40ca91e8064d
--- /dev/null
+++ b/nixos/tests/yabar.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ pkgs, lib }:
+
+with lib;
+
+{
+  name = "yabar";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  nodes.yabar = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    services.xserver.displayManager.auto.user = "bob";
+
+    programs.yabar.enable = true;
+  };
+
+  testScript = ''
+    $yabar->start;
+    $yabar->waitForX;
+
+    $yabar->waitForUnit("yabar.service", "bob");
+  '';
+})
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
new file mode 100644
index 000000000000..a6908024de9b
--- /dev/null
+++ b/nixos/tests/zfs.nix
@@ -0,0 +1,85 @@
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+
+let
+
+  makeTest = import ./make-test.nix;
+
+  makeZfsTest = name:
+    { kernelPackage ? pkgs.linuxPackages_latest
+    , enableUnstable ? false
+    , extraTest ? ""
+    }:
+    makeTest {
+      name = "zfs-" + name;
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ adisbladis ];
+      };
+
+      machine = { config, lib, pkgs, ... }:
+        {
+          virtualisation.emptyDiskImages = [ 4096 ];
+          networking.hostId = "deadbeef";
+          boot.kernelPackages = kernelPackage;
+          boot.supportedFilesystems = [ "zfs" ];
+          boot.zfs.enableUnstable = enableUnstable;
+
+          environment.systemPackages = with pkgs; [
+            parted
+          ];
+        };
+
+      testScript = ''
+        $machine->succeed("modprobe zfs");
+        $machine->succeed("zpool status");
+
+        $machine->succeed("ls /dev");
+
+        $machine->succeed(
+          "mkdir /tmp/mnt",
+
+          "udevadm settle",
+          "parted --script /dev/vdb mklabel msdos",
+          "parted --script /dev/vdb -- mkpart primary 1024M -1s",
+          "udevadm settle",
+
+          "zpool create rpool /dev/vdb1",
+          "zfs create -o mountpoint=legacy rpool/root",
+          "mount -t zfs rpool/root /tmp/mnt",
+          "udevadm settle",
+
+          "umount /tmp/mnt",
+          "zpool destroy rpool",
+          "udevadm settle"
+
+        );
+
+      '' + extraTest;
+
+    };
+
+
+in {
+
+  stable = makeZfsTest "stable" { };
+
+  unstable = makeZfsTest "unstable" {
+    enableUnstable = true;
+    extraTest = ''
+      $machine->succeed(
+        "echo password | zpool create -o altroot='/tmp/mnt' -O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
+        "zfs create -o mountpoint=legacy rpool/root",
+        "mount -t zfs rpool/root /tmp/mnt",
+        "udevadm settle",
+
+        "umount /tmp/mnt",
+        "zpool destroy rpool",
+        "udevadm settle"
+      );
+    '';
+  };
+
+  installer = (import ./installer.nix { }).zfsroot;
+
+}
diff --git a/nixos/tests/zookeeper.nix b/nixos/tests/zookeeper.nix
new file mode 100644
index 000000000000..d247654adade
--- /dev/null
+++ b/nixos/tests/zookeeper.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "zookeeper";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes = {
+    server = { pkgs, config, ... }: {
+      services.zookeeper = {
+        enable = true;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 2181 ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("zookeeper");
+    $server->waitForUnit("network.target");
+    $server->waitForOpenPort(2181);
+
+    $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 create /foo bar");
+    $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 set /foo hello");
+    $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 get /foo | grep hello");
+  '';
+})