summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/administration/imperative-containers.xml13
-rw-r--r--nixos/doc/manual/configuration/file-systems.xml6
-rw-r--r--nixos/doc/manual/installation/installing-usb.xml5
-rw-r--r--nixos/doc/manual/release-notes/rl-1709.xml31
-rw-r--r--nixos/lib/make-disk-image.nix204
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix5
-rw-r--r--nixos/modules/config/i18n.nix28
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix6
-rw-r--r--nixos/modules/installer/tools/nixos-prepare-root.sh4
-rw-r--r--nixos/modules/misc/extra-arguments.nix10
-rw-r--r--nixos/modules/misc/ids.nix10
-rw-r--r--nixos/modules/misc/nixpkgs.nix7
-rw-r--r--nixos/modules/module-list.nix11
-rw-r--r--nixos/modules/profiles/hardened.nix62
-rw-r--r--nixos/modules/programs/browserpass.nix26
-rw-r--r--nixos/modules/programs/command-not-found/command-not-found.nix4
-rw-r--r--nixos/modules/programs/environment.nix2
-rw-r--r--nixos/modules/programs/qt5ct.nix31
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.nix66
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix81
-rw-r--r--nixos/modules/programs/zsh/zsh.nix16
-rw-r--r--nixos/modules/rename.nix3
-rw-r--r--nixos/modules/security/grsecurity.nix2
-rw-r--r--nixos/modules/security/grsecurity.xml8
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix36
-rw-r--r--nixos/modules/services/backup/tarsnap.nix13
-rw-r--r--nixos/modules/services/backup/znapzend.nix13
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix1
-rw-r--r--nixos/modules/services/databases/clickhouse.nix75
-rw-r--r--nixos/modules/services/hardware/amd-hybrid-graphics.nix15
-rw-r--r--nixos/modules/services/misc/bepasty.nix36
-rw-r--r--nixos/modules/services/misc/gogs.nix62
-rw-r--r--nixos/modules/services/misc/jackett.nix7
-rw-r--r--nixos/modules/services/misc/plex.nix2
-rw-r--r--nixos/modules/services/misc/radarr.nix7
-rw-r--r--nixos/modules/services/misc/sonarr.nix7
-rw-r--r--nixos/modules/services/monitoring/cadvisor.nix91
-rw-r--r--nixos/modules/services/monitoring/graphite.nix16
-rw-r--r--nixos/modules/services/monitoring/longview.nix112
-rw-r--r--nixos/modules/services/monitoring/munin.nix4
-rw-r--r--nixos/modules/services/monitoring/ups.nix2
-rw-r--r--nixos/modules/services/networking/aria2.nix135
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix8
-rw-r--r--nixos/modules/services/networking/i2pd.nix5
-rw-r--r--nixos/modules/services/networking/networkmanager.nix12
-rw-r--r--nixos/modules/services/networking/radicale.nix2
-rw-r--r--nixos/modules/services/networking/xrdp.nix153
-rw-r--r--nixos/modules/services/scheduling/fcron.nix2
-rw-r--r--nixos/modules/services/security/shibboleth-sp.nix73
-rw-r--r--nixos/modules/services/security/sshguard.nix140
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix2
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mediawiki.nix14
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix9
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix1
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix54
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/slim.nix7
-rw-r--r--nixos/modules/services/x11/xserver.nix125
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix6
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh19
-rw-r--r--nixos/modules/tasks/trackpoint.nix2
-rw-r--r--nixos/modules/virtualisation/azure-image.nix96
-rw-r--r--nixos/modules/virtualisation/docker.nix6
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix100
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix13
-rw-r--r--nixos/release-combined.nix1
-rw-r--r--nixos/release.nix3
-rw-r--r--nixos/tests/grsecurity.nix46
-rw-r--r--nixos/tests/hardened.nix36
-rw-r--r--nixos/tests/misc.nix9
-rw-r--r--nixos/tests/mysql-replication.nix10
-rw-r--r--nixos/tests/radicale.nix80
-rw-r--r--nixos/tests/slim.nix66
-rw-r--r--nixos/tests/xrdp.nix45
75 files changed, 1906 insertions, 518 deletions
diff --git a/nixos/doc/manual/administration/imperative-containers.xml b/nixos/doc/manual/administration/imperative-containers.xml
index 258e1ea948da..9851eb08afb5 100644
--- a/nixos/doc/manual/administration/imperative-containers.xml
+++ b/nixos/doc/manual/administration/imperative-containers.xml
@@ -29,8 +29,10 @@ line. For instance, to create a container that has
 <literal>root</literal>:
 
 <screen>
-# nixos-container create foo --config 'services.openssh.enable = true; \
-  users.extraUsers.root.openssh.authorizedKeys.keys = ["ssh-dss AAAAB3N…"];'
+# nixos-container create foo --config '
+  services.openssh.enable = true;
+  users.extraUsers.root.openssh.authorizedKeys.keys = ["ssh-dss AAAAB3N…"];
+'
 </screen>
 
 </para>
@@ -97,8 +99,11 @@ This will build and activate the new configuration. You can also
 specify a new configuration on the command line:
 
 <screen>
-# nixos-container update foo --config 'services.httpd.enable = true; \
-  services.httpd.adminAddr = "foo@example.org";'
+# nixos-container update foo --config '
+  services.httpd.enable = true;
+  services.httpd.adminAddr = "foo@example.org";
+  networking.firewall.allowedTCPPorts = [ 80 ];
+'
 
 # curl http://$(nixos-container show-ip foo)/
 &lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">…
diff --git a/nixos/doc/manual/configuration/file-systems.xml b/nixos/doc/manual/configuration/file-systems.xml
index d1b324af3f12..ae3d124cd6bb 100644
--- a/nixos/doc/manual/configuration/file-systems.xml
+++ b/nixos/doc/manual/configuration/file-systems.xml
@@ -35,6 +35,12 @@ or <literal>ext4</literal>, then it’s best to specify
 <option>fsType</option> to ensure that the kernel module is
 available.</para>
 
+<note><para>System startup will fail if any of the filesystems fails to mount,
+dropping you to the emergency shell.
+You can make a mount asynchronous and non-critical by adding
+<literal>options = [ "nofail" ];</literal>.
+</para></note>
+
 <xi:include href="luks-file-systems.xml" />
 
 </chapter>
diff --git a/nixos/doc/manual/installation/installing-usb.xml b/nixos/doc/manual/installation/installing-usb.xml
index dae733060569..4a74e406b14c 100644
--- a/nixos/doc/manual/installation/installing-usb.xml
+++ b/nixos/doc/manual/installation/installing-usb.xml
@@ -34,6 +34,11 @@ ISO, copy its contents verbatim to your drive, then either:
     in <link xlink:href="https://www.kernel.org/doc/Documentation/kernel-parameters.txt">
     the kernel documentation</link> for more details).</para>
   </listitem>
+  <listitem>
+    <para>If you want to load the contents of the ISO to ram after bootin
+    (So you can remove the stick after bootup) you can append the parameter
+    <literal>copytoram</literal>to the <literal>options</literal> field.</para>
+  </listitem>
 </itemizedlist>
 </para>
 
diff --git a/nixos/doc/manual/release-notes/rl-1709.xml b/nixos/doc/manual/release-notes/rl-1709.xml
index 5fba4c34ec82..257664397599 100644
--- a/nixos/doc/manual/release-notes/rl-1709.xml
+++ b/nixos/doc/manual/release-notes/rl-1709.xml
@@ -17,7 +17,29 @@ has the following highlights: </para>
       A consequence is that UIDs and GIDs are no longer reused.
     </para>
   </listitem>
-
+  <listitem>
+    <para>
+      The module option <option>services.xserver.xrandrHeads</option> now
+      causes the first head specified in this list to be set as the primary
+      head. Apart from that, it's now possible to also set additional options
+      by using an attribute set, for example:
+<programlisting>
+{ services.xserver.xrandrHeads = [
+    "HDMI-0"
+    {
+      output = &quot;DVI-0&quot;;
+      primary = true;
+      monitorConfig = ''
+        Option &quot;Rotate&quot; &quot;right&quot;
+      '';
+    }
+  ];
+}
+</programlisting>
+      This will set the <literal>DVI-0</literal> output to be the primary head,
+      even though <literal>HDMI-0</literal> is the first head in the list.
+    </para>
+  </listitem>
 </itemizedlist>
 
 <para>The following new services were added since the last release:</para>
@@ -39,6 +61,13 @@ following incompatible changes:</para>
       All JetBrains IDEs are now at <literal>jetbrains</literal>.
     </para>
   </listitem>
+  <listitem>
+    <para>
+      <literal>flexget</literal>'s state database cannot be upgraded to its
+      new internal format, requiring removal of any existing
+      <literal>db-config.sqlite</literal> which will be automatically recreated.
+    </para>
+  </listitem>
 </itemizedlist>
 
 
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 8c5de22f30ff..56766ec9047f 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -33,42 +33,124 @@
 
 , name ? "nixos-disk-image"
 
-  # This prevents errors while checking nix-store validity, see
-  # https://github.com/NixOS/nix/issues/1134
-, fixValidity ? true
-
 , format ? "raw"
 }:
 
 with lib;
 
