summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorUli Baum <xeji@cat3.de>2018-09-13 10:08:53 +0200
committerUli Baum <xeji@cat3.de>2018-09-13 10:08:53 +0200
commit1df2560dde3df459dd0e71883c5eac014100b0b0 (patch)
treeb0151bf1948574b52b6d29fd8a656f576cfbf7f7 /nixos
parent1428d00aa4a58095ba510145eb25233591ac6051 (diff)
parent0f2b10d1ac41c2f573fddbdf110a1ae435ffdd8a (diff)
downloadnixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.tar
nixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.tar.gz
nixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.tar.bz2
nixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.tar.lz
nixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.tar.xz
nixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.tar.zst
nixlib-1df2560dde3df459dd0e71883c5eac014100b0b0.zip
Merge branch 'master' into staging-next
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/installation/upgrading.xml9
-rw-r--r--nixos/doc/manual/release-notes/rl-1809.xml15
-rw-r--r--nixos/lib/eval-config.nix8
-rw-r--r--nixos/modules/config/shells-environment.nix21
-rw-r--r--nixos/modules/config/xdg/mime.nix14
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix8
-rw-r--r--nixos/modules/misc/ids.nix2
-rw-r--r--nixos/modules/misc/nixpkgs.nix12
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/programs/bash/bash.nix12
-rw-r--r--nixos/modules/programs/dconf.nix2
-rw-r--r--nixos/modules/programs/fish.nix19
-rw-r--r--nixos/modules/programs/zsh/zsh.nix14
-rw-r--r--nixos/modules/security/acme.nix8
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix10
-rw-r--r--nixos/modules/services/desktops/gnome3/rygel.nix30
-rw-r--r--nixos/modules/services/misc/weechat.nix56
-rw-r--r--nixos/modules/services/misc/weechat.xml61
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/riemann.nix22
-rw-r--r--nixos/modules/services/networking/hylafax/default.nix29
-rw-r--r--nixos/modules/services/networking/hylafax/faxq-default.nix12
-rwxr-xr-xnixos/modules/services/networking/hylafax/faxq-wait.sh29
-rw-r--r--nixos/modules/services/networking/hylafax/hfaxd-default.nix10
-rw-r--r--nixos/modules/services/networking/hylafax/modem-default.nix22
-rw-r--r--nixos/modules/services/networking/hylafax/options.nix375
-rwxr-xr-xnixos/modules/services/networking/hylafax/spool.sh111
-rw-r--r--nixos/modules/services/networking/hylafax/systemd.nix249
-rw-r--r--nixos/modules/services/networking/i2pd.nix604
-rw-r--r--nixos/modules/services/networking/iperf3.nix87
-rw-r--r--nixos/modules/services/networking/networkmanager.nix14
-rw-r--r--nixos/modules/services/networking/zeronet.nix20
-rw-r--r--nixos/modules/services/security/sks.nix100
-rw-r--r--nixos/modules/services/system/kerberos.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix1
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl2
-rw-r--r--nixos/modules/system/activation/top-level.nix1
-rw-r--r--nixos/modules/system/boot/networkd.nix6
-rw-r--r--nixos/modules/tasks/network-interfaces.nix2
-rw-r--r--nixos/release.nix2
-rw-r--r--nixos/tests/novacomd.nix8
-rw-r--r--nixos/tests/opensmtpd.nix8
43 files changed, 1714 insertions, 315 deletions
diff --git a/nixos/doc/manual/installation/upgrading.xml b/nixos/doc/manual/installation/upgrading.xml
index 85e5082575d3..69668b1d4bd6 100644
--- a/nixos/doc/manual/installation/upgrading.xml
+++ b/nixos/doc/manual/installation/upgrading.xml
@@ -52,10 +52,13 @@
    </listitem>
   </itemizedlist>
   To see what channels are available, go to
-  <link
-xlink:href="https://nixos.org/channels"/>. (Note that the URIs of the
+  <link xlink:href="https://nixos.org/channels"/>. (Note that the URIs of the
   various channels redirect to a directory that contains the channel’s latest
-  version and includes ISO images and VirtualBox appliances.)
+  version and includes ISO images and VirtualBox appliances.) Please note that
+  during the release process, channels that are not yet released will be
+  present here as well. See the Getting NixOS page
+  <link xlink:href="https://nixos.org/nixos/download.html"/> to find the newest
+  supported stable release.
  </para>
  <para>
   When you first install NixOS, you’re automatically subscribed to the NixOS
diff --git a/nixos/doc/manual/release-notes/rl-1809.xml b/nixos/doc/manual/release-notes/rl-1809.xml
index 53ffef31e3cc..01421fc5dda7 100644
--- a/nixos/doc/manual/release-notes/rl-1809.xml
+++ b/nixos/doc/manual/release-notes/rl-1809.xml
@@ -283,6 +283,14 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
       from your config without any issues.
     </para>
    </listitem>
+   <listitem>
+    <para>
+      <literal>stdenv.system</literal> and <literal>system</literal> in nixpkgs now refer to the host platform instead of the build platform.
+      For native builds this is not change, let alone a breaking one.
+      For cross builds, it is a breaking change, and <literal>stdenv.buildPlatform.system</literal> can be used instead for the old behavior.
+      They should be using that anyways for clarity.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -536,6 +544,13 @@ inherit (pkgs.nixos {
       a new paragraph.
     </para>
   </listitem>
+  <listitem>
+    <para>
+      Top-level <literal>buildPlatform</literal>, <literal>hostPlatform</literal>, and <literal>targetPlatform</literal> in Nixpkgs are deprecated.
+      Please use their equivalents in <literal>stdenv</literal> instead:
+      <literal>stdenv.buildPlatform</literal>, <literal>stdenv.hostPlatform</literal>, and <literal>stdenv.targetPlatform</literal>.
+    </para>
+  </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index 97c79487df4c..f71e264c3478 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -28,7 +28,7 @@
 
 let extraArgs_ = extraArgs; pkgs_ = pkgs;
     extraModules = let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
-                   in if e == "" then [] else [(import (builtins.toPath e))];
+                   in if e == "" then [] else [(import e)];
 in
 
 let
@@ -36,7 +36,11 @@ let
     _file = ./eval-config.nix;
     key = _file;
     config = {
-      nixpkgs.localSystem = lib.mkDefault { inherit system; };
+      # Explicit `nixpkgs.system` or `nixpkgs.localSystem` should override
+      # this.  Since the latter defaults to the former, the former should
+      # default to the argument. That way this new default could propagate all
+      # they way through, but has the last priority behind everything else.
+      nixpkgs.system = lib.mkDefault system;
       _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_);
     };
   };
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 31adc9b82620..555db459f57a 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -163,15 +163,24 @@ in
         /bin/sh
       '';
 
+    # For resetting environment with `. /etc/set-environment` when needed
+    # and discoverability (see motivation of #30418).
+    environment.etc."set-environment".source = config.system.build.setEnvironment;
+
     system.build.setEnvironment = pkgs.writeText "set-environment"
-       ''
-         ${exportedEnvVars}
+      ''
+        # DO NOT EDIT -- this file has been generated automatically.
+
+        # Prevent this file from being sourced by child shells.
+        export __NIXOS_SET_ENVIRONMENT_DONE=1
 
-         ${cfg.extraInit}
+        ${exportedEnvVars}
 
-         # ~/bin if it exists overrides other bin directories.
-         export PATH="$HOME/bin:$PATH"
-       '';
+        ${cfg.extraInit}
+
+        # ~/bin if it exists overrides other bin directories.
+        export PATH="$HOME/bin:$PATH"
+      '';
 
     system.activationScripts.binsh = stringAfter [ "stdio" ]
       ''
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index f1b672234a34..4323a49ea1dd 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -7,7 +7,7 @@ with lib;
       type = types.bool;
       default = true;
       description = ''
-        Whether to install files to support the 
+        Whether to install files to support the
         <link xlink:href="https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html">XDG Shared MIME-info specification</link> and the
         <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html">XDG MIME Applications specification</link>.
       '';
@@ -17,18 +17,18 @@ with lib;
   config = mkIf config.xdg.mime.enable {
     environment.pathsToLink = [ "/share/mime" ];
 
-    environment.systemPackages = [ 
-      # this package also installs some useful data, as well as its utilities 
-      pkgs.shared-mime-info 
+    environment.systemPackages = [
+      # this package also installs some useful data, as well as its utilities
+      pkgs.shared-mime-info
     ];
 
     environment.extraSetup = ''
-      if [ -w $out/share/mime ]; then
-        XDG_DATA_DIRS=$out/share ${pkgs.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
+      if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then
+          XDG_DATA_DIRS=$out/share ${pkgs.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
       fi
 
       if [ -w $out/share/applications ]; then
-        ${pkgs.desktop-file-utils}/bin/update-desktop-database $out/share/applications
+          ${pkgs.desktop-file-utils}/bin/update-desktop-database $out/share/applications
       fi
     '';
   };
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 6611a6ca0790..cb182a08a830 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,6 +1,6 @@
 {
-  x86_64-linux = "/nix/store/r9i30v8nasafg2851wflg71ln49fw03y-nix-2.1";
-  i686-linux = "/nix/store/dsg3pr7wwrk51f7la9wgby173j18llqh-nix-2.1";
-  aarch64-linux = "/nix/store/m3qgnch4xin21pmd1azas8kkcp9rhkr6-nix-2.1";
-  x86_64-darwin = "/nix/store/n7fvy0k555gwkkdszdkhi3h0aahca8h3-nix-2.1";
+  x86_64-linux = "/nix/store/h180y3n5k1ypxgm1pcvj243qix5j45zz-nix-2.1.1";
+  i686-linux = "/nix/store/v2y4k4v9ml07jmfq739wyflapg3b7b5k-nix-2.1.1";
+  aarch64-linux = "/nix/store/v485craglq7xm5996ci8qy5dyc17dab0-nix-2.1.1";
+  x86_64-darwin = "/nix/store/lc3ymlix73kaad5srjdgaxp9ngr1sg6g-nix-2.1.1";
 }
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 8292cdc995e0..aafeb997c326 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -53,7 +53,7 @@
       tomcat = 16;
       #audio = 17; # unused
       #floppy = 18; # unused
-      #uucp = 19; # unused
+      uucp = 19;
       #lp = 20; # unused
       #proc = 21; # unused
       pulseaudio = 22; # must match `pulseaudio' GID
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 8fbe218b232a..7f9833e184ab 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -62,12 +62,11 @@ in
     pkgs = mkOption {
       defaultText = literalExample
         ''import "''${nixos}/.." {
-            inherit (config.nixpkgs) config overlays localSystem crossSystem;
+            inherit (cfg) config overlays localSystem crossSystem;
           }
         '';
       default = import ../../.. {
-        localSystem = { inherit (cfg) system; } // cfg.localSystem;
-        inherit (cfg) config overlays crossSystem;
+        inherit (cfg) config overlays localSystem crossSystem;
       };
       type = pkgsType;
       example = literalExample ''import <nixpkgs> {}'';
@@ -140,8 +139,11 @@ in
 
     localSystem = mkOption {
       type = types.attrs; # TODO utilize lib.systems.parsedPlatform
-      default = { system = builtins.currentSystem; };
+      default = { inherit (cfg) system; };
       example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      # Make sure that the final value has all fields for sake of other modules
+      # referring to this. TODO make `lib.systems` itself use the module system.
+      apply = lib.systems.elaborate;
       defaultText = literalExample
         ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
       description = ''
@@ -180,6 +182,7 @@ in
     system = mkOption {
       type = types.str;
       example = "i686-linux";
+      default = { system = builtins.currentSystem; };
       description = ''
         Specifies the Nix platform type on which NixOS should be built.
         It is better to specify <code>nixpkgs.localSystem</code> instead.
@@ -196,6 +199,7 @@ in
         </programlisting>
         See <code>nixpkgs.localSystem</code> for more information.
 
+        Ignored when <code>nixpkgs.localSystem</code> is set.
         Ignored when <code>nixpkgs.pkgs</code> is set.
       '';
     };
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 4795922abcfb..f51a30aec2e9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -245,6 +245,7 @@
   ./services/desktops/gnome3/gnome-user-share.nix
   ./services/desktops/gnome3/gpaste.nix
   ./services/desktops/gnome3/gvfs.nix
+  ./services/desktops/gnome3/rygel.nix
   ./services/desktops/gnome3/seahorse.nix
   ./services/desktops/gnome3/sushi.nix
   ./services/desktops/gnome3/tracker.nix
@@ -406,6 +407,7 @@
   ./services/misc/taskserver
   ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
+  ./services/misc/weechat.nix
   ./services/misc/xmr-stak.nix
   ./services/misc/zookeeper.nix
   ./services/monitoring/apcupsd.nix
@@ -515,9 +517,11 @@
   ./services/networking/heyefi.nix
   ./services/networking/hostapd.nix
   ./services/networking/htpdate.nix
+  ./services/networking/hylafax/default.nix
   ./services/networking/i2pd.nix
   ./services/networking/i2p.nix
   ./services/networking/iodine.nix
+  ./services/networking/iperf3.nix
   ./services/networking/ircd-hybrid/default.nix
   ./services/networking/iwd.nix
   ./services/networking/keepalived/default.nix
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 69a1a482d074..424e1506b4c5 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -126,7 +126,9 @@ in
     programs.bash = {
 
       shellInit = ''
-        ${config.system.build.setEnvironment.text}
+        if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+            . ${config.system.build.setEnvironment}
+        fi
 
         ${cfge.shellInit}
       '';
@@ -166,11 +168,11 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/profile.local; then
-          . /etc/profile.local
+            . /etc/profile.local
         fi
 
         if [ -n "''${BASH_VERSION:-}" ]; then
-          . /etc/bashrc
+            . /etc/bashrc
         fi
       '';
 
@@ -191,12 +193,12 @@ in
 
         # We are not always an interactive shell.
         if [ -n "$PS1" ]; then
-          ${cfg.interactiveShellInit}
+            ${cfg.interactiveShellInit}
         fi
 
         # Read system-wide modifications.
         if test -f /etc/bashrc.local; then
-          . /etc/bashrc.local
+            . /etc/bashrc.local
         fi
       '';
 
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index b7d8a345e65c..9c9765b06b6f 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -32,6 +32,8 @@ in
     environment.etc = optionals (cfg.profiles != {})
       (mapAttrsToList mkDconfProfile cfg.profiles);
 
+    services.dbus.packages = [ pkgs.gnome3.dconf ];
+
     environment.variables.GIO_EXTRA_MODULES = optional cfg.enable
       "${pkgs.gnome3.dconf.lib}/lib/gio/modules";
     # https://github.com/NixOS/nixpkgs/pull/31891
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index c8d94a47be28..c3f742acde2e 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -27,7 +27,7 @@ in
         '';
         type = types.bool;
       };
-      
+
       vendor.config.enable = mkOption {
         type = types.bool;
         default = true;
@@ -43,7 +43,7 @@ in
           Whether fish should use completion files provided by other packages.
         '';
       };
-      
+
       vendor.functions.enable = mkOption {
         type = types.bool;
         default = true;
@@ -107,9 +107,11 @@ in
       # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently
       # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish
       set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $__fish_datadir/functions
-      
+
       # source the NixOS environment config
-      fenv source ${config.system.build.setEnvironment}
+      if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
+          fenv source ${config.system.build.setEnvironment}
+      end
 
       # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish
       set -e fish_function_path
@@ -123,7 +125,7 @@ in
         set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
         fenv source /etc/fish/foreign-env/shellInit > /dev/null
         set -e fish_function_path[1]
-        
+
         ${cfg.shellInit}
 
         # and leave a note so we don't source this config section again from
@@ -137,7 +139,7 @@ in
         set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
         fenv source /etc/fish/foreign-env/loginShellInit > /dev/null
         set -e fish_function_path[1]
-        
+
         ${cfg.loginShellInit}
 
         # and leave a note so we don't source this config section again from
@@ -149,12 +151,11 @@ in
       status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced
       and begin
         ${fishAliases}
-        
 
         set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
         fenv source /etc/fish/foreign-env/interactiveShellInit > /dev/null
         set -e fish_function_path[1]
-        
+
         ${cfg.promptInit}
         ${cfg.interactiveShellInit}
 
@@ -170,7 +171,7 @@ in
       ++ optional cfg.vendor.config.enable "/share/fish/vendor_conf.d"
       ++ optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d"
       ++ optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d";
-    
+
     environment.systemPackages = [ pkgs.fish ];
 
     environment.shells = [
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index d30b3415411f..b4ca8730958c 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -70,7 +70,7 @@ in
       promptInit = mkOption {
         default = ''
           if [ "$TERM" != dumb ]; then
-            autoload -U promptinit && promptinit && prompt walters
+              autoload -U promptinit && promptinit && prompt walters
           fi
         '';
         description = ''
@@ -116,7 +116,9 @@ in
         if [ -n "$__ETC_ZSHENV_SOURCED" ]; then return; fi
         export __ETC_ZSHENV_SOURCED=1
 
-        ${config.system.build.setEnvironment.text}
+        if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+            . ${config.system.build.setEnvironment}
+        fi
 
         ${cfge.shellInit}
 
@@ -124,7 +126,7 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/zshenv.local; then
-          . /etc/zshenv.local
+            . /etc/zshenv.local
         fi
       '';
 
@@ -143,7 +145,7 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/zprofile.local; then
-          . /etc/zprofile.local
+            . /etc/zprofile.local
         fi
       '';
 
@@ -169,7 +171,7 @@ in
 
         # Tell zsh how to find installed completions
         for p in ''${(z)NIX_PROFILES}; do
-          fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
+            fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
         done
 
         ${optionalString cfg.enableGlobalCompInit "autoload -U compinit && compinit"}
@@ -184,7 +186,7 @@ in
 
         # Read system-wide modifications.
         if test -f /etc/zshrc.local; then
-          . /etc/zshrc.local
+            . /etc/zshrc.local
         fi
       '';
 
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 946da92d80e7..092704c6fc3f 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -302,15 +302,15 @@ in
                       workdir="$(mktemp -d)"
 
                       # Create CA
-                      openssl genrsa -des3 -passout pass:x -out $workdir/ca.pass.key 2048
-                      openssl rsa -passin pass:x -in $workdir/ca.pass.key -out $workdir/ca.key
+                      openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048
+                      openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key
                       openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \
                         -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com"
                       openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt
 
                       # Create key
-                      openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
-                      openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
+                      openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048
+                      openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key
                       openssl req -new -key $workdir/server.key -out $workdir/server.csr \
                         -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
                       openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 1e1c5bc9f035..09174ed39f5e 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -8,6 +8,7 @@ let
   # configuration file can be generated by http://slurm.schedmd.com/configurator.html
   configFile = pkgs.writeTextDir "slurm.conf"
     ''
+      ClusterName=${cfg.clusterName}
       ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''}
       ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''}
       ${optionalString (cfg.nodeName != null) ''nodeName=${cfg.nodeName}''}
@@ -105,6 +106,15 @@ in
         '';
       };
 
+      clusterName = mkOption {
+        type = types.str;
+        default = "default";
+        example = "myCluster";
+        description = ''
+          Necessary to distinguish accounting records in a multi-cluster environment.
+        '';
+      };
+
       nodeName = mkOption {
         type = types.nullOr types.str;
         default = null;
diff --git a/nixos/modules/services/desktops/gnome3/rygel.nix b/nixos/modules/services/desktops/gnome3/rygel.nix
new file mode 100644
index 000000000000..55d5e703aa19
--- /dev/null
+++ b/nixos/modules/services/desktops/gnome3/rygel.nix
@@ -0,0 +1,30 @@
+# rygel service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+  options = {
+    services.gnome3.rygel = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable Rygel UPnP Mediaserver.
+
+          You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>.
+        '';
+        type = types.bool;
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gnome3.rygel.enable {
+    environment.systemPackages = [ pkgs.gnome3.rygel ];
+
+    services.dbus.packages = [ pkgs.gnome3.rygel ];
+
+    systemd.packages = [ pkgs.gnome3.rygel ];
+  };
+}
diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix
new file mode 100644
index 000000000000..1fcfb440485d
--- /dev/null
+++ b/nixos/modules/services/misc/weechat.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.weechat;
+in
+
+{
+  options.services.weechat = {
+    enable = mkEnableOption "weechat";
+    root = mkOption {
+      description = "Weechat state directory.";
+      type = types.str;
+      default = "/var/lib/weechat";
+    };
+    sessionName = mkOption {
+      description = "Name of the `screen' session for weechat.";
+      default = "weechat-screen";
+      type = types.str;
+    };
+    binary = mkOption {
+      description = "Binary to execute (by default \${weechat}/bin/weechat).";
+      example = literalExample ''
+        ''${pkgs.weechat}/bin/weechat-headless
+      '';
+      default = "${pkgs.weechat}/bin/weechat";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users = {
+      groups.weechat = {};
+      users.weechat = {
+        createHome = true;
+        group = "weechat";
+        home = cfg.root;
+        isSystemUser = true;
+      };
+    };
+
+    systemd.services.weechat = {
+      environment.WEECHAT_HOME = cfg.root;
+      serviceConfig = {
+        User = "weechat";
+        Group = "weechat";
+        RemainAfterExit = "yes";
+      };
+      script = "exec ${pkgs.screen}/bin/screen -Dm -S ${cfg.sessionName} ${cfg.binary}";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+    };
+  };
+
+  meta.doc = ./weechat.xml;
+}
diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml
new file mode 100644
index 000000000000..de86dede2eb5
--- /dev/null
+++ b/nixos/modules/services/misc/weechat.xml
@@ -0,0 +1,61 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-weechat">
+
+<title>WeeChat</title>
+<para><link xlink:href="https://weechat.org/">WeeChat</link> is a fast and extensible IRC client.</para>
+
+<section><title>Basic Usage</title>
+<para>
+By default, the module creates a
+<literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal> unit
+which runs the chat client in a detached
+<literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal> session.
+
+</para>
+
+<para>
+This can be done by enabling the <literal>weechat</literal> service:
+
+<programlisting>
+{ ... }:
+
+{
+  <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true;
+}
+</programlisting>
+</para>
+<para>
+The service is managed by a dedicated user
+named <literal>weechat</literal> in the state directory
+<literal>/var/lib/weechat</literal>.
+</para>
+</section>
+<section><title>Re-attaching to WeeChat</title>
+<para>
+WeeChat runs in a screen session owned by a dedicated user. To explicitly
+allow your another user to attach to this session, the <literal>screenrc</literal> needs to be tweaked
+by adding <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link> support:
+
+<programlisting>
+{
+  <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = ''
+    multiuser on
+    acladd normal_user
+  '';
+}
+</programlisting>
+
+Now, the session can be re-attached like this:
+
+<programlisting>
+screen -r weechat-screen
+</programlisting>
+</para>
+<para>
+<emphasis>The session name can be changed using <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
+</para>
+</section>
+</chapter>
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 3e801f9b838d..c30647f5460b 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -235,7 +235,7 @@ in {
         but without GF_ prefix
       '';
       default = {};
-      type = types.attrsOf types.str;
+      type = with types; attrsOf (either str path);
     };
   };
 
diff --git a/nixos/modules/services/monitoring/riemann.nix b/nixos/modules/services/monitoring/riemann.nix
index 237de53456f9..13d2b1cc0602 100644
--- a/nixos/modules/services/monitoring/riemann.nix
+++ b/nixos/modules/services/monitoring/riemann.nix
@@ -17,9 +17,9 @@ let
 
   launcher = writeScriptBin "riemann" ''
     #!/bin/sh
-    exec ${jdk}/bin/java ${concatStringsSep "\n" cfg.extraJavaOpts} \
+    exec ${jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOpts} \
       -cp ${classpath} \
-      riemann.bin ${writeText "riemann-config.clj" riemannConfig}
+      riemann.bin ${cfg.configFile}
   '';
 
 in {
@@ -37,7 +37,8 @@ in {
       config = mkOption {
         type = types.lines;
         description = ''
-          Contents of the Riemann configuration file.
+          Contents of the Riemann configuration file. For more complicated
+          config you should use configFile.
         '';
       };
       configFiles = mkOption {
@@ -47,7 +48,15 @@ in {
           Extra files containing Riemann configuration. These files will be
           loaded at runtime by Riemann (with Clojure's
           <literal>load-file</literal> function) at the end of the
-          configuration.
+          configuration if you use the config option, this is ignored if you
+          use configFile.
+        '';
+      };
+      configFile = mkOption {
+        type = types.str;
+        description = ''
+          A Riemann config file. Any files in the same directory as this file
+          will be added to the classpath by Riemann.
         '';
       };
       extraClasspathEntries = mkOption {
@@ -77,6 +86,10 @@ in {
       group = "riemann";
     };
 
+    services.riemann.configFile = mkDefault (
+      writeText "riemann-config.clj" riemannConfig
+    );
+
     systemd.services.riemann = {
       wantedBy = [ "multi-user.target" ];
       path = [ inetutils ];
@@ -84,6 +97,7 @@ in {
         User = "riemann";
         ExecStart = "${launcher}/bin/riemann";
       };
+      serviceConfig.LimitNOFILE = 65536;
     };
 
   };
diff --git a/nixos/modules/services/networking/hylafax/default.nix b/nixos/modules/services/networking/hylafax/default.nix
new file mode 100644
index 000000000000..4c63b822d165
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/default.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  imports = [
+    ./options.nix
+    ./systemd.nix
+  ];
+
+  config = lib.modules.mkIf config.services.hylafax.enable {
+    environment.systemPackages = [ pkgs.hylafaxplus ];
+    users.users.uucp = {
+      uid = config.ids.uids.uucp;
+      group = "uucp";
+      description = "Unix-to-Unix CoPy system";
+      isSystemUser = true;
+      inherit (config.users.users.nobody) home;
+    };
+    assertions = [{
+      assertion = config.services.hylafax.modems != {};
+      message = ''
+        HylaFAX cannot be used without modems.
+        Please define at least one modem with
+        <option>config.services.hylafax.modems</option>.
+      '';
+    }];
+  };
+
+}
diff --git a/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixos/modules/services/networking/hylafax/faxq-default.nix
new file mode 100644
index 000000000000..a2630ce66b71
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/faxq-default.nix
@@ -0,0 +1,12 @@
+{ ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+  ModemGroup = [ ''"any:.*"'' ];
+  ServerTracing = "0x78701";
+  SessionTracing = "0x78701";
+  UUCPLockDir = "/var/lock";
+
+}
diff --git a/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixos/modules/services/networking/hylafax/faxq-wait.sh
new file mode 100755
index 000000000000..8c39e9d20c18
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/faxq-wait.sh
@@ -0,0 +1,29 @@
+#! @shell@ -e
+
+# skip this if there are no modems at all
+if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1
+then
+  exit 0
+fi
+
+echo "faxq started, waiting for modem(s) to initialize..."
+
+for i in `seq @timeoutSec@0 -1 0`  # gracefully timeout
+do
+  sleep 0.1
+  # done if status files exist, but don't mention initialization
+  if \
+    stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \
+    && \
+    ! grep --silent --ignore-case 'initializing server' \
+    "@spoolAreaPath@"/status/*
+  then
+    echo "modem(s) apparently ready"
+    exit 0
+  fi
+  # if i reached 0, modems probably failed to initialize
+  if test $i -eq 0
+  then
+    echo "warning: modem initialization timed out"
+  fi
+done
diff --git a/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixos/modules/services/networking/hylafax/hfaxd-default.nix
new file mode 100644
index 000000000000..8999dae57f41
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/hfaxd-default.nix
@@ -0,0 +1,10 @@
+{ ... }:
+
+# see man:hfaxd(8)
+
+{
+
+  ServerTracing = "0x91";
+  XferLogFile = "/clientlog";
+
+}
diff --git a/nixos/modules/services/networking/hylafax/modem-default.nix b/nixos/modules/services/networking/hylafax/modem-default.nix
new file mode 100644
index 000000000000..7529b5b0aafd
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/modem-default.nix
@@ -0,0 +1,22 @@
+{ pkgs, ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+  TagLineFont = "etc/LiberationSans-25.pcf";
+  TagLineLocale = ''en_US.UTF-8'';
+
+  AdminGroup = "root";  # groups that can change server config
+  AnswerRotary = "fax";  # don't accept anything else but faxes
+  LogFileMode = "0640";
+  PriorityScheduling = true;
+  RecvFileMode = "0640";
+  ServerTracing = "0x78701";
+  SessionTracing = "0x78701";
+  UUCPLockDir = "/var/lock";
+
+  SendPageCmd = ''${pkgs.coreutils}/bin/false'';  # prevent pager transmit
+  SendUUCPCmd = ''${pkgs.coreutils}/bin/false'';  # prevent UUCP transmit
+
+}
diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
new file mode 100644
index 000000000000..4ac6d3fa8432
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -0,0 +1,375 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib.options) literalExample mkEnableOption mkOption;
+  inherit (lib.types) bool enum int lines loaOf nullOr path str submodule;
+  inherit (lib.modules) mkDefault mkIf mkMerge;
+
+  commonDescr = ''
+    Values can be either strings or integers
+    (which will be added to the config file verbatimly)
+    or lists thereof
+    (which will be translated to multiple
+    lines with the same configuration key).
+    Boolean values are translated to "Yes" or "No".
+    The default contains some reasonable
+    configuration to yield an operational system.
+  '';
+
+  str1 = lib.types.addCheck str (s: s!="");  # non-empty string
+  int1 = lib.types.addCheck int (i: i>0);  # positive integer
+
+  configAttrType =
+    # Options in HylaFAX configuration files can be
+    # booleans, strings, integers, or list thereof
+    # representing multiple config directives with the same key.
+    # This type definition resolves all
+    # those types into a list of strings.
+    let
+      inherit (lib.types) attrsOf coercedTo listOf;
+      innerType = coercedTo bool (x: if x then "Yes" else "No")
+        (coercedTo int (toString) str);
+    in
+      attrsOf (coercedTo innerType lib.singleton (listOf innerType));
+
+  cfg = config.services.hylafax;
+
+  modemConfigOptions = { name, config, ... }: {
+    options = {
+      name = mkOption {
+        type = str1;
+        example = "ttyS1";
+        description = ''
+          Name of modem device,
+          will be searched for in <filename>/dev</filename>.
+        '';
+      };
+      type = mkOption {
+        type = str1;
+        example = "cirrus";
+        description = ''
+          Name of modem configuration file,
+          will be searched for in <filename>config</filename>
+          in the spooling area directory.
+        '';
+      };
+      config = mkOption {
+        type = configAttrType;
+        example = {
+          AreaCode = "49";
+          LocalCode = "30";
+          FAXNumber = "123456";
+          LocalIdentifier = "LostInBerlin";
+        };
+        description = ''
+          Attribute set of values for the given modem.
+          ${commonDescr}
+          Options defined here override options in
+          <option>commonModemConfig</option> for this modem.
+        '';
+      };
+    };
+    config.name = mkDefault name;
+    config.config.Include = [ "config/${config.type}" ];
+  };
+
+  defaultConfig =
+    let
+      inherit (config.security) wrapperDir;
+      inherit (config.services.mail.sendmailSetuidWrapper) program;
+      mkIfDefault = cond: value: mkIf cond (mkDefault value);
+      noWrapper = config.services.mail.sendmailSetuidWrapper==null;
+      # If a sendmail setuid wrapper exists,
+      # we add the path to the default configuration file.
+      # Otherwise, we use `false` to provoke
+      # an error if hylafax tries to use it.
+      c.sendmailPath = mkMerge [
+        (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'')
+        (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'')
+      ];
+      importDefaultConfig = file:
+        lib.attrsets.mapAttrs
+        (lib.trivial.const mkDefault)
+        (import file { inherit pkgs; });
+      c.commonModemConfig = importDefaultConfig ./modem-default.nix;
+      c.faxqConfig = importDefaultConfig ./faxq-default.nix;
+      c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
+    in
+      c;
+
+  localConfig =
+    let
+      c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
+      c.faxqConfig = lib.attrsets.mapAttrs
+        (lib.trivial.const (v: mkIf (v!=null) v))
+        {
+          AreaCode = cfg.areaCode;
+          CountryCode = cfg.countryCode;
+          LongDistancePrefix = cfg.longDistancePrefix;
+          InternationalPrefix = cfg.internationalPrefix;
+        };
+      c.commonModemConfig = c.faxqConfig;
+    in
+      c;
+
+in
+
+
+{
+
+
+  options.services.hylafax = {
+
+    enable = mkEnableOption ''HylaFAX server'';
+
+    autostart = mkOption {
+      type = bool;
+      default = true;
+      example = false;
+      description = ''
+        Autostart the HylaFAX queue manager at system start.
+        If this is <literal>false</literal>, the queue manager
+        will still be started if there are pending
+        jobs or if a user tries to connect to it.
+      '';
+    };
+
+    countryCode = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "49";
+      description = ''Country code for server and all modems.'';
+    };
+
+    areaCode = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "30";
+      description = ''Area code for server and all modems.'';
+    };
+
+    longDistancePrefix = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "0";
+      description = ''Long distance prefix for server and all modems.'';
+    };
+
+    internationalPrefix = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "00";
+      description = ''International prefix for server and all modems.'';
+    };
+
+    spoolAreaPath = mkOption {
+      type = path;
+      default = "/var/spool/fax";
+      description = ''
+        The spooling area will be created/maintained
+        at the location given here.
+      '';
+    };
+
+    userAccessFile = mkOption {
+      type = path;
+      default = "/etc/hosts.hfaxd";
+      description = ''
+        The <filename>hosts.hfaxd</filename>
+        file entry in the spooling area
+        will be symlinked to the location given here.
+        This file must exist and be
+        readable only by the <literal>uucp</literal> user.
+        See hosts.hfaxd(5) for details.
+        This configuration permits access for all users:
+        <literal>
+          environment.etc."hosts.hfaxd" = {
+            mode = "0600";
+            user = "uucp";
+            text = ".*";
+          };
+        </literal>
+        Note that host-based access can be controlled with
+        <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
+        by default, only 127.0.0.1 is permitted to connect.
+      '';
+    };
+
+    sendmailPath = mkOption {
+      type = path;
+      example = literalExample "''${pkgs.postfix}/bin/sendmail";
+      # '' ;  # fix vim
+      description = ''
+        Path to <filename>sendmail</filename> program.
+        The default uses the local sendmail wrapper
+        (see <option>config.services.mail.sendmailSetuidWrapper</option>),
+        otherwise the <filename>false</filename>
+        binary to cause an error if used.
+      '';
+    };
+
+    hfaxdConfig = mkOption {
+      type = configAttrType;
+      example.RecvqProtection = "0400";
+      description = ''
+        Attribute set of lines for the global
+        hfaxd config file <filename>etc/hfaxd.conf</filename>.
+        ${commonDescr}
+      '';
+    };
+
+    faxqConfig = mkOption {
+      type = configAttrType;
+      example = {
+        InternationalPrefix = "00";
+        LongDistancePrefix = "0";
+      };
+      description = ''
+        Attribute set of lines for the global
+        faxq config file <filename>etc/config</filename>.
+        ${commonDescr}
+      '';
+    };
+
+    commonModemConfig = mkOption {
+      type = configAttrType;
+      example = {
+        InternationalPrefix = "00";
+        LongDistancePrefix = "0";
+      };
+      description = ''
+        Attribute set of default values for
+        modem config files <filename>etc/config.*</filename>.
+        ${commonDescr}
+        Think twice before changing
+        paths of fax-processing scripts.
+      '';
+    };
+
+    modems = mkOption {
+      type = loaOf (submodule [ modemConfigOptions ]);
+      default = {};
+      example.ttyS1 = {
+        type = "cirrus";
+        config = {
+          FAXNumber = "123456";
+          LocalIdentifier = "Smith";
+        };
+      };
+      description = ''
+        Description of installed modems.
+        At least on modem must be defined
+        to enable the HylaFAX server.
+      '';
+    };
+
+    spoolExtraInit = mkOption {
+      type = lines;
+      default = "";
+      example = ''chmod 0755 .  # everyone may read my faxes'';
+      description = ''
+        Additional shell code that is executed within the
+        spooling area directory right after its setup.
+      '';
+    };
+
+    faxcron.enable.spoolInit = mkEnableOption ''
+      Purge old files from the spooling area with
+      <filename>faxcron</filename>
+      each time the spooling area is initialized.
+    '';
+    faxcron.enable.frequency = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "daily";
+      description = ''
+        Purge old files from the spooling area with
+        <filename>faxcron</filename> with the given frequency
+        (see systemd.time(7)).
+      '';
+    };
+    faxcron.infoDays = mkOption {
+      type = int1;
+      default = 30;
+      description = ''
+        Set the expiration time for data in the
+        remote machine information directory in days.
+      '';
+    };
+    faxcron.logDays = mkOption {
+      type = int1;
+      default = 30;
+      description = ''
+        Set the expiration time for
+        session trace log files in days.
+      '';
+    };
+    faxcron.rcvDays = mkOption {
+      type = int1;
+      default = 7;
+      description = ''
+        Set the expiration time for files in
+        the received facsimile queue in days.
+      '';
+    };
+
+    faxqclean.enable.spoolInit = mkEnableOption ''
+      Purge old files from the spooling area with
+      <filename>faxqclean</filename>
+      each time the spooling area is initialized.
+    '';
+    faxqclean.enable.frequency = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "daily";
+      description = ''
+        Purge old files from the spooling area with
+        <filename>faxcron</filename> with the given frequency
+        (see systemd.time(7)).
+      '';
+    };
+    faxqclean.archiving = mkOption {
+      type = enum [ "never" "as-flagged" "always" ];
+      default = "as-flagged";
+      example = "always";
+      description = ''
+        Enable or suppress job archiving:
+        <literal>never</literal> disables job archiving,
+        <literal>as-flagged</literal> archives jobs that
+        have been flagged for archiving by sendfax,
+        <literal>always</literal> forces archiving of all jobs.
+        See also sendfax(1) and faxqclean(8).
+      '';
+    };
+    faxqclean.doneqMinutes = mkOption {
+      type = int1;
+      default = 15;
+      example = literalExample ''24*60'';
+      description = ''
+        Set the job
+        age threshold (in minutes) that controls how long
+        jobs may reside in the doneq directory.
+      '';
+    };
+    faxqclean.docqMinutes = mkOption {
+      type = int1;
+      default = 60;
+      example = literalExample ''24*60'';
+      description = ''
+        Set the document
+        age threshold (in minutes) that controls how long
+        unreferenced files may reside in the docq directory.
+      '';
+    };
+
+  };
+
+
+  config.services.hylafax =
+    mkIf
+    (config.services.hylafax.enable)
+    (mkMerge [ defaultConfig localConfig ])
+  ;
+
+}
diff --git a/nixos/modules/services/networking/hylafax/spool.sh b/nixos/modules/services/networking/hylafax/spool.sh
new file mode 100755
index 000000000000..31e930e8c597
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/spool.sh
@@ -0,0 +1,111 @@
+#! @shell@ -e
+
+# The following lines create/update the HylaFAX spool directory:
+# Subdirectories/files with persistent data are kept,
+# other directories/files are removed/recreated,
+# mostly from the template spool
+# directory in the HylaFAX package.
+
+# This block explains how the spool area is
+# derived from the spool template in the HylaFAX package:
+#
+#                  + capital letter: directory; file otherwise
+#                  + P/p: persistent directory
+#                  + F/f: directory with symlinks per entry
+#                  + T/t: temporary data
+#                  + S/s: single symlink into package
+#                  |
+#                  | + u: change ownership to uucp:uucp
+#                  | + U: ..also change access mode to user-only
+#                  | |
+# archive          P U
+# bin              S
+# client           T u  (client connection info)
+# config           S
+# COPYRIGHT        s
+# dev              T u  (maybe some FIFOs)
+# docq             P U
+# doneq            P U
+# etc              F    contains customized config files!
+# etc/hosts.hfaxd  f
+# etc/xferfaxlog   f
+# info             P u  (database of called devices)
+# log              P u  (communication logs)
+# pollq            P U
+# recvq            P u
+# sendq            P U
+# status           T u  (modem status info files)
+# tmp              T U
+
+
+shopt -s dotglob  # if bash sees "*", it also includes dot files
+lnsym () { ln --symbol "$@" ; }
+lnsymfrc () { ln --symbolic --force "$@" ; }
+cprd () { cp --remove-destination "$@" ; }
+update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; }
+
+
+## create/update spooling area
+
+update --mode=0750 -d "@spoolAreaPath@"
+cd "@spoolAreaPath@"
+
+persist=(archive docq doneq info log pollq recvq sendq)
+
+# remove entries that don't belong here
+touch dummy  # ensure "*" resolves to something
+for k in *
+do
+  keep=0
+  for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun
+  do
+    if test "$k" == "$j"
+    then
+      keep=1
+      break
+    fi
+  done
+  if test "$keep" == "0"
+  then
+    rm --recursive "$k"
+  fi
+done
+
+# create persistent data directories (unless they exist already)
+update --mode=0700 -d "${persist[@]}"
+chmod 0755 info log recvq
+
+# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog``
+touch clientlog faxcron.lastrun xferfaxlog
+chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog
+
+# create symlinks for frozen directories/files
+lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config}
+
+# create empty temporary directories
+update --mode=0700 -d client dev status
+update -d tmp
+
+
+## create and fill etc
+
+install -d "@spoolAreaPath@/etc"
+cd "@spoolAreaPath@/etc"
+
+# create symlinks to all files in template's etc
+lnsym --target-directory=. "@hylafax@/spool/etc"/*
+
+# set LOCKDIR in setup.cache
+sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache
+
+# etc/{xferfaxlog,lastrun} are stored in the spool root
+lnsymfrc --target-directory=. ../xferfaxlog
+lnsymfrc --no-target-directory ../faxcron.lastrun lastrun
+
+# etc/hosts.hfaxd is provided by the NixOS configuration
+lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd
+
+# etc/config and etc/config.${DEVID} must be copied:
+# hfaxd reads these file after locking itself up in a chroot
+cprd --no-target-directory "@globalConfigPath@" config
+cprd --target-directory=. "@modemConfigPath@"/*
diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix
new file mode 100644
index 000000000000..91d9c1a37da6
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/systemd.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, ... }:
+
+
+let
+
+  inherit (lib) mkIf mkMerge;
+  inherit (lib) concatStringsSep optionalString;
+
+  cfg = config.services.hylafax;
+  mapModems = lib.flip map (lib.attrValues cfg.modems);
+
+  mkConfigFile = name: conf:
+    # creates hylafax config file,
+    # makes sure "Include" is listed *first*
+    let
+      mkLines = conf:
+        (lib.concatLists
+        (lib.flip lib.mapAttrsToList conf
+        (k: map (v: ''${k}: ${v}'')
+      )));
+      include = mkLines { Include = conf.Include or []; };
+      other = mkLines ( conf // { Include = []; } );
+    in
+      pkgs.writeText ''hylafax-config${name}''
+      (concatStringsSep "\n" (include ++ other));
+
+  globalConfigPath = mkConfigFile "" cfg.faxqConfig;
+
+  modemConfigPath =
+    let
+      mkModemConfigFile = { config, name, ... }:
+        mkConfigFile ''.${name}''
+        (cfg.commonModemConfig // config);
+      mkLine = { name, type, ... }@modem: ''
+        # check if modem config file exists:
+        test -f "${pkgs.hylafaxplus}/spool/config/${type}"
+        ln \
+          --symbolic \
+          --no-target-directory \
+          "${mkModemConfigFile modem}" \
+          "$out/config.${name}"
+      '';
+    in
+      pkgs.runCommand "hylafax-config-modems" {}
+      ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
+
+  setupSpoolScript = pkgs.substituteAll {
+    name = "hylafax-setup-spool.sh";
+    src = ./spool.sh;
+    isExecutable = true;
+    inherit (pkgs.stdenv) shell;
+    hylafax = pkgs.hylafaxplus;
+    faxuser = "uucp";
+    faxgroup = "uucp";
+    lockPath = "/var/lock";
+    inherit globalConfigPath modemConfigPath;
+    inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
+  };
+
+  waitFaxqScript = pkgs.substituteAll {
+    # This script checks the modems status files
+    # and waits until all modems report readiness.
+    name = "hylafax-faxq-wait-start.sh";
+    src = ./faxq-wait.sh;
+    isExecutable = true;
+    timeoutSec = toString 10;
+    inherit (pkgs.stdenv) shell;
+    inherit (cfg) spoolAreaPath;
+  };
+
+  sockets."hylafax-hfaxd" = {
+    description = "HylaFAX server socket";
+    documentation = [ "man:hfaxd(8)" ];
+    wantedBy = [ "multi-user.target" ];
+    listenStreams = [ "127.0.0.1:4559" ];
+    socketConfig.FreeBind = true;
+    socketConfig.Accept = true;
+  };
+
+  paths."hylafax-faxq" = {
+    description = "HylaFAX queue manager sendq watch";
+    documentation = [ "man:faxq(8)" "man:sendq(5)" ];
+    wantedBy = [ "multi-user.target" ];
+    pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ];
+  };
+
+  timers = mkMerge [
+    (
+      mkIf (cfg.faxcron.enable.frequency!=null)
+      { "hylafax-faxcron".timerConfig.Persistent = true; }
+    )
+    (
+      mkIf (cfg.faxqclean.enable.frequency!=null)
+      { "hylafax-faxqclean".timerConfig.Persistent = true; }
+    )
+  ];
+
+  hardenService =
+    # Add some common systemd service hardening settings,
+    # but allow each service (here) to override
+    # settings by explicitely setting those to `null`.
+    # More hardening would be nice but makes
+    # customizing hylafax setups very difficult.
+    # If at all, it should only be added along
+    # with some options to customize it.
+    let
+      hardening = {
+        PrivateDevices = true;  # breaks /dev/tty...
+        PrivateNetwork = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        #ProtectHome = true;  # breaks custom spool dirs
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        #ProtectSystem = "strict";  # breaks custom spool dirs
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+      };
+      filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
+      apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
+    in
+      service: service // { serviceConfig = apply service; };
+
+  services."hylafax-spool" = {
+    description = "HylaFAX spool area preparation";
+    documentation = [ "man:hylafax-server(4)" ];
+    script = ''
+      ${setupSpoolScript}
+      cd "${cfg.spoolAreaPath}"
+      ${cfg.spoolExtraInit}
+      if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
+      then
+        echo hosts.hfaxd is missing
+        exit 1
+      fi
+    '';
+    serviceConfig.ExecStop = ''${setupSpoolScript}'';
+    serviceConfig.RemainAfterExit = true;
+    serviceConfig.Type = "oneshot";
+    unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
+  };
+
+  services."hylafax-faxq" = {
+    description = "HylaFAX queue manager";
+    documentation = [ "man:faxq(8)" ];
+    requires = [ "hylafax-spool.service" ];
+    after = [ "hylafax-spool.service" ];
+    wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' );
+    wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
+    serviceConfig.Type = "forking";
+    serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
+    # This delays the "readiness" of this service until
+    # all modems are initialized (or a timeout is reached).
+    # Otherwise, sending a fax with the fax service
+    # stopped will always yield a failed send attempt:
+    # The fax service is started when the job is created with
+    # `sendfax`, but modems need some time to initialize.
+    serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ];
+    # faxquit fails if the pipe is already gone
+    # (e.g. the service is already stopping)
+    serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
+    # disable some systemd hardening settings
+    serviceConfig.PrivateDevices = null;
+    serviceConfig.RestrictRealtime = null;
+  };
+
+  services."hylafax-hfaxd@" = {
+    description = "HylaFAX server";
+    documentation = [ "man:hfaxd(8)" ];
+    after = [ "hylafax-faxq.service" ];
+    requires = [ "hylafax-faxq.service" ];
+    serviceConfig.StandardInput = "socket";
+    serviceConfig.StandardOutput = "socket";
+    serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
+    unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
+    # disable some systemd hardening settings
+    serviceConfig.PrivateDevices = null;
+    serviceConfig.PrivateNetwork = null;
+  };
+
+  services."hylafax-faxcron" = rec {
+    description = "HylaFAX spool area maintenance";
+    documentation = [ "man:faxcron(8)" ];
+    after = [ "hylafax-spool.service" ];
+    requires = [ "hylafax-spool.service" ];
+    wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
+    startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
+    serviceConfig.ExecStart = concatStringsSep " " [
+      ''${pkgs.hylafaxplus}/spool/bin/faxcron''
+      ''-q "${cfg.spoolAreaPath}"''
+      ''-info ${toString cfg.faxcron.infoDays}''
+      ''-log  ${toString cfg.faxcron.logDays}''
+      ''-rcv  ${toString cfg.faxcron.rcvDays}''
+    ];
+  };
+
+  services."hylafax-faxqclean" = rec {
+    description = "HylaFAX spool area queue cleaner";
+    documentation = [ "man:faxqclean(8)" ];
+    after = [ "hylafax-spool.service" ];
+    requires = [ "hylafax-spool.service" ];
+    wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
+    startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
+    serviceConfig.ExecStart = concatStringsSep " " [
+      ''${pkgs.hylafaxplus}/spool/bin/faxqclean''
+      ''-q "${cfg.spoolAreaPath}"''
+      ''-v''
+      (optionalString (cfg.faxqclean.archiving!="never") ''-a'')
+      (optionalString (cfg.faxqclean.archiving=="always")  ''-A'')
+      ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
+      ''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
+    ];
+  };
+
+  mkFaxgettyService = { name, ... }:
+    lib.nameValuePair ''hylafax-faxgetty@${name}'' rec {
+      description = "HylaFAX faxgetty for %I";
+      documentation = [ "man:faxgetty(8)" ];
+      bindsTo = [ "dev-%i.device" ];
+      requires = [ "hylafax-spool.service" ];
+      after = bindsTo ++ requires;
+      before = [ "hylafax-faxq.service" "getty.target" ];
+      unitConfig.StopWhenUnneeded = true;
+      unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I'';
+      serviceConfig.UtmpIdentifier = "%I";
+      serviceConfig.TTYPath = "/dev/%I";
+      serviceConfig.Restart = "always";
+      serviceConfig.KillMode = "process";
+      serviceConfig.IgnoreSIGPIPE = false;
+      serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
+      # faxquit fails if the pipe is already gone
+      # (e.g. the service is already stopping)
+      serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
+      # disable some systemd hardening settings
+      serviceConfig.PrivateDevices = null;
+      serviceConfig.RestrictRealtime = null;
+    };
+
+  modemServices =
+    lib.listToAttrs (mapModems mkFaxgettyService);
+
+in
+
+{
+  config.systemd = mkIf cfg.enable {
+    inherit sockets timers paths;
+    services = lib.mapAttrs (lib.const hardenService) (services // modemServices);
+  };
+}
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 3afafaf3fed5..0e9b354cfcaf 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -8,6 +8,17 @@ let
 
   homeDir = "/var/lib/i2pd";
 
+  strOpt = k: v: k + " = " + v;
+  boolOpt = k: v: k + " = " + boolToString v;
+  intOpt = k: v: k + " = " + toString v;
+  lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
+  optionalNullString = o: s: optional (! isNull s) (strOpt o s);
+  optionalNullBool = o: b: optional (! isNull b) (boolOpt o b);
+  optionalNullInt = o: i: optional (! isNull i) (intOpt o i);
+  optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
+
+  mkEnableTrueOption = name: mkEnableOption name // { default = true; };
+
   mkEndpointOpt = name: addr: port: {
     enable = mkEnableOption name;
     name = mkOption {
@@ -18,42 +29,54 @@ let
     address = mkOption {
       type = types.str;
       default = addr;
-      description = "Bind address for ${name} endpoint. Default: " + addr;
+      description = "Bind address for ${name} endpoint.";
     };
     port = mkOption {
       type = types.int;
       default = port;
-      description = "Bind port for ${name} endoint. Default: " + toString port;
+      description = "Bind port for ${name} endoint.";
     };
   };
 
-  mkKeyedEndpointOpt = name: addr: port: keyFile:
+  i2cpOpts = name: {
+    length = mkOption {
+      type = types.int;
+      description = "Guaranteed minimum hops for ${name} tunnels.";
+      default = 3;
+    };
+    quantity = mkOption {
+      type = types.int;
+      description = "Number of simultaneous ${name} tunnels.";
+      default = 5;
+    };
+  };
+
+  mkKeyedEndpointOpt = name: addr: port: keyloc:
     (mkEndpointOpt name addr port) // {
       keys = mkOption {
-        type = types.str;
-        default = "";
+        type = with types; nullOr str;
+        default = keyloc;
         description = ''
           File to persist ${lib.toUpper name} keys.
         '';
       };
-    };
-
-  commonTunOpts = let
-    i2cpOpts = {
-      length = mkOption {
-        type = types.int;
-        description = "Guaranteed minimum hops.";
-        default = 3;
+      inbound = i2cpOpts name;
+      outbound = i2cpOpts name;
+      latency.min = mkOption {
+        type = with types; nullOr int;
+        description = "Min latency for tunnels.";
+        default = null;
       };
-      quantity = mkOption {
-        type = types.int;
-        description = "Number of simultaneous tunnels.";
-        default = 5;
+      latency.max = mkOption {
+        type = with types; nullOr int;
+        description = "Max latency for tunnels.";
+        default = null;
       };
     };
-  in name: {
-    outbound = i2cpOpts;
-    inbound = i2cpOpts;
+
+  commonTunOpts = name: {
+    outbound = i2cpOpts name;
+    inbound = i2cpOpts name;
     crypto.tagsToSend = mkOption {
       type = types.int;
       description = "Number of ElGamal/AES tags to send.";
@@ -70,94 +93,142 @@ let
     };
   } // mkEndpointOpt name "127.0.0.1" 0;
 
-  i2pdConf = pkgs.writeText "i2pd.conf" ''
-    # DO NOT EDIT -- this file has been generated automatically.
-    loglevel = ${cfg.logLevel}
-
-    ipv4 = ${boolToString cfg.enableIPv4}
-    ipv6 = ${boolToString cfg.enableIPv6}
-    notransit = ${boolToString cfg.notransit}
-    floodfill = ${boolToString cfg.floodfill}
-    netid = ${toString cfg.netid}
-    ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" }
-    ${if isNull cfg.port then "" else "port = ${toString cfg.port}"}
-
-    [limits]
-    transittunnels = ${toString cfg.limits.transittunnels}
-
-    [upnp]
-    enabled = ${boolToString cfg.upnp.enable}
-    name = ${cfg.upnp.name}
-
-    [precomputation]
-    elgamal = ${boolToString cfg.precomputation.elgamal}
-
-    [reseed]
-    verify = ${boolToString cfg.reseed.verify}
-    file = ${cfg.reseed.file}
-    urls = ${builtins.concatStringsSep "," cfg.reseed.urls}
-
-    [addressbook]
-    defaulturl = ${cfg.addressbook.defaulturl}
-    subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions}
-
-    ${flip concatMapStrings
+  sec = name: "\n[" + name + "]";
+  notice = "# DO NOT EDIT -- this file has been generated automatically.";
+  i2pdConf = let
+    opts = [
+      notice
+      (strOpt "loglevel" cfg.logLevel)
+      (boolOpt "logclftime" cfg.logCLFTime)
+      (boolOpt "ipv4" cfg.enableIPv4)
+      (boolOpt "ipv6" cfg.enableIPv6)
+      (boolOpt "notransit" cfg.notransit)
+      (boolOpt "floodfill" cfg.floodfill)
+      (intOpt "netid" cfg.netid)
+    ] ++ (optionalNullInt "bandwidth" cfg.bandwidth)
+      ++ (optionalNullInt "port" cfg.port)
+      ++ (optionalNullString "family" cfg.family)
+      ++ (optionalNullString "datadir" cfg.dataDir)
+      ++ (optionalNullInt "share" cfg.share)
+      ++ (optionalNullBool "ssu" cfg.ssu)
+      ++ (optionalNullBool "ntcp" cfg.ntcp)
+      ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
+      ++ (optionalNullString "ifname" cfg.ifname)
+      ++ (optionalNullString "ifname4" cfg.ifname4)
+      ++ (optionalNullString "ifname6" cfg.ifname6)
+      ++ [
+      (sec "limits")
+      (intOpt "transittunnels" cfg.limits.transittunnels)
+      (intOpt "coresize" cfg.limits.coreSize)
+      (intOpt "openfiles" cfg.limits.openFiles)
+      (intOpt "ntcphard" cfg.limits.ntcpHard)
+      (intOpt "ntcpsoft" cfg.limits.ntcpSoft)
+      (intOpt "ntcpthreads" cfg.limits.ntcpThreads)
+      (sec "upnp")
+      (boolOpt "enabled" cfg.upnp.enable)
+      (sec "precomputation")
+      (boolOpt "elgamal" cfg.precomputation.elgamal)
+      (sec "reseed")
+      (boolOpt "verify" cfg.reseed.verify)
+    ] ++ (optionalNullString "file" cfg.reseed.file)
+      ++ (optionalEmptyList "urls" cfg.reseed.urls)
+      ++ (optionalNullString "floodfill" cfg.reseed.floodfill)
+      ++ (optionalNullString "zipfile" cfg.reseed.zipfile)
+      ++ (optionalNullString "proxy" cfg.reseed.proxy)
+      ++ [
+      (sec "trust")
+      (boolOpt "enabled" cfg.trust.enable)
+      (boolOpt "hidden" cfg.trust.hidden)
+    ] ++ (optionalEmptyList "routers" cfg.trust.routers)
+      ++ (optionalNullString "family" cfg.trust.family)
+      ++ [
+      (sec "websockets")
+      (boolOpt "enabled" cfg.websocket.enable)
+      (strOpt "address" cfg.websocket.address)
+      (intOpt "port" cfg.websocket.port)
+      (sec "exploratory")
+      (intOpt "inbound.length" cfg.exploratory.inbound.length)
+      (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
+      (intOpt "outbound.length" cfg.exploratory.outbound.length)
+      (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
+      (sec "ntcp2")
+      (boolOpt "enabled" cfg.ntcp2.enable)
+      (boolOpt "published" cfg.ntcp2.published)
+      (intOpt "port" cfg.ntcp2.port)
+      (sec "addressbook")
+      (strOpt "defaulturl" cfg.addressbook.defaulturl)
+    ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
+      ++ (flip map
       (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto)
-      (proto: ''
-        [${proto.name}]
-        enabled = ${boolToString proto.enable}
-        address = ${proto.address}
-        port = ${toString proto.port}
-        ${if proto ? keys then "keys = ${proto.keys}" else ""}
-        ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""}
-        ${if proto ? user then "user = ${proto.user}" else ""}
-        ${if proto ? pass then "pass = ${proto.pass}" else ""}
-        ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""}
-        ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""}
-      '')
-    }
-  '';
-
-  i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" ''
-    # DO NOT EDIT -- this file has been generated automatically.
-    ${flip concatMapStrings
+      (proto: let protoOpts = [
+        (sec proto.name)
+        (boolOpt "enabled" proto.enable)
+        (strOpt "address" proto.address)
+        (intOpt "port" proto.port)
+        ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else [])
+        ++ (if proto ? auth then optionalNullBool "auth" proto.auth else [])
+        ++ (if proto ? user then optionalNullString "user" proto.user else [])
+        ++ (if proto ? pass then optionalNullString "pass" proto.pass else [])
+        ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else [])
+        ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else [])
+        ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else [])
+        ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else [])
+        ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []);
+        in (concatStringsSep "\n" protoOpts)
+      ));
+  in
+    pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
+
+  tunnelConf = let opts = [
+    notice
+    (flip map
       (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
-      (tun: ''
-        [${tun.name}]
-        type = client
-        destination = ${tun.destination}
-        destinationport = ${toString tun.destinationPort}
-        keys = ${tun.keys}
-        address = ${tun.address}
-        port = ${toString tun.port}
-        inbound.length = ${toString tun.inbound.length}
-        outbound.length = ${toString tun.outbound.length}
-        inbound.quantity = ${toString tun.inbound.quantity}
-        outbound.quantity = ${toString tun.outbound.quantity}
-        crypto.tagsToSend = ${toString tun.crypto.tagsToSend}
-      '')
-    }
-    ${flip concatMapStrings
+      (tun: let outTunOpts = [
+        (sec tun.name)
+        "type = client"
+        (intOpt "port" tun.port)
+        (strOpt "destination" tun.destination)
+        ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else [])
+        ++ (if tun ? keys then
+            optionalNullString "keys" tun.keys else [])
+        ++ (if tun ? address then
+            optionalNullString "address" tun.address else [])
+        ++ (if tun ? inbound.length then
+            optionalNullInt "inbound.length" tun.inbound.length else [])
+        ++ (if tun ? inbound.quantity then
+            optionalNullInt "inbound.quantity" tun.inbound.quantity else [])
+        ++ (if tun ? outbound.length then
+            optionalNullInt "outbound.length" tun.outbound.length else [])
+        ++ (if tun ? outbound.quantity then
+            optionalNullInt "outbound.quantity" tun.outbound.quantity else [])
+        ++ (if tun ? crypto.tagsToSend then
+            optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []);
+        in concatStringsSep "\n" outTunOpts))
+    (flip map
       (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
-      (tun: ''
-        [${tun.name}]
-        type = server
-        destination = ${tun.destination}
-        keys = ${tun.keys}
-        host = ${tun.address}
-        port = ${toString tun.port}
-        inport = ${toString tun.inPort}
-        accesslist = ${builtins.concatStringsSep "," tun.accessList}
-      '')
-    }
-  '';
+      (tun: let inTunOpts = [
+        (sec tun.name)
+        "type = server"
+        (intOpt "port" tun.port)
+        (strOpt "host" tun.address)
+      ] ++ (if tun ? destination then
+            optionalNullString "destination" tun.destination else [])
+        ++ (if tun ? keys then
+            optionalNullString "keys" tun.keys else [])
+        ++ (if tun ? inPort then
+            optionalNullInt "inport" tun.inPort else [])
+        ++ (if tun ? accessList then
+            optionalEmptyList "accesslist" tun.accessList else []);
+        in concatStringsSep "\n" inTunOpts))];
+    in pkgs.writeText "i2pd-tunnels.conf" opts;
 
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
     exec ${pkgs.i2pd}/bin/i2pd \
       ${if isNull cfg.address then "" else "--host="+cfg.address} \
+      --service \
       --conf=${i2pdConf} \
-      --tunconf=${i2pdTunnelConf}
+      --tunconf=${tunnelConf}
   '';
 
 in
@@ -170,9 +241,7 @@ in
 
     services.i2pd = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
+      enable = mkEnableOption "I2Pd daemon" // {
         description = ''
           Enables I2Pd as a running service upon activation.
           Please read http://i2pd.readthedocs.io/en/latest/ for further
@@ -192,6 +261,8 @@ in
         '';
       };
 
+      logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
+
       address = mkOption {
         type = with types; nullOr str;
         default = null;
@@ -200,17 +271,72 @@ in
         '';
       };
 
-      notransit = mkOption {
-        type = types.bool;
-        default = false;
+      family = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Specify a family the router belongs to.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
+        '';
+      };
+
+      share = mkOption {
+        type = types.int;
+        default = 100;
+        description = ''
+          Limit of transit traffic from max bandwidth in percents.
+        '';
+      };
+
+      ifname = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Network interface to bind to.
+        '';
+      };
+
+      ifname4 = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          IPv4 interface to bind to.
+        '';
+      };
+
+      ifname6 = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          IPv6 interface to bind to.
+        '';
+      };
+
+      ntcpProxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Proxy URL for NTCP transport.
+        '';
+      };
+
+      ntcp = mkEnableTrueOption "ntcp";
+      ssu = mkEnableTrueOption "ssu";
+
+      notransit = mkEnableOption "notransit" // {
         description = ''
           Tells the router to not accept transit tunnels during startup.
         '';
       };
 
-      floodfill = mkOption {
-        type = types.bool;
-        default = false;
+      floodfill = mkEnableOption "floodfill" // {
         description = ''
           If the router is declared to be unreachable and needs introduction nodes.
         '';
@@ -241,131 +367,178 @@ in
         '';
       };
 
-      enableIPv4 = mkOption {
-        type = types.bool;
-        default = true;
+      enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
+      enableIPv6 = mkEnableOption "IPv6 connectivity";
+      nat = mkEnableTrueOption "NAT bypass";
+
+      upnp.enable = mkEnableOption "UPnP service discovery";
+      upnp.name = mkOption {
+        type = types.str;
+        default = "I2Pd";
         description = ''
-          Enables IPv4 connectivity. Enabled by default.
+          Name i2pd appears in UPnP forwardings list.
         '';
       };
 
-      enableIPv6 = mkOption {
-        type = types.bool;
-        default = false;
+      precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
         description = ''
-          Enables IPv6 connectivity. Disabled by default.
+          Whenever to use precomputated tables for ElGamal.
+          <command>i2pd</command> defaults to <literal>false</literal>
+          to save 64M of memory (and looses some performance).
+
+          We default to <literal>true</literal> as that is what most
+          users want anyway.
         '';
       };
 
-      nat = mkOption {
-        type = types.bool;
-        default = true;
+      reseed.verify = mkEnableOption "SU3 signature verification";
+
+      reseed.file = mkOption {
+        type = with types; nullOr str;
+        default = null;
         description = ''
-          Assume router is NATed. Enabled by default.
+          Full path to SU3 file to reseed from.
         '';
       };
 
-      upnp = {
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Enables UPnP.
-          '';
-        };
+      reseed.urls = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Reseed URLs.
+        '';
+      };
 
-        name = mkOption {
-          type = types.str;
-          default = "I2Pd";
-          description = ''
-            Name i2pd appears in UPnP forwardings list.
-          '';
-        };
+      reseed.floodfill = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Path to router info of floodfill to reseed from.
+        '';
       };
 
-      precomputation.elgamal = mkOption {
-        type = types.bool;
-        default = true;
+      reseed.zipfile = mkOption {
+        type = with types; nullOr str;
+        default = null;
         description = ''
-          Whenever to use precomputated tables for ElGamal.
-          <command>i2pd</command> defaults to <literal>false</literal>
-          to save 64M of memory (and looses some performance).
+          Path to local .zip file to reseed from.
+        '';
+      };
 
-          We default to <literal>true</literal> as that is what most
-          users want anyway.
+      reseed.proxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          URL for reseed proxy, supports http/socks.
         '';
       };
 
-      reseed = {
-        verify = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Request SU3 signature verification
-          '';
-        };
+     addressbook.defaulturl = mkOption {
+        type = types.str;
+        default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
+        description = ''
+          AddressBook subscription URL for initial setup
+        '';
+      };
+     addressbook.subscriptions = mkOption {
+        type = with types; listOf str;
+        default = [
+          "http://inr.i2p/export/alive-hosts.txt"
+          "http://i2p-projekt.i2p/hosts.txt"
+          "http://stats.i2p/cgi-bin/newhosts.txt"
+        ];
+        description = ''
+          AddressBook subscription URLs
+        '';
+      };
 
-        file = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            Full path to SU3 file to reseed from
-          '';
-        };
+      trust.enable = mkEnableOption "Explicit trust options";
 
-        urls = mkOption {
-          type = with types; listOf str;
-          default = [
-            "https://reseed.i2p-project.de/"
-            "https://i2p.mooo.com/netDb/"
-            "https://netdb.i2p2.no/"
-            "https://us.reseed.i2p2.no:444/"
-            "https://uk.reseed.i2p2.no:444/"
-            "https://i2p.manas.ca:8443/"
-          ];
-          description = ''
-            Reseed URLs
-          '';
-        };
+      trust.family = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Router Familiy to trust for first hops.
+        '';
       };
 
-      addressbook = {
-       defaulturl = mkOption {
-          type = types.str;
-          default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
-          description = ''
-            AddressBook subscription URL for initial setup
-          '';
-        };
-       subscriptions = mkOption {
-          type = with types; listOf str;
-          default = [
-            "http://inr.i2p/export/alive-hosts.txt"
-            "http://i2p-projekt.i2p/hosts.txt"
-            "http://stats.i2p/cgi-bin/newhosts.txt"
-          ];
-          description = ''
-            AddressBook subscription URLs
-          '';
-        };
+      trust.routers = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Only connect to the listed routers.
+        '';
+      };
+
+      trust.hidden = mkEnableOption "Router concealment.";
+
+      websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
+
+      exploratory.inbound = i2cpOpts "exploratory";
+      exploratory.outbound = i2cpOpts "exploratory";
+
+      ntcp2.enable = mkEnableTrueOption "NTCP2.";
+      ntcp2.published = mkEnableOption "NTCP2 publication.";
+      ntcp2.port = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Port to listen for incoming NTCP2 connections (0=auto).
+        '';
       };
 
       limits.transittunnels = mkOption {
         type = types.int;
         default = 2500;
         description = ''
-          Maximum number of active transit sessions
+          Maximum number of active transit sessions.
+        '';
+      };
+
+      limits.coreSize = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum size of corefile in Kb (0 - use system limit).
+        '';
+      };
+
+      limits.openFiles = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum number of open files (0 - use system default).
+        '';
+      };
+
+      limits.ntcpHard = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum number of active transit sessions.
+        '';
+      };
+
+      limits.ntcpSoft = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
+        '';
+      };
+
+      limits.ntcpThreads = mkOption {
+        type = types.int;
+        default = 1;
+        description = ''
+          Maximum number of threads used by NTCP DH worker.
         '';
       };
 
       proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
-        auth = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Enable authentication for webconsole.
-          '';
-        };
+
+        auth = mkEnableOption "Webconsole authentication";
+
         user = mkOption {
           type = types.str;
           default = "i2pd";
@@ -373,6 +546,7 @@ in
             Username for webconsole access
           '';
         };
+
         pass = mkOption {
           type = types.str;
           default = "i2pd";
@@ -380,11 +554,35 @@ in
             Password for webconsole access.
           '';
         };
+
+        strictHeaders = mkOption {
+          type = with types; nullOr bool;
+          default = null;
+          description = ''
+            Enable strict host checking on WebUI.
+          '';
+        };
+
+        hostname = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            Expected hostname for WebUI.
+          '';
+        };
       };
 
-      proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "";
-      proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "")
+      proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
+      // {
+        outproxy = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Upstream outproxy bind address.";
+        };
+      };
+      proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
       // {
+        outproxyEnable = mkEnableOption "SOCKS outproxy";
         outproxy = mkOption {
           type = types.str;
           default = "127.0.0.1";
@@ -408,8 +606,8 @@ in
           { name, ... }: {
             options = {
               destinationPort = mkOption {
-                type = types.int;
-                default = 0;
+                type = with types; nullOr int;
+                default = null;
                 description = "Connect to particular port at destination.";
               };
             } // commonTunOpts name;
diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix
new file mode 100644
index 000000000000..742404a5692f
--- /dev/null
+++ b/nixos/modules/services/networking/iperf3.nix
@@ -0,0 +1,87 @@
+{ config, lib, pkgs, ... }: with lib;
+let
+  cfg = config.services.iperf3;
+
+  api = {
+    enable = mkEnableOption "iperf3 network throughput testing server";
+    port = mkOption {
+      type        = types.ints.u16;
+      default     = 5201;
+      description = "Server port to listen on for iperf3 client requsts.";
+    };
+    affinity = mkOption {
+      type        = types.nullOr types.ints.unsigned;
+      default     = null;
+      description = "CPU affinity for the process.";
+    };
+    bind = mkOption {
+      type        = types.nullOr types.str;
+      default     = null;
+      description = "Bind to the specific interface associated with the given address.";
+    };
+    verbose = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Give more detailed output.";
+    };
+    forceFlush = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Force flushing output at every interval.";
+    };
+    debug = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Emit debugging output.";
+    };
+    rsaPrivateKey = mkOption {
+      type        = types.nullOr types.path;
+      default     = null;
+      description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
+    };
+    authorizedUsersFile = mkOption {
+      type        = types.nullOr types.path;
+      default     = null;
+      description = "Path to the configuration file containing authorized users credentials to run iperf tests.";
+    };
+    extraFlags = mkOption {
+      type        = types.listOf types.str;
+      default     = [ ];
+      description = "Extra flags to pass to iperf3(1).";
+    };
+  };
+
+  imp = {
+    systemd.services.iperf3 = {
+      description = "iperf3 daemon";
+      unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = 2;
+        DynamicUser = true;
+        PrivateDevices = true;
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ExecStart = ''
+          ${pkgs.iperf3}/bin/iperf \
+            --server \
+            --port ${toString cfg.port} \
+            ${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \
+            ${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \
+            ${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \
+            ${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \
+            ${optionalString cfg.verbose "--verbose"} \
+            ${optionalString cfg.debug "--debug"} \
+            ${optionalString cfg.forceFlush "--forceflush"} \
+            ${escapeShellArgs cfg.extraFlags}
+        '';
+      };
+    };
+  };
+in {
+  options.services.iperf3 = api;
+  config = mkIf cfg.enable imp;
+}
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index d5af4648e8f9..2d76e0676b24 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -406,25 +406,25 @@ in {
       { source = configFile;
         target = "NetworkManager/NetworkManager.conf";
       }
-      { source = "${networkmanager-openvpn}/etc/NetworkManager/VPN/nm-openvpn-service.name";
+      { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
         target = "NetworkManager/VPN/nm-openvpn-service.name";
       }
-      { source = "${networkmanager-vpnc}/etc/NetworkManager/VPN/nm-vpnc-service.name";
+      { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
         target = "NetworkManager/VPN/nm-vpnc-service.name";
       }
-      { source = "${networkmanager-openconnect}/etc/NetworkManager/VPN/nm-openconnect-service.name";
+      { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
         target = "NetworkManager/VPN/nm-openconnect-service.name";
       }
-      { source = "${networkmanager-fortisslvpn}/etc/NetworkManager/VPN/nm-fortisslvpn-service.name";
+      { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
         target = "NetworkManager/VPN/nm-fortisslvpn-service.name";
       }
-      { source = "${networkmanager-l2tp}/etc/NetworkManager/VPN/nm-l2tp-service.name";
+      { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
         target = "NetworkManager/VPN/nm-l2tp-service.name";
       }
-      { source = "${networkmanager_strongswan}/etc/NetworkManager/VPN/nm-strongswan-service.name";
+      { source = "${networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
         target = "NetworkManager/VPN/nm-strongswan-service.name";
       }
-      { source = "${networkmanager-iodine}/etc/NetworkManager/VPN/nm-iodine-service.name";
+      { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
         target = "NetworkManager/VPN/nm-iodine-service.name";
       }
     ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == [])
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 2377cb2c8f11..8b60799891ca 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -12,6 +12,8 @@ let
       log_dir = ${cfg.logDir}
     '' + lib.optionalString (cfg.port != null) ''
       ui_port = ${toString cfg.port}
+    '' + lib.optionalString (cfg.torAlways) ''
+      tor = always
     '' + cfg.extraConfig;
   };
 in with lib; {
@@ -35,13 +37,19 @@ in with lib; {
     port = mkOption {
       type = types.nullOr types.int;
       default = null;
-      example = 15441;
-      description = "Optional zeronet port.";
+      example = 43110;
+      description = "Optional zeronet web UI port.";
     };
 
     tor = mkOption {
       type = types.bool;
       default = false;
+      description = "Use TOR for zeronet traffic where possible.";
+    };
+
+    torAlways = mkOption {
+      type = types.bool;
+      default = false;
       description = "Use TOR for all zeronet traffic.";
     };
 
@@ -60,9 +68,13 @@ in with lib; {
     services.tor = mkIf cfg.tor {
       enable = true;
       controlPort = 9051;
-      extraConfig = "CookieAuthentication 1";
+      extraConfig = ''
+        CacheDirectoryGroupReadable 1
+        CookieAuthentication 1
+        CookieAuthFileGroupReadable 1
+      '';
     };
-    
+
     systemd.services.zeronet = {
       description = "zeronet";
       after = [ "network.target" (optionalString cfg.tor "tor.service") ];
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 62308428f326..9f0261038d5b 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -3,78 +3,112 @@
 with lib;
 
 let
-
   cfg = config.services.sks;
-
   sksPkg = cfg.package;
 
-in
-
-{
+in {
+  meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
 
   options = {
 
     services.sks = {
 
-      enable = mkEnableOption "sks";
+      enable = mkEnableOption ''
+        SKS (synchronizing key server for OpenPGP) and start the database
+        server. You need to create "''${dataDir}/dump/*.gpg" for the initial
+        import'';
 
       package = mkOption {
         default = pkgs.sks;
         defaultText = "pkgs.sks";
         type = types.package;
-        description = "
-          Which sks derivation to use.
-        ";
+        description = "Which SKS derivation to use.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/sks";
+        example = "/var/lib/sks";
+        # TODO: The default might change to "/var/lib/sks" as this is more
+        # common. There's also https://github.com/NixOS/nixpkgs/issues/26256
+        # and "/var/db" is not FHS compliant (seems to come from BSD).
+        description = ''
+          Data directory (-basedir) for SKS, where the database and all
+          configuration files are located (e.g. KDB, PTree, membership and
+          sksconf).
+        '';
       };
 
       hkpAddress = mkOption {
         default = [ "127.0.0.1" "::1" ];
         type = types.listOf types.str;
-        description = "
-          Wich ip addresses the sks-keyserver is listening on.
-        ";
+        description = ''
+          Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
+          requests.
+        '';
       };
 
       hkpPort = mkOption {
         default = 11371;
-        type = types.int;
-        description = "
-          Which port the sks-keyserver is listening on.
-        ";
+        type = types.ints.u16;
+        description = "HKP port to listen on.";
+      };
+
+      webroot = mkOption {
+        type = types.nullOr types.path;
+        default = "${sksPkg.webSamples}/OpenPKG";
+        defaultText = "\${pkgs.sks.webSamples}/OpenPKG";
+        description = ''
+          Source directory (will be symlinked, if not null) for the files the
+          built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
+          provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
+          The index file can be named index.html, index.htm, index.xhtm, or
+          index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg,
+          .png, or .gif are supported. Subdirectories and filenames with
+          anything other than alphanumeric characters and the '.' character
+          will be ignored.
+        '';
       };
     };
   };
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ sksPkg ];
-    
-    users.users.sks = {
-      createHome = true;
-      home = "/var/db/sks";
-      isSystemUser = true;
-      shell = "${pkgs.coreutils}/bin/true";
+    users = {
+      users.sks = {
+        isSystemUser = true;
+        description = "SKS user";
+        home = cfg.dataDir;
+        createHome = true;
+        group = "sks";
+        useDefaultShell = true;
+        packages = [ sksPkg pkgs.db ];
+      };
+      groups.sks = { };
     };
 
     systemd.services = let
       hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
       hkpPort = builtins.toString cfg.hkpPort;
-      home = config.users.users.sks.home;
-      user = config.users.users.sks.name;
     in {
-      sks-keyserver = {
+      "sks-db" = {
+        description = "SKS database server";
+        after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
         preStart = ''
-          mkdir -p ${home}/dump
-          ${pkgs.sks}/bin/sks build ${home}/dump/*.gpg -n 10 -cache 100 || true #*/
-          ${pkgs.sks}/bin/sks cleandb || true
-          ${pkgs.sks}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+          ${lib.optionalString (cfg.webroot != null)
+            "ln -sfT \"${cfg.webroot}\" web"}
+          mkdir -p dump
+          ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
+          ${sksPkg}/bin/sks cleandb || true
+          ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
         '';
         serviceConfig = {
-          WorkingDirectory = home;
-          User = user;
+          WorkingDirectory = "~";
+          User = "sks";
+          Group = "sks";
           Restart = "always";
-          ExecStart = "${pkgs.sks}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
+          ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
         };
       };
     };
diff --git a/nixos/modules/services/system/kerberos.nix b/nixos/modules/services/system/kerberos.nix
index d151385d2f9b..e2c45ed64ac0 100644
--- a/nixos/modules/services/system/kerberos.nix
+++ b/nixos/modules/services/system/kerberos.nix
@@ -42,7 +42,7 @@ in
         protocol = "tcp";
         user = "root";
         server = "${pkgs.tcp_wrappers}/bin/tcpd";
-        serverArgs = "${pkgs.heimdalFull}/bin/kadmind";
+        serverArgs = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
       };
 
     systemd.services.kdc = {
@@ -51,13 +51,13 @@ in
       preStart = ''
         mkdir -m 0755 -p ${stateDir}
       '';
-      script = "${heimdalFull}/bin/kdc";
+      script = "${heimdalFull}/libexec/heimdal/kdc";
     };
 
     systemd.services.kpasswdd = {
       description = "Kerberos Password Changing daemon";
       wantedBy = [ "multi-user.target" ];
-      script = "${heimdalFull}/bin/kpasswdd";
+      script = "${heimdalFull}/libexec/heimdal/kpasswdd";
     };
   };
 
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 6fa3ec3b9255..04e380b61530 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -66,7 +66,7 @@ in
       '';
     }];
 
-    security.wrappers = (import (builtins.toPath "${e.enlightenment}/e-wrappers.nix")).security.wrappers;
+    security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers;
 
     environment.etc = singleton
       { source = xcfg.xkbDir;
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index faf5214130db..eb86f7b53bb6 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -110,6 +110,7 @@ in {
     services.gnome3.gnome-terminal-server.enable = mkDefault true;
     services.gnome3.gnome-user-share.enable = mkDefault true;
     services.gnome3.gvfs.enable = true;
+    services.gnome3.rygel.enable = mkDefault true;
     services.gnome3.seahorse.enable = mkDefault true;
     services.gnome3.sushi.enable = mkDefault true;
     services.gnome3.tracker.enable = mkDefault true;
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index b3fe6caf62dc..c3e469e4b8a1 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -419,7 +419,7 @@ while (my $f = <$listActiveUsers>) {
     my ($uid, $name) = ($+{uid}, $+{user});
     print STDERR "reloading user units for $name...\n";
 
-    system("su", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
+    system("su", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
 }
 
 close $listActiveUsers;
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index fff88e2c2bf3..9797ef641e4f 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -115,6 +115,7 @@ let
 
       inherit (pkgs) utillinux coreutils;
       systemd = config.systemd.package;
+      inherit (pkgs.stdenv) shell;
 
       inherit children;
       kernelParams = config.boot.kernelParams;
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 4bacf0f126a4..63a6f7fbe099 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -208,7 +208,6 @@ let
       "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck"
       "MTUBytes"
     ])
-    (assertHasField "Gateway")
   ];
 
   checkDhcp = checkUnitConfig "DHCP" [
@@ -249,13 +248,14 @@ let
   # .network files have a [Link] section with different options than in .netlink files
   checkNetworkLink = checkUnitConfig "Link" [
     (assertOnlyFields [
-      "MACAddress" "MTUBytes" "ARP" "Unmanaged" "RequiredForOnline"
+      "MACAddress" "MTUBytes" "ARP" "Multicast" "Unmanaged" "RequiredForOnline"
     ])
     (assertMacAddress "MACAddress")
     (assertByteFormat "MTUBytes")
     (assertValueOneOf "ARP" boolValues)
+    (assertValueOneOf "Multicast" boolValues)
     (assertValueOneOf "Unmanaged" boolValues)
-    (assertValueOneOf "RquiredForOnline" boolValues)
+    (assertValueOneOf "RequiredForOnline" boolValues)
   ];
 
 
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 20a740ce1f0c..815523093dde 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -341,7 +341,7 @@ in
         You should try to make this ID unique among your machines. You can
         generate a random 32-bit ID using the following commands:
 
-        <literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal>
+        <literal>head -c 8 /etc/machine-id</literal>
 
         (this derives it from the machine-id that systemd generates) or
 
diff --git a/nixos/release.nix b/nixos/release.nix
index 17f51d977c98..0fd8d694641f 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -399,7 +399,7 @@ in rec {
   tests.slurm = callTest tests/slurm.nix {};
   tests.smokeping = callTest tests/smokeping.nix {};
   tests.snapper = callTest tests/snapper.nix {};
-  tests.statsd = callTest tests/statsd.nix {};
+  #tests.statsd = callTest tests/statsd.nix {}; # statsd is broken: #45946
   tests.strongswan-swanctl = callTest tests/strongswan-swanctl.nix {};
   tests.sudo = callTest tests/sudo.nix {};
   tests.systemd = callTest tests/systemd.nix {};
diff --git a/nixos/tests/novacomd.nix b/nixos/tests/novacomd.nix
index 2b56aee0a2e7..4eb60c0feb5c 100644
--- a/nixos/tests/novacomd.nix
+++ b/nixos/tests/novacomd.nix
@@ -9,12 +9,16 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   testScript = ''
-    startAll;
+    $machine->waitForUnit("multi-user.target");
 
+    # multi-user.target wants novacomd.service, but let's make sure
     $machine->waitForUnit("novacomd.service");
 
     # Check status and try connecting with novacom
     $machine->succeed("systemctl status novacomd.service >&2");
+    # to prevent non-deterministic failure,
+    # make sure the daemon is really listening
+    $machine->waitForOpenPort(6968);
     $machine->succeed("novacom -l");
 
     # Stop the daemon, double-check novacom fails if daemon isn't working
@@ -23,6 +27,8 @@ import ./make-test.nix ({ pkgs, ...} : {
 
     # And back again for good measure
     $machine->startJob("novacomd");
+    # make sure the daemon is really listening
+    $machine->waitForOpenPort(6968);
     $machine->succeed("novacom -l");
   '';
 })
diff --git a/nixos/tests/opensmtpd.nix b/nixos/tests/opensmtpd.nix
index 5079779f35b4..4c0cbca21010 100644
--- a/nixos/tests/opensmtpd.nix
+++ b/nixos/tests/opensmtpd.nix
@@ -102,11 +102,17 @@ import ./make-test.nix {
   testScript = ''
     startAll;
 
-    $client->waitForUnit("network.target");
+    $client->waitForUnit("network-online.target");
     $smtp1->waitForUnit('opensmtpd');
     $smtp2->waitForUnit('opensmtpd');
     $smtp2->waitForUnit('dovecot2');
 
+    # To prevent sporadic failures during daemon startup, make sure
+    # services are listening on their ports before sending requests
+    $smtp1->waitForOpenPort(25);
+    $smtp2->waitForOpenPort(25);
+    $smtp2->waitForOpenPort(143);
+
     $client->succeed('send-a-test-mail');
     $smtp1->waitUntilFails('smtpctl show queue | egrep .');
     $smtp2->waitUntilFails('smtpctl show queue | egrep .');