about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorMatthew Bauer <mjbauer95@gmail.com>2019-02-09 12:14:06 -0500
committerMatthew Bauer <mjbauer95@gmail.com>2019-02-09 12:14:06 -0500
commit5c09d977c794d9243ddac17f6c429b5432431f8f (patch)
tree1acbd7e7f0c01546d955d47c6a8fddfec8e632b6 /nixos
parent21d991b1fd78214023551a0dada17b129cbd5cd5 (diff)
parent18d059a4ac001fbaa9c2abc750a2830a52e1dae5 (diff)
downloadnixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.tar
nixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.tar.gz
nixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.tar.bz2
nixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.tar.lz
nixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.tar.xz
nixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.tar.zst
nixlib-5c09d977c794d9243ddac17f6c429b5432431f8f.zip
Merge remote-tracking branch 'origin/master' into staging
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/default.nix1
-rw-r--r--nixos/doc/manual/release-notes/rl-1903.xml44
-rw-r--r--nixos/lib/testing.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix6
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl4
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/programs/iotop.nix17
-rw-r--r--nixos/modules/programs/sway-beta.nix3
-rw-r--r--nixos/modules/services/games/minecraft-server.nix201
-rw-r--r--nixos/modules/services/mail/roundcube.nix20
-rw-r--r--nixos/modules/services/misc/docker-registry.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix7
-rw-r--r--nixos/modules/services/misc/home-assistant.nix81
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix11
-rw-r--r--nixos/modules/services/misc/redmine.nix37
-rw-r--r--nixos/modules/services/misc/weechat.xml4
-rw-r--r--nixos/modules/services/monitoring/grafana.nix184
-rw-r--r--nixos/modules/services/monitoring/munin.nix209
-rw-r--r--nixos/modules/services/networking/ndppd.nix170
-rw-r--r--nixos/modules/services/networking/syncthing.nix2
-rw-r--r--nixos/modules/services/networking/unifi.nix3
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix35
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix2
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix2
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix6
-rw-r--r--nixos/modules/tasks/filesystems.nix2
-rw-r--r--nixos/tests/all-tests.nix4
-rw-r--r--nixos/tests/home-assistant.nix18
-rw-r--r--nixos/tests/hydra/default.nix160
-rw-r--r--nixos/tests/matrix-synapse.nix50
-rw-r--r--nixos/tests/munin.nix4
-rw-r--r--nixos/tests/ndppd.nix61
-rw-r--r--nixos/tests/neo4j.nix20
-rw-r--r--nixos/tests/osrm-backend.nix53
-rw-r--r--nixos/tests/postgresql.nix15
-rw-r--r--nixos/tests/redmine.nix84
-rw-r--r--nixos/tests/roundcube.nix4
38 files changed, 1304 insertions, 227 deletions
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index faae4f205443..02b91773f5da 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -265,6 +265,7 @@ in rec {
       xsltproc \
         ${manualXsltprocOptions} \
         --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
+        --stringparam id.warnings "1" \
         --nonet --output $dst/ \
         ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
         ${manual-combined}/manual-combined.xml
diff --git a/nixos/doc/manual/release-notes/rl-1903.xml b/nixos/doc/manual/release-notes/rl-1903.xml
index 5050c10ff3e7..270acda9b042 100644
--- a/nixos/doc/manual/release-notes/rl-1903.xml
+++ b/nixos/doc/manual/release-notes/rl-1903.xml
@@ -410,6 +410,23 @@
     (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/54637">#54637</link>)
    </para>
   </listitem>
+  <listitem>
+   <para>
+    <literal>matrix-synapse</literal> has been updated to version 0.99. It will
+    <link xlink:href="https://github.com/matrix-org/synapse/pull/4509">no longer generate a self-signed certificate on first launch</link>
+    and will be <link xlink:href="https://matrix.org/blog/2019/02/05/synapse-0-99-0/">the last version to accept self-signed certificates</link>.
+    As such, it is now recommended to use a proper certificate verified by a
+    root CA (for example Let's Encrypt).
+   </para>
+  </listitem>
+   <listitem>
+    <para>
+     <literal>mailutils</literal> now works by default when
+     <literal>sendmail</literal> is not in a setuid wrapper. As a consequence,
+     the <literal>sendmailPath</literal> argument, having lost its main use, has
+     been removed.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -464,6 +481,11 @@
    </listitem>
    <listitem>
      <para>
+       The astah-community package was removed from nixpkgs due to it being discontinued and the downloads not being available anymore.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
        The httpd service now saves log files with a .log file extension by default for
        easier integration with the logrotate service.
      </para>
@@ -503,6 +525,28 @@
      </para>
     </note>
    </listitem>
+   <listitem>
+    <para>
+     The <link xlink:href="https://github.com/DanielAdolfsson/ndppd"><literal>ndppd</literal></link> module
+     now supports <link linkend="opt-services.ndppd.enable">all config options</link> provided by the current
+     upstream version as service options. Additionally the <literal>ndppd</literal> package doesn't contain
+     the systemd unit configuration from upstream anymore, the unit is completely configured by the NixOS module now.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     New installs of NixOS will default to the Redmine 4.x series unless otherwise specified in
+     <literal>services.redmine.package</literal> while existing installs of NixOS will default to
+     the Redmine 3.x series.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <link linkend="opt-services.grafana.enable">Grafana module</link> now supports declarative
+     <link xlink:href="http://docs.grafana.org/administration/provisioning/">datasource and dashboard</link>
+     provisioning.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/lib/testing.nix b/nixos/lib/testing.nix
index e68563ef48d2..a13e76a69560 100644
--- a/nixos/lib/testing.nix
+++ b/nixos/lib/testing.nix
@@ -3,7 +3,7 @@
   # Use a minimal kernel?
 , minimal ? false
   # Ignored
-, config ? null
+, config ? {}
   # Modules to add to each VM
 , extraConfigurations ? [] }:
 
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 9475da23b1ff..e78e290e7438 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -339,11 +339,11 @@ let
     #   dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i)
     ''
       mkdir ./contents && cd ./contents
-      cp -rp "${efiDir}"/* .
+      cp -rp "${efiDir}"/EFI .
       mkdir ./boot
       cp -p "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}" \
         "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}" ./boot/
-      touch --date=@0 ./*
+      touch --date=@0 ./EFI ./boot
 
       usage_size=$(du -sb --apparent-size . | tr -cd '[:digit:]')
       # Make the image 110% as big as the files need to make up for FAT overhead
@@ -355,7 +355,7 @@ let
       echo "Image size: $image_size"
       truncate --size=$image_size "$out"
       ${pkgs.libfaketime}/bin/faketime "2000-01-01 00:00:00" ${pkgs.dosfstools}/sbin/mkfs.vfat -i 12345678 -n EFIBOOT "$out"
-      mcopy -psvm -i "$out" ./* ::
+      mcopy -psvm -i "$out" ./EFI ./boot ::
       # Verify the FAT partition.
       ${pkgs.dosfstools}/sbin/fsck.vfat -vn "$out"
     ''; # */
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index e58392ad05b4..3bcf90258d79 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -340,6 +340,8 @@ foreach my $fs (read_file("/proc/self/mountinfo")) {
     chomp $fs;
     my @fields = split / /, $fs;
     my $mountPoint = $fields[4];
+    $mountPoint =~ s/\\040/ /g; # account for mount points with spaces in the name (\040 is the escape character)
+    $mountPoint =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
     next unless -d $mountPoint;
     my @mountOptions = split /,/, $fields[5];
 
@@ -355,6 +357,8 @@ foreach my $fs (read_file("/proc/self/mountinfo")) {
     my $fsType = $fields[$n];
     my $device = $fields[$n + 1];
     my @superOptions = split /,/, $fields[$n + 2];
+    $device =~ s/\\040/ /g; # account for devices with spaces in the name (\040 is the escape character)
+    $device =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
 
     # Skip the read-only bind-mount on /nix/store.
     next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 915281cf863e..003b9df95d72 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -101,6 +101,7 @@
   ./programs/gnupg.nix
   ./programs/gphoto2.nix
   ./programs/iftop.nix
+  ./programs/iotop.nix
   ./programs/java.nix
   ./programs/kbdlight.nix
   ./programs/less.nix
diff --git a/nixos/modules/programs/iotop.nix b/nixos/modules/programs/iotop.nix
new file mode 100644
index 000000000000..5512dbc62f72
--- /dev/null
+++ b/nixos/modules/programs/iotop.nix
@@ -0,0 +1,17 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.iotop;
+in {
+  options = {
+    programs.iotop.enable = mkEnableOption "iotop + setcap wrapper";
+  };
+  config = mkIf cfg.enable {
+    security.wrappers.iotop = {
+      source = "${pkgs.iotop}/bin/iotop";
+      capabilities = "cap_net_admin+p";
+    };
+  };
+}
diff --git a/nixos/modules/programs/sway-beta.nix b/nixos/modules/programs/sway-beta.nix
index 7fc5979a38aa..3c235de0ce63 100644
--- a/nixos/modules/programs/sway-beta.nix
+++ b/nixos/modules/programs/sway-beta.nix
@@ -60,10 +60,11 @@ in {
     extraPackages = mkOption {
       type = with types; listOf package;
       default = with pkgs; [
+        swaylock swayidle
         xwayland rxvt_unicode dmenu
       ];
       defaultText = literalExample ''
-        with pkgs; [ xwayland rxvt_unicode dmenu ];
+        with pkgs; [ swaylock swayidle xwayland rxvt_unicode dmenu ];
       '';
       example = literalExample ''
         with pkgs; [
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index f50d2897843a..7d26d1501650 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -4,8 +4,41 @@ with lib;
 
 let
   cfg = config.services.minecraft-server;
-in
-{
+
+  # We don't allow eula=false anyways
+  eulaFile = builtins.toFile "eula.txt" ''
+    # eula.txt managed by NixOS Configuration
+    eula=true
+  '';
+
+  whitelistFile = pkgs.writeText "whitelist.json"
+    (builtins.toJSON
+      (mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
+
+  cfgToString = v: if builtins.isBool v then boolToString v else toString v;
+
+  serverPropertiesFile = pkgs.writeText "server.properties" (''
+    # server.properties managed by NixOS configuration
+  '' + concatStringsSep "\n" (mapAttrsToList
+    (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
+
+
+  # To be able to open the firewall, we need to read out port values in the
+  # server properties, but fall back to the defaults when those don't exist.
+  # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3
+  defaultServerPort = 25565;
+
+  serverPort = cfg.serverProperties.server-port or defaultServerPort;
+
+  rconPort = if cfg.serverProperties.enable-rcon or false
+    then cfg.serverProperties."rcon.port" or 25575
+    else null;
+
+  queryPort = if cfg.serverProperties.enable-query or false
+    then cfg.serverProperties."query.port" or 25565
+    else null;
+
+in {
   options = {
     services.minecraft-server = {
 
@@ -13,10 +46,32 @@ in
         type = types.bool;
         default = false;
         description = ''
-          If enabled, start a Minecraft Server. The listening port for
-          the server is always <literal>25565</literal>. The server
+          If enabled, start a Minecraft Server. The server
           data will be loaded from and saved to
-          <literal>${cfg.dataDir}</literal>.
+          <option>services.minecraft-server.dataDir</option>.
+        '';
+      };
+
+      declarative = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use a declarative Minecraft server configuration.
+          Only if set to <literal>true</literal>, the options
+          <option>services.minecraft-server.whitelist</option> and
+          <option>services.minecraft-server.serverProperties</option> will be
+          applied.
+        '';
+      };
+
+      eula = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether you agree to
+          <link xlink:href="https://account.mojang.com/documents/minecraft_eula">
+          Mojangs EULA</link>. This option must be set to
+          <literal>true</literal> to run Minecraft server.
         '';
       };
 
@@ -24,7 +79,7 @@ in
         type = types.path;
         default = "/var/lib/minecraft";
         description = ''
-          Directory to store minecraft database and other state/data files.
+          Directory to store Minecraft database and other state/data files.
         '';
       };
 
@@ -32,21 +87,84 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Whether to open ports in the firewall (if enabled) for the server.
+          Whether to open ports in the firewall for the server.
+        '';
+      };
+
+      whitelist = mkOption {
+        type = let
+          minecraftUUID = types.strMatching
+            "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
+              description = "Minecraft UUID";
+            };
+          in types.attrsOf minecraftUUID;
+        default = {};
+        description = ''
+          Whitelisted players, only has an effect when
+          <option>services.minecraft-server.declarative</option> is
+          <literal>true</literal> and the whitelist is enabled
+          via <option>services.minecraft-server.serverProperties</option> by
+          setting <literal>white-list</literal> to <literal>true</literal>.
+          This is a mapping from Minecraft usernames to UUIDs.
+          You can use <link xlink:href="https://mcuuid.net/"/> to get a
+          Minecraft UUID for a username.
+        '';
+        example = literalExample ''
+          {
+            username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+            username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
+          };
+        '';
+      };
+
+      serverProperties = mkOption {
+        type = with types; attrsOf (either bool (either int str));
+        default = {};
+        example = literalExample ''
+          {
+            server-port = 43000;
+            difficulty = 3;
+            gamemode = 1;
+            max-players = 5;
+            motd = "NixOS Minecraft server!";
+            white-list = true;
+            enable-rcon = true;
+            "rcon.password" = "hunter2";
+          }
+        '';
+        description = ''
+          Minecraft server properties for the server.properties file. Only has
+          an effect when <option>services.minecraft-server.declarative</option>
+          is set to <literal>true</literal>. See
+          <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/>
+          for documentation on these values.
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.minecraft-server;
+        defaultText = "pkgs.minecraft-server";
+        example = literalExample "pkgs.minecraft-server_1_12_2";
+        description = "Version of minecraft-server to run.";
+      };
+
       jvmOpts = mkOption {
-        type = types.str;
+        type = types.separatedString " ";
         default = "-Xmx2048M -Xms2048M";
-        description = "JVM options for the Minecraft Service.";
+        # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
+        example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+          + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+          + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
+        description = "JVM options for the Minecraft server.";
       };
     };
   };
 
   config = mkIf cfg.enable {
+
     users.users.minecraft = {
-      description     = "Minecraft Server Service user";
+      description     = "Minecraft server service user";
       home            = cfg.dataDir;
       createHome      = true;
       uid             = config.ids.uids.minecraft;
@@ -57,17 +175,60 @@ in
       wantedBy      = [ "multi-user.target" ];
       after         = [ "network.target" ];
 
-      serviceConfig.Restart = "always";
-      serviceConfig.User    = "minecraft";
-      script = ''
-        cd ${cfg.dataDir}
-        exec ${pkgs.minecraft-server}/bin/minecraft-server ${cfg.jvmOpts}
-      '';
-    };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
+        Restart = "always";
+        User = "minecraft";
+        WorkingDirectory = cfg.dataDir;
+      };
+
+      preStart = ''
+        ln -sf ${eulaFile} eula.txt
+      '' + (if cfg.declarative then ''
+
+        if [ -e .declarative ]; then
+
+          # Was declarative before, no need to back up anything
+          ln -sf ${whitelistFile} whitelist.json
+          cp -f ${serverPropertiesFile} server.properties
+
+        else
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedUDPPorts = [ 25565 ];
-      allowedTCPPorts = [ 25565 ];
+          # Declarative for the first time, backup stateful files
+          ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
+          cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
+
+          # server.properties must have write permissions, because every time
+          # the server starts it first parses the file and then regenerates it..
+          chmod +w server.properties
+          echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+            > .declarative
+
+        fi
+      '' else ''
+        if [ -e .declarative ]; then
+          rm .declarative
+        fi
+      '');
     };
+
+    networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
+      allowedUDPPorts = [ serverPort ];
+      allowedTCPPorts = [ serverPort ]
+        ++ optional (! isNull queryPort) queryPort
+        ++ optional (! isNull rconPort) rconPort;
+    } else {
+      allowedUDPPorts = [ defaultServerPort ];
+      allowedTCPPorts = [ defaultServerPort ];
+    });
+
+    assertions = [
+      { assertion = cfg.eula;
+        message = "You must agree to Mojangs EULA to run minecraft-server."
+          + " Read https://account.mojang.com/documents/minecraft_eula and"
+          + " set `services.minecraft-server.eula` to `true` if you agree.";
+      }
+    ];
+
   };
 }
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 6d81c7374f4d..66b1c1e3e6f9 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -25,6 +25,20 @@ in
       description = "Hostname to use for the nginx vhost";
     };
 
+    package = mkOption {
+      type = types.package;
+      default = pkgs.roundcube;
+
+      example = literalExample ''
+        roundcube.withPlugins (plugins: [ plugins.persistent_login ])
+      '';
+
+      description = ''
+        The package which contains roundcube's sources. Can be overriden to create
+        an environment which contains roundcube and third-party plugins.
+      '';
+    };
+
     database = {
       username = mkOption {
         type = types.str;
@@ -86,7 +100,7 @@ in
           forceSSL = mkDefault true;
           enableACME = mkDefault true;
           locations."/" = {
-            root = pkgs.roundcube;
+            root = cfg.package;
             index = "index.php";
             extraConfig = ''
               location ~* \.php$ {
@@ -140,12 +154,12 @@ in
             ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create database ${cfg.database.dbname} with owner ${cfg.database.username}";
           fi
           PGPASSWORD=${cfg.database.password} ${pkgs.postgresql}/bin/psql -U ${cfg.database.username} \
-            -f ${pkgs.roundcube}/SQL/postgres.initial.sql \
+            -f ${cfg.package}/SQL/postgres.initial.sql \
             -h ${cfg.database.host} ${cfg.database.dbname}
           touch /var/lib/roundcube/db-created
         fi
 
-        ${pkgs.php}/bin/php ${pkgs.roundcube}/bin/update.sh
+        ${pkgs.php}/bin/php ${cfg.package}/bin/update.sh
       '';
       serviceConfig.Type = "oneshot";
     };
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index 9a3966ab30aa..f3d90e532c88 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -18,7 +18,7 @@ let
       delete.enabled = cfg.enableDelete;
     };
     http = {
-      addr = ":${builtins.toString cfg.port}";
+      addr = "${cfg.listenAddress}:${builtins.toString cfg.port}";
       headers.X-Content-Type-Options = ["nosniff"];
     };
     health.storagedriver = {
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 769a9526cf64..25c258ebe134 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -497,7 +497,12 @@ in {
     systemd.services.gitaly = {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [ gitAndTools.git cfg.packages.gitaly.rubyEnv cfg.packages.gitaly.rubyEnv.wrappedRuby ];
+      path = with pkgs; [
+        openssh
+        gitAndTools.git
+        cfg.packages.gitaly.rubyEnv
+        cfg.packages.gitaly.rubyEnv.wrappedRuby
+      ];
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 2e9aa33aeeee..4ccfa22c89e7 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -6,9 +6,18 @@ let
   cfg = config.services.home-assistant;
 
   # cfg.config != null can be assumed here
-  configFile = pkgs.writeText "configuration.json"
+  configJSON = pkgs.writeText "configuration.json"
     (builtins.toJSON (if cfg.applyDefaultConfig then
-    (lib.recursiveUpdate defaultConfig cfg.config) else cfg.config));
+    (recursiveUpdate defaultConfig cfg.config) else cfg.config));
+  configFile = pkgs.runCommand "configuration.yaml" { } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+  '';
+
+  lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
+    (builtins.toJSON cfg.lovelaceConfig);
+  lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
+  '';
 
   availableComponents = pkgs.home-assistant.availableComponents;
 
@@ -44,7 +53,9 @@ let
   # If you are changing this, please update the description in applyDefaultConfig
   defaultConfig = {
     homeassistant.time_zone = config.time.timeZone;
-    http.server_port = (toString cfg.port);
+    http.server_port = cfg.port;
+  } // optionalAttrs (cfg.lovelaceConfig != null) {
+    lovelace.mode = "yaml";
   };
 
 in {
@@ -99,6 +110,53 @@ in {
       '';
     };
 
+    configWritable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to make <filename>configuration.yaml</filename> writable.
+        This only has an effect if <option>config</option> is set.
+        This will allow you to edit it from Home Assistant's web interface.
+        However, bear in mind that it will be overwritten at every start of the service.
+      '';
+    };
+
+    lovelaceConfig = mkOption {
+      default = null;
+      type = with types; nullOr attrs;
+      # from https://www.home-assistant.io/lovelace/yaml-mode/
+      example = literalExample ''
+        {
+          title = "My Awesome Home";
+          views = [ {
+            title = "Example";
+            cards = [ {
+              type = "markdown";
+              title = "Lovelace";
+              content = "Welcome to your **Lovelace UI**.";
+            } ];
+          } ];
+        }
+      '';
+      description = ''
+        Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
+        Setting this option will automatically add
+        <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>.
+        Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
+      '';
+    };
+
+    lovelaceConfigWritable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to make <filename>ui-lovelace.yaml</filename> writable.
+        This only has an effect if <option>lovelaceConfig</option> is set.
+        This will allow you to edit it from Home Assistant's web interface.
+        However, bear in mind that it will be overwritten at every start of the service.
+      '';
+    };
+
     package = mkOption {
       default = pkgs.home-assistant;
       defaultText = "pkgs.home-assistant";
@@ -144,12 +202,17 @@ in {
     systemd.services.home-assistant = {
       description = "Home Assistant";
       after = [ "network.target" ];
-      preStart = lib.optionalString (cfg.config != null) ''
-        config=${cfg.configDir}/configuration.yaml
-        rm -f $config
-        ${pkgs.remarshal}/bin/json2yaml -i ${configFile} -o $config
-        chmod 444 $config
-      '';
+      preStart = optionalString (cfg.config != null) (if cfg.configWritable then ''
+        cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
+      '' else ''
+        rm -f "${cfg.configDir}/configuration.yaml"
+        ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
+      '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then ''
+        cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+      '' else ''
+        rm -f "${cfg.configDir}/ui-lovelace.yaml"
+        ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+      '');
       serviceConfig = {
         ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
         User = "hass";
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 18e13f6ac030..a01e34d73629 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -651,12 +651,16 @@ in {
 
     services.postgresql.enable = mkIf usePostgresql (mkDefault true);
 
-    systemd.services.matrix-synapse = {
+    systemd.services.matrix-synapse =
+    let
+      python = (pkgs.python3.withPackages (ps: with ps; [ (ps.toPythonModule cfg.package) ]));
+    in
+    {
       description = "Synapse Matrix homeserver";
       after = [ "network.target" "postgresql.service" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        ${cfg.package}/bin/homeserver \
+        ${python.interpreter} -m synapse.app.homeserver \
           --config-path ${configFile} \
           --keys-directory ${cfg.dataDir} \
           --generate-keys
@@ -687,10 +691,11 @@ in {
         WorkingDirectory = cfg.dataDir;
         PermissionsStartOnly = true;
         ExecStart = ''
-          ${cfg.package}/bin/homeserver \
+          ${python.interpreter} -m synapse.app.homeserver \
             ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
             --keys-directory ${cfg.dataDir}
         '';
+        ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 3c322ba1c3e6..98e9c8953c84 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -30,6 +30,13 @@ let
     ${cfg.extraConfig}
   '';
 
+  additionalEnvironment = pkgs.writeText "additional_environment.rb" ''
+    config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576)
+    config.logger.level = Logger::INFO
+
+    ${cfg.extraEnv}
+  '';
+
   unpackTheme = unpack "theme";
   unpackPlugin = unpack "plugin";
   unpack = id: (name: source:
@@ -54,12 +61,20 @@ in
         description = "Enable the Redmine service.";
       };
 
+      # default to the 4.x series not forcing major version upgrade of those on the 3.x series
       package = mkOption {
         type = types.package;
-        default = pkgs.redmine;
+        default = if versionAtLeast config.system.stateVersion "19.03"
+          then pkgs.redmine_4
+          else pkgs.redmine
+        ;
         defaultText = "pkgs.redmine";
-        description = "Which Redmine package to use.";
-        example = "pkgs.redmine.override { ruby = pkgs.ruby_2_3; }";
+        description = ''
+          Which Redmine package to use. This defaults to version 3.x if
+          <literal>system.stateVersion &lt; 19.03</literal> and version 4.x
+          otherwise.
+        '';
+        example = "pkgs.redmine_4.override { ruby = pkgs.ruby_2_4; }";
       };
 
       user = mkOption {
@@ -103,6 +118,19 @@ in
         '';
       };
 
+      extraEnv = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration in additional_environment.rb.
+
+          See https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example
+        '';
+        example = literalExample ''
+          config.logger.level = Logger::DEBUG
+        '';
+      };
+
       themes = mkOption {
         type = types.attrsOf types.path;
         default = {};
@@ -249,6 +277,9 @@ in
         # link in the application configuration
         ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml"
 
+        # link in the additional environment configuration
+        ln -fs ${additionalEnvironment} "${cfg.stateDir}/config/additional_environment.rb"
+
 
         # link in all user specified themes
         rm -rf "${cfg.stateDir}/public/themes/"*
diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml
index b7f755bbc5c7..7255edfb9da3 100644
--- a/nixos/modules/services/misc/weechat.xml
+++ b/nixos/modules/services/misc/weechat.xml
@@ -8,7 +8,7 @@
   <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
   extensible IRC client.
  </para>
- <section>
+ <section xml:id="module-services-weechat-basic-usage">
   <title>Basic Usage</title>
 
   <para>
@@ -35,7 +35,7 @@
    in the state directory <literal>/var/lib/weechat</literal>.
   </para>
  </section>
- <section>
+ <section xml:id="module-services-weechat-reattach">
   <title>Re-attaching to WeeChat</title>
 
   <para>
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 5fb3e3771221..85879cfe0b33 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -50,6 +50,158 @@ let
     SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
   } // cfg.extraOptions;
 
+  datasourceConfiguration = {
+    apiVersion = 1;
+    datasources = cfg.provision.datasources;
+  };
+
+  datasourceFile = pkgs.writeText "datasource.yaml" (builtins.toJSON datasourceConfiguration);
+
+  dashboardConfiguration = {
+    apiVersion = 1;
+    providers = cfg.provision.dashboards;
+  };
+
+  dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration);
+
+  provisionConfDir =  pkgs.runCommand "grafana-provisioning" { } ''
+    mkdir -p $out/{datasources,dashboards}
+    ln -sf ${datasourceFile} $out/datasources/datasource.yaml
+    ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml
+  '';
+
+  # Get a submodule without any embedded metadata:
+  _filter = x: filterAttrs (k: v: k != "_module") x;
+
+  # http://docs.grafana.org/administration/provisioning/#datasources
+  grafanaTypes.datasourceConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        description = "Name of the datasource. Required";
+      };
+      type = mkOption {
+        type = types.enum ["graphite" "prometheus" "cloudwatch" "elasticsearch" "influxdb" "opentsdb" "mysql" "mssql" "postgres" "loki"];
+        description = "Datasource type. Required";
+      };
+      access = mkOption {
+        type = types.enum ["proxy" "direct"];
+        default = "proxy";
+        description = "Access mode. proxy or direct (Server or Browser in the UI). Required";
+      };
+      orgId = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Org id. will default to orgId 1 if not specified";
+      };
+      url = mkOption {
+        type = types.str;
+        description = "Url of the datasource";
+      };
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database password, if used";
+      };
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database user, if used";
+      };
+      database = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database name, if used";
+      };
+      basicAuth = mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Enable/disable basic auth";
+      };
+      basicAuthUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Basic auth username";
+      };
+      basicAuthPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Basic auth password";
+      };
+      withCredentials = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable/disable with credentials headers";
+      };
+      isDefault = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Mark as default datasource. Max one per org";
+      };
+      jsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Datasource specific configuration";
+      };
+      secureJsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Datasource specific secure configuration";
+      };
+      version = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Version";
+      };
+      editable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Allow users to edit datasources from the UI.";
+      };
+    };
+  };
+
+  # http://docs.grafana.org/administration/provisioning/#dashboards
+  grafanaTypes.dashboardConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = "default";
+        description = "Provider name";
+      };
+      orgId = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Organization ID";
+      };
+      folder = mkOption {
+        type = types.str;
+        default = "";
+        description = "Add dashboards to the speciied folder";
+      };
+      type = mkOption {
+        type = types.str;
+        default = "file";
+        description = "Dashboard provider type";
+      };
+      disableDeletion = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable deletion when JSON file is removed";
+      };
+      updateIntervalSeconds = mkOption {
+        type = types.int;
+        default = 10;
+        description = "How often Grafana will scan for changed dashboards";
+      };
+      options = {
+        path = mkOption {
+          type = types.path;
+          description = "Path grafana will watch for dashboards";
+        };
+      };
+    };
+  };
 in {
   options.services.grafana = {
     enable = mkEnableOption "grafana";
@@ -175,6 +327,22 @@ in {
       };
     };
 
+    provision = {
+      enable = mkEnableOption "provision";
+      datasources = mkOption {
+        description = "Grafana datasources configuration";
+        default = [];
+        type = types.listOf grafanaTypes.datasourceConfig;
+        apply = x: map _filter x;
+      };
+      dashboards = mkOption {
+        description = "Grafana dashboard configuration";
+        default = [];
+        type = types.listOf grafanaTypes.dashboardConfig;
+        apply = x: map _filter x;
+      };
+    };
+
     security = {
       adminUser = mkOption {
         description = "Default admin username.";
@@ -313,10 +481,15 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optional (
-      cfg.database.password != opt.database.password.default ||
-      cfg.security.adminPassword != opt.security.adminPassword.default
-    ) "Grafana passwords will be stored as plaintext in the Nix store!";
+    warnings = flatten [
+      (optional (
+        cfg.database.password != opt.database.password.default ||
+        cfg.security.adminPassword != opt.security.adminPassword.default
+      ) "Grafana passwords will be stored as plaintext in the Nix store!")
+      (optional (
+        any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
+      ) "Datasource passwords will be stored as plaintext in the Nix store!")
+    ];
 
     environment.systemPackages = [ cfg.package ];
 
@@ -359,6 +532,9 @@ in {
         ${optionalString (cfg.smtp.passwordFile != null) ''
           export GF_SMTP_PASSWORD="$(cat ${escapeShellArg cfg.smtp.passwordFile})"
         ''}
+        ${optionalString cfg.provision.enable ''
+          export GF_PATHS_PROVISIONING=${provisionConfDir};
+        ''}
         exec ${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}
       '';
       serviceConfig = {
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index 2b265d5b5a90..f67986327245 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -4,7 +4,7 @@
 # TODO: LWP/Pg perl libs aren't recognized
 
 # TODO: support fastcgi
-# http://munin-monitoring.org/wiki/CgiHowto2
+# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
 # spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
 # spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
@@ -24,6 +24,8 @@ let
       logdir    /var/log/munin
       rundir    /run/munin
 
+      ${lib.optionalString (cronCfg.extraCSS != "") "staticdir ${customStaticDir}"}
+
       ${cronCfg.extraGlobalConfig}
 
       ${cronCfg.hosts}
@@ -63,6 +65,11 @@ let
       [ipmi*]
       user root
       group root
+
+      [munin*]
+      env.UPDATE_STATSFILE /var/lib/munin/munin-update.stats
+
+      ${nodeCfg.extraPluginConfig}
     '';
 
   pluginConfDir = pkgs.stdenv.mkDerivation {
@@ -72,6 +79,54 @@ let
       ln -s ${pluginConf} $out/nixos-config
     '';
   };
+
+  # Copy one Munin plugin into the Nix store with a specific name.
+  # This is suitable for use with plugins going directly into /etc/munin/plugins,
+  # i.e. munin.extraPlugins.
+  internOnePlugin = name: path:
+    "cp -a '${path}' '${name}'";
+
+  # Copy an entire tree of Munin plugins into a single directory in the Nix
+  # store, with no renaming.
+  # This is suitable for use with munin-node-configure --suggest, i.e.
+  # munin.extraAutoPlugins.
+  internManyPlugins = name: path:
+    "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
+
+  # Use the appropriate intern-fn to copy the plugins into the store and patch
+  # them afterwards in an attempt to get them to run on NixOS.
+  internAndFixPlugins = name: intern-fn: paths:
+    pkgs.runCommand name {} ''
+      mkdir -p "$out"
+      cd "$out"
+      ${lib.concatStringsSep "\n"
+          (lib.attrsets.mapAttrsToList intern-fn paths)}
+      chmod -R u+w .
+      find . -type f -exec sed -E -i '
+        s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
+      ' '{}' '+'
+    '';
+
+  # TODO: write a derivation for munin-contrib, so that for contrib plugins
+  # you can just refer to them by name rather than needing to include a copy
+  # of munin-contrib in your nixos configuration.
+  extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
+    internOnePlugin nodeCfg.extraPlugins;
+
+  extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
+    internManyPlugins
+    (builtins.listToAttrs
+      (map
+        (path: { name = baseNameOf path; value = path; })
+        nodeCfg.extraAutoPlugins));
+
+  customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
+    cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
+    cd "$out"
+    chmod -R u+w .
+    echo "${cronCfg.extraCSS}" >> style.css
+    echo "${cronCfg.extraCSS}" >> style-new.css
+  '';
 in
 
 {
@@ -82,11 +137,12 @@ in
 
       enable = mkOption {
         default = false;
+        type = types.bool;
         description = ''
           Enable Munin Node agent. Munin node listens on 0.0.0.0 and
           by default accepts connections only from 127.0.0.1 for security reasons.
 
-          See <link xlink:href='http://munin-monitoring.org/wiki/munin-node.conf' />.
+          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/architecture/index.html' />.
         '';
       };
 
@@ -95,18 +151,108 @@ in
         type = types.lines;
         description = ''
           <filename>munin-node.conf</filename> extra configuration. See
-          <link xlink:href='http://munin-monitoring.org/wiki/munin-node.conf' />
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html' />
+        '';
+      };
+
+      extraPluginConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          <filename>plugin-conf.d</filename> extra plugin configuration. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/plugin/use.html' />
+        '';
+        example = ''
+          [fail2ban_*]
+          user root
         '';
       };
 
-      # TODO: add option to add additional plugins
+      extraPlugins = mkOption {
+        default = {};
+        type = with types; attrsOf path;
+        description = ''
+          Additional Munin plugins to activate. Keys are the name of the plugin
+          symlink, values are the path to the underlying plugin script. You
+          can use the same plugin script multiple times (e.g. for wildcard
+          plugins).
+
+          Note that these plugins do not participate in autoconfiguration. If
+          you want to autoconfigure additional plugins, use
+          <option>services.munin-node.extraAutoPlugins</option>.
+
+          Plugins enabled in this manner take precedence over autoconfigured
+          plugins.
+
+          Plugins will be copied into the Nix store, and it will attempt to
+          modify them to run properly by fixing hardcoded references to
+          <literal>/bin</literal>, <literal>/usr/bin</literal>,
+          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+        '';
+        example = literalExample ''
+          {
+            zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+            zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+            zfs_list = /src/munin-contrib/plugins/zfs/zfs_list;
+          };
+        '';
+      };
 
+      extraAutoPlugins = mkOption {
+        default = [];
+        type = with types; listOf path;
+        description = ''
+          Additional Munin plugins to autoconfigure, using
+          <literal>munin-node-configure --suggest</literal>. These should be
+          the actual paths to the plugin files (or directories containing them),
+          not just their names.
+
+          If you want to manually enable individual plugins instead, use
+          <option>services.munin-node.extraPlugins</option>.
+
+          Note that only plugins that have the 'autoconfig' capability will do
+          anything if listed here, since plugins that cannot autoconfigure
+          won't be automatically enabled by
+          <literal>munin-node-configure</literal>.
+
+          Plugins will be copied into the Nix store, and it will attempt to
+          modify them to run properly by fixing hardcoded references to
+          <literal>/bin</literal>, <literal>/usr/bin</literal>,
+          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+        '';
+        example = literalExample ''
+          [
+            /src/munin-contrib/plugins/zfs
+            /src/munin-contrib/plugins/ssh
+          ];
+        '';
+      };
+
+      disabledPlugins = mkOption {
+        # TODO: figure out why Munin isn't writing the log file and fix it.
+        # In the meantime this at least suppresses a useless graph full of
+        # NaNs in the output.
+        default = [ "munin_stats" ];
+        type = with types; listOf string;
+        description = ''
+          Munin plugins to disable, even if
+          <literal>munin-node-configure --suggest</literal> tries to enable
+          them. To disable a wildcard plugin, use an actual wildcard, as in
+          the example.
+
+          munin_stats is disabled by default as it tries to read
+          <literal>/var/log/munin/munin-update.log</literal> for timing
+          information, and the NixOS build of Munin does not write this file.
+        '';
+        example = [ "diskstats" "zfs_usage_*" ];
+      };
     };
 
     services.munin-cron = {
 
       enable = mkOption {
         default = false;
+        type = types.bool;
         description = ''
           Enable munin-cron. Takes care of all heavy lifting to collect data from
           nodes and draws graphs to html. Runs munin-update, munin-limits,
@@ -119,11 +265,12 @@ in
 
       extraGlobalConfig = mkOption {
         default = "";
+        type = types.lines;
         description = ''
           <filename>munin.conf</filename> extra global configuration.
-          See <link xlink:href='http://munin-monitoring.org/wiki/munin.conf' />.
+          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />.
           Useful to setup notifications, see
-          <link xlink:href='http://munin-monitoring.org/wiki/HowToContact' />
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/tutorial/alert.html' />
         '';
         example = ''
           contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
@@ -131,14 +278,34 @@ in
       };
 
       hosts = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Definitions of hosts of nodes to collect data from. Needs at least one
+          host for cron to succeed. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />
+        '';
         example = ''
           [''${config.networking.hostName}]
           address localhost
         '';
+      };
+
+      extraCSS = mkOption {
+        default = "";
+        type = types.lines;
         description = ''
-          Definitions of hosts of nodes to collect data from. Needs at least one
-          hosts for cron to succeed. See
-          <link xlink:href='http://munin-monitoring.org/wiki/munin.conf' />
+          Custom styling for the HTML that munin-cron generates. This will be
+          appended to the CSS files used by munin-cron and will thus take
+          precedence over the builtin styles.
+        '';
+        example = ''
+          /* A simple dark theme. */
+          html, body { background: #222222; }
+          #header, #footer { background: #333333; }
+          img.i, img.iwarn, img.icrit, img.iunkn {
+            filter: invert(100%) hue-rotate(-30deg);
+          }
         '';
       };
 
@@ -155,6 +322,7 @@ in
       description = "Munin monitoring user";
       group = "munin";
       uid = config.ids.uids.munin;
+      home = "/var/lib/munin";
     }];
 
     users.groups = [{
@@ -173,14 +341,27 @@ in
       environment.MUNIN_PLUGSTATE = "/run/munin";
       environment.MUNIN_LOGDIR = "/var/log/munin";
       preStart = ''
-        echo "updating munin plugins..."
+        echo "Updating munin plugins..."
 
         mkdir -p /etc/munin/plugins
         rm -rf /etc/munin/plugins/*
+
+        # Autoconfigure builtin plugins
         ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${pkgs.munin}/lib/plugins --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
 
-        # NOTE: we disable disktstats because plugin seems to fail and it hangs html generation (100% CPU + memory leak)
-        rm /etc/munin/plugins/diskstats || true
+        # Autoconfigure extra plugins
+        ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${extraAutoPluginDir} --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
+
+        ${lib.optionalString (nodeCfg.extraPlugins != {}) ''
+            # Link in manually enabled plugins
+            ln -f -s -t /etc/munin/plugins ${extraPluginDir}/*
+          ''}
+
+        ${lib.optionalString (nodeCfg.disabledPlugins != []) ''
+            # Disable plugins
+            cd /etc/munin/plugins
+            rm -f ${toString nodeCfg.disabledPlugins}
+          ''}
       '';
       serviceConfig = {
         ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}";
@@ -192,6 +373,10 @@ in
 
   }) (mkIf cronCfg.enable {
 
+    # Munin is hardcoded to use DejaVu Mono and the graphs come out wrong if
+    # it's not available.
+    fonts.fonts = [ pkgs.dejavu_fonts ];
+
     systemd.timers.munin-cron = {
       description = "batch Munin master programs";
       wantedBy = [ "timers.target" ];
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index 1d6c48dd8d37..ba17f1ba825a 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -5,43 +5,163 @@ with lib;
 let
   cfg = config.services.ndppd;
 
-  configFile = pkgs.runCommand "ndppd.conf" {} ''
-    substitute ${pkgs.ndppd}/etc/ndppd.conf $out \
-      --replace eth0 ${cfg.interface} \
-      --replace 1111:: ${cfg.network}
-  '';
-in {
-  options = {
-    services.ndppd = {
-      enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+  render = s: f: concatStringsSep "\n" (mapAttrsToList f s);
+  prefer = a: b: if a != null then a else b;
+
+  ndppdConf = prefer cfg.configFile (pkgs.writeText "ndppd.conf" ''
+    route-ttl ${toString cfg.routeTTL}
+    ${render cfg.proxies (proxyInterfaceName: proxy: ''
+    proxy ${prefer proxy.interface proxyInterfaceName} {
+      router ${boolToString proxy.router}
+      timeout ${toString proxy.timeout}
+      ttl ${toString proxy.ttl}
+      ${render proxy.rules (ruleNetworkName: rule: ''
+      rule ${prefer rule.network ruleNetworkName} {
+        ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+      }'')}
+    }'')}
+  '');
+
+  proxy = types.submodule {
+    options = {
       interface = mkOption {
-        type = types.string;
-        default = "eth0";
-        example = "ens3";
-        description = "Interface which is on link-level with router.";
+        type = types.nullOr types.str;
+        description = ''
+          Listen for any Neighbor Solicitation messages on this interface,
+          and respond to them according to a set of rules.
+          Defaults to the name of the attrset.
+        '';
+        default = null;
+      };
+      router = mkOption {
+        type = types.bool;
+        description = ''
+          Turns on or off the router flag for Neighbor Advertisement Messages.
+        '';
+        default = true;
+      };
+      timeout = mkOption {
+        type = types.int;
+        description = ''
+          Controls how long to wait for a Neighbor Advertisment Message before 
+          invalidating the entry, in milliseconds.
+        '';
+        default = 500;
+      };
+      ttl = mkOption {
+        type = types.int;
+        description = ''
+          Controls how long a valid or invalid entry remains in the cache, in 
+          milliseconds.
+        '';
+        default = 30000;
       };
+      rules = mkOption {
+        type = types.attrsOf rule;
+        description = ''
+          This is a rule that the target address is to match against. If no netmask
+          is provided, /128 is assumed. You may have several rule sections, and the
+          addresses may or may not overlap.
+        '';
+        default = {};
+      };
+    };
+  };
+
+  rule = types.submodule {
+    options = {
       network = mkOption {
-        type = types.string;
-        default = "1111::";
-        example = "2001:DB8::/32";
-        description = "Network that we proxy.";
+        type = types.nullOr types.str;
+        description = ''
+          This is the target address is to match against. If no netmask
+          is provided, /128 is assumed. The addresses of serveral rules
+          may or may not overlap.
+          Defaults to the name of the attrset.
+        '';
+        default = null;
+      };
+      method = mkOption {
+        type = types.enum [ "static" "iface" "auto" ];
+        description = ''
+          static: Immediately answer any Neighbor Solicitation Messages
+            (if they match the IP rule).
+          iface: Forward the Neighbor Solicitation Message through the specified
+            interface and only respond if a matching Neighbor Advertisement
+            Message is received.
+          auto: Same as iface, but instead of manually specifying the outgoing
+            interface, check for a matching route in /proc/net/ipv6_route.
+        '';
+        default = "auto";
       };
-      configFile = mkOption {
-        type = types.nullOr types.path;
+      interface = mkOption {
+        type = types.nullOr types.str;
+        description = "Interface to use when method is iface.";
         default = null;
-        description = "Path to configuration file.";
       };
     };
   };
 
+in {
+  options.services.ndppd = {
+    enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+    interface = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        Interface which is on link-level with router.
+        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+      '';
+      default = null;
+      example = "eth0";
+    };
+    network = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        Network that we proxy.
+        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+      '';
+      default = null;
+      example = "1111::/64";
+    };
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      description = "Path to configuration file.";
+      default = null;
+    };
+    routeTTL = mkOption {
+      type = types.int;
+      description = ''
+        This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
+        in milliseconds.
+      '';
+      default = 30000;
+    };
+    proxies = mkOption {
+      type = types.attrsOf proxy;
+      description = ''
+        This sets up a listener, that will listen for any Neighbor Solicitation
+        messages, and respond to them according to a set of rules.
+      '';
+      default = {};
+      example = { "eth0".rules."1111::/64" = {}; };
+    };
+  };
+
   config = mkIf cfg.enable {
-    systemd.packages = [ pkgs.ndppd ];
-    environment.etc."ndppd.conf".source = if (cfg.configFile != null) then cfg.configFile else configFile;
+    warnings = mkIf (cfg.interface != null && cfg.network != null) [ ''
+      The options services.ndppd.interface and services.ndppd.network will probably be removed soon,
+      please use services.ndppd.proxies.<interface>.rules.<network> instead.
+    '' ];
+
+    services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
+      "${cfg.interface}".rules."${cfg.network}" = {};
+    };
+
     systemd.services.ndppd = {
-      serviceConfig.RuntimeDirectory = [ "ndppd" ];
+      description = "NDP Proxy Daemon";
+      documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
+      after = [ "network-pre.target" ];
       wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
     };
   };
-
-  meta.maintainers = with maintainers; [ gnidorah ];
 }
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index b2ef1885a955..702481ec5177 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -122,7 +122,7 @@ in {
 
     systemd.packages = [ pkgs.syncthing ];
 
-    users = mkIf (cfg.user == defaultUser) {
+    users = mkIf (cfg.systemService && cfg.user == defaultUser) {
       users."${defaultUser}" =
         { group = cfg.group;
           home  = cfg.dataDir;
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 89b9ac4eadf5..c82e0af2803d 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -121,11 +121,12 @@ in
     };
 
     networking.firewall = mkIf cfg.openPorts {
-      # https://help.ubnt.com/hc/en-us/articles/204910084-UniFi-Change-Default-Ports-for-Controller-and-UAPs
+      # https://help.ubnt.com/hc/en-us/articles/218506997
       allowedTCPPorts = [
         8080  # Port for UAP to inform controller.
         8880  # Port for HTTP portal redirect, if guest portal is enabled.
         8843  # Port for HTTPS portal redirect, ditto.
+        6789  # Port for UniFi mobile speed test.
       ];
       allowedUDPPorts = [
         3478  # UDP port used for STUN.
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index ecb1c5615d58..ee1354d6a997 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -426,7 +426,7 @@ in {
               "~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\\.php(?:$|/)" = {
                 priority = 500;
                 extraConfig = ''
-                  include ${pkgs.nginxMainline}/conf/fastcgi.conf;
+                  include ${config.services.nginx.package}/conf/fastcgi.conf;
                   fastcgi_split_path_info ^(.+\.php)(/.*)$;
                   fastcgi_param PATH_INFO $fastcgi_path_info;
                   fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
index d1ee076e9185..efec943c0075 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -6,12 +6,14 @@ let
 
   dmcfg = config.services.xserver.displayManager;
   ldmcfg = dmcfg.lightdm;
+  xcfg = config.services.xserver;
   cfg = ldmcfg.greeters.gtk;
 
   inherit (pkgs) writeText;
 
   theme = cfg.theme.package;
   icons = cfg.iconTheme.package;
+  cursors = cfg.cursorTheme.package;
 
   # The default greeter provided with this expression is the GTK greeter.
   # Again, we need a few things in the environment for the greeter to run with
@@ -28,7 +30,8 @@ let
         --set GTK_EXE_PREFIX "${theme}" \
         --set GTK_DATA_PREFIX "${theme}" \
         --set XDG_DATA_DIRS "${theme}/share:${icons}/share" \
-        --set XDG_CONFIG_HOME "${theme}/share"
+        --set XDG_CONFIG_HOME "${theme}/share" \
+        --set XCURSOR_PATH "${cursors}/share/icons"
 
       cat - > $out/lightdm-gtk-greeter.desktop << EOF
       [Desktop Entry]
@@ -44,9 +47,12 @@ let
     [greeter]
     theme-name = ${cfg.theme.name}
     icon-theme-name = ${cfg.iconTheme.name}
+    cursor-theme-name = ${cfg.cursorTheme.name}
+    cursor-theme-size = ${toString cfg.cursorTheme.size}
     background = ${ldmcfg.background}
     ${optionalString (cfg.clock-format != null) "clock-format = ${cfg.clock-format}"}
     ${optionalString (cfg.indicators != null) "indicators = ${concatStringsSep ";" cfg.indicators}"}
+    ${optionalString (xcfg.dpi != null) "xft-dpi=${toString xcfg.dpi}"}
     ${cfg.extraConfig}
     '';
 
@@ -106,6 +112,33 @@ in
 
       };
 
+      cursorTheme = {
+
+        package = mkOption {
+          default = pkgs.gnome3.defaultIconTheme;
+          defaultText = "pkgs.gnome3.defaultIconTheme";
+          description = ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the cursor theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+
+        size = mkOption {
+          type = types.int;
+          default = 16;
+          description = ''
+            Size of the cursor theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+      };
+
       clock-format = mkOption {
         type = types.nullOr types.str;
         default = null;
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 047651dc6426..7db60daa60b8 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -33,7 +33,7 @@ let
       avoid_warnings=1
     '' + optional isAarch64 ''
       # Boot in 64-bit mode.
-      arm_control=0x200
+      arm_64bit=1
     '' + (if cfg.uboot.enable then ''
       kernel=u-boot-rpi.bin
     '' else ''
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index 7b756b70e2fc..d225778a3878 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -78,7 +78,7 @@ let cfg = config.system.autoUpgrade; in
           HOME = "/root";
         } // config.networking.proxy.envVars;
 
-      path = [ pkgs.gnutar pkgs.xz.bin config.nix.package.out ];
+      path = [ pkgs.gnutar pkgs.xz.bin pkgs.gitMinimal config.nix.package.out ];
 
       script = ''
         ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch ${toString cfg.flags}
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 2ffbb8777068..2c9231f55236 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -19,21 +19,21 @@ let
         description = "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
       };
 
-      options.blkDev = mkOption {
+      blkDev = mkOption {
         default = null;
         example = "/dev/sda1";
         type = types.nullOr types.str;
         description = "Location of the backing encrypted device.";
       };
 
-      options.label = mkOption {
+      label = mkOption {
         default = null;
         example = "rootfs";
         type = types.nullOr types.str;
         description = "Label of the unlocked encrypted device. Set <literal>fileSystems.&lt;name?&gt;.device</literal> to <literal>/dev/mapper/&lt;label&gt;</literal> to mount the unlocked device.";
       };
 
-      options.keyFile = mkOption {
+      keyFile = mkOption {
         default = null;
         example = "/mnt-root/root/.swapkey";
         type = types.nullOr types.str;
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 9e4057b50897..07f8214cea2c 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -231,7 +231,7 @@ in
         fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" ];
         skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
         # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
-        escape = string: builtins.replaceStrings [ " " ] [ "\\040" ] string;
+        escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
       in ''
         # This is a generated file.  Do not edit!
         #
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 08eea32c6b84..fb68159d6677 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -129,6 +129,7 @@ in
   matrix-synapse = handleTest ./matrix-synapse.nix {};
   memcached = handleTest ./memcached.nix {};
   mesos = handleTest ./mesos.nix {};
+  minio = handleTest ./minio.nix {};
   misc = handleTest ./misc.nix {};
   mongodb = handleTest ./mongodb.nix {};
   morty = handleTest ./morty.nix {};
@@ -142,6 +143,8 @@ in
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
   nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  ndppd = handleTest ./ndppd.nix {};
+  neo4j = handleTest ./neo4j.nix {};
   netdata = handleTest ./netdata.nix {};
   networking.networkd = handleTest ./networking.nix { networkd = true; };
   networking.scripted = handleTest ./networking.nix { networkd = false; };
@@ -161,6 +164,7 @@ in
   opensmtpd = handleTest ./opensmtpd.nix {};
   openssh = handleTest ./openssh.nix {};
   osquery = handleTest ./osquery.nix {};
+  osrm-backend = handleTest ./osrm-backend.nix {};
   ostree = handleTest ./ostree.nix {};
   pam-oath-login = handleTest ./pam-oath-login.nix {};
   pam-u2f = handleTest ./pam-u2f.nix {};
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 2febdd7b2870..a93360b252f6 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -50,6 +50,18 @@ in {
               }
             ];
           };
+          lovelaceConfig = {
+            title = "My Awesome Home";
+            views = [ {
+              title = "Example";
+              cards = [ {
+                type = "markdown";
+                title = "Lovelace";
+                content = "Welcome to your **Lovelace UI**.";
+              } ];
+            } ];
+          };
+          lovelaceConfigWritable = true;
         };
       };
   };
@@ -59,8 +71,10 @@ in {
     $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");
+    # converted from JSON to YAML, and linked to the config dir
+    $hass->succeed("test -L ${configDir}/configuration.yaml");
+    # The lovelace config is copied because lovelaceConfigWritable = true
+    $hass->succeed("test -f ${configDir}/ui-lovelace.yaml");
 
     # Check that Home Assistant's web interface and API can be reached
     $hass->waitForOpenPort(8123);
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index db4e97e0039b..882bced86d39 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -1,77 +1,91 @@
-import ../make-test.nix ({ pkgs, ...} :
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
 
 let
-   trivialJob = pkgs.writeTextDir "trivial.nix" ''
-     { trivial = builtins.derivation {
-         name = "trivial";
-         system = "x86_64-linux";
-         builder = "/bin/sh";
-         args = ["-c" "echo success > $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 ma27 ];
-  };
 
-  machine =
-    { 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" ];
-        }];
-
-        binaryCaches = [];
-      };
-    };
-
-  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');
+  trivialJob = pkgs.writeTextDir "trivial.nix" ''
+   { trivial = builtins.derivation {
+       name = "trivial";
+       system = "x86_64-linux";
+       builder = "/bin/sh";
+       args = ["-c" "echo success > $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}
     '';
-})
+  };
+
+  callTest = f: f { inherit system pkgs; };
+
+  hydraPkgs = {
+    inherit (pkgs) nixStable nixUnstable;
+  };
+
+  tests = pkgs.lib.flip pkgs.lib.mapAttrs hydraPkgs (name: nix:
+    callTest (import ../make-test.nix ({ pkgs, lib, ... }:
+      {
+        name = "hydra-with-${name}";
+        meta = with pkgs.stdenv.lib.maintainers; {
+          maintainers = [ pstn lewo ma27 ];
+        };
+
+        machine = { 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";
+
+              package = pkgs.hydra.override { inherit nix; };
+            };
+            nix = {
+              buildMachines = [{
+                hostName = "localhost";
+                systems = [ "x86_64-linux" ];
+              }];
+
+              binaryCaches = [];
+            };
+          };
+
+        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');
+        '';
+      })));
+
+in
+  tests
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
index 8504a7c0d057..882e4b75814b 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix-synapse.nix
@@ -1,4 +1,32 @@
-import ./make-test.nix ({ pkgs, ... } : {
+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=snakeoil-ca"
+  '';
+  key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+  csr = runWithOpenSSL "matrix.csr" ''
+    openssl req \
+       -new -key ${key} \
+       -out $out -subj "/CN=localhost" \
+  '';
+  cert = runWithOpenSSL "matrix_cert.pem" ''
+    openssl x509 \
+      -req -in ${csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} \
+      -CAcreateserial -out $out \
+      -days 365
+  '';
+
+in {
 
   name = "matrix-synapse";
   meta = with pkgs.stdenv.lib.maintainers; {
@@ -8,23 +36,31 @@ import ./make-test.nix ({ pkgs, ... } : {
   nodes = {
     # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
     serverpostgres = args: {
-      services.matrix-synapse.enable = true;
-      services.matrix-synapse.database_type = "psycopg2";
+      services.matrix-synapse = {
+        enable = true;
+        database_type = "psycopg2";
+        tls_certificate_path = "${cert}";
+        tls_private_key_path = "${key}";
+      };
     };
 
     serversqlite = args: {
-      services.matrix-synapse.enable = true;
-      services.matrix-synapse.database_type = "sqlite3";
+      services.matrix-synapse = {
+        enable = true;
+        database_type = "sqlite3";
+        tls_certificate_path = "${cert}";
+        tls_private_key_path = "${key}";
+      };
     };
   };
 
   testScript = ''
     startAll;
     $serverpostgres->waitForUnit("matrix-synapse.service");
-    $serverpostgres->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $serverpostgres->waitUntilSucceeds("curl -L --cacert ${ca_pem} https://localhost:8448/");
     $serverpostgres->requireActiveUnit("postgresql.service");
     $serversqlite->waitForUnit("matrix-synapse.service");
-    $serversqlite->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $serversqlite->waitUntilSucceeds("curl -L --cacert ${ca_pem} https://localhost:8448/");
     $serversqlite->mustSucceed("[ -e /var/lib/matrix-synapse/homeserver.db ]");
   '';
 
diff --git a/nixos/tests/munin.nix b/nixos/tests/munin.nix
index 9f66005292ab..95cecf17b8cc 100644
--- a/nixos/tests/munin.nix
+++ b/nixos/tests/munin.nix
@@ -15,9 +15,7 @@ import ./make-test.nix ({ pkgs, ...} : {
            munin-node = {
              enable = true;
              # disable a failing plugin to prevent irrelevant error message, see #23049
-             extraConfig = ''
-               ignore_file ^apc_nis$
-             '';
+             disabledPlugins = [ "apc_nis" ];
            };
            munin-cron = {
             enable = true;
diff --git a/nixos/tests/ndppd.nix b/nixos/tests/ndppd.nix
new file mode 100644
index 000000000000..9f24eb6d9d45
--- /dev/null
+++ b/nixos/tests/ndppd.nix
@@ -0,0 +1,61 @@
+import ./make-test.nix ({ pkgs, lib, ...} : {
+  name = "ndppd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes = {
+    upstream = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::1"; prefixLength = 112; }
+          ];
+          ipv6.routes = [
+            { address = "fd42::";
+              prefixLength = 112;
+            }
+          ];
+        };
+      };
+    };
+    server = { pkgs, ... }: {
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = "1";
+        "net.ipv6.conf.default.forwarding" = "1";
+      };
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::2"; prefixLength = 112; }
+          ];
+        };
+      };
+      services.ndppd = {
+        enable = true;
+        interface = "eth1";
+        network = "fd42::/112";
+      };
+      containers.client = {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "192.168.255.1";
+        localAddress = "192.168.255.2";
+        hostAddress6 = "fd42::1";
+        localAddress6 = "fd42::2";
+        config = {};
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $server->waitForUnit("multi-user.target");
+    $upstream->waitForUnit("multi-user.target");
+    $upstream->waitUntilSucceeds("ping -c5 fd42::2");
+  '';
+})
diff --git a/nixos/tests/neo4j.nix b/nixos/tests/neo4j.nix
new file mode 100644
index 000000000000..86ed8970517c
--- /dev/null
+++ b/nixos/tests/neo4j.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix {
+  name = "neo4j";
+
+  nodes = {
+    master =
+      { ... }:
+
+      {
+        services.neo4j.enable = true;
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("neo4j");
+    $master->sleep(20); # Hopefully this is long enough!!
+    $master->succeed("curl http://localhost:7474/");
+  '';
+}
diff --git a/nixos/tests/osrm-backend.nix b/nixos/tests/osrm-backend.nix
new file mode 100644
index 000000000000..6e2d098d4adb
--- /dev/null
+++ b/nixos/tests/osrm-backend.nix
@@ -0,0 +1,53 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+let
+  port = 5000;
+in {
+  name = "osrm-backend";
+  meta.maintainers = [ lib.maintainers.erictapen ];
+
+  machine = { config, pkgs, ... }:{
+
+    services.osrm = {
+      enable = true;
+      inherit port;
+      dataFile = let
+        filename = "monaco";
+        osrm-data = pkgs.stdenv.mkDerivation {
+          name = "osrm-data";
+
+          buildInputs = [ pkgs.osrm-backend ];
+
+          # This is a pbf file of monaco, downloaded at 2019-01-04 from
+          # http://download.geofabrik.de/europe/monaco-latest.osm.pbf
+          # as apparently no provider of OSM files guarantees immutability,
+          # this is hosted as a gist on GitHub.
+          src = pkgs.fetchgit {
+            url = "https://gist.github.com/erictapen/01e39f73a6c856eac53ba809a94cdb83";
+            rev = "9b1ff0f24deb40e5cf7df51f843dbe860637b8ce";
+            sha256 = "1scqhmrfnpwsy5i2a9jpggqnvfgj4hv9p4qyvc79321pzkbv59nx";
+          };
+
+          buildCommand = ''
+            cp $src/${filename}.osm.pbf .
+            ${pkgs.osrm-backend}/bin/osrm-extract -p ${pkgs.osrm-backend}/share/osrm/profiles/car.lua ${filename}.osm.pbf
+            ${pkgs.osrm-backend}/bin/osrm-partition ${filename}.osrm
+            ${pkgs.osrm-backend}/bin/osrm-customize ${filename}.osrm
+            mkdir -p $out
+            cp ${filename}* $out/
+          '';
+        };
+      in "${osrm-data}/${filename}.osrm";
+    };
+
+    environment.systemPackages = [ pkgs.jq ];
+  };
+
+  testScript = let
+    query = "http://localhost:${toString port}/route/v1/driving/7.41720,43.73304;7.42463,43.73886?steps=true";
+  in ''
+    $machine->waitForUnit("osrm.service");
+    $machine->waitForOpenPort(${toString port});
+    $machine->succeed("curl --silent '${query}' | jq .waypoints[0].name | grep -F 'Boulevard Rainier III'");
+    $machine->succeed("curl --silent '${query}' | jq .waypoints[1].name | grep -F 'Avenue de la Costa'");
+  '';
+})
diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix
index 9e1f4f235af0..ae5d6d095ea2 100644
--- a/nixos/tests/postgresql.nix
+++ b/nixos/tests/postgresql.nix
@@ -7,7 +7,7 @@ with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
-  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs pkgs;
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
   test-sql = pkgs.writeText "postgresql-test" ''
     CREATE EXTENSION pgcrypto; -- just to check if lib loading works
     CREATE TABLE sth (
@@ -67,12 +67,7 @@ let
 
   };
 in
-  (mapAttrs' (name: package: { inherit name; value=make-postgresql-test name package false;}) postgresql-versions) // (
-    # just pick one version for the dump all test
-    let
-      first = head (attrNames postgresql-versions);
-      name = "${first}-backup-all";
-    in {
-      ${name} = make-postgresql-test name postgresql-versions.${first} true;
-    }
-  )
+  (mapAttrs' (name: package: { inherit name; value=make-postgresql-test name package false;}) postgresql-versions) // {
+    postgresql_11-backup-all = make-postgresql-test "postgresql_11-backup-all" postgresql-versions.postgresql_11 true;
+  }
+
diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix
index 330f72854cac..ea72a0121d11 100644
--- a/nixos/tests/redmine.nix
+++ b/nixos/tests/redmine.nix
@@ -1,40 +1,58 @@
-import ./make-test.nix ({ pkgs, lib, ... }:
-{
-  name = "redmine";
-  meta.maintainers = [ lib.maintainers.aanderse ];
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
 
-  machine =
-    { config, pkgs, ... }:
-    { services.mysql.enable = true;
-      services.mysql.package = pkgs.mariadb;
-      services.mysql.ensureDatabases = [ "redmine" ];
-      services.mysql.ensureUsers = [
-        { name = "redmine";
-          ensurePermissions = { "redmine.*" = "ALL PRIVILEGES"; };
-        }
-      ];
+let
+  redmineTest = package: makeTest {
+    machine =
+      { config, pkgs, ... }:
+      { services.mysql.enable = true;
+        services.mysql.package = pkgs.mariadb;
+        services.mysql.ensureDatabases = [ "redmine" ];
+        services.mysql.ensureUsers = [
+          { name = "redmine";
+            ensurePermissions = { "redmine.*" = "ALL PRIVILEGES"; };
+          }
+        ];
 
-      services.redmine.enable = true;
-      services.redmine.database.socket = "/run/mysqld/mysqld.sock";
-      services.redmine.plugins = {
-        redmine_env_auth = pkgs.fetchurl {
-          url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip;
-          sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
+        services.redmine.enable = true;
+        services.redmine.package = package;
+        services.redmine.database.socket = "/run/mysqld/mysqld.sock";
+        services.redmine.plugins = {
+          redmine_env_auth = pkgs.fetchurl {
+            url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip;
+            sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc";
+          };
         };
-      };
-      services.redmine.themes = {
-        dkuk-redmine_alex_skin = pkgs.fetchurl {
-          url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
-          sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+        services.redmine.themes = {
+          dkuk-redmine_alex_skin = pkgs.fetchurl {
+            url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+            sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+          };
         };
       };
-    };
 
-  testScript = ''
-    startAll;
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('redmine.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+in
+{
+  redmine_3 = redmineTest pkgs.redmine // {
+    name = "redmine_3";
+    meta.maintainers = [ maintainers.aanderse ];
+  };
 
-    $machine->waitForUnit('redmine.service');
-    $machine->waitForOpenPort('3000');
-    $machine->succeed("curl --fail http://localhost:3000/");
-  '';
-})
+  redmine_4 = redmineTest pkgs.redmine_4 // {
+    name = "redmine_4";
+    meta.maintainers = [ maintainers.aanderse ];
+  };
+}
diff --git a/nixos/tests/roundcube.nix b/nixos/tests/roundcube.nix
index 178134fd9b30..ed0ebd7dd19d 100644
--- a/nixos/tests/roundcube.nix
+++ b/nixos/tests/roundcube.nix
@@ -10,6 +10,8 @@ import ./make-test.nix ({ pkgs, ...} : {
         enable = true;
         hostName = "roundcube";
         database.password = "notproduction";
+        package = pkgs.roundcube.withPlugins (plugins: [ plugins.persistent_login ]);
+        plugins = [ "persistent_login" ];
       };
       services.nginx.virtualHosts.roundcube = {
         forceSSL = false;
@@ -23,6 +25,6 @@ import ./make-test.nix ({ pkgs, ...} : {
     $roundcube->waitForUnit("postgresql.service");
     $roundcube->waitForUnit("phpfpm-roundcube.service");
     $roundcube->waitForUnit("nginx.service");
-    $roundcube->succeed("curl -sSfL http://roundcube/");
+    $roundcube->succeed("curl -sSfL http://roundcube/ | grep 'Keep me logged in'");
   '';
 })