-pkgs.vmTools.runInLinuxVM (
+let
+  # Copied from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/cd-dvd/channel.nix
+  # TODO: factor out more cleanly
+
+  # Do not include these things:
+  #   - The '.git' directory
+  #   - Result symlinks from nix-build ('result', 'result-2', 'result-bin', ...)
+  #   - VIM/Emacs swap/backup files ('.swp', '.swo', '.foo.swp', 'foo~', ...)
+  filterFn = path: type: let basename = baseNameOf (toString path); in
+    if type == "directory" then basename != ".git"
+    else if type == "symlink" then builtins.match "^result(|-.*)$" basename == null
+    else builtins.match "^((|\..*)\.sw[a-z]|.*~)$" basename == null;
+
+  nixpkgs = builtins.filterSource filterFn pkgs.path;
+
+  channelSources = pkgs.runCommand "nixos-${config.system.nixosVersion}" {} ''
+    mkdir -p $out
+    cp -prd ${nixpkgs} $out/nixos
+    chmod -R u+w $out/nixos
+    if [ ! -e $out/nixos/nixpkgs ]; then
+      ln -s . $out/nixos/nixpkgs
+    fi
+    rm -rf $out/nixos/.git
+    echo -n ${config.system.nixosVersionSuffix} > $out/nixos/.version-suffix
+  '';
+
+  metaClosure = pkgs.writeText "meta" ''
+    ${config.system.build.toplevel}
+    ${config.nix.package.out}
+    ${channelSources}
+  '';
+
+  prepareImageInputs = with pkgs; [ rsync utillinux parted e2fsprogs lkl fakeroot config.system.build.nixos-prepare-root ] ++ stdenv.initialPath;
+
+  # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
+  # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
+  # !!! should use XML.
+  sources = map (x: x.source) contents;
+  targets = map (x: x.target) contents;
+
+  prepareImage = ''
+    export PATH=${pkgs.lib.makeSearchPathOutput "bin" "bin" prepareImageInputs}
+
+    mkdir $out
+    diskImage=nixos.raw
+    truncate -s ${toString diskSize}M $diskImage
+
+    ${if partitioned then ''
+      parted $diskImage -- mklabel msdos mkpart primary ext4 1M -1s
+      offset=$((2048*512))
+    '' else ''
+      offset=0
+    ''}
+
+    mkfs.${fsType} -F -L nixos -E offset=$offset $diskImage
+  
+    root="$PWD/root"
+    mkdir -p $root
+
+    # Copy arbitrary other files into the image
+    # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
+    # https://github.com/NixOS/nixpkgs/issues/23052.
+    set -f
+    sources_=(${concatStringsSep " " sources})
+    targets_=(${concatStringsSep " " targets})
+    set +f
+
+    for ((i = 0; i < ''${#targets_[@]}; i++)); do
+      source="''${sources_[$i]}"
+      target="''${targets_[$i]}"
+
+      if [[ "$source" =~ '*' ]]; then
+        # If the source name contains '*', perform globbing.
+        mkdir -p $root/$target
+        for fn in $source; do
+          rsync -a --no-o --no-g "$fn" $root/$target/
+        done
+      else
+        mkdir -p $root/$(dirname $target)
+        if ! [ -e $root/$target ]; then
+          rsync -a --no-o --no-g $source $root/$target
+        else
+          echo "duplicate entry $target -> $source"
+          exit 1
+        fi
+      fi
+    done
+
+    # TODO: Nix really likes to chown things it creates to its current user...
+    fakeroot nixos-prepare-root $root ${channelSources} ${config.system.build.toplevel} closure
+
+    echo "copying staging root to image..."
+    cptofs ${pkgs.lib.optionalString partitioned "-P 1"} -t ${fsType} -i $diskImage $root/* /
+  '';
+in pkgs.vmTools.runInLinuxVM (
   pkgs.runCommand name
-    { preVM =
-        ''
-          mkdir $out
-          diskImage=$out/nixos.${if format == "qcow2" then "qcow2" else "img"}
-          ${pkgs.vmTools.qemu}/bin/qemu-img create -f ${format} $diskImage "${toString diskSize}M"
-          mv closure xchg/
-        '';
-      buildInputs = with pkgs; [ utillinux perl e2fsprogs parted rsync ];
-
-      # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
-      # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
-      # !!! should use XML.
-      sources = map (x: x.source) contents;
-      targets = map (x: x.target) contents;
-
-      exportReferencesGraph =
-        [ "closure" config.system.build.toplevel ];
-      inherit postVM;
+    { preVM = prepareImage;
+      buildInputs = with pkgs; [ utillinux e2fsprogs ];
+      exportReferencesGraph = [ "closure" metaClosure ];
+      postVM = ''
+        ${if format == "raw" then ''
+          mv $diskImage $out/nixos.img
+          diskImage=$out/nixos.img
+        '' else ''
+          ${pkgs.qemu}/bin/qemu-img convert -f raw -O qcow2 $diskImage $out/nixos.qcow2
+          diskImage=$out/nixos.qcow2
+        ''}
+        ${postVM}
+      '';
       memSize = 1024;
     }
     ''
       ${if partitioned then ''
-        # Create a single / partition.
-        parted /dev/vda mklabel msdos
-        parted /dev/vda -- mkpart primary ext2 1M -1s
         . /sys/class/block/vda1/uevent
         mknod /dev/vda1 b $MAJOR $MINOR
         rootDisk=/dev/vda1
@@ -76,74 +158,34 @@ pkgs.vmTools.runInLinuxVM (
         rootDisk=/dev/vda
       ''}
 
-      # Create an empty filesystem and mount it.
-      mkfs.${fsType} -L nixos $rootDisk
-      mkdir /mnt
-      mount $rootDisk /mnt
-
-      # Register the paths in the Nix database.
-      printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \
-          ${config.nix.package.out}/bin/nix-store --load-db --option build-users-group ""
-
-      ${if fixValidity then ''
-        # Add missing size/hash fields to the database. FIXME:
-        # exportReferencesGraph should provide these directly.
-        ${config.nix.package.out}/bin/nix-store --verify --check-contents --option build-users-group ""
-      '' else ""}
-
-      # In case the bootloader tries to write to /dev/sda…
+      # Some tools assume these exist
       ln -s vda /dev/xvda
       ln -s vda /dev/sda
 
-      # Install the closure onto the image
-      USER=root ${config.system.build.nixos-install}/bin/nixos-install \
-        --closure ${config.system.build.toplevel} \
-        --no-channel-copy \
-        --no-root-passwd \
-        ${optionalString (!installBootLoader) "--no-bootloader"}
+      mountPoint=/mnt
+      mkdir $mountPoint
+      mount $rootDisk $mountPoint
 
-      # Install a configuration.nix.
+      # Install a configuration.nix
       mkdir -p /mnt/etc/nixos
       ${optionalString (configFile != null) ''
         cp ${configFile} /mnt/etc/nixos/configuration.nix
       ''}
 
-      # Remove /etc/machine-id so that each machine cloning this image will get its own id
-      rm -f /mnt/etc/machine-id
-
-      # Copy arbitrary other files into the image
-      # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
-      # https://github.com/NixOS/nixpkgs/issues/23052.
-      set -f
-      sources_=($sources)
-      targets_=($targets)
-      set +f
-
-      for ((i = 0; i < ''${#targets_[@]}; i++)); do
-        source="''${sources_[$i]}"
-        target="''${targets_[$i]}"
+      mount --rbind /dev  $mountPoint/dev
+      mount --rbind /proc $mountPoint/proc
+      mount --rbind /sys  $mountPoint/sys
 
-        if [[ "$source" =~ '*' ]]; then
+      # Set up core system link, GRUB, etc.
+      NIXOS_INSTALL_BOOTLOADER=1 chroot $mountPoint /nix/var/nix/profiles/system/bin/switch-to-configuration boot
 
-          # If the source name contains '*', perform globbing.
-          mkdir -p /mnt/$target
-          for fn in $source; do
-            rsync -a --no-o --no-g "$fn" /mnt/$target/
-          done
+      # TODO: figure out if I should activate, but for now I won't
+      # chroot $mountPoint /nix/var/nix/profiles/system/activate
 
-        else
-
-          mkdir -p /mnt/$(dirname $target)
-          if ! [ -e /mnt/$target ]; then
-            rsync -a --no-o --no-g $source /mnt/$target
-          else
-            echo "duplicate entry $target -> $source"
-            exit 1
-          fi
-        fi
-      done
+      # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
+      rm -f $mountPoint/etc/machine-id
 
-      umount /mnt
+      umount -R /mnt
 
       # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
       # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index b4190df8335b..cdfac71634d4 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -6,10 +6,7 @@ let
   cfg = config.amazonImage;
 in {
 
-  imports =
-    [ ../../../modules/installer/cd-dvd/channel.nix
-      ../../../modules/virtualisation/amazon-image.nix
-    ];
+  imports = [ ../../../modules/virtualisation/amazon-image.nix ];
 
   options.amazonImage = {
     contents = mkOption {
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index 799f0793c74f..65ef95127805 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -2,21 +2,27 @@
 
 with lib;
 
-let
-
-  glibcLocales = pkgs.glibcLocales.override {
-    allLocales = any (x: x == "all") config.i18n.supportedLocales;
-    locales = config.i18n.supportedLocales;
-  };
-
-in
-
 {
   ###### interface
 
   options = {
 
     i18n = {
+      glibcLocales = mkOption {
+        type = types.path;
+        default = pkgs.glibcLocales.override {
+          allLocales = any (x: x == "all") config.i18n.supportedLocales;
+          locales = config.i18n.supportedLocales;
+        };
+        example = literalExample "pkgs.glibcLocales";
+        description = ''
+          Customized pkg.glibcLocales package.
+
+          Changing this option can disable handling of i18n.defaultLocale
+          and supportedLocale.
+        '';
+      };
+
       defaultLocale = mkOption {
         type = types.str;
         default = "en_US.UTF-8";
@@ -118,7 +124,7 @@ in
         '');
 
     environment.systemPackages =
-      optional (config.i18n.supportedLocales != []) glibcLocales;
+      optional (config.i18n.supportedLocales != []) config.i18n.glibcLocales;
 
     environment.sessionVariables =
       { LANG = config.i18n.defaultLocale;
@@ -126,7 +132,7 @@ in
       };
 
     systemd.globalEnvironment = mkIf (config.i18n.supportedLocales != []) {
-      LOCALE_ARCHIVE = "${glibcLocales}/lib/locale/locale-archive";
+      LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
     };
 
     # ‘/etc/locale.conf’ is used by systemd.
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 0c9981470d72..833782477199 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,5 +1,5 @@
 {
-  x86_64-linux = "/nix/store/j6q3pb75q1sbk0xsa5x6a629ph98ycdl-nix-1.11.8";
-  i686-linux = "/nix/store/4m6ps568l988bbr1p2k3w9raq3rblppi-nix-1.11.8";
-  x86_64-darwin = "/nix/store/cc5q944yn3j2hrs8k0kxx9r2mk9mni8a-nix-1.11.8";
+  x86_64-linux = "/nix/store/71im965h634iy99zsmlncw6qhx5jcclx-nix-1.11.9";
+  i686-linux = "/nix/store/cgvavixkayc36l6kl92i8mxr6k0p2yhy-nix-1.11.9";
+  x86_64-darwin = "/nix/store/w1c96v5yxvdmq4nvqlxjvg6kp7xa2lag-nix-1.11.9";
 }
diff --git a/nixos/modules/installer/tools/nixos-prepare-root.sh b/nixos/modules/installer/tools/nixos-prepare-root.sh
index c374330f8464..0bd70d2d349c 100644
--- a/nixos/modules/installer/tools/nixos-prepare-root.sh
+++ b/nixos/modules/installer/tools/nixos-prepare-root.sh
@@ -37,7 +37,7 @@ mkdir -m 0755 -p $mountPoint/tmp/root
 mkdir -m 0755 -p $mountPoint/var
 mkdir -m 0700 -p $mountPoint/root
 
-ln -s /run $mountPoint/var/run
+ln -sf /run $mountPoint/var/run
 
 # Create the necessary Nix directories on the target device
 mkdir -m 0755 -p \
@@ -70,7 +70,7 @@ for i in $closures; do
             rsync -a $j $mountPoint/nix/store/
         done
 
-        nix-store --register-validity < $i
+        nix-store --option build-users-group root --register-validity < $i
     fi
 done
 
diff --git a/nixos/modules/misc/extra-arguments.nix b/nixos/modules/misc/extra-arguments.nix
index 19002b17dace..f4ee94ecc0d7 100644
--- a/nixos/modules/misc/extra-arguments.nix
+++ b/nixos/modules/misc/extra-arguments.nix
@@ -2,16 +2,6 @@
 
 {
   _module.args = {
-    pkgs_i686 = import ../../.. {
-      system = "i686-linux";
-      # FIXME: we enable config.allowUnfree to make packages like
-      # nvidia-x11 available. This isn't a problem because if the user has
-      # ‘nixpkgs.config.allowUnfree = false’, then evaluation will fail on
-      # the 64-bit package anyway. However, it would be cleaner to respect
-      # nixpkgs.config here.
-      config.allowUnfree = true;
-    };
-
     utils = import ../../lib/utils.nix pkgs;
   };
 }
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index feecee3225be..d217b3452fb5 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -289,6 +289,11 @@
       rpc = 271;
       geoip = 272;
       fcron = 273;
+      sonarr = 274;
+      radarr = 275;
+      jackett = 276;
+      aria2 = 277;
+      clickhouse = 278;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -547,6 +552,11 @@
       #rpc = 271; # unused
       #geoip = 272; # unused
       fcron = 273;
+      sonarr = 274;
+      radarr = 275;
+      jackett = 276;
+      aria2 = 277;
+      clickhouse = 278;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index b8824e6374b3..1793c1447d60 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -42,6 +42,8 @@ let
     merge = lib.mergeOneOption;
   };
 
+  _pkgs = import ../../.. config.nixpkgs;
+
 in
 
 {
@@ -97,6 +99,9 @@ in
   };
 
   config = {
-    _module.args.pkgs = import ../../.. config.nixpkgs;
+    _module.args = {
+      pkgs = _pkgs;
+      pkgs_i686 = _pkgs.pkgsi686Linux;
+    };
   };
 }
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 530ae1d1cf03..7e523ac483cd 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -71,6 +71,7 @@
   ./programs/atop.nix
   ./programs/bash/bash.nix
   ./programs/blcr.nix
+  ./programs/browserpass.nix
   ./programs/cdemu.nix
   ./programs/chromium.nix
   ./programs/command-not-found/command-not-found.nix
@@ -88,6 +89,7 @@
   ./programs/mtr.nix
   ./programs/nano.nix
   ./programs/oblogout.nix
+  ./programs/qt5ct.nix
   ./programs/screen.nix
   ./programs/slock.nix
   ./programs/shadow.nix
@@ -102,7 +104,9 @@
   ./programs/wvdial.nix
   ./programs/xfs_quota.nix
   ./programs/xonsh.nix
+  ./programs/zsh/oh-my-zsh.nix
   ./programs/zsh/zsh.nix
+  ./programs/zsh/zsh-syntax-highlighting.nix
   ./rename.nix
   ./security/acme.nix
   ./security/apparmor.nix
@@ -114,6 +118,7 @@
   ./security/duosec.nix
   ./security/grsecurity.nix
   ./security/hidepid.nix
+  ./security/lock-kernel-modules.nix
   ./security/oath.nix
   ./security/pam.nix
   ./security/pam_usb.nix
@@ -161,6 +166,7 @@
   ./services/continuous-integration/jenkins/slave.nix
   ./services/databases/4store-endpoint.nix
   ./services/databases/4store.nix
+  ./services/databases/clickhouse.nix
   ./services/databases/couchdb.nix
   ./services/databases/firebird.nix
   ./services/databases/hbase.nix
@@ -502,6 +508,7 @@
   ./services/networking/wpa_supplicant.nix
   ./services/networking/xinetd.nix
   ./services/networking/xl2tpd.nix
+  ./services/networking/xrdp.nix
   ./services/networking/zerobin.nix
   ./services/networking/zerotierone.nix
   ./services/networking/znc.nix
@@ -527,8 +534,10 @@
   ./services/security/munge.nix
   ./services/security/oauth2_proxy.nix
   ./services/security/physlock.nix
-  ./services/security/torify.nix
+  ./services/security/shibboleth-sp.nix
+  ./services/security/sshguard.nix
   ./services/security/tor.nix
+  ./services/security/torify.nix
   ./services/security/torsocks.nix
   ./services/system/cgmanager.nix
   ./services/system/cloud-init.nix
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
new file mode 100644
index 000000000000..8bde2e4f4984
--- /dev/null
+++ b/nixos/modules/profiles/hardened.nix
@@ -0,0 +1,62 @@
+# A profile with most (vanilla) hardening options enabled by default,
+# potentially at the cost of features and performance.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  boot.kernelPackages = mkDefault pkgs.linuxPackages_hardened;
+
+  security.hideProcessInformation = mkDefault true;
+
+  security.lockKernelModules = mkDefault true;
+
+  security.apparmor.enable = mkDefault true;
+
+  boot.kernelParams = [
+    # Overwrite free'd memory
+    "page_poison=1"
+
+    # Disable legacy virtual syscalls
+    "vsyscall=none"
+
+    # Disable hibernation (allows replacing the running kernel)
+    "nohibernate"
+  ];
+
+  # Restrict ptrace() usage to processes with a pre-defined relationship
+  # (e.g., parent/child)
+  boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkOverride 500 1;
+
+  # Prevent replacing the running kernel image w/o reboot
+  boot.kernel.sysctl."kernel.kexec_load_disabled" = mkDefault true;
+
+  # Restrict access to kernel ring buffer (information leaks)
+  boot.kernel.sysctl."kernel.dmesg_restrict" = mkDefault true;
+
+  # Hide kptrs even for processes with CAP_SYSLOG
+  boot.kernel.sysctl."kernel.kptr_restrict" = mkOverride 500 2;
+
+  # Unprivileged access to bpf() has been used for privilege escalation in
+  # the past
+  boot.kernel.sysctl."kernel.unprivileged_bpf_disabled" = mkDefault true;
+
+  # Disable bpf() JIT (to eliminate spray attacks)
+  boot.kernel.sysctl."net.core.bpf_jit_enable" = mkDefault false;
+
+  # ... or at least apply some hardening to it
+  boot.kernel.sysctl."net.core.bpf_jit_harden" = mkDefault true;
+
+  # A recurring problem with user namespaces is that there are
+  # still code paths where the kernel's permission checking logic
+  # fails to account for namespacing, instead permitting a
+  # namespaced process to act outside the namespace with the
+  # same privileges as it would have inside it.  This is particularly
+  # bad in the common case of running as root within the namespace.
+  #
+  # Setting the number of allowed userns to 0 effectively disables
+  # the feature at runtime.  Attempting to create a user namespace
+  # with unshare will then fail with "no space left on device".
+  boot.kernel.sysctl."user.max_user_namespaces" = mkDefault 0;
+}
diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
new file mode 100644
index 000000000000..2b7ec1856431
--- /dev/null
+++ b/nixos/modules/programs/browserpass.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+  options = {
+    programs.browserpass = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to install the NativeMessaging configuration for installed browsers.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.programs.browserpass.enable {
+    environment.systemPackages = [ pkgs.browserpass ];
+    environment.etc."chromium/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
+    environment.etc."opt/chrome/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
+  };
+}
diff --git a/nixos/modules/programs/command-not-found/command-not-found.nix b/nixos/modules/programs/command-not-found/command-not-found.nix
index 6fb926fe1d5f..55529d73cb60 100644
--- a/nixos/modules/programs/command-not-found/command-not-found.nix
+++ b/nixos/modules/programs/command-not-found/command-not-found.nix
@@ -44,7 +44,7 @@ in
       ''
         # This function is called whenever a command is not found.
         command_not_found_handle() {
-          local p=${commandNotFound}
+          local p=${commandNotFound}/bin/command-not-found
           if [ -x $p -a -f ${cfg.dbPath} ]; then
             # Run the helper program.
             $p "$@"
@@ -65,7 +65,7 @@ in
       ''
         # This function is called whenever a command is not found.
         command_not_found_handler() {
-          local p=${commandNotFound}
+          local p=${commandNotFound}/bin/command-not-found
           if [ -x $p -a -f ${cfg.dbPath} ]; then
             # Run the helper program.
             $p "$@"
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index a1615c920c02..48a1e2a0a883 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -20,6 +20,7 @@ in
       { NIXPKGS_CONFIG = "/etc/nix/nixpkgs-config.nix";
         PAGER = mkDefault "less -R";
         EDITOR = mkDefault "nano";
+        XCURSOR_PATH = "$HOME/.icons";
       };
 
     environment.profiles =
@@ -42,6 +43,7 @@ in
         GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" ];
         XDG_CONFIG_DIRS = [ "/etc/xdg" ];
         XDG_DATA_DIRS = [ "/share" ];
+        XCURSOR_PATH = [ "/share/icons" ];
         MOZ_PLUGIN_PATH = [ "/lib/mozilla/plugins" ];
         LIBEXEC_PATH = [ "/lib/libexec" ];
       };
diff --git a/nixos/modules/programs/qt5ct.nix b/nixos/modules/programs/qt5ct.nix
new file mode 100644
index 000000000000..550634e65be9
--- /dev/null
+++ b/nixos/modules/programs/qt5ct.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta.maintainers = [ maintainers.romildo ];
+
+  ###### interface
+  options = {
+    programs.qt5ct = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable the Qt5 Configuration Tool (qt5ct), a
+          program that allows users to configure Qt5 settings (theme,
+          font, icons, etc.) under desktop environments or window
+          manager without Qt integration.
+
+          Official home page: <link xlink:href="https://sourceforge.net/projects/qt5ct/">https://sourceforge.net/projects/qt5ct/</link>
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.programs.qt5ct.enable {
+    environment.variables.QT_QPA_PLATFORMTHEME = "qt5ct";
+    environment.systemPackages = [ pkgs.qt5ct ];
+  };
+}
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix
new file mode 100644
index 000000000000..335f596ca80f
--- /dev/null
+++ b/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.zsh.oh-my-zsh;
+in
+  {
+    options = {
+      programs.zsh.oh-my-zsh = {
+        enable = mkOption {
+          default = false;
+          description = ''
+            Enable oh-my-zsh.
+          '';
+        };
+
+        plugins = mkOption {
+          default = [];
+          type = types.listOf(types.str);
+          description = ''
+            List of oh-my-zsh plugins
+          '';
+        };
+
+        custom = mkOption {
+          default = "";
+          type = types.str;
+          description = ''
+            Path to a custom oh-my-zsh package to override config of oh-my-zsh.
+          '';
+        };
+
+        theme = mkOption {
+          default = "";
+          type = types.str;
+          description = ''
+            Name of the theme to be used by oh-my-zsh.
+          '';
+        };
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = with pkgs; [ oh-my-zsh ];
+
+      programs.zsh.interactiveShellInit = with pkgs; with builtins; ''
+        # oh-my-zsh configuration generated by NixOS
+        export ZSH=${oh-my-zsh}/share/oh-my-zsh
+
+        ${optionalString (length(cfg.plugins) > 0)
+          "plugins=(${concatStringsSep " " cfg.plugins})"
+        }
+
+        ${optionalString (stringLength(cfg.custom) > 0)
+          "ZSH_CUSTOM=\"${cfg.custom}\""
+        }
+
+        ${optionalString (stringLength(cfg.theme) > 0)
+          "ZSH_THEME=\"${cfg.theme}\""
+        }
+
+        source $ZSH/oh-my-zsh.sh
+      '';
+    };
+  }
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
new file mode 100644
index 000000000000..e5246bb4260a
--- /dev/null
+++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.zsh.syntax-highlighting;
+in
+  {
+    options = {
+      programs.zsh.syntax-highlighting = {
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Enable zsh-syntax-highlighting.
+          '';
+        };
+
+        highlighters = mkOption {
+          default = [ "main" ];
+
+          # https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters.md
+          type = types.listOf(types.enum([
+            "main"
+            "brackets"
+            "pattern"
+            "cursor"
+            "root"
+            "line"
+          ]));
+
+          description = ''
+            Specifies the highlighters to be used by zsh-syntax-highlighting.
+
+            The following defined options can be found here:
+            https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters.md
+          '';
+        };
+
+        patterns = mkOption {
+          default = [];
+          type = types.listOf(types.listOf(types.string));
+
+          example = literalExample ''
+            [
+              ["rm -rf *" "fg=white,bold,bg=red"]
+            ]
+          '';
+
+          description = ''
+            Specifies custom patterns to be highlighted by zsh-syntax-highlighting.
+
+            Please refer to the docs for more information about the usage:
+            https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters/pattern.md
+          '';
+        };
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = with pkgs; [ zsh-syntax-highlighting ];
+
+      programs.zsh.interactiveShellInit = with pkgs; with builtins; ''
+        source ${zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
+
+        ${optionalString (length(cfg.highlighters) > 0)
+          "ZSH_HIGHLIGHT_HIGHLIGHTERS=(${concatStringsSep " " cfg.highlighters})"
+        }
+
+        ${optionalString (length(cfg.patterns) > 0)
+          (assert(elem "pattern" cfg.highlighters); (foldl (
+            a: b:
+              assert(length(b) == 2); ''
+                ${a}
+                ZSH_HIGHLIGHT_PATTERNS+=('${elemAt b 0}' '${elemAt b 1}')
+              ''
+          ) "") cfg.patterns)
+        }
+      '';
+    };
+  }
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index 990e6648e82b..acb3e987aee6 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -84,14 +84,6 @@ in
         type = types.bool;
       };
 
-      enableSyntaxHighlighting = mkOption {
-        default = false;
-        description = ''
-          Enable zsh-syntax-highlighting
-        '';
-        type = types.bool;
-      };
-      
       enableAutosuggestions = mkOption {
         default = false;
         description = ''
@@ -130,10 +122,6 @@ in
 
         ${if cfg.enableCompletion then "autoload -U compinit && compinit" else ""}
 
-        ${optionalString (cfg.enableSyntaxHighlighting)
-          "source ${pkgs.zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
-        }
-
         ${optionalString (cfg.enableAutosuggestions)
           "source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh"
         }
@@ -143,7 +131,6 @@ in
 
         ${cfge.interactiveShellInit}
 
-
         HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help"
       '';
 
@@ -206,8 +193,7 @@ in
     environment.etc."zinputrc".source = ./zinputrc;
 
     environment.systemPackages = [ pkgs.zsh ]
-      ++ optional cfg.enableCompletion pkgs.nix-zsh-completions
-      ++ optional cfg.enableSyntaxHighlighting pkgs.zsh-syntax-highlighting;
+      ++ optional cfg.enableCompletion pkgs.nix-zsh-completions;
 
     environment.pathsToLink = optional cfg.enableCompletion "/share/zsh";
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 0174fe544e35..8a313f6c7fca 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -204,5 +204,8 @@ with lib;
       "Set the option `services.xserver.displayManager.sddm.package' instead.")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
+
+    # ZSH
+    (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntax-highlighting" "enable" ])
   ];
 }
diff --git a/nixos/modules/security/grsecurity.nix b/nixos/modules/security/grsecurity.nix
index 657b059faf2e..d23c7f2e86de 100644
--- a/nixos/modules/security/grsecurity.nix
+++ b/nixos/modules/security/grsecurity.nix
@@ -13,7 +13,7 @@ in
 
 {
   meta = {
-    maintainers = with maintainers; [ joachifm ];
+    maintainers = with maintainers; [ ];
     doc = ./grsecurity.xml;
   };
 
diff --git a/nixos/modules/security/grsecurity.xml b/nixos/modules/security/grsecurity.xml
index 620e8f653f99..0a884b3f9b55 100644
--- a/nixos/modules/security/grsecurity.xml
+++ b/nixos/modules/security/grsecurity.xml
@@ -26,9 +26,11 @@
     <link xlink:href="https://wiki.archlinux.org/index.php/Grsecurity">Arch
     Linux wiki page on grsecurity</link>.
 
-    <note><para>grsecurity/PaX is only available for the latest linux -stable
-    kernel; patches against older kernels are available from upstream only for
-    a fee.</para></note>
+    <warning><para>Upstream has ceased free support for grsecurity/PaX.  See
+    <link xlink:href="https://grsecurity.net/passing_the_baton.php">
+    the announcement</link> for more information.  Consequently, NixOS
+    support for grsecurity/PaX also must cease.  Enabling this module will
+    result in a build error.</para></warning>
     <note><para>We standardise on a desktop oriented configuration primarily due
     to lack of resources.  The grsecurity/PaX configuration state space is huge
     and each configuration requires quite a bit of testing to ensure that the
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
new file mode 100644
index 000000000000..260ec3fc9464
--- /dev/null
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -0,0 +1,36 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+  options = {
+    security.lockKernelModules = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Disable kernel module loading once the system is fully initialised.
+        Module loading is disabled until the next reboot.  Problems caused
+        by delayed module loading can be fixed by adding the module(s) in
+        question to <option>boot.kernelModules</option>.
+      '';
+    };
+  };
+
+  config = mkIf config.security.lockKernelModules {
+    systemd.services.disable-kernel-module-loading = rec {
+      description = "Disable kernel module loading";
+
+      wantedBy = [ config.systemd.defaultUnit ];
+      after = [ "systemd-udev-settle.service" "firewall.service" "systemd-modules-load.service" ] ++ wantedBy;
+
+      script = "echo -n 1 > /proc/sys/kernel/modules_disabled";
+
+      unitConfig.ConditionPathIsReadWrite = "/proc/sys/kernel";
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix
index 67112343c335..7c9dedb67ad2 100644
--- a/nixos/modules/services/backup/tarsnap.nix
+++ b/nixos/modules/services/backup/tarsnap.nix
@@ -230,6 +230,14 @@ in
                   Download bandwidth rate limit in bytes.
                 '';
               };
+
+              verbose = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Whether to produce verbose logging output.
+                '';
+              };
             };
           }
         ));
@@ -293,7 +301,10 @@ in
         '';
 
         script =
-          let run = ''tarsnap --configfile "/etc/tarsnap/${name}.conf" -c -f "${name}-$(date +"%Y%m%d%H%M%S")" ${concatStringsSep " " cfg.directories}'';
+          let run = ''tarsnap --configfile "/etc/tarsnap/${name}.conf" \
+                        -c -f "${name}-$(date +"%Y%m%d%H%M%S")" \
+                        ${optionalString cfg.verbose "-v"} \
+                        ${concatStringsSep " " cfg.directories}'';
           in if (cfg.cachedir != null) then ''
             mkdir -p ${cfg.cachedir}
             chmod 0700 ${cfg.cachedir}
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index 648089f90b7b..35c0308c9dc8 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -20,15 +20,12 @@ in
         description = "ZnapZend - ZFS Backup System";
         after       = [ "zfs.target" ];
 
-        path = with pkgs; [ znapzend zfs mbuffer openssh ];
+        path = with pkgs; [ zfs mbuffer openssh ];
 
-        script = ''
-          znapzend
-        '';
-
-        reload = ''
-          /bin/kill -HUP $MAINPID
-        '';
+        serviceConfig = {
+          ExecStart = "${pkgs.znapzend}/bin/znapzend";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        };
       };
     };
 
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 57f592a2e550..c515622d11a0 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -233,6 +233,7 @@ in
           hydra_logo ${cfg.logo}
         ''}
         gc_roots_dir ${cfg.gcRootsDir}
+        use-substitutes = ${if cfg.useSubstitutes then "1" else "0"}
       '';
 
     environment.systemPackages = [ cfg.package ];
diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
new file mode 100644
index 000000000000..631d7f8cba79
--- /dev/null
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.clickhouse;
+  confDir = "/etc/clickhouse-server";
+  stateDir = "/var/lib/clickhouse";
+in
+with lib;
+{
+
+  ###### interface
+
+  options = {
+
+    services.clickhouse = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable ClickHouse database server.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.extraUsers.clickhouse = {
+      name = "clickhouse";
+      uid = config.ids.uids.clickhouse;
+      group = "clickhouse";
+      description = "ClickHouse server user";
+    };
+
+    users.extraGroups.clickhouse.gid = config.ids.gids.clickhouse;
+
+    systemd.services.clickhouse = {
+      description = "ClickHouse server";
+
+      wantedBy = [ "multi-user.target" ];
+
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -p ${stateDir}
+        chown clickhouse:clickhouse ${confDir} ${stateDir}
+      '';
+
+      script = ''
+        cd "${confDir}"
+        exec ${pkgs.clickhouse}/bin/clickhouse-server
+      '';
+
+      serviceConfig = {
+        User = "clickhouse";
+        Group = "clickhouse";
+        PermissionsStartOnly = true;
+      };
+    };
+
+    environment.etc = {
+      "clickhouse-server/config.xml" = {
+        source = "${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+      };
+
+      "clickhouse-server/users.xml" = {
+        source = "${pkgs.clickhouse}/etc/clickhouse-server/users.xml";
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/amd-hybrid-graphics.nix b/nixos/modules/services/hardware/amd-hybrid-graphics.nix
index 087bd0e04098..b0f9ff56d1b2 100644
--- a/nixos/modules/services/hardware/amd-hybrid-graphics.nix
+++ b/nixos/modules/services/hardware/amd-hybrid-graphics.nix
@@ -25,15 +25,22 @@
       path = [ pkgs.bash ];
       description = "Disable AMD Card";
       after = [ "sys-kernel-debug.mount" ];
-      requires = [ "sys-kernel-debug.mount" ];
-      wantedBy = [ "multi-user.target" ];
+      before = [ "systemd-vconsole-setup.service" "display-manager.service" ];
+      requires = [ "sys-kernel-debug.mount" "vgaswitcheroo.path" ];
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
-        ExecStart = "${pkgs.bash}/bin/sh -c 'echo -e \"IGD\\nOFF\" > /sys/kernel/debug/vgaswitcheroo/switch; exit 0'";
-        ExecStop = "${pkgs.bash}/bin/sh -c 'echo ON >/sys/kernel/debug/vgaswitcheroo/switch; exit 0'";
+        ExecStart = "${pkgs.bash}/bin/sh -c 'echo -e \"IGD\\nOFF\" > /sys/kernel/debug/vgaswitcheroo/switch'";
+        ExecStop = "${pkgs.bash}/bin/sh -c 'echo ON >/sys/kernel/debug/vgaswitcheroo/switch'";
       };
     };
+    systemd.paths."vgaswitcheroo" = {
+      pathConfig = {
+        PathExists = "/sys/kernel/debug/vgaswitcheroo/switch";
+        Unit = "amd-hybrid-graphics.service";
+      };
+      wantedBy = ["multi-user.target"];
+    };
   };
 
 }
diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix
index 52719222db66..4d78cddcb54f 100644
--- a/nixos/modules/services/misc/bepasty.nix
+++ b/nixos/modules/services/misc/bepasty.nix
@@ -21,7 +21,7 @@ in
         configure a number of bepasty servers which will be started with
         gunicorn.
         '';
-      type = with types ; attrsOf (submodule ({
+      type = with types ; attrsOf (submodule ({ config, ... } : {
 
         options = {
 
@@ -34,7 +34,6 @@ in
             default = "127.0.0.1:8000";
           };
 
-
           dataDir = mkOption {
             type = types.str;
             description = ''
@@ -73,10 +72,28 @@ in
             type = types.str;
             description = ''
               server secret for safe session cookies, must be set.
+
+              Warning: this secret is stored in the WORLD-READABLE Nix store!
+
+              It's recommended to use <option>secretKeyFile</option>
+              which takes precedence over <option>secretKey</option>.
               '';
             default = "";
           };
 
+          secretKeyFile = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = ''
+              A file that contains the server secret for safe session cookies, must be set.
+
+              <option>secretKeyFile</option> takes precedence over <option>secretKey</option>.
+
+              Warning: when <option>secretKey</option> is non-empty <option>secretKeyFile</option>
+              defaults to a file in the WORLD-READABLE Nix store containing that secret.
+              '';
+          };
+
           workDir = mkOption {
             type = types.str;
             description = ''
@@ -87,11 +104,22 @@ in
           };
 
         };
+        config = {
+          secretKeyFile = mkDefault (
+            if config.secretKey != ""
+            then toString (pkgs.writeTextFile {
+              name = "bepasty-secret-key";
+              text = config.secretKey;
+            })
+            else null
+          );
+        };
       }));
     };
   };
 
   config = mkIf cfg.enable {
+
     environment.systemPackages = [ bepasty ];
 
     # creates gunicorn systemd service for each configured server
@@ -115,7 +143,7 @@ in
           serviceConfig = {
             Type = "simple";
             PrivateTmp = true;
-            ExecStartPre = assert server.secretKey != ""; pkgs.writeScript "bepasty-server.${name}-init" ''
+            ExecStartPre = assert !isNull server.secretKeyFile; pkgs.writeScript "bepasty-server.${name}-init" ''
               #!/bin/sh
               mkdir -p "${server.workDir}"
               mkdir -p "${server.dataDir}"
@@ -123,7 +151,7 @@ in
               cat > ${server.workDir}/bepasty-${name}.conf <<EOF
               SITENAME="${name}"
               STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}"
-              SECRET_KEY="${server.secretKey}"
+              SECRET_KEY="$(cat "${server.secretKeyFile}")"
               DEFAULT_PERMISSIONS="${server.defaultPermissions}"
               ${server.extraConfig}
               EOF
diff --git a/nixos/modules/services/misc/gogs.nix b/nixos/modules/services/misc/gogs.nix
index ca8fc06e4835..ad2e36d04d53 100644
--- a/nixos/modules/services/misc/gogs.nix
+++ b/nixos/modules/services/misc/gogs.nix
@@ -14,7 +14,7 @@ let
     HOST = ${cfg.database.host}:${toString cfg.database.port}
     NAME = ${cfg.database.name}
     USER = ${cfg.database.user}
-    PASSWD = ${cfg.database.password}
+    PASSWD = #dbpass#
     PATH = ${cfg.database.path}
 
     [repository]
@@ -26,6 +26,10 @@ let
     HTTP_PORT = ${toString cfg.httpPort}
     ROOT_URL = ${cfg.rootUrl}
 
+    [session]
+    COOKIE_NAME = session
+    COOKIE_SECURE = ${boolToString cfg.cookieSecure}
+
     [security]
     SECRET_KEY = #secretkey#
     INSTALL_LOCK = true
@@ -102,7 +106,21 @@ in
         password = mkOption {
           type = types.str;
           default = "";
-          description = "Database password.";
+          description = ''
+            The password corresponding to <option>database.user</option>.
+            Warning: this is stored in cleartext in the Nix store!
+            Use <option>database.passwordFile</option> instead.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/gogs-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
         };
 
         path = mkOption {
@@ -148,6 +166,15 @@ in
         description = "HTTP listen port.";
       };
 
+      cookieSecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Marks session cookies as "secure" as a hint for browsers to only send
+          them via HTTPS. This option is recommend, if Gogs is being served over HTTPS.
+        '';
+      };
+
       extraConfig = mkOption {
         type = types.str;
         default = "";
@@ -164,13 +191,25 @@ in
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.gogs.bin ];
 
-      preStart = ''
+      preStart = let
+        runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+        secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+      in ''
         # copy custom configuration and generate a random secret key if needed
         ${optionalString (cfg.useWizard == false) ''
           mkdir -p ${cfg.stateDir}/custom/conf
-          cp -f ${configFile} ${cfg.stateDir}/custom/conf/app.ini
-          KEY=$(head -c 16 /dev/urandom | tr -dc A-Za-z0-9)
-          sed -i "s,#secretkey#,$KEY,g" ${cfg.stateDir}/custom/conf/app.ini
+          cp -f ${configFile} ${runConfig}
+
+          if [ ! -e ${secretKey} ]; then
+              head -c 16 /dev/urandom | base64 > ${secretKey}
+          fi
+
+          KEY=$(head -n1 ${secretKey})
+          DBPASS=$(head -n1 ${cfg.database.passwordFile})
+          sed -e "s,#secretkey#,$KEY,g" \
+              -e "s,#dbpass#,$DBPASS,g" \
+              -i ${runConfig}
+          chmod 440 ${runConfig} ${secretKey}
         ''}
 
         mkdir -p ${cfg.repositoryRoot}
@@ -212,5 +251,16 @@ in
       };
       extraGroups.gogs.gid = config.ids.gids.gogs;
     };
+
+    warnings = optional (cfg.database.password != "")
+      ''config.services.gogs.database.password will be stored as plaintext
+        in the Nix store. Use database.passwordFile instead.'';
+
+    # Create database passwordFile default when password is configured.
+    services.gogs.database.passwordFile = mkIf (cfg.database.password != "")
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "gogs-database-password";
+        text = cfg.database.password;
+      })));
   };
 }
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index e467e7ee85b8..87a41ee70b54 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -22,14 +22,14 @@ in
           echo "Creating jackett data directory in /var/lib/jackett/"
           mkdir -p /var/lib/jackett/
         }
-        chown -R jackett /var/lib/jackett/
+        chown -R jackett:jackett /var/lib/jackett/
         chmod 0700 /var/lib/jackett/
       '';
 
       serviceConfig = {
         Type = "simple";
         User = "jackett";
-        Group = "nogroup";
+        Group = "jackett";
         PermissionsStartOnly = "true";
         ExecStart = "${pkgs.jackett}/bin/Jackett";
         Restart = "on-failure";
@@ -37,8 +37,11 @@ in
     };
 
     users.extraUsers.jackett = {
+      uid = config.ids.uids.jackett;
       home = "/var/lib/jackett";
+      group = "jackett";
     };
+    users.extraGroups.jackett.gid = config.ids.gids.jackett;
 
   };
 }
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index 9c0bea8d3bff..ecd9a6f52da2 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -91,7 +91,7 @@ in
         # Copy the database skeleton files to /var/lib/plex/.skeleton
         # See the the Nix expression for Plex's package for more information on
         # why this is done.
-        test -d "${cfg.dataDir}/.skeleton" || mkdir "${cfg.dataDir}/.skeleton"
+        install --owner ${cfg.user} --group ${cfg.group} -d "${cfg.dataDir}/.skeleton"
         for db in "com.plexapp.plugins.library.db"; do
             if [ ! -e  "${cfg.dataDir}/.skeleton/$db" ]; then
               cp "${cfg.package}/usr/lib/plexmediaserver/Resources/base_$db" "${cfg.dataDir}/.skeleton/$db"
diff --git a/nixos/modules/services/misc/radarr.nix b/nixos/modules/services/misc/radarr.nix
index cc5efffca448..245ad9f9a6df 100644
--- a/nixos/modules/services/misc/radarr.nix
+++ b/nixos/modules/services/misc/radarr.nix
@@ -22,14 +22,14 @@ in
           echo "Creating radarr data directory in /var/lib/radarr/"
           mkdir -p /var/lib/radarr/
         }
-        chown -R radarr /var/lib/radarr/
+        chown -R radarr:radarr /var/lib/radarr/
         chmod 0700 /var/lib/radarr/
       '';
 
       serviceConfig = {
         Type = "simple";
         User = "radarr";
-        Group = "nogroup";
+        Group = "radarr";
         PermissionsStartOnly = "true";
         ExecStart = "${pkgs.radarr}/bin/Radarr";
         Restart = "on-failure";
@@ -37,8 +37,11 @@ in
     };
 
     users.extraUsers.radarr = {
+      uid = config.ids.uids.radarr;
       home = "/var/lib/radarr";
+      group = "radarr";
     };
+    users.extraGroups.radarr.gid = config.ids.gids.radarr;
 
   };
 }
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index 6d96daa6c3d4..ecde2c33bfa9 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -22,14 +22,14 @@ in
           echo "Creating sonarr data directory in /var/lib/sonarr/"
           mkdir -p /var/lib/sonarr/
         }
-        chown -R sonarr /var/lib/sonarr/
+        chown -R sonarr:sonarr /var/lib/sonarr/
         chmod 0700 /var/lib/sonarr/
       '';
 
       serviceConfig = {
         Type = "simple";
         User = "sonarr";
-        Group = "nogroup";
+        Group = "sonarr";
         PermissionsStartOnly = "true";
         ExecStart = "${pkgs.sonarr}/bin/NzbDrone --no-browser";
         Restart = "on-failure";
@@ -37,8 +37,11 @@ in
     };
 
     users.extraUsers.sonarr = {
+      uid = config.ids.uids.sonarr;
       home = "/var/lib/sonarr";
+      group = "sonarr";
     };
+    users.extraGroups.sonarr.gid = config.ids.gids.sonarr;
 
   };
 }
diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix
index 8ae8b12056ce..6ca420a05b23 100644
--- a/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixos/modules/services/monitoring/cadvisor.nix
@@ -54,7 +54,29 @@ in {
       storageDriverPassword = mkOption {
         default = "root";
         type = types.str;
-        description = "Cadvisor storage driver password.";
+        description = ''
+          Cadvisor storage driver password.
+
+          Warning: this password is stored in the world-readable Nix store. It's
+          recommended to use the <option>storageDriverPasswordFile</option> option
+          since that gives you control over the security of the password.
+          <option>storageDriverPasswordFile</option> also takes precedence over <option>storageDriverPassword</option>.
+        '';
+      };
+
+      storageDriverPasswordFile = mkOption {
+        type = types.str;
+        description = ''
+          File that contains the cadvisor storage driver password.
+
+          <option>storageDriverPasswordFile</option> takes precedence over <option>storageDriverPassword</option>
+
+          Warning: when <option>storageDriverPassword</option> is non-empty this defaults to a file in the
+          world-readable Nix store that contains the value of <option>storageDriverPassword</option>.
+
+          It's recommended to override this with a path not in the Nix store.
+          Tip: use <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'>nixops key management</link>
+        '';
       };
 
       storageDriverSecure = mkOption {
@@ -65,35 +87,44 @@ in {
     };
   };
 
-  config = mkIf cfg.enable {
-    systemd.services.cadvisor = {
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "docker.service" "influxdb.service" ];
-
-      postStart = mkBefore ''
-        until ${pkgs.curl.bin}/bin/curl -s -o /dev/null 'http://${cfg.listenAddress}:${toString cfg.port}/containers/'; do
-          sleep 1;
-        done
-      '';
-
-      serviceConfig = {
-        ExecStart = ''${pkgs.cadvisor}/bin/cadvisor \
-          -logtostderr=true \
-          -listen_ip=${cfg.listenAddress} \
-          -port=${toString cfg.port} \
-          ${optionalString (cfg.storageDriver != null) ''
-            -storage_driver ${cfg.storageDriver} \
-            -storage_driver_user ${cfg.storageDriverHost} \
-            -storage_driver_db ${cfg.storageDriverDb} \
-            -storage_driver_user ${cfg.storageDriverUser} \
-            -storage_driver_password ${cfg.storageDriverPassword} \
-            ${optionalString cfg.storageDriverSecure "-storage_driver_secure"}
-          ''}
+  config = mkMerge [
+    { services.cadvisor.storageDriverPasswordFile = mkIf (cfg.storageDriverPassword != "") (
+        mkDefault (toString (pkgs.writeTextFile {
+          name = "cadvisor-storage-driver-password";
+          text = cfg.storageDriverPassword;
+        }))
+      );
+    }
+
+    (mkIf cfg.enable {
+      systemd.services.cadvisor = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" "docker.service" "influxdb.service" ];
+
+        postStart = mkBefore ''
+          until ${pkgs.curl.bin}/bin/curl -s -o /dev/null 'http://${cfg.listenAddress}:${toString cfg.port}/containers/'; do
+            sleep 1;
+          done
         '';
-        TimeoutStartSec=300;
-      };
-    };
 
-    virtualisation.docker.enable = mkDefault true;
-  };
+        script = ''
+          exec ${pkgs.cadvisor}/bin/cadvisor \
+            -logtostderr=true \
+            -listen_ip="${cfg.listenAddress}" \
+            -port="${toString cfg.port}" \
+            ${optionalString (cfg.storageDriver != null) ''
+              -storage_driver "${cfg.storageDriver}" \
+              -storage_driver_user "${cfg.storageDriverHost}" \
+              -storage_driver_db "${cfg.storageDriverDb}" \
+              -storage_driver_user "${cfg.storageDriverUser}" \
+              -storage_driver_password "$(cat "${cfg.storageDriverPasswordFile}")" \
+              ${optionalString cfg.storageDriverSecure "-storage_driver_secure"}
+            ''}
+        '';
+
+        serviceConfig.TimeoutStartSec=300;
+      };
+      virtualisation.docker.enable = mkDefault true;
+    })
+  ];
 }
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index 98931e65bb56..6b24ac2c7c62 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -400,7 +400,8 @@ in {
 
           mkdir -p ${cfg.dataDir}/whisper
           chmod 0700 ${cfg.dataDir}/whisper
-          chown -R graphite:graphite ${cfg.dataDir}
+          chown graphite:graphite ${cfg.dataDir}
+          chown graphite:graphite ${cfg.dataDir}/whisper
         '';
       };
     })
@@ -487,9 +488,11 @@ in {
             # create index
             ${pkgs.python27Packages.graphite_web}/bin/build-index.sh
 
-            touch ${dataDir}/db-created
+            chown graphite:graphite ${cfg.dataDir}
+            chown graphite:graphite ${cfg.dataDir}/whisper
+            chown -R graphite:graphite ${cfg.dataDir}/log
 
-            chown -R graphite:graphite ${cfg.dataDir}
+            touch ${dataDir}/db-created
           fi
         '';
       };
@@ -526,9 +529,10 @@ in {
             mkdir -p ${dataDir}/cache/
             chmod 0700 ${dataDir}/cache/
 
-            touch ${dataDir}/db-created
+            chown graphite:graphite ${cfg.dataDir}
+            chown -R graphite:graphite ${cfg.dataDir}/cache
 
-            chown -R graphite:graphite ${cfg.dataDir}
+            touch ${dataDir}/db-created
           fi
         '';
       };
@@ -549,7 +553,7 @@ in {
         preStart = ''
           if ! test -e ${dataDir}/db-created; then
             mkdir -p ${dataDir}
-            chown -R graphite:graphite ${dataDir}
+            chown graphite:graphite ${dataDir}
           fi
         '';
       };
diff --git a/nixos/modules/services/monitoring/longview.nix b/nixos/modules/services/monitoring/longview.nix
index 770d56e60efb..9c38956f9ba8 100644
--- a/nixos/modules/services/monitoring/longview.nix
+++ b/nixos/modules/services/monitoring/longview.nix
@@ -5,22 +5,10 @@ with lib;
 let
   cfg = config.services.longview;
 
-  pidFile = "/run/longview.pid";
-
-  apacheConf = optionalString (cfg.apacheStatusUrl != "") ''
-    location ${cfg.apacheStatusUrl}?auto
-  '';
-  mysqlConf = optionalString (cfg.mysqlUser != "") ''
-    username ${cfg.mysqlUser}
-    password ${cfg.mysqlPassword}
-  '';
-  nginxConf = optionalString (cfg.nginxStatusUrl != "") ''
-    location ${cfg.nginxStatusUrl}
-  '';
-
-in
-
-{
+  runDir = "/run/longview";
+  configsDir = "${runDir}/longview.d";
+
+in {
   options = {
 
     services.longview = {
@@ -35,10 +23,27 @@ in
 
       apiKey = mkOption {
         type = types.str;
+        default = "";
         example = "01234567-89AB-CDEF-0123456789ABCDEF";
         description = ''
           Longview API key. To get this, look in Longview settings which
           are found at https://manager.linode.com/longview/.
+
+          Warning: this secret is stored in the world-readable Nix store!
+          Use <option>apiKeyFile</option> instead.
+        '';
+      };
+
+      apiKeyFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/longview-api-key";
+        description = ''
+          A file containing the Longview API key.
+          To get this, look in Longview settings which
+          are found at https://manager.linode.com/longview/.
+
+          <option>apiKeyFile</option> takes precedence over <option>apiKey</option>.
         '';
       };
 
@@ -77,11 +82,23 @@ in
 
       mysqlPassword = mkOption {
         type = types.str;
+        default = "";
         description = ''
-          The password corresponding to mysqlUser.  Warning: this is
-          stored in cleartext in the Nix store!
+          The password corresponding to <option>mysqlUser</option>.
+          Warning: this is stored in cleartext in the Nix store!
+          Use <option>mysqlPasswordFile</option> instead.
         '';
       };
+
+      mysqlPasswordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/dbpassword";
+        description = ''
+          A file containing the password corresponding to <option>mysqlUser</option>.
+        '';
+      };
+
     };
 
   };
@@ -94,25 +111,50 @@ in
         serviceConfig.Type = "forking";
         serviceConfig.ExecStop = "-${pkgs.coreutils}/bin/kill -TERM $MAINPID";
         serviceConfig.ExecReload = "-${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        serviceConfig.PIDFile = pidFile;
+        serviceConfig.PIDFile = "${runDir}/longview.pid";
         serviceConfig.ExecStart = "${pkgs.longview}/bin/longview";
+        preStart = ''
+          umask 077
+          mkdir -p ${configsDir}
+        '' + (optionalString (cfg.apiKeyFile != null) ''
+          cp --no-preserve=all "${cfg.apiKeyFile}" ${runDir}/longview.key
+        '') + (optionalString (cfg.apacheStatusUrl != "") ''
+          cat > ${configsDir}/Apache.conf <<EOF
+          location ${cfg.apacheStatusUrl}?auto
+          EOF
+        '') + (optionalString (cfg.mysqlUser != "" && cfg.mysqlPasswordFile != null) ''
+          cat > ${configsDir}/MySQL.conf <<EOF
+          username ${cfg.mysqlUser}
+          password `head -n1 "${cfg.mysqlPasswordFile}"`
+          EOF
+        '') + (optionalString (cfg.nginxStatusUrl != "") ''
+          cat > ${configsDir}/Nginx.conf <<EOF
+          location ${cfg.nginxStatusUrl}
+          EOF
+        '');
       };
 
-    environment.etc."linode/longview.key" = {
-      mode = "0400";
-      text = cfg.apiKey;
-    };
-    environment.etc."linode/longview.d/Apache.conf" = {
-      mode = "0400";
-      text = apacheConf;
-    };
-    environment.etc."linode/longview.d/MySQL.conf" = {
-      mode = "0400";
-      text = mysqlConf;
-    };
-    environment.etc."linode/longview.d/Nginx.conf" = {
-      mode = "0400";
-      text = nginxConf;
-    };
+    warnings = let warn = k: optional (cfg.${k} != "")
+                 "config.services.longview.${k} is insecure. Use ${k}File instead.";
+               in concatMap warn [ "apiKey" "mysqlPassword" ];
+
+    assertions = [
+      { assertion = cfg.apiKeyFile != null;
+        message = "Longview needs an API key configured";
+      }
+    ];
+
+    # Create API key file if not configured.
+    services.longview.apiKeyFile = mkIf (cfg.apiKey != "")
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "longview.key";
+        text = cfg.apiKey;
+      })));
+
+    # Create MySQL password file if not configured.
+    services.longview.mysqlPasswordFile = mkDefault (toString (pkgs.writeTextFile {
+      name = "mysql-password-file";
+      text = cfg.mysqlPassword;
+    }));
   };
 }
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index b8c26a5c89ba..b26bcba64059 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -34,7 +34,7 @@ let
         cap=$(sed -nr 's/.*#%#\s+capabilities\s*=\s*(.+)/\1/p' $file)
 
         wrapProgram $file \
-          --set PATH "/run/wrappers/bin:/run/current-system/sw/bin:/run/current-system/sw/bin" \
+          --set PATH "/run/wrappers/bin:/run/current-system/sw/bin" \
           --set MUNIN_LIBDIR "${pkgs.munin}/lib" \
           --set MUNIN_PLUGSTATE "/var/run/munin"
 
@@ -184,7 +184,7 @@ in
 
         mkdir -p /etc/munin/plugins
         rm -rf /etc/munin/plugins/*
-        PATH="/run/wrappers/bin:/run/current-system/sw/bin:/run/current-system/sw/bin" ${pkgs.munin}/sbin/munin-node-configure --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${muninPlugins} --servicedir=/etc/munin/plugins 2>/dev/null | ${pkgs.bash}/bin/bash
+        PATH="/run/wrappers/bin:/run/current-system/sw/bin" ${pkgs.munin}/sbin/munin-node-configure --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${muninPlugins} --servicedir=/etc/munin/plugins 2>/dev/null | ${pkgs.bash}/bin/bash
       '';
       serviceConfig = {
         ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/";
diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix
index c4c4ed227b35..29dc68f90cc9 100644
--- a/nixos/modules/services/monitoring/ups.nix
+++ b/nixos/modules/services/monitoring/ups.nix
@@ -80,7 +80,7 @@ let
     };
 
     config = {
-      directives = mkHeader ([
+      directives = mkOrder 10 ([
         "driver = ${config.driver}"
         "port = ${config.port}"
         ''desc = "${config.description}"''
diff --git a/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix
new file mode 100644
index 000000000000..ad4ac9bf45e3
--- /dev/null
+++ b/nixos/modules/services/networking/aria2.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.aria2;
+
+  homeDir = "/var/lib/aria2";
+
+  settingsDir = "${homeDir}";
+  sessionFile = "${homeDir}/aria2.session";
+  downloadDir = "${homeDir}/Downloads";
+  
+  rangesToStringList = map (x: builtins.toString x.from +"-"+ builtins.toString x.to);
+  
+  settingsFile = pkgs.writeText "aria2.conf"
+  ''
+    dir=${cfg.downloadDir}
+    listen-port=${concatStringsSep "," (rangesToStringList cfg.listenPortRange)}
+    rpc-listen-port=${toString cfg.rpcListenPort}
+    rpc-secret=${cfg.rpcSecret}
+  '';
+
+in
+{
+  options = {
+    services.aria2 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable the headless Aria2 daemon service.
+
+          Aria2 daemon can be controlled via the RPC interface using
+          one of many WebUI (http://localhost:6800/ by default).
+
+          Targets are downloaded to ${downloadDir} by default and are
+          accessible to users in the "aria2" group.
+        '';
+      };
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open listen and RPC ports found in listenPortRange and rpcListenPort
+          options in the firewall.
+        '';
+      };
+      downloadDir = mkOption {
+        type = types.string;
+        default = "${downloadDir}";
+        description = ''
+          Directory to store downloaded files.
+        '';
+      };
+      listenPortRange = mkOption {
+        type = types.listOf types.attrs;
+        default = [ { from = 6881; to = 6999; } ];
+        description = ''
+          Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker.
+        '';
+      };
+      rpcListenPort = mkOption {
+        type = types.int;
+        default = 6800;
+        description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
+      };
+      rpcSecret = mkOption {
+        type = types.string;
+        default = "aria2rpc";
+        description = ''
+          Set RPC secret authorization token.
+          Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used.
+        '';
+      };
+      extraArguments = mkOption {
+        type = types.string;
+        example = "--rpc-listen-all --remote-time=true";
+        default = "";
+        description = ''
+          Additional arguments to be passed to Aria2.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    # Need to open ports for proper functioning
+    networking.firewall = mkIf cfg.openPorts {
+      allowedUDPPortRanges = config.services.aria2.listenPortRange;
+      allowedTCPPorts = [ config.services.aria2.rpcListenPort ];
+    };
+
+    users.extraUsers.aria2 = {
+      group = "aria2";
+      uid = config.ids.uids.aria2;
+      description = "aria2 user";
+      home = homeDir;
+      createHome = false;
+    };
+
+    users.extraGroups.aria2.gid = config.ids.gids.aria2;
+
+    systemd.services.aria2 = {
+      description = "aria2 Service";
+      after = [ "local-fs.target" "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0770 -p "${homeDir}"
+        chown aria2:aria2 "${homeDir}"
+        if [[ ! -d "${config.services.aria2.downloadDir}" ]]
+        then 
+          mkdir -m 0770 -p "${config.services.aria2.downloadDir}"
+          chown aria2:aria2 "${config.services.aria2.downloadDir}"
+        fi
+        if [[ ! -e "${sessionFile}" ]]
+        then 
+          touch "${sessionFile}"
+          chown aria2:aria2 "${sessionFile}"
+        fi
+        cp -f "${settingsFile}" "${settingsDir}/aria2.conf"
+      '';
+
+      serviceConfig = {
+        Restart = "on-abort";
+        ExecStart = "${pkgs.aria2}/bin/aria2c --enable-rpc --conf-path=${settingsDir}/aria2.conf ${config.services.aria2.extraArguments} --save-session=${sessionFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = "aria2";
+        Group = "aria2";
+        PermissionsStartOnly = true;
+      };
+    };
+  };
+}
\ No newline at end of file
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 3985f8561d35..f4d0fc822dea 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -33,6 +33,9 @@ let
     publish-hinfo=${yesNo publish.hinfo}
     publish-workstation=${yesNo publish.workstation}
     publish-domain=${yesNo publish.domain}
+
+    [reflector]
+    enable-reflector=${yesNo reflector}
   '';
 
 in
@@ -113,6 +116,11 @@ in
         description = ''Whether to enable wide-area service discovery.'';
       };
 
+      reflector = mkOption {
+        default = false;
+        description = ''Reflect incoming mDNS requests to all allowed network interfaces.'';
+      };
+
       publish = {
         enable = mkOption {
           default = false;
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 24a3196bed46..4e176353fc28 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -8,8 +8,6 @@ let
 
   homeDir = "/var/lib/i2pd";
 
-  extip = "EXTIP=\$(${pkgs.curl.bin}/bin/curl -sLf \"http://jsonip.com\" | ${pkgs.gawk}/bin/awk -F'\"' '{print $4}')";
-
   mkEndpointOpt = name: addr: port: {
     enable = mkEnableOption name;
     name = mkOption {
@@ -152,9 +150,8 @@ let
 
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
-    ${if isNull cfg.extIp then extip else ""}
     ${pkgs.i2pd}/bin/i2pd \
-      --host=${if isNull cfg.extIp then "$EXTIP" else cfg.extIp} \
+      ${if isNull cfg.extIp then "" else "--host="+cfg.extIp} \
       --conf=${i2pdConf} \
       --tunconf=${i2pdTunnelConf}
   '';
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 220107a24118..0c12b2c1dfd4 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -12,6 +12,7 @@ let
   configFile = writeText "NetworkManager.conf" ''
     [main]
     plugins=keyfile
+    dns=${if cfg.useDnsmasq then "dnsmasq" else "default"}
 
     [keyfile]
     ${optionalString (config.networking.hostName != "")
@@ -158,6 +159,17 @@ in {
       ethernet.macAddress = macAddressOpt;
       wifi.macAddress = macAddressOpt;
 
+      useDnsmasq = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable NetworkManager's dnsmasq integration. NetworkManager will run
+          dnsmasq as a local caching nameserver, using a "split DNS"
+          configuration if you are connected to a VPN, and then update
+          resolv.conf to point to the local nameserver.
+        '';
+      };
+
       dispatcherScripts = mkOption {
         type = types.listOf (types.submodule {
           options = {
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index f9300fdabc57..ef860e7e5df4 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -57,4 +57,6 @@ in
       serviceConfig.Group = "radicale";
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ aneeshusa ];
 }
diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix
new file mode 100644
index 000000000000..bf59130ce5b9
--- /dev/null
+++ b/nixos/modules/services/networking/xrdp.nix
@@ -0,0 +1,153 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xrdp;
+  confDir = pkgs.runCommand "xrdp.conf" { } ''
+    mkdir $out
+
+    cp ${cfg.package}/etc/xrdp/{km-*,xrdp,sesman,xrdp_keyboard}.ini $out
+
+    cat > $out/startwm.sh <<EOF
+    #!/bin/sh
+    . /etc/profile
+    ${cfg.defaultWindowManager}
+    EOF
+    chmod +x $out/startwm.sh
+
+    substituteInPlace $out/xrdp.ini \
+      --replace "#rsakeys_ini=" "rsakeys_ini=/var/run/xrdp/rsakeys.ini" \
+      --replace "certificate=" "certificate=${cfg.sslCert}" \
+      --replace "key_file=" "key_file=${cfg.sslKey}" \
+      --replace LogFile=xrdp.log LogFile=/dev/null \
+      --replace EnableSyslog=true EnableSyslog=false
+
+    substituteInPlace $out/sesman.ini \
+      --replace LogFile=xrdp-sesman.log LogFile=/dev/null \
+      --replace EnableSyslog=1 EnableSyslog=0
+  '';
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.xrdp = {
+
+      enable = mkEnableOption "Whether xrdp should be run on startup.";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.xrdp;
+        defaultText = "pkgs.xrdp";
+        description = ''
+          The package to use for the xrdp daemon's binary.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 3389;
+        description = ''
+          Specifies on which port the xrdp daemon listens.
+        '';
+      };
+
+      sslKey = mkOption {
+        type = types.str;
+        default = "/etc/xrdp/key.pem";
+        example = "/path/to/your/key.pem";
+        description = ''
+          ssl private key path
+          A self-signed certificate will be generated if file not exists.
+        '';
+      };
+
+      sslCert = mkOption {
+        type = types.str;
+        default = "/etc/xrdp/cert.pem";
+        example = "/path/to/your/cert.pem";
+        description = ''
+          ssl certificate path
+          A self-signed certificate will be generated if file not exists.
+        '';
+      };
+
+      defaultWindowManager = mkOption {
+        type = types.str;
+        default = "xterm";
+        example = "xfce4-session";
+        description = ''
+          The script to run when user log in, usually a window manager, e.g. "icewm", "xfce4-session"
+          This is per-user overridable, if file ~/startwm.sh exists it will be used instead.
+        '';
+      };
+
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      services.xrdp = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        description = "xrdp daemon";
+        requires = [ "xrdp-sesman.service" ];
+        preStart = ''
+          # prepare directory for unix sockets (the sockets will be owned by loggedinuser:xrdp)
+          mkdir -p /tmp/.xrdp || true
+          chown xrdp:xrdp /tmp/.xrdp
+          chmod 3777 /tmp/.xrdp
+
+          # generate a self-signed certificate
+          if [ ! -s ${cfg.sslCert} -o ! -s ${cfg.sslKey} ]; then
+            mkdir -p $(dirname ${cfg.sslCert}) || true
+            mkdir -p $(dirname ${cfg.sslKey}) || true
+            ${pkgs.openssl.bin}/bin/openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 365 \
+              -subj /C=US/ST=CA/L=Sunnyvale/O=xrdp/CN=www.xrdp.org \
+              -config ${cfg.package}/share/xrdp/openssl.conf \
+              -keyout ${cfg.sslKey} -out ${cfg.sslCert}
+            chown root:xrdp ${cfg.sslKey} ${cfg.sslCert}
+            chmod 440 ${cfg.sslKey} ${cfg.sslCert}
+          fi
+          if [ ! -s /var/run/xrdp/rsakeys.ini ]; then
+            mkdir -p /var/run/xrdp
+            ${cfg.package}/bin/xrdp-keygen xrdp /var/run/xrdp/rsakeys.ini
+          fi
+        '';
+        serviceConfig = {
+          User = "xrdp";
+          Group = "xrdp";
+          PermissionsStartOnly = true;
+          ExecStart = "${cfg.package}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${confDir}/xrdp.ini";
+        };
+      };
+
+      services.xrdp-sesman = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        description = "xrdp session manager";
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/xrdp-sesman --nodaemon --config ${confDir}/sesman.ini";
+        };
+      };
+
+    };
+
+    users.users.xrdp = {
+      description   = "xrdp daemon user";
+      isSystemUser  = true;
+      group         = "xrdp";
+    };
+    users.groups.xrdp = {};
+
+    security.pam.services.xrdp-sesman = { allowNullPassword = true; startSession = true; };
+  };
+
+}
diff --git a/nixos/modules/services/scheduling/fcron.nix b/nixos/modules/services/scheduling/fcron.nix
index bc631bdd0447..af4f9f41fd04 100644
--- a/nixos/modules/services/scheduling/fcron.nix
+++ b/nixos/modules/services/scheduling/fcron.nix
@@ -149,7 +149,7 @@ in
           --group fcron \
           --directory /var/spool/fcron
         # load system crontab file
-        #${pkgs.fcron}/bin/fcrontab -u systab ${pkgs.writeText "systab" cfg.systab}
+        /run/wrappers/bin/fcrontab -u systab ${pkgs.writeText "systab" cfg.systab}
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix
new file mode 100644
index 000000000000..07acf27f0f63
--- /dev/null
+++ b/nixos/modules/services/security/shibboleth-sp.nix
@@ -0,0 +1,73 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+let
+  cfg = config.services.shibboleth-sp;
+in {
+  options = {
+    services.shibboleth-sp = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the shibboleth service";
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        example = "${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml";
+        description = "Path to shibboleth config file";
+      };
+
+      fastcgi.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to include the shibauthorizer and shibresponder FastCGI processes";
+      };
+
+      fastcgi.shibAuthorizerPort = mkOption {
+        type = types.int;
+        default = 9100;
+        description = "Port for shibauthorizer FastCGI proccess to bind to";
+      };
+
+      fastcgi.shibResponderPort = mkOption {
+        type = types.int;
+        default = 9101;
+        description = "Port for shibauthorizer FastCGI proccess to bind to";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.shibboleth-sp = {
+      description = "Provides SSO and federation for web applications";
+      after       = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ];
+      wantedBy    = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.shibboleth-sp}/bin/shibd -F -d ${pkgs.shibboleth-sp} -c ${cfg.configFile}";
+      };
+    };
+
+    systemd.services.shibresponder = mkIf cfg.fastcgi.enable {
+      description = "Provides SSO through Shibboleth via FastCGI";
+      after       = [ "network.target" ];
+      wantedBy    = [ "multi-user.target" ];
+      path    	  = [ "${pkgs.spawn_fcgi}" ];
+      environment.SHIBSP_CONFIG = "${cfg.configFile}";
+      serviceConfig = {
+        ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibResponderPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibresponder";
+      };
+    };
+
+    systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable {
+      description = "Provides SSO through Shibboleth via FastCGI";
+      after       = [ "network.target" ];
+      wantedBy    = [ "multi-user.target" ];
+      path    	  = [ "${pkgs.spawn_fcgi}" ];
+      environment.SHIBSP_CONFIG = "${cfg.configFile}";
+      serviceConfig = {
+        ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibAuthorizerPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibauthorizer";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix
new file mode 100644
index 000000000000..5a183443f71d
--- /dev/null
+++ b/nixos/modules/services/security/sshguard.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sshguard;
+in {
+
+  ###### interface
+
+  options = {
+
+    services.sshguard = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to enable the sshguard service.";
+      };
+
+      attack_threshold = mkOption {
+        default = 30;
+        type = types.int;
+        description = ''
+            Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
+          '';
+      };
+
+      blacklist_threshold = mkOption {
+        default = null;
+        example = 120;
+        type = types.nullOr types.int;
+        description = ''
+            Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+          '';
+      };
+
+      blacklist_file = mkOption {
+        default = "/var/lib/sshguard/blacklist.db";
+        type = types.path;
+        description = ''
+            Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+          '';
+      };
+
+      blocktime = mkOption {
+        default = 120;
+        type = types.int;
+        description = ''
+            Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
+
+            sshguard unblocks attacks at random intervals, so actual block times will be longer.
+          '';
+      };
+
+      detection_time = mkOption {
+        default = 1800;
+        type = types.int;
+        description = ''
+            Remember potential attackers for up to detection_time seconds before resetting their score.
+          '';
+      };
+
+      whitelist = mkOption {
+        default = [ ];
+        example = [ "198.51.100.56" "198.51.100.2" ];
+        type = types.listOf types.str;
+        description = ''
+            Whitelist a list of addresses, hostnames, or address blocks.
+          '';
+      };
+
+      services = mkOption {
+        default = [ "sshd" ];
+        example = [ "sshd" "exim" ];
+        type = types.listOf types.str;
+        description = ''
+            Systemd services sshguard should receive logs of.
+          '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.sshguard pkgs.iptables pkgs.ipset ];
+
+    environment.etc."sshguard.conf".text = let 
+        list_services = ( name:  "-t ${name} ");
+      in ''
+        BACKEND="${pkgs.sshguard}/libexec/sshg-fw-ipset"
+        LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl -afb -p info -n1 ${toString (map list_services cfg.services)} -o cat"
+      '';
+
+    systemd.services.sshguard =
+      { description = "SSHGuard brute-force attacks protection system";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        partOf = optional config.networking.firewall.enable "firewall.service";
+
+        path = [ pkgs.iptables pkgs.ipset pkgs.iproute pkgs.systemd ];
+
+        postStart = ''
+          mkdir -p /var/lib/sshguard
+          ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:ip family inet
+          ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:ip family inet6
+          ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP
+          ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
+        '';
+
+        preStop = ''
+          ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP
+          ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
+        '';
+
+        unitConfig.Documentation = "man:sshguard(8)";
+
+        serviceConfig = {
+            Type = "simple";
+            ExecStart = let
+                list_whitelist = ( name:  "-w ${name} ");
+              in ''
+                 ${pkgs.sshguard}/bin/sshguard -a ${toString cfg.attack_threshold} ${optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file} "}-i /run/sshguard/sshguard.pid -p ${toString cfg.blocktime} -s ${toString cfg.detection_time} ${toString (map list_whitelist cfg.whitelist)}
+              '';
+            PIDFile = "/run/sshguard/sshguard.pid";
+            Restart = "always";
+
+            ReadOnlyDirectories = "/";
+            ReadWriteDirectories = "/run/sshguard /var/lib/sshguard";
+            RuntimeDirectory = "sshguard";
+            CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
+         };
+      };
+  };
+}
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index 2d9287577de8..c1d7d4ea06d4 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -103,7 +103,7 @@ in
       requires = [ "postgresql.service" ];
       after = [ "postgresql.service" ];
 
-      path = [ cfg.jrePackage ];
+      path = [ cfg.jrePackage pkgs.bash ];
 
       environment = {
         CONF_USER = cfg.user;
diff --git a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
index 1ed489bcb095..405dc0307fac 100644
--- a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
@@ -91,12 +91,16 @@ let
     };
 
     skins = config.skins;
+    extensions = config.extensions;
 
     buildPhase =
       ''
         for skin in $skins; do
           cp -prvd $skin/* skins/
         done
+        for extension in $extensions; do
+          cp -prvd $extension/* extensions/
+        done
       ''; # */
 
     installPhase =
@@ -287,6 +291,16 @@ in
         '';
     };
 
+    extensions = mkOption {
+      default = [];
+      type = types.listOf types.path;
+      description =
+        ''
+          List of paths whose content is copied to the 'extensions'
+          subdirectory of the MediaWiki installation.
+        '';
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index af01f6acad18..d56050c36269 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -8,7 +8,7 @@ let
   cfg = xcfg.desktopManager;
 
   # If desktop manager `d' isn't capable of setting a background and
-  # the xserver is enabled, the `feh' program is used as a fallback.
+  # the xserver is enabled, `feh' or `xsetroot' are used as a fallback.
   needBGCond = d: ! (d ? bgSupport && d.bgSupport) && xcfg.enable;
 
 in
@@ -44,8 +44,11 @@ in
             manage = "desktop";
             start = d.start
             + optionalString (needBGCond d) ''
-              if test -e $HOME/.background-image; then
+              if [ -e $HOME/.background-image ]; then
                 ${pkgs.feh}/bin/feh --bg-scale $HOME/.background-image
+              else
+                # Use a solid black background as fallback
+                ${pkgs.xorg.xsetroot}/bin/xsetroot -solid black
               fi
             '';
           }) list;
@@ -80,6 +83,6 @@ in
   config = {
     services.xserver.displayManager.session = cfg.session.list;
     environment.systemPackages =
-      mkIf cfg.session.needBGPackages [ pkgs.feh ];
+      mkIf cfg.session.needBGPackages [ pkgs.feh ]; # xsetroot via xserver.enable
   };
 }
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index d981cd5328e1..2216104be31a 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -183,6 +183,7 @@ in
       environment.variables = {
         # Enable GTK applications to load SVG icons
         GDK_PIXBUF_MODULE_FILE = "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache";
+        QT_PLUGIN_PATH = "/run/current-system/sw/lib/qt5/plugins";
       };
 
       fonts.fonts = with pkgs; [ noto-fonts hack-font ];
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 543fd9399147..cf6efb7dae79 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -32,14 +32,32 @@ let
     ''
       #! ${pkgs.bash}/bin/bash
 
-      # SDDM splits "Exec" line in .desktop file by whitespace and pass script path as $1
-      if [[ "$0" = "$1" ]]; then
-        # remove superfluous $1 again
-        shift
-        # join arguments again and evaluate them in a shell context
-        # to interpret shell quoting
-        eval exec "$0" "$@"
-      fi
+      # Expected parameters:
+      #   $1 = <desktop-manager>+<window-manager>
+
+      # Actual parameters (FIXME):
+      # SDDM is calling this script like the following:
+      #   $1 = /nix/store/xxx-xsession (= $0)
+      #   $2 = <desktop-manager>+<window-manager>
+      # SLiM is using the following parameter:
+      #   $1 = /nix/store/xxx-xsession <desktop-manager>+<window-manager>
+      # LightDM keeps the double quotes:
+      #   $1 = /nix/store/xxx-xsession "<desktop-manager>+<window-manager>"
+      # The fake/auto display manager doesn't use any parameters and GDM is
+      # broken.
+      # If you want to "debug" this script don't print the parameters to stdout
+      # or stderr because this script will be executed multiple times and the
+      # output won't be visible in the log when the script is executed for the
+      # first time (e.g. append them to a file instead)!
+
+      # All of the above cases are handled by the following hack (FIXME).
+      # Since this line is *very important* for *all display managers* it is
+      # very important to test changes to the following line with all display
+      # managers:
+      if [ "''${1:0:1}" = "/" ]; then eval exec "$1" "$2" ; fi
+
+      # Now it should be safe to assume that the script was called with the
+      # expected parameters.
 
       ${optionalString cfg.displayManager.logToJournal ''
         if [ -z "$_DID_SYSTEMD_CAT" ]; then
@@ -113,15 +131,16 @@ let
           fi
       fi
 
-      # The session type is "<desktop-manager> + <window-manager>", so
-      # extract those.
-      windowManager="''${sessionType##* + }"
+      # The session type is "<desktop-manager>+<window-manager>", so
+      # extract those (see:
+      # http://wiki.bash-hackers.org/syntax/pe#substring_removal).
+      windowManager="''${sessionType##*+}"
       : ''${windowManager:=${cfg.windowManager.default}}
-      desktopManager="''${sessionType% + *}"
+      desktopManager="''${sessionType%%+*}"
       : ''${desktopManager:=${cfg.desktopManager.default}}
 
       # Start the window manager.
-      case $windowManager in
+      case "$windowManager" in
         ${concatMapStrings (s: ''
           (${s.name})
             ${s.start}
@@ -131,7 +150,7 @@ let
       esac
 
       # Start the desktop manager.
-      case $desktopManager in
+      case "$desktopManager" in
         ${concatMapStrings (s: ''
           (${s.name})
             ${s.start}
@@ -148,6 +167,9 @@ let
       exit 0
     '';
 
+  # Desktop Entry Specification:
+  # - https://standards.freedesktop.org/desktop-entry-spec/latest/
+  # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
   mkDesktops = names: pkgs.runCommand "desktops"
     { # trivial derivation
       preferLocalBuild = true;
@@ -161,7 +183,7 @@ let
         Version=1.0
         Type=XSession
         TryExec=${cfg.displayManager.session.script}
-        Exec=${cfg.displayManager.session.script} '${n}'
+        Exec=${cfg.displayManager.session.script} "${n}"
         X-GDM-BypassXsession=true
         Name=${n}
         Comment=
@@ -244,7 +266,7 @@ in
           wm = filter (s: s.manage == "window") list;
           dm = filter (s: s.manage == "desktop") list;
           names = flip concatMap dm
-            (d: map (w: d.name + optionalString (w.name != "none") (" + " + w.name))
+            (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name))
               (filter (w: d.name != "none" || w.name != "none") wm));
           desktops = mkDesktops names;
           script = xsession wm dm;
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 82b9a2fce5ab..256bfb9ce3f4 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -61,7 +61,7 @@ let
     let
       dm = xcfg.desktopManager.default;
       wm = xcfg.windowManager.default;
-    in dm + optionalString (wm != "none") (" + " + wm);
+    in dm + optionalString (wm != "none") ("+" + wm);
 in
 {
   # Note: the order in which lightdm greeter modules are imported
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 99c03ca81c2d..2eb7ddcb1ec0 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -69,7 +69,7 @@ let
     let
       dm = xcfg.desktopManager.default;
       wm = xcfg.windowManager.default;
-    in dm + optionalString (wm != "none") (" + " + wm);
+    in dm + optionalString (wm != "none") ("+" + wm);
 
 in
 {
diff --git a/nixos/modules/services/x11/display-managers/slim.nix b/nixos/modules/services/x11/display-managers/slim.nix
index 05b979eef47f..0c4dd1973b53 100644
--- a/nixos/modules/services/x11/display-managers/slim.nix
+++ b/nixos/modules/services/x11/display-managers/slim.nix
@@ -17,6 +17,7 @@ let
       login_cmd exec ${pkgs.stdenv.shell} ${dmcfg.session.script} "%session"
       halt_cmd ${config.systemd.package}/sbin/shutdown -h now
       reboot_cmd ${config.systemd.package}/sbin/shutdown -r now
+      logfile /dev/stderr
       ${optionalString (cfg.defaultUser != null) ("default_user " + cfg.defaultUser)}
       ${optionalString (cfg.defaultUser != null) ("focus_password yes")}
       ${optionalString cfg.autoLogin "auto_login yes"}
@@ -128,11 +129,7 @@ in
   config = mkIf cfg.enable {
 
     services.xserver.displayManager.job =
-      { preStart =
-          ''
-            rm -f /var/log/slim.log
-          '';
-        environment =
+      { environment =
           { SLIM_CFGFILE = slimConfig;
             SLIM_THEMESDIR = slimThemesDir;
           };
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 8438e6dcc702..09fcdd0b72ba 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -31,18 +31,51 @@ let
       pkgs.xorg.fontadobe75dpi
     ];
 
+  xrandrOptions = {
+    output = mkOption {
+      type = types.str;
+      example = "DVI-0";
+      description = ''
+        The output name of the monitor, as shown by <citerefentry>
+          <refentrytitle>xrandr</refentrytitle>
+          <manvolnum>1</manvolnum>
+        </citerefentry> invoked without arguments.
+      '';
+    };
+
+    primary = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether this head is treated as the primary monitor,
+      '';
+    };
+
+    monitorConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        DisplaySize 408 306
+        Option "DPMS" "false"
+      '';
+      description = ''
+        Extra lines to append to the <literal>Monitor</literal> section
+        verbatim.
+      '';
+    };
+  };
 
   # Just enumerate all heads without discarding XRandR output information.
   xrandrHeads = let
-    mkHead = num: output: {
+    mkHead = num: config: {
       name = "multihead${toString num}";
-      inherit output;
+      inherit config;
     };
   in imap mkHead cfg.xrandrHeads;
 
   xrandrDeviceSection = let
     monitors = flip map xrandrHeads (h: ''
-      Option "monitor-${h.output}" "${h.name}"
+      Option "monitor-${h.config.output}" "${h.name}"
     '');
     # First option is indented through the space in the config but any
     # subsequent options aren't so we need to apply indentation to
@@ -62,9 +95,13 @@ let
       value = ''
         Section "Monitor"
           Identifier "${current.name}"
+          ${optionalString (current.config.primary) ''
+          Option "Primary" "true"
+          ''}
           ${optionalString (previous != []) ''
           Option "RightOf" "${(head previous).name}"
           ''}
+          ${current.config.monitorConfig}
         EndSection
       '';
     } ++ previous;
@@ -258,7 +295,7 @@ in
         type = types.str;
         default = "us";
         description = ''
-          Keyboard layout.
+          Keyboard layout, or multiple keyboard layouts separated by commas.
         '';
       };
 
@@ -329,13 +366,39 @@ in
 
       xrandrHeads = mkOption {
         default = [];
-        example = [ "HDMI-0" "DVI-0" ];
-        type = with types; listOf string;
+        example = [
+          "HDMI-0"
+          { output = "DVI-0"; primary = true; }
+          { output = "DVI-1"; monitorConfig = "Option \"Rotate\" \"left\""; }
+        ];
+        type = with types; listOf (coercedTo str (output: {
+          inherit output;
+        }) (submodule { options = xrandrOptions; }));
+        # Set primary to true for the first head if no other has been set
+        # primary already.
+        apply = heads: let
+          hasPrimary = any (x: x.primary) heads;
+          firstPrimary = head heads // { primary = true; };
+          newHeads = singleton firstPrimary ++ tail heads;
+        in if heads != [] && !hasPrimary then newHeads else heads;
         description = ''
-          Simple multiple monitor configuration, just specify a list of XRandR
-          outputs which will be mapped from left to right in the order of the
+          Multiple monitor configuration, just specify a list of XRandR
+          outputs. The individual elements should be either simple strings or
+          an attribute set of output options.
+
+          If the element is a string, it is denoting the physical output for a
+          monitor, if it's an attribute set, you must at least provide the
+          <option>output</option> option.
+
+          The monitors will be mapped from left to right in the order of the
           list.
 
+          By default, the first monitor will be set as the primary monitor if
+          none of the elements contain an option that has set
+          <option>primary</option> to <literal>true</literal>.
+
+          <note><para>Only one monitor is allowed to be primary.</para></note>
+
           Be careful using this option with multiple graphic adapters or with
           drivers that have poor support for XRandR, unexpected things might
           happen with those.
@@ -469,11 +532,18 @@ in
 
     nixpkgs.config.xorg = optionalAttrs (elem "vboxvideo" cfg.videoDrivers) { abiCompat = "1.18"; };
 
-    assertions =
-      [ { assertion = config.security.polkit.enable;
-          message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
-        }
-      ];
+    assertions = [
+      { assertion = config.security.polkit.enable;
+        message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
+      }
+      (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
+        assertion = length primaryHeads < 2;
+        message = "Only one head is allowed to be primary in "
+                + "‘services.xserver.xrandrHeads’, but there are "
+                + "${toString (length primaryHeads)} heads set to primary: "
+                + concatMapStringsSep ", " (x: x.output) primaryHeads;
+      })
+    ];
 
     environment.etc =
       (optionals cfg.exportConfiguration
@@ -578,6 +648,35 @@ in
 
     services.xserver.xkbDir = mkDefault "${pkgs.xkeyboard_config}/etc/X11/xkb";
 
+    system.extraDependencies = [
+      (pkgs.runCommand "xkb-layouts-exist" {
+            layouts=cfg.layout;
+        } ''
+        missing=()
+        while read -d , layout
+        do
+          [[ -f "${cfg.xkbDir}/symbols/$layout" ]] || missing+=($layout)
+        done <<< "$layouts,"
+        if [[ ''${#missing[@]} -eq 0 ]]
+        then
+          touch $out
+          exit 0
+        fi
+
+        cat >&2 <<EOF
+
+        Some of the selected keyboard layouts do not exist:
+
+          ''${missing[@]}
+
+        Set services.xserver.layout to the name of an existing keyboard
+        layout (check ${cfg.xkbDir}/symbols for options).
+
+        EOF
+        exit -1
+      '')
+    ];
+
     services.xserver.config =
       ''
         Section "ServerFlags"
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 2d6bf2d58a9e..cf47aed9fa99 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -239,6 +239,12 @@ in
           menuentry "Windows 7" {
             chainloader (hd0,4)+1
           }
+
+          # GRUB 2 with UEFI example, chainloading another distro
+          menuentry "Fedora" {
+            set root=(hd1,1)
+            chainloader /efi/fedora/grubx64.efi
+          }
         '';
         description = ''
           Any additional entries you want added to the GRUB boot menu.
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index c75e637124a9..9a125dcb0aeb 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -154,6 +154,9 @@ for o in $(cat /proc/cmdline); do
             fi
             ln -s "$root" /dev/root
             ;;
+        copytoram)
+            copytoram=1
+            ;;
     esac
 done
 
@@ -474,6 +477,22 @@ while read -u 3 mountPoint; do
     # doing something with $device right now.
     udevadm settle
 
+    # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs.
+    if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then
+      fsType=$(blkid -o value -s TYPE "$device")
+      fsSize=$(blockdev --getsize64 "$device")
+
+      mkdir -p /tmp-iso
+      mount -t "$fsType" /dev/root /tmp-iso
+      mountFS tmpfs /iso size="$fsSize" tmpfs
+
+      cp -r /tmp-iso/* /mnt-root/iso/
+
+      umount /tmp-iso
+      rmdir /tmp-iso
+      continue
+    fi
+
     mountFS "$device" "$mountPoint" "$options" "$fsType"
 done
 
diff --git a/nixos/modules/tasks/trackpoint.nix b/nixos/modules/tasks/trackpoint.nix
index 32e69dd2bf58..1f8f2891e98c 100644
--- a/nixos/modules/tasks/trackpoint.nix
+++ b/nixos/modules/tasks/trackpoint.nix
@@ -81,7 +81,7 @@ with lib;
       services.xserver.inputClassSections =
         [''
         Identifier "Trackpoint Wheel Emulation"
-          MatchProduct "${if cfg.fakeButtons then "PS/2 Generic Mouse" else "Elantech PS/2 TrackPoint|TPPS/2 IBM TrackPoint|DualPoint Stick|Synaptics Inc. Composite TouchPad / TrackPoint|ThinkPad USB Keyboard with TrackPoint|USB Trackpoint pointing device|Composite TouchPad / TrackPoint"}"
+          MatchProduct "${if cfg.fakeButtons then "PS/2 Generic Mouse" else "ETPS/2 Elantech TrackPoint|Elantech PS/2 TrackPoint|TPPS/2 IBM TrackPoint|DualPoint Stick|Synaptics Inc. Composite TouchPad / TrackPoint|ThinkPad USB Keyboard with TrackPoint|USB Trackpoint pointing device|Composite TouchPad / TrackPoint"}"
           MatchDevicePath "/dev/input/event*"
           Option "EmulateWheel" "true"
           Option "EmulateWheelButton" "2"
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index 33f84986cac6..cb756842f369 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/nixos/modules/virtualisation/azure-image.nix
@@ -2,93 +2,19 @@
 
 with lib;
 let
-  diskSize = "30720";
+  diskSize = 30720;
 in
 {
-  system.build.azureImage =
-    pkgs.vmTools.runInLinuxVM (
-      pkgs.runCommand "azure-image"
-        { preVM =
-            ''
-              mkdir $out
-              diskImage=$out/$diskImageBase
-
-              cyl=$(((${diskSize}*1024*1024)/(512*63*255)))
-              size=$(($cyl*255*63*512))              
-              roundedsize=$((($size/(1024*1024)+1)*(1024*1024)))
-              ${pkgs.vmTools.qemu-220}/bin/qemu-img create -f raw $diskImage $roundedsize
-              mv closure xchg/
-            '';
-
-          postVM =
-            ''
-              mkdir -p $out
-              ${pkgs.vmTools.qemu-220}/bin/qemu-img convert -f raw -o subformat=fixed -O vpc $diskImage $out/disk.vhd
-              rm $diskImage
-            '';
-          diskImageBase = "nixos-image-${config.system.nixosLabel}-${pkgs.stdenv.system}.raw";
-          buildInputs = [ pkgs.utillinux pkgs.perl ];
-          exportReferencesGraph =
-            [ "closure" config.system.build.toplevel ];
-        }
-        ''
-          # Create partition table
-          ${pkgs.parted}/sbin/parted /dev/vda mklabel msdos
-          ${pkgs.parted}/sbin/parted /dev/vda mkpart primary ext4 1 ${diskSize}M
-          ${pkgs.parted}/sbin/parted /dev/vda print
-          . /sys/class/block/vda1/uevent
-          mknod /dev/vda1 b $MAJOR $MINOR
-
-          # Create an empty filesystem and mount it.
-          ${pkgs.e2fsprogs}/sbin/mkfs.ext4 -L nixos /dev/vda1
-          ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
-
-          mkdir /mnt
-          mount /dev/vda1 /mnt
-
-          # The initrd expects these directories to exist.
-          mkdir /mnt/dev /mnt/proc /mnt/sys
-
-          mount --bind /proc /mnt/proc
-          mount --bind /dev /mnt/dev
-          mount --bind /sys /mnt/sys
-
-          # Copy all paths in the closure to the filesystem.
-          storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure)
-
-          mkdir -p /mnt/nix/store
-          echo "copying everything (will take a while)..."
-          cp -prd $storePaths /mnt/nix/store/
-
-          echo Register the paths in the Nix database.
-          printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \
-              chroot /mnt ${config.nix.package.out}/bin/nix-store --load-db --option build-users-group ""
-
-          echo Create the system profile to allow nixos-rebuild to work.
-          chroot /mnt ${config.nix.package.out}/bin/nix-env \
-              -p /nix/var/nix/profiles/system --set ${config.system.build.toplevel} --option build-users-group ""
-
-          echo nixos-rebuild requires an /etc/NIXOS.
-          mkdir -p /mnt/etc
-          touch /mnt/etc/NIXOS
-
-          echo switch-to-configuration requires a /bin/sh
-          mkdir -p /mnt/bin
-          ln -s ${config.system.build.binsh}/bin/sh /mnt/bin/sh
-
-          echo Install a configuration.nix.
-          mkdir -p /mnt/etc/nixos /mnt/boot/grub
-          cp ${./azure-config-user.nix} /mnt/etc/nixos/configuration.nix
-
-          echo Generate the GRUB menu.
-          ln -s vda /dev/sda
-          chroot /mnt ${config.system.build.toplevel}/bin/switch-to-configuration boot
-
-          echo Almost done
-          umount /mnt/proc /mnt/dev /mnt/sys
-          umount /mnt
-        ''
-    );
+  system.build.azureImage = import ../../lib/make-disk-image.nix {
+    name = "azure-image";
+    postVM = ''
+      ${pkgs.vmTools.qemu-220}/bin/qemu-img convert -f raw -o subformat=fixed -O vpc $diskImage $out/disk.vhd
+    '';
+    configFile = ./azure-config-user.nix;
+    format = "raw";
+    inherit diskSize;
+    inherit config lib pkgs;
+  };
 
   imports = [ ./azure-common.nix ];
 
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index f1101d7ea66e..c26cae06cd1d 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -7,8 +7,7 @@ with lib;
 let
 
   cfg = config.virtualisation.docker;
-  pro = config.networking.proxy.default;
-  proxy_env = optionalAttrs (pro != null) { Environment = "\"http_proxy=${pro}\""; };
+  proxy_env = config.networking.proxy.envVars;
 
 in
 
@@ -106,6 +105,7 @@ in
 
       systemd.services.docker = {
         wantedBy = optional cfg.enableOnBoot "multi-user.target";
+        environment = proxy_env;
         serviceConfig = {
           ExecStart = [
             ""
@@ -122,7 +122,7 @@ in
             ""
             "${pkgs.procps}/bin/kill -s HUP $MAINPID"
           ];
-        } // proxy_env;
+        };
 
         path = [ pkgs.kmod ] ++ (optional (cfg.storageDriver == "zfs") pkgs.zfs);
       };
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 5673d55b3394..ff39f1bf8dae 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -2,7 +2,7 @@
 
 with lib;
 let
-  diskSize = "1G";
+  diskSize = 1024; # MB
 in
 {
   imports = [ ../profiles/headless.nix ../profiles/qemu-guest.nix ./grow-partition.nix ];
@@ -10,89 +10,21 @@ in
   # https://cloud.google.com/compute/docs/tutorials/building-images
   networking.firewall.enable = mkDefault false;
 
-  system.build.googleComputeImage =
-    pkgs.vmTools.runInLinuxVM (
-      pkgs.runCommand "google-compute-image"
-        { preVM =
-            ''
-              mkdir $out
-              diskImage=$out/$diskImageBase
-              truncate $diskImage --size ${diskSize}
-              mv closure xchg/
-            '';
-
-          postVM =
-            ''
-              PATH=$PATH:${pkgs.stdenv.lib.makeBinPath [ pkgs.gnutar pkgs.gzip ]}
-              pushd $out
-              mv $diskImageBase disk.raw
-              tar -Szcf $diskImageBase.tar.gz disk.raw
-              rm $out/disk.raw
-              popd
-            '';
-          diskImageBase = "nixos-image-${config.system.nixosLabel}-${pkgs.stdenv.system}.raw";
-          buildInputs = [ pkgs.utillinux pkgs.perl ];
-          exportReferencesGraph =
-            [ "closure" config.system.build.toplevel ];
-        }
-        ''
-          # Create partition table
-          ${pkgs.parted}/sbin/parted /dev/vda mklabel msdos
-          ${pkgs.parted}/sbin/parted /dev/vda mkpart primary ext4 1 ${diskSize}
-          ${pkgs.parted}/sbin/parted /dev/vda print
-          . /sys/class/block/vda1/uevent
-          mknod /dev/vda1 b $MAJOR $MINOR
-
-          # Create an empty filesystem and mount it.
-          ${pkgs.e2fsprogs}/sbin/mkfs.ext4 -L nixos /dev/vda1
-          ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
-
-          mkdir /mnt
-          mount /dev/vda1 /mnt
-
-          # The initrd expects these directories to exist.
-          mkdir /mnt/dev /mnt/proc /mnt/sys
-
-          mount --bind /proc /mnt/proc
-          mount --bind /dev /mnt/dev
-          mount --bind /sys /mnt/sys
-
-          # Copy all paths in the closure to the filesystem.
-          storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure)
-
-          mkdir -p /mnt/nix/store
-          echo "copying everything (will take a while)..."
-          ${pkgs.rsync}/bin/rsync -a $storePaths /mnt/nix/store/
-
-          # Register the paths in the Nix database.
-          printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \
-              chroot /mnt ${config.nix.package.out}/bin/nix-store --load-db --option build-users-group ""
-
-          # Create the system profile to allow nixos-rebuild to work.
-          chroot /mnt ${config.nix.package.out}/bin/nix-env \
-              -p /nix/var/nix/profiles/system --set ${config.system.build.toplevel} \
-              --option build-users-group ""
-
-          # `nixos-rebuild' requires an /etc/NIXOS.
-          mkdir -p /mnt/etc
-          touch /mnt/etc/NIXOS
-
-          # `switch-to-configuration' requires a /bin/sh
-          mkdir -p /mnt/bin
-          ln -s ${config.system.build.binsh}/bin/sh /mnt/bin/sh
-
-          # Install a configuration.nix.
-          mkdir -p /mnt/etc/nixos /mnt/boot/grub
-          cp ${./google-compute-config.nix} /mnt/etc/nixos/configuration.nix
-
-          # Generate the GRUB menu.
-          ln -s vda /dev/sda
-          chroot /mnt ${config.system.build.toplevel}/bin/switch-to-configuration boot
-
-          umount /mnt/proc /mnt/dev /mnt/sys
-          umount /mnt
-        ''
-    );
+  system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
+    name = "google-compute-image";
+    postVM = ''
+      PATH=$PATH:${pkgs.stdenv.lib.makeBinPath [ pkgs.gnutar pkgs.gzip ]}
+      pushd $out
+      mv $diskImage disk.raw
+      tar -Szcf nixos-image-${config.system.nixosLabel}-${pkgs.stdenv.system}.raw.tar.gz disk.raw
+      rm $out/disk.raw
+      popd
+    '';
+    configFile = ./google-compute-config.nix;
+    format = "raw";
+    inherit diskSize;
+    inherit config lib pkgs;
+  };
 
   fileSystems."/" = {
     device = "/dev/disk/by-label/nixos";
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 1933f11d1fff..c75edfcd8cf6 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -75,6 +75,7 @@ let
       exec ${qemu}/bin/qemu-kvm \
           -name ${vmName} \
           -m ${toString config.virtualisation.memorySize} \
+          -smp ${toString config.virtualisation.cores} \
           ${optionalString (pkgs.stdenv.system == "x86_64-linux") "-cpu kvm64"} \
           ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
           -virtfs local,path=/nix/store,security_model=none,mount_tag=store \
@@ -244,6 +245,18 @@ in
           '';
       };
 
+    virtualisation.cores =
+      mkOption {
+        default = 1;
+        type = types.int;
+        description =
+          ''
+            Specify the number of cores the guest is permitted to use.
+            The number can be higher than the available cores on the
+            host system.
+          '';
+      };
+
     virtualisation.pathsInNixDB =
       mkOption {
         default = [];
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 5c1112a1c6d6..4217f5940ec6 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -94,6 +94,7 @@ in rec {
         (all nixos.tests.proxy)
         (all nixos.tests.sddm.default)
         (all nixos.tests.simple)
+        (all nixos.tests.slim)
         (all nixos.tests.udisks2)
         (all nixos.tests.xfce)
 
diff --git a/nixos/release.nix b/nixos/release.nix
index 95b284cb7056..aaf23d7ffb79 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -248,7 +248,7 @@ in rec {
   tests.gocd-server = callTest tests/gocd-server.nix {};
   tests.gnome3 = callTest tests/gnome3.nix {};
   tests.gnome3-gdm = callTest tests/gnome3-gdm.nix {};
-  tests.grsecurity = callTest tests/grsecurity.nix {};
+  tests.hardened = callTest tests/hardened.nix { };
   tests.hibernate = callTest tests/hibernate.nix {};
   tests.hound = callTest tests/hound.nix {};
   tests.i3wm = callTest tests/i3wm.nix {};
@@ -299,6 +299,7 @@ in rec {
   tests.samba = callTest tests/samba.nix {};
   tests.sddm = callSubTests tests/sddm.nix {};
   tests.simple = callTest tests/simple.nix {};
+  tests.slim = callTest tests/slim.nix {};
   tests.smokeping = callTest tests/smokeping.nix {};
   tests.taskserver = callTest tests/taskserver.nix {};
   tests.tomcat = callTest tests/tomcat.nix {};
diff --git a/nixos/tests/grsecurity.nix b/nixos/tests/grsecurity.nix
deleted file mode 100644
index d4a419fd0e39..000000000000
--- a/nixos/tests/grsecurity.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-# Basic test to make sure grsecurity works
-
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "grsecurity";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ copumpkin joachifm ];
-  };
-
-  machine = { config, pkgs, ... }:
-    { security.grsecurity.enable = true;
-      boot.kernel.sysctl."kernel.grsecurity.audit_mount" = 0;
-      boot.kernel.sysctl."kernel.grsecurity.deter_bruteforce" = 0;
-      networking.useDHCP = false;
-    };
-
-  testScript = ''
-    subtest "grsec-lock", sub {
-      $machine->succeed("systemctl is-active grsec-lock");
-      $machine->succeed("grep -Fq 1 /proc/sys/kernel/grsecurity/grsec_lock");
-      $machine->fail("echo -n 0 >/proc/sys/kernel/grsecurity/grsec_lock");
-    };
-
-    subtest "paxtest", sub {
-      # TODO: running paxtest blackhat hangs the vm
-      my @pax_mustkill = (
-        "anonmap", "execbss", "execdata", "execheap", "execstack",
-        "mprotanon", "mprotbss", "mprotdata", "mprotheap", "mprotstack",
-      );
-      foreach my $name (@pax_mustkill) {
-        my $paxtest = "${pkgs.paxtest}/lib/paxtest/" . $name;
-        $machine->succeed($paxtest) =~ /Killed/ or die
-      }
-    };
-
-    # tcc -run executes run-time generated code and so allows us to test whether
-    # paxmark actually works (otherwise, the process should be terminated)
-    subtest "tcc", sub {
-      $machine->execute("echo -e '#include <stdio.h>\nint main(void) { puts(\"hello\"); return 0; }' >main.c");
-      $machine->succeed("${pkgs.tinycc}/bin/tcc -run main.c");
-    };
-
-    subtest "RBAC", sub {
-      $machine->succeed("[ -c /dev/grsec ]");
-    };
-  '';
-})
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
new file mode 100644
index 000000000000..1d9a9043e03a
--- /dev/null
+++ b/nixos/tests/hardened.nix
@@ -0,0 +1,36 @@
+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 ];
+    };
+
+  testScript =
+    ''
+      # Test hidepid
+      subtest "hidepid", sub {
+          $machine->succeed("grep -Fq hidepid=2 /proc/mounts");
+          $machine->succeed("[ `su - sybil -c 'pgrep -c -u root'` = 0 ]");
+          $machine->succeed("[ `su - alice -c 'pgrep -c -u root'` != 0 ]");
+      };
+
+      # Test kernel module hardening
+      subtest "lock-modules", sub {
+          $machine->waitForUnit("multi-user.target");
+          # note: this better a be module we normally wouldn't load ...
+          $machine->fail("modprobe dccp");
+      };
+
+      # Test userns
+      subtest "userns", sub {
+          $machine->fail("unshare --user");
+      };
+    '';
+})
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index cd4086cb8f62..b926a62194b4 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -25,8 +25,6 @@ import ./make-test.nix ({ pkgs, ...} : {
         };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
       security.sudo = { enable = true; wheelNeedsPassword = false; };
-      security.hideProcessInformation = true;
-      users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
     };
 
   testScript =
@@ -119,12 +117,5 @@ import ./make-test.nix ({ pkgs, ...} : {
       subtest "sudo", sub {
           $machine->succeed("su - sybil -c 'sudo true'");
       };
-
-      # Test hidepid
-      subtest "hidepid", sub {
-          $machine->succeed("grep -Fq hidepid=2 /etc/mtab");
-          $machine->succeed("[ `su - sybil -c 'pgrep -c -u root'` = 0 ]");
-          $machine->succeed("[ `su - alice -c 'pgrep -c -u root'` != 0 ]");
-      };
     '';
 })
diff --git a/nixos/tests/mysql-replication.nix b/nixos/tests/mysql-replication.nix
index b20bce8edce6..75c6d793febc 100644
--- a/nixos/tests/mysql-replication.nix
+++ b/nixos/tests/mysql-replication.nix
@@ -56,11 +56,19 @@ in
   testScript = ''
     $master->start;
     $master->waitForUnit("mysql");
+    $master->waitForOpenPort(3306);
     $slave1->start;
     $slave2->start;
     $slave1->waitForUnit("mysql");
+    $slave1->waitForOpenPort(3306);
     $slave2->waitForUnit("mysql");
-    $slave2->sleep(100); # Hopefully this is long enough!!
+    $slave2->waitForOpenPort(3306);
     $slave2->succeed("echo 'use testdb; select * from tests' | mysql -u root -N | grep 4");
+    $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->waitForOpenPort(3306);
+    $slave2->succeed("echo 'select * from testdb.tests where Id = 123;' | mysql -u root -N | grep 456");
   '';
 })
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
new file mode 100644
index 000000000000..4c2ed8456ddd
--- /dev/null
+++ b/nixos/tests/radicale.nix
@@ -0,0 +1,80 @@
+let
+  port = 5232;
+  radicaleOverlay = self: super: {
+    radicale = super.radicale.overrideAttrs (oldAttrs: {
+      propagatedBuildInputs = with self.pythonPackages;
+        (oldAttrs.propagatedBuildInputs or []) ++ [
+          passlib
+        ];
+    });
+  };
+  common = { config, pkgs, ...}: {
+    services.radicale = {
+      enable = true;
+      config = let home = config.users.extraUsers.radicale.home; in ''
+        [server]
+        hosts = 127.0.0.1:${builtins.toString port}
+        daemon = False
+        [encoding]
+        [well-known]
+        [auth]
+        type = htpasswd
+        htpasswd_filename = /etc/radicale/htpasswd
+        htpasswd_encryption = bcrypt
+        [git]
+        [rights]
+        [storage]
+        type = filesystem
+        filesystem_folder = ${home}/collections
+        [logging]
+        [headers]
+      '';
+    };
+    # WARNING: DON'T DO THIS IN PRODUCTION!
+    # This puts secrets (albeit hashed) directly into the Nix store for ease of testing.
+    environment.etc."radicale/htpasswd".source = with pkgs; let
+      py = python.withPackages(ps: with ps; [ passlib ]);
+    in runCommand "htpasswd" {} ''
+        ${py}/bin/python -c "
+from passlib.apache import HtpasswdFile
+ht = HtpasswdFile(
+    '$out',
+    new=True,
+    default_scheme='bcrypt'
+)
+ht.set_password('someuser', 'really_secret_password')
+ht.save()
+"
+    '';
+  };
+
+in import ./make-test.nix ({ lib, ... }: {
+  name = "radicale";
+  meta.maintainers = with lib.maintainers; [ aneeshusa ];
+
+  # Test radicale with bcrypt-based htpasswd authentication
+  nodes = {
+    py2 = { config, pkgs, ... }@args: (common args) // {
+      nixpkgs.overlays = [
+        radicaleOverlay
+      ];
+    };
+    py3 = { config, pkgs, ... }@args: (common args) // {
+      nixpkgs.overlays = [
+        (self: super: {
+          python = self.python3;
+          pythonPackages = self.python3.pkgs;
+        })
+        radicaleOverlay
+      ];
+    };
+  };
+
+  testScript = ''
+    for my $machine ($py2, $py3) {
+      $machine->waitForUnit('radicale.service');
+      $machine->waitForOpenPort(${builtins.toString port});
+      $machine->succeed('curl -s http://someuser:really_secret_password@127.0.0.1:${builtins.toString port}/someuser/calendar.ics/');
+    }
+  '';
+})
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/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");
+  '';
+})