summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-unstable.xml15
-rw-r--r--nixos/modules/misc/ids.nix4
-rw-r--r--nixos/modules/module-list.nix2
-rw-r--r--nixos/modules/programs/ibus.nix2
-rw-r--r--nixos/modules/programs/zsh/zsh.nix22
-rw-r--r--nixos/modules/security/pam.nix26
-rw-r--r--nixos/modules/services/misc/bepasty.nix151
-rw-r--r--nixos/modules/services/monitoring/longview.nix118
-rw-r--r--nixos/modules/services/networking/chrony.nix69
-rw-r--r--nixos/modules/services/networking/i2pd.nix172
-rw-r--r--nixos/modules/services/torrent/transmission.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/kde5.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix10
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix2
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix2
-rw-r--r--nixos/release.nix1
-rw-r--r--nixos/tests/sddm.nix28
18 files changed, 570 insertions, 68 deletions
diff --git a/nixos/doc/manual/release-notes/rl-unstable.xml b/nixos/doc/manual/release-notes/rl-unstable.xml
index 65aa36586cb0..97ac03a770f6 100644
--- a/nixos/doc/manual/release-notes/rl-unstable.xml
+++ b/nixos/doc/manual/release-notes/rl-unstable.xml
@@ -74,6 +74,21 @@ nginx.override {
     </para>
   </listitem>
 
+  <listitem>
+    <para><command>s3sync</command> is removed, as it hasn't been
+    developed by upstream for 4 years and only runs with ruby 1.8.
+    For an actively-developer alternative look at
+    <command>tarsnap</command> and others.
+    </para>
+  </listitem>
+
+  <listitem>
+    <para><command>ruby_1_8</command> has been removed as it's not
+    supported from upstream anymore and probably contains security
+    issues.
+    </para>
+  </listitem>
+
 </itemizedlist>
 
 </section>
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index b1130c2b124b..2b40120641a0 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -236,6 +236,7 @@
       xtreemfs = 212;
       calibre-server = 213;
       heapster = 214;
+      bepasty = 215;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -304,7 +305,7 @@
       nslcd = 58;
       scanner = 59;
       nginx = 60;
-      #chrony = 61; # unused
+      chrony = 61;
       systemd-journal = 62;
       smtpd = 63;
       smtpq = 64;
@@ -449,6 +450,7 @@
       #kibana = 211;
       xtreemfs = 212;
       calibre-server = 213;
+      bepasty = 215;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index ecdf2264d698..a8cf38f1c8fe 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -190,6 +190,7 @@
   ./services/mail/spamassassin.nix
   ./services/misc/apache-kafka.nix
   ./services/misc/autofs.nix
+  ./services/misc/bepasty.nix
   ./services/misc/canto-daemon.nix
   ./services/misc/calibre-server.nix
   ./services/misc/cpuminer-cryptonight.nix
@@ -240,6 +241,7 @@
   ./services/monitoring/grafana.nix
   ./services/monitoring/graphite.nix
   ./services/monitoring/heapster.nix
+  ./services/monitoring/longview.nix
   ./services/monitoring/monit.nix
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
diff --git a/nixos/modules/programs/ibus.nix b/nixos/modules/programs/ibus.nix
index b8702a743d8a..a42753a292b2 100644
--- a/nixos/modules/programs/ibus.nix
+++ b/nixos/modules/programs/ibus.nix
@@ -27,7 +27,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.ibus ];
+    environment.systemPackages = [ pkgs.ibus pkgs.gnome3.dconf ];
 
     gtkPlugins = [ pkgs.ibus ];
     qtPlugins = [ pkgs.ibus-qt ];
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index 74dd6af0bdde..dae7e446b4cf 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -25,7 +25,7 @@ in
       enable = mkOption {
         default = false;
         description = ''
-          Whenever to configure Zsh as an interactive shell.
+          Whether to configure zsh as an interactive shell.
         '';
         type = types.bool;
       };
@@ -73,6 +73,14 @@ in
         type = types.lines;
       };
 
+      enableCompletion = mkOption {
+        default = true;
+        description = ''
+          Enable zsh completion for all interactive zsh shells.
+        '';
+        type = types.bool;
+      };
+
     };
 
   };
@@ -101,6 +109,13 @@ in
         export HISTFILE=$HOME/.zsh_history
 
         setopt HIST_IGNORE_DUPS SHARE_HISTORY HIST_FCNTL_LOCK
+
+        # 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)
+        done
+
+        ${if cfg.enableCompletion then "autoload -U compinit && compinit" else ""}
       '';
 
     };
@@ -161,7 +176,10 @@ in
 
     environment.etc."zinputrc".source = ./zinputrc;
 
-    environment.systemPackages = [ pkgs.zsh ];
+    environment.systemPackages = [ pkgs.zsh ]
+      ++ optional cfg.enableCompletion pkgs.nix-zsh-completions;
+
+    environment.pathsToLink = optional cfg.enableCompletion "/share/zsh";
 
     #users.defaultUserShell = mkDefault "/run/current-system/sw/bin/zsh";
 
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 88760574cbc6..2ee8a803d2fe 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -218,7 +218,7 @@ let
       # Samba stuff to the Samba module.  This requires that the PAM
       # module provides the right hooks.
       text = mkDefault
-        ''
+        (''
           # Account management.
           account sufficient pam_unix.so
           ${optionalString config.users.ldap.enable
@@ -241,12 +241,22 @@ let
               "auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"}
           ${optionalString cfg.usbAuth
               "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"}
+        '' +
+          # Modules in this block require having the password set in PAM_AUTHTOK.
+          # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
+          # after it succeeds. Certain modules need to run after pam_unix
+          # prompts the user for password so we run it once with 'required' at an
+          # earlier point and it will run again with 'sufficient' further down.
+          # We use try_first_pass the second time to avoid prompting password twice
+          (optionalString (cfg.unixAuth && (config.security.pam.enableEcryptfs || cfg.pamMount)) ''
+              auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth
+              ${optionalString config.security.pam.enableEcryptfs
+                "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
+              ${optionalString cfg.pamMount
+                "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
+            '') + ''
           ${optionalString cfg.unixAuth
-              "auth ${if (config.security.pam.enableEcryptfs || cfg.pamMount) then "required" else "sufficient"} pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth"}
-          ${optionalString cfg.pamMount
-              "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
-          ${optionalString config.security.pam.enableEcryptfs
-              "auth required ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
+              "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
           ${optionalString cfg.otpwAuth
               "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
           ${optionalString cfg.oathAuth
@@ -258,7 +268,7 @@ let
             auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
             auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
           ''}
-          ${optionalString (!(config.security.pam.enableEcryptfs || cfg.pamMount)) "auth required pam_deny.so"}
+          auth required pam_deny.so
 
           # Password management.
           password requisite pam_unix.so nullok sha512
@@ -306,7 +316,7 @@ let
               "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
           ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable)
               "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"}
-        '';
+        '');
     };
 
   };
diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix
new file mode 100644
index 000000000000..12671cb1b6cd
--- /dev/null
+++ b/nixos/modules/services/misc/bepasty.nix
@@ -0,0 +1,151 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  gunicorn = pkgs.pythonPackages.gunicorn;
+  bepasty = pkgs.pythonPackages.bepasty-server;
+  gevent = pkgs.pythonPackages.gevent;
+  python = pkgs.pythonPackages.python;
+  cfg = config.services.bepasty;
+  user = "bepasty";
+  group = "bepasty";
+  default_home = "/var/lib/bepasty";
+in
+{
+  options.services.bepasty = {
+    enable = mkEnableOption "Bepasty servers";
+
+    servers = mkOption {
+      default = {};
+      description = ''
+        configure a number of bepasty servers which will be started with
+        gunicorn.
+        '';
+      type = with types ; attrsOf (submodule ({
+
+        options = {
+
+          bind = mkOption {
+            type = types.str;
+            description = ''
+              Bind address to be used for this server.
+              '';
+            example = "0.0.0.0:8000";
+            default = "127.0.0.1:8000";
+          };
+
+
+          dataDir = mkOption {
+            type = types.str;
+            description = ''
+              Path to the directory where the pastes will be saved to
+              '';
+            default = default_home+"/data";
+          };
+
+          defaultPermissions = mkOption {
+            type = types.str;
+            description = ''
+              default permissions for all unauthenticated accesses.
+              '';
+            example = "read,create,delete";
+            default = "read";
+          };
+
+          extraConfig = mkOption {
+            type = types.str;
+            description = ''
+              Extra configuration for bepasty server to be appended on the
+              configuration.
+              see https://bepasty-server.readthedocs.org/en/latest/quickstart.html#configuring-bepasty
+              for all options.
+              '';
+            default = "";
+            example = ''
+              PERMISSIONS = {
+                'myadminsecret': 'admin,list,create,read,delete',
+              }
+              MAX_ALLOWED_FILE_SIZE = 5 * 1000 * 1000
+              '';
+          };
+
+          secretKey = mkOption {
+            type = types.str;
+            description = ''
+              server secret for safe session cookies, must be set.
+              '';
+            default = "";
+          };
+
+          workDir = mkOption {
+            type = types.str;
+            description = ''
+              Path to the working directory (used for config and pidfile).
+              Defaults to the users home directory.
+              '';
+            default = default_home;
+          };
+
+        };
+      }));
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ bepasty ];
+
+    # creates gunicorn systemd service for each configured server
+    systemd.services = mapAttrs' (name: server:
+      nameValuePair ("bepasty-server-${name}-gunicorn")
+        ({
+          description = "Bepasty Server ${name}";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          restartIfChanged = true;
+
+          environment = {
+            BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf";
+            PYTHONPATH= "${bepasty}/lib/${python.libPrefix}/site-packages:${gevent}/lib/${python.libPrefix}/site-packages";
+          };
+
+          serviceConfig = {
+            Type = "simple";
+            PrivateTmp = true;
+            ExecStartPre = assert server.secretKey != ""; pkgs.writeScript "bepasty-server.${name}-init" ''
+              #!/bin/sh
+              mkdir -p "${server.workDir}"
+              mkdir -p "${server.dataDir}"
+              chown ${user}:${group} "${server.workDir}" "${server.dataDir}"
+              cat > ${server.workDir}/bepasty-${name}.conf <<EOF
+              SITENAME="${name}"
+              STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}"
+              SECRET_KEY="${server.secretKey}"
+              DEFAULT_PERMISSIONS="${server.defaultPermissions}"
+              ${server.extraConfig}
+              EOF
+            '';
+            ExecStart = ''${gunicorn}/bin/gunicorn bepasty.wsgi --name ${name} \
+              -u ${user} \
+              -g ${group} \
+              --workers 3 --log-level=info \
+              --bind=${server.bind} \
+              --pid ${server.workDir}/gunicorn-${name}.pid \
+              -k gevent
+            '';
+          };
+        })
+    ) cfg.servers;
+
+    users.extraUsers = [{
+      uid = config.ids.uids.bepasty;
+      name = user;
+      group = group;
+      home = default_home;
+    }];
+
+    users.extraGroups = [{
+      name = group;
+      gid = config.ids.gids.bepasty;
+    }];
+  };
+}
diff --git a/nixos/modules/services/monitoring/longview.nix b/nixos/modules/services/monitoring/longview.nix
new file mode 100644
index 000000000000..770d56e60efb
--- /dev/null
+++ b/nixos/modules/services/monitoring/longview.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.longview;
+
+  pidFile = "/run/longview.pid";
+
+  apacheConf = optionalString (cfg.apacheStatusUrl != "") ''
+    location ${cfg.apacheStatusUrl}?auto
+  '';
+  mysqlConf = optionalString (cfg.mysqlUser != "") ''
+    username ${cfg.mysqlUser}
+    password ${cfg.mysqlPassword}
+  '';
+  nginxConf = optionalString (cfg.nginxStatusUrl != "") ''
+    location ${cfg.nginxStatusUrl}
+  '';
+
+in
+
+{
+  options = {
+
+    services.longview = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, system metrics will be sent to Linode LongView.
+        '';
+      };
+
+      apiKey = mkOption {
+        type = types.str;
+        example = "01234567-89AB-CDEF-0123456789ABCDEF";
+        description = ''
+          Longview API key. To get this, look in Longview settings which
+          are found at https://manager.linode.com/longview/.
+        '';
+      };
+
+      apacheStatusUrl = mkOption {
+        type = types.str;
+        default = "";
+        example = "http://127.0.0.1/server-status";
+        description = ''
+          The Apache status page URL. If provided, Longview will
+          gather statistics from this location. This requires Apache
+          mod_status to be loaded and enabled.
+        '';
+      };
+
+      nginxStatusUrl = mkOption {
+        type = types.str;
+        default = "";
+        example = "http://127.0.0.1/nginx_status";
+        description = ''
+          The Nginx status page URL. Longview will gather statistics
+          from this URL. This requires the Nginx stub_status module to
+          be enabled and configured at the given location.
+        '';
+      };
+
+      mysqlUser = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          The user for connecting to the MySQL database. If provided,
+          Longview will connect to MySQL and collect statistics about
+          queries, etc. This user does not need to have been granted
+          any extra privileges.
+        '';
+      };
+
+      mysqlPassword = mkOption {
+        type = types.str;
+        description = ''
+          The password corresponding to mysqlUser.  Warning: this is
+          stored in cleartext in the Nix store!
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.longview =
+      { description = "Longview Metrics Collection";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.Type = "forking";
+        serviceConfig.ExecStop = "-${pkgs.coreutils}/bin/kill -TERM $MAINPID";
+        serviceConfig.ExecReload = "-${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        serviceConfig.PIDFile = pidFile;
+        serviceConfig.ExecStart = "${pkgs.longview}/bin/longview";
+      };
+
+    environment.etc."linode/longview.key" = {
+      mode = "0400";
+      text = cfg.apiKey;
+    };
+    environment.etc."linode/longview.d/Apache.conf" = {
+      mode = "0400";
+      text = apacheConf;
+    };
+    environment.etc."linode/longview.d/MySQL.conf" = {
+      mode = "0400";
+      text = mysqlConf;
+    };
+    environment.etc."linode/longview.d/Nginx.conf" = {
+      mode = "0400";
+      text = nginxConf;
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/chrony.nix b/nixos/modules/services/networking/chrony.nix
index fe062b30e4b7..1cd678e7c621 100644
--- a/nixos/modules/services/networking/chrony.nix
+++ b/nixos/modules/services/networking/chrony.nix
@@ -8,26 +8,10 @@ let
 
   stateDir = "/var/lib/chrony";
 
-  chronyUser = "chrony";
+  keyFile = "/etc/chrony.keys";
 
   cfg = config.services.chrony;
 
-  configFile = pkgs.writeText "chrony.conf" ''
-    ${toString (map (server: "server " + server + "\n") cfg.servers)}
-
-    ${optionalString cfg.initstepslew.enabled ''
-      initstepslew ${toString cfg.initstepslew.threshold} ${toString (map (server: server + " ") cfg.initstepslew.servers)}
-    ''}
-
-    driftfile ${stateDir}/chrony.drift
-
-    ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
-
-    ${cfg.extraConfig}
-  '';
-
-  chronyFlags = "-m -f ${configFile} -u ${chronyUser}";
-
 in
 
 {
@@ -47,12 +31,7 @@ in
       };
 
       servers = mkOption {
-        default = [
-          "0.nixos.pool.ntp.org"
-          "1.nixos.pool.ntp.org"
-          "2.nixos.pool.ntp.org"
-          "3.nixos.pool.ntp.org"
-        ];
+        default = config.services.ntp.servers;
         description = ''
           The set of NTP servers from which to synchronise.
         '';
@@ -90,28 +69,60 @@ in
     # Make chronyc available in the system path
     environment.systemPackages = [ pkgs.chrony ];
 
+    environment.etc."chrony.conf".text =
+      ''
+        ${concatMapStringsSep "\n" (server: "server " + server) cfg.servers}
+
+        ${optionalString
+          cfg.initstepslew.enabled
+          "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.initstepslew.servers}"
+        }
+
+        driftfile ${stateDir}/chrony.drift
+
+        keyfile ${keyFile}
+        generatecommandkey
+
+        ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
+
+        ${cfg.extraConfig}
+      '';
+
+    users.extraGroups = singleton
+      { name = "chrony";
+        gid = config.ids.gids.chrony;
+      };
+
     users.extraUsers = singleton
-      { name = chronyUser;
+      { name = "chrony";
         uid = config.ids.uids.chrony;
+        group = "chrony";
         description = "chrony daemon user";
         home = stateDir;
       };
 
-    jobs.chronyd =
-      { description = "chrony daemon";
+    systemd.services.ntpd.enable = false;
+
+    systemd.services.chronyd =
+      { description = "chrony NTP daemon";
 
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
+        conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
 
-        path = [ chrony ];
+        path = [ pkgs.chrony ];
 
         preStart =
           ''
             mkdir -m 0755 -p ${stateDir}
-            chown ${chronyUser} ${stateDir}
+            touch ${keyFile}
+            chmod 0640 ${keyFile}
+            chown chrony:chrony ${stateDir} ${keyFile}
           '';
 
-        exec = "chronyd -n ${chronyFlags}";
+        serviceConfig =
+          { ExecStart = "${pkgs.chrony}/bin/chronyd -n -m -u chrony";
+          };
       };
 
   };
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 7ee78f01d497..af9424ecfeaf 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -10,23 +10,59 @@ let
 
   extip = "EXTIP=\$(${pkgs.curl}/bin/curl -sf \"http://jsonip.com\" | ${pkgs.gawk}/bin/awk -F'\"' '{print $4}')";
 
-  i2pSh = pkgs.writeScriptBin "i2pd" ''
+  toOneZero = b: if b then "1" else "0";
+
+  i2pdConf = pkgs.writeText "i2pd.conf" ''
+      v6 = ${toOneZero cfg.enableIPv6}
+      unreachable = ${toOneZero cfg.unreachable}
+      floodfill = ${toOneZero cfg.floodfill}
+      ${if isNull cfg.port then "" else "port = ${toString cfg.port}"}
+      httpproxyport = ${toString cfg.proxy.httpPort}
+      socksproxyport = ${toString cfg.proxy.socksPort}
+      ircaddress = ${cfg.irc.host}
+      ircport = ${toString cfg.irc.port}
+      ircdest = ${cfg.irc.dest}
+      irckeys = ${cfg.irc.keyFile}
+      eepport = ${toString cfg.eep.port}
+      ${if isNull cfg.sam.port then "" else "--samport=${toString cfg.sam.port}"}
+      eephost = ${cfg.eep.host}
+      eepkeys = ${cfg.eep.keyFile}
+  '';
+
+  i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" ''
+  ${flip concatMapStrings
+    (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
+    (tun: let portStr = toString tun.port; in ''
+  [${tun.name}]
+  type = client
+  destination = ${tun.destination}
+  keys = ${tun.keys}
+  address = ${tun.address}
+  port = ${toString tun.port}
+  '')
+  }
+  ${flip concatMapStrings
+    (collect (tun: tun ? port && tun ? host) cfg.outTunnels)
+    (tun: let portStr = toString tun.port; in ''
+  [${tun.name}]
+  type = server
+  destination = ${tun.destination}
+  keys = ${tun.keys}
+  host = ${tun.address}
+  port = ${tun.port}
+  inport = ${tun.inPort}
+  accesslist = ${concatStringSep "," tun.accessList}
+  '')
+  }
+  '';
+
+  i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
     ${if isNull cfg.extIp then extip else ""}
-    ${pkgs.i2pd}/bin/i2p --log=1 --daemon=0 --service=0 \
-      --v6=${if cfg.enableIPv6 then "1" else "0"} \
-      --unreachable=${if cfg.unreachable then "1" else "0"} \
+    ${pkgs.i2pd}/bin/i2pd --log=1 --daemon=0 --service=0 \
       --host=${if isNull cfg.extIp then "$EXTIP" else cfg.extIp} \
-      ${if isNull cfg.port then "" else "--port=${toString cfg.port}"} \
-      --httpproxyport=${toString cfg.proxy.httpPort} \
-      --socksproxyport=${toString cfg.proxy.socksPort} \
-      --ircport=${toString cfg.irc.port} \
-      --ircdest=${cfg.irc.dest} \
-      --irckeys=${cfg.irc.keyFile} \
-      --eepport=${toString cfg.eep.port} \
-      ${if isNull cfg.sam.port then "" else "--samport=${toString cfg.sam.port}"} \
-      --eephost=${cfg.eep.host} \
-      --eepkeys=${cfg.eep.keyFile}
+      --conf=${i2pdConf} \
+      --tunnelscfg=${i2pdTunnelConf}
   '';
 
 in
@@ -63,11 +99,19 @@ in
         '';
       };
 
+      floodfill = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If the router is declared to be unreachable and needs introduction nodes.
+        '';
+      };
+
       port = mkOption {
         type = with types; nullOr int;
         default = null;
         description = ''
-          I2P listen port. If no one is given the router will pick between 9111 and 30777.
+	        I2P listen port. If no one is given the router will pick between 9111 and 30777.
         '';
       };
 
@@ -107,6 +151,13 @@ in
       };
 
       irc = {
+        host = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = ''
+            Address to forward incoming traffic to. 127.0.0.1 by default.
+          '';
+        };
         dest = mkOption {
           type = types.str;
           default = "irc.postman.i2p";
@@ -163,6 +214,94 @@ in
           '';
         };
       };
+
+      outTunnels = mkOption {
+        default = {};
+	      type = with types; loaOf optionSet;
+	      description = ''
+	      '';
+	      options = [ ({ name, config, ... }: {
+
+	        options = {
+	          name = mkOption {
+	            type = types.str;
+	            description = "The name of the tunnel.";
+	          };
+	          destination = mkOption {
+	            type = types.str;
+	            description = "Remote endpoint, I2P hostname or b32.i2p address.";
+	          };
+	          keys = mkOption {
+	            type = types.str;
+	            default = name + "-keys.dat";
+	            description = "Keyset used for tunnel identity.";
+	          };
+	          address = mkOption {
+	            type = types.str;
+	            default = "127.0.0.1";
+	            description = "Local bind address for tunnel.";
+	          };
+	          port = mkOption {
+	            type = types.int;
+	            default = 0;
+	            description = "Local tunnel listen port.";
+	          };
+	        };
+
+	        config = {
+	          name = mkDefault name;
+	        };
+
+	      }) ];
+      };
+
+      inTunnels = mkOption {
+        default = {};
+	      type = with types; loaOf optionSet;
+	      description = ''
+	      '';
+	      options = [ ({ name, config, ... }: {
+
+	        options = {
+
+	          name = mkOption {
+	            type = types.str;
+	            description = "The name of the tunnel.";
+	          };
+	          keys = mkOption {
+	            type = types.path;
+	            default = name + "-keys.dat";
+	            description = "Keyset used for tunnel identity.";
+	          };
+	          address = mkOption {
+	            type = types.str;
+	            default = "127.0.0.1";
+	            description = "Local service IP address.";
+	          };
+	          port = mkOption {
+	            type = types.int;
+	            default = 0;
+	            description = "Local tunnel listen port.";
+	          };
+	          inPort = mkOption {
+	            type = types.int;
+	            default = 0;
+	            description = "I2P service port. Default to the tunnel's listen port.";
+	          };
+	          accessList = mkOption {
+	            type = with types; listOf str;
+	            default = [];
+	            description = "I2P nodes that are allowed to connect to this service.";
+	          };
+
+	        };
+
+	        config = {
+	          name = mkDefault name;
+	        };
+
+	      }) ];
+      };
     };
   };
 
@@ -190,9 +329,8 @@ in
         User = "i2pd";
         WorkingDirectory = homeDir;
         Restart = "on-abort";
-        ExecStart = "${i2pSh}/bin/i2pd";
+        ExecStart = "${i2pdSh}/bin/i2pd";
       };
     };
   };
 }
-#
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index cf548bc696ca..1c9149224049 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -9,7 +9,7 @@ let
   homeDir = "/var/lib/transmission";
   downloadDir = "${homeDir}/Downloads";
   incompleteDir = "${homeDir}/.incomplete";
-  
+
   settingsDir = "${homeDir}/.config/transmission-daemon";
   settingsFile = pkgs.writeText "settings.json" (builtins.toJSON fullSettings);
 
@@ -21,7 +21,7 @@ let
     else toString ''"${x}"'';
 
   # for users in group "transmission" to have access to torrents
-  fullSettings = cfg.settings // { umask = 2; };
+  fullSettings = { download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings // { umask = 2; };
 in
 {
   options = {
@@ -35,7 +35,7 @@ in
           Transmission daemon can be controlled via the RPC interface using
           transmission-remote or the WebUI (http://localhost:9091/ by default).
 
-          Torrents are downloaded to ${homeDir}/Downloads/ by default and are
+          Torrents are downloaded to ${downloadDir} by default and are
           accessible to users in the "transmission" group.
         '';
       };
@@ -83,7 +83,7 @@ in
       # 1) Only the "transmission" user and group have access to torrents.
       # 2) Optionally update/force specific fields into the configuration file.
       serviceConfig.ExecStartPre = ''
-          ${pkgs.stdenv.shell} -c "chmod 770 ${homeDir} && mkdir -p ${settingsDir} ${downloadDir} ${incompleteDir} && rm -f ${settingsDir}/settings.json && cp -f ${settingsFile} ${settingsDir}/settings.json"
+          ${pkgs.stdenv.shell} -c "mkdir -p ${homeDir} ${settingsDir} ${fullSettings.download-dir} ${fullSettings.incomplete-dir} && chmod 770 ${homeDir} ${settingsDir} ${fullSettings.download-dir} ${fullSettings.incomplete-dir} && rm -f ${settingsDir}/settings.json && cp -f ${settingsFile} ${settingsDir}/settings.json"
       '';
       serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port}";
       serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/x11/desktop-managers/kde5.nix b/nixos/modules/services/x11/desktop-managers/kde5.nix
index 6fdd5b4fa36d..dc6aa137cbd3 100644
--- a/nixos/modules/services/x11/desktop-managers/kde5.nix
+++ b/nixos/modules/services/x11/desktop-managers/kde5.nix
@@ -108,7 +108,7 @@ in
         kdeApps.okular
         kdeApps.print-manager
 
-        kdeApps.oxygen-icons
+        (kdeApps.oxygen-icons or kf5.oxygen-icons5)
         pkgs.hicolor_icon_theme
 
         plasma5.kde-gtk-config
@@ -155,7 +155,7 @@ in
         GST_PLUGIN_SYSTEM_PATH_1_0 = [ "/lib/gstreamer-1.0" ];
       };
 
-    fonts.fonts = [ plasma5.oxygen-fonts ];
+    fonts.fonts = [ (plasma5.oxygen-fonts or pkgs.noto-fonts) ];
 
     programs.ssh.askPassword = "${plasma5.ksshaskpass}/bin/ksshaskpass";
 
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 88eefa13de35..33b6dd32c193 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -18,6 +18,14 @@ in
       description = "Enable the Xfce desktop environment.";
     };
 
+    services.xserver.desktopManager.xfce.thunarPlugins = mkOption {
+      default = [];
+      type = types.listOf types.package;
+      example = literalExample "[ pkgs.xfce.thunar-archive-plugin ]";
+      description = ''
+        A list of plugin that should be installed with Thunar.
+      '';
+    };
   };
 
 
@@ -49,7 +57,7 @@ in
         pkgs.xfce.mousepad
         pkgs.xfce.ristretto
         pkgs.xfce.terminal
-        pkgs.xfce.thunar
+       (pkgs.xfce.thunar.override { thunarPlugins = cfg.thunarPlugins; })
         pkgs.xfce.xfce4icontheme
         pkgs.xfce.xfce4panel
         pkgs.xfce.xfce4session
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 58eb6f050131..52847d2f8d2c 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -162,7 +162,7 @@ in
 
       gdm.text = ''
         auth     requisite      pam_nologin.so
-        auth     required       pam_env.so
+        auth     required       pam_env.so envfile=${config.system.build.pamEnvironment}
 
         auth     required       pam_succeed_if.so uid >= 1000 quiet
         auth     optional       ${gnome3.gnome_keyring}/lib/security/pam_gnome_keyring.so
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 11e21c9d917f..8452b1ec33cd 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -150,7 +150,7 @@ in
       allowNullPassword = true;
       startSession = true;
       text = ''
-        auth     required pam_env.so
+        auth     required pam_env.so envfile=${config.system.build.pamEnvironment}
         auth     required pam_permit.so
 
         account  required pam_permit.so
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 331531cee151..457b86e95ab5 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -30,7 +30,7 @@ let
       label = mkOption {
         default = null;
         example = "rootfs";
-        type = types.uniq (types.nullOr types.str);
+        type = types.nullOr types.str;
         description = "Label of the unlocked encrypted device. Set <literal>fileSystems.&lt;name?&gt;.device</literal> to <literal>/dev/mapper/&lt;label&gt;</literal> to mount the unlocked device.";
       };
 
diff --git a/nixos/release.nix b/nixos/release.nix
index e48954ceaf59..f0df3fe3e1ef 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -285,6 +285,7 @@ in rec {
   tests.proxy = callTest tests/proxy.nix {};
   tests.quake3 = callTest tests/quake3.nix {};
   tests.runInMachine = callTest tests/run-in-machine.nix {};
+  tests.sddm = callTest tests/sddm.nix {};
   tests.simple = callTest tests/simple.nix {};
   tests.tomcat = callTest tests/tomcat.nix {};
   tests.udisks2 = callTest tests/udisks2.nix {};
diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
new file mode 100644
index 000000000000..e11b5714d5c2
--- /dev/null
+++ b/nixos/tests/sddm.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "sddm";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ttuegel ];
+  };
+
+  machine = { lib, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm = {
+      enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+    services.xserver.windowManager.default = "icewm";
+    services.xserver.windowManager.icewm.enable = true;
+    services.xserver.desktopManager.default = "none";
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: ''
+    startAll;
+    $machine->waitForWindow("^IceWM ");
+  '';
+})