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-1803.xml8
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix8
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix10
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix7
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix1
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl2
-rw-r--r--nixos/modules/misc/crashdump.nix39
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/programs/sway.nix68
-rw-r--r--nixos/modules/rename.nix3
-rw-r--r--nixos/modules/security/pam.nix11
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agent.nix47
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/default.nix9
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix2
-rw-r--r--nixos/modules/services/mail/opendkim.nix26
-rw-r--r--nixos/modules/services/mail/postfix.nix17
-rw-r--r--nixos/modules/services/misc/gitea.nix270
-rw-r--r--nixos/modules/services/misc/gitlab.nix7
-rw-r--r--nixos/modules/services/misc/tzupdate.nix45
-rw-r--r--nixos/modules/services/monitoring/munin.nix13
-rw-r--r--nixos/modules/services/monitoring/prometheus/node-exporter.nix22
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix14
-rw-r--r--nixos/modules/services/networking/libreswan.nix7
-rw-r--r--nixos/modules/services/search/elasticsearch.nix12
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix2
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix4
-rw-r--r--nixos/modules/services/x11/compton.nix71
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix6
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix11
-rw-r--r--nixos/modules/services/x11/redshift.nix44
-rw-r--r--nixos/modules/system/boot/plymouth.nix2
-rw-r--r--nixos/modules/tasks/filesystems.nix15
-rw-r--r--nixos/modules/tasks/filesystems/ecryptfs.nix14
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix12
-rw-r--r--nixos/release.nix2
-rw-r--r--nixos/tests/gitolite.nix4
37 files changed, 707 insertions, 134 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1803.xml b/nixos/doc/manual/release-notes/rl-1803.xml
index 47522fe3cc92..6fa14b553862 100644
--- a/nixos/doc/manual/release-notes/rl-1803.xml
+++ b/nixos/doc/manual/release-notes/rl-1803.xml
@@ -55,7 +55,13 @@ following incompatible changes:</para>
 
 <itemizedlist>
   <listitem>
-    <para></para>
+    <para>
+      Dollar signs in options under <option>services.postfix</option> are
+      passed verbatim to Postfix, which will interpret them as the beginning of
+      a parameter expression. This was already true for string-valued options
+      in the previous release, but not for list-valued options. If you need to
+      pass literal dollar signs through Postfix, double them.
+    </para>
   </listitem>
 </itemizedlist>
 
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index d76fb644d029..2e67edf8ee3d 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -9,6 +9,12 @@ in {
   imports = [ ../../../modules/virtualisation/amazon-image.nix ];
 
   options.amazonImage = {
+    name = mkOption {
+      type = types.str;
+      description = "The name of the generated derivation";
+      default = "nixos-disk-image";
+    };
+
     contents = mkOption {
       example = literalExample ''
         [ { source = pkgs.memtest86 + "/memtest.bin";
@@ -38,7 +44,7 @@ in {
 
   config.system.build.amazonImage = import ../../../lib/make-disk-image.nix {
     inherit lib config;
-    inherit (cfg) contents format;
+    inherit (cfg) contents format name;
     pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
     partitioned = config.ec2.hvm;
     diskSize = cfg.sizeMB;
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index 8d80ceff84e0..c94dc7d40194 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -10,6 +10,7 @@ let
 in
 {
   imports = [
+    ../../profiles/base.nix
     ../../profiles/installation-device.nix
     ./sd-image.nix
   ];
@@ -27,9 +28,12 @@ in
   boot.loader.generic-extlinux-compatible.enable = true;
 
   boot.kernelPackages = pkgs.linuxPackages_latest;
-  # Increase the amount of CMA to ensure the virtual console on the RPi3 works.
-  boot.kernelParams = ["cma=32M" "console=ttyS0,115200n8" "console=tty0"];
-  boot.consoleLogLevel = 7;
+
+  # The serial ports listed here are:
+  # - ttyS0: for Tegra (Jetson TX1)
+  # - ttyAMA0: for QEMU's -machine virt
+  # Also increase the amount of CMA to ensure the virtual console on the RPi3 works.
+  boot.kernelParams = ["cma=32M" "console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
 
   # FIXME: this probably should be in installation-device.nix
   users.extraUsers.root.initialHashedPassword = "";
diff --git a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
index 1c664e025036..880a6bf2e1e8 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -10,6 +10,7 @@ let
 in
 {
   imports = [
+    ../../profiles/base.nix
     ../../profiles/installation-device.nix
     ./sd-image.nix
   ];
@@ -27,6 +28,12 @@ in
   boot.loader.generic-extlinux-compatible.enable = true;
 
   boot.kernelPackages = pkgs.linuxPackages_latest;
+  # The serial ports listed here are:
+  # - ttyS0: for Tegra (Jetson TK1)
+  # - ttymxc0: for i.MX6 (Wandboard)
+  # - ttyAMA0: for Allwinner (pcDuino3 Nano) and QEMU's -machine virt
+  # - ttyO0: for OMAP (BeagleBone Black)
+  # - ttySAC2: for Exynos (ODROID-XU3)
   boot.kernelParams = ["console=ttyS0,115200n8" "console=ttymxc0,115200n8" "console=ttyAMA0,115200n8" "console=ttyO0,115200n8" "console=ttySAC2,115200n8" "console=tty0"];
 
   # FIXME: this probably should be in installation-device.nix
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
index 3f2970757bd2..eb676eae05e8 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -10,6 +10,7 @@ let
 in
 {
   imports = [
+    ../../profiles/base.nix
     ../../profiles/installation-device.nix
     ./sd-image.nix
   ];
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 926d1e3133f7..c0df2977856e 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -103,7 +103,7 @@ if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
 
     foreach $e (@desired_governors) {
         if (index($governors, $e) != -1) {
-            last if (push @attrs, "powerManagement.cpuFreqGovernor = \"$e\";");
+            last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";");
         }
     }
 }
diff --git a/nixos/modules/misc/crashdump.nix b/nixos/modules/misc/crashdump.nix
index 5ef4b7781bd0..6e0b49fa9af0 100644
--- a/nixos/modules/misc/crashdump.nix
+++ b/nixos/modules/misc/crashdump.nix
@@ -18,26 +18,23 @@ in
           default = false;
           description = ''
             If enabled, NixOS will set up a kernel that will
-            boot on crash, and leave the user to a stage1 debug1devices
-            interactive shell to be able to save the crashed kernel dump.
+            boot on crash, and leave the user in systemd rescue
+            to be able to save the crashed kernel dump at
+            /proc/vmcore.
             It also activates the NMI watchdog.
           '';
         };
-        kernelPackages = mkOption {
-          type = types.package;
-          default = pkgs.linuxPackages;
-          # We don't want to evaluate all of linuxPackages for the manual
-          # - some of it might not even evaluate correctly.
-          defaultText = "pkgs.linuxPackages";
-          example = literalExample "pkgs.linuxPackages_2_6_25";
+        reservedMemory = mkOption {
+          default = "128M";
           description = ''
-            This will override the boot.kernelPackages, and will add some
-            kernel configuration parameters for the crash dump to work.
+            The amount of memory reserved for the crashdump kernel.
+            If you choose a too high value, dmesg will mention
+            "crashkernel reservation failed".
           '';
         };
         kernelParams = mkOption {
           type = types.listOf types.str;
-          default = [ "debug1devices" ];
+          default = [ "1" "boot.shell_on_fail" ];
           description = ''
             Parameters that will be passed to the kernel kexec-ed on crash.
           '';
@@ -51,29 +48,29 @@ in
   config = mkIf crashdump.enable {
     boot = {
       postBootCommands = ''
+        echo "loading crashdump kernel...";
         ${pkgs.kexectools}/sbin/kexec -p /run/current-system/kernel \
         --initrd=/run/current-system/initrd \
-        --append="init=$(readlink -f /run/current-system/init) system=$(readlink -f /run/current-system) irqpoll maxcpus=1 reset_devices ${kernelParams}" --reset-vga --console-vga
+        --reset-vga --console-vga \
+        --command-line="systemConfig=$(readlink -f /run/current-system) init=$(readlink -f /run/current-system/init) irqpoll maxcpus=1 reset_devices ${kernelParams}"
       '';
       kernelParams = [
-       "crashkernel=64M"
+       "crashkernel=${crashdump.reservedMemory}"
        "nmi_watchdog=panic"
        "softlockup_panic=1"
        "idle=poll"
       ];
-      kernelPackages = mkOverride 50 (crashdump.kernelPackages // {
-        kernel = crashdump.kernelPackages.kernel.override 
-          (attrs: {
-            extraConfig = (optionalString (attrs ? extraConfig) attrs.extraConfig) +
-              ''
+      kernelPatches = [ {
+        name = "crashdump-config";
+        patch = null;
+        extraConfig = ''
                 CRASH_DUMP y
                 DEBUG_INFO y
                 PROC_VMCORE y
                 LOCKUP_DETECTOR y
                 HARDLOCKUP_DETECTOR y
               '';
-          });
-      });
+        } ];
     };
   };
 }
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index a8cb957ffe21..6f00a97dd3ff 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -99,6 +99,7 @@
   ./programs/ssh.nix
   ./programs/ssmtp.nix
   ./programs/sysdig.nix
+  ./programs/sway.nix
   ./programs/thefuck.nix
   ./programs/tmux.nix
   ./programs/venus.nix
@@ -296,6 +297,7 @@
   ./services/misc/fstrim.nix
   ./services/misc/gammu-smsd.nix
   ./services/misc/geoip-updater.nix
+  ./services/misc/gitea.nix
   #./services/misc/gitit.nix
   ./services/misc/gitlab.nix
   ./services/misc/gitolite.nix
@@ -343,6 +345,7 @@
   ./services/misc/svnserve.nix
   ./services/misc/synergy.nix
   ./services/misc/taskserver
+  ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
   ./services/misc/zookeeper.nix
   ./services/monitoring/apcupsd.nix
@@ -693,6 +696,7 @@
   ./tasks/filesystems/bcachefs.nix
   ./tasks/filesystems/btrfs.nix
   ./tasks/filesystems/cifs.nix
+  ./tasks/filesystems/ecryptfs.nix
   ./tasks/filesystems/exfat.nix
   ./tasks/filesystems/ext.nix
   ./tasks/filesystems/f2fs.nix
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
new file mode 100644
index 000000000000..2934fba96dda
--- /dev/null
+++ b/nixos/modules/programs/sway.nix
@@ -0,0 +1,68 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.sway;
+  sway = pkgs.sway;
+
+  swayWrapped = pkgs.writeScriptBin "sway" ''
+    #! ${pkgs.stdenv.shell}
+    ${cfg.extraSessionCommands}
+    PATH="${sway}/bin:$PATH"
+    exec ${pkgs.dbus.dbus-launch} --exit-with-session "${sway}/bin/sway"
+  '';
+  swayJoined = pkgs.symlinkJoin {
+    name = "sway-wrapped";
+    paths = [ swayWrapped sway ];
+  };
+in
+{
+  options.programs.sway = {
+    enable = mkEnableOption "sway";
+
+    extraSessionCommands = mkOption {
+      default     = "";
+      type        = types.lines;
+      example = ''
+        export XKB_DEFAULT_LAYOUT=us,de
+        export XKB_DEFAULT_VARIANT=,nodeadkeys
+        export XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle,
+      '';
+      description = ''
+        Shell commands executed just before sway is started.
+      '';
+    };
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = with pkgs; [
+        i3status xwayland rxvt_unicode dmenu
+      ];
+      example = literalExample ''
+        with pkgs; [
+          i3status xwayland rxvt_unicode dmenu
+        ]
+      '';
+      description = ''
+        Extra packages to be installed system wide.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ swayJoined ] ++ cfg.extraPackages;
+    security.wrappers.sway = {
+      source = "${swayJoined}/bin/sway";
+      capabilities = "cap_sys_ptrace,cap_sys_tty_config=eip";
+      owner = "root";
+      group = "sway";
+      permissions = "u+rx,g+rx";
+    };
+
+    users.extraGroups.sway = {};
+
+    hardware.opengl.enable = mkDefault true;
+    fonts.enableDefaultFonts = mkDefault true;
+  };
+}
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index b1b3f67a41d0..f30cbe427f09 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -112,6 +112,9 @@ with lib;
 
     (mkAliasOptionModule [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ])
 
+    # opendkim
+    (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ])
+
     # XBMC
     (mkRenamedOptionModule [ "services" "xserver" "windowManager" "xbmc" ] [ "services" "xserver" "desktopManager" "kodi" ])
     (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "xbmc" ] [ "services" "xserver" "desktopManager" "kodi" ])
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index ede4ace5ed03..5ded36329f33 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -486,8 +486,9 @@ in
       ++ optionals config.krb5.enable [pam_krb5 pam_ccreds]
       ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
       ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ]
-      ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ]
-      ++ optionals config.security.pam.enableEcryptfs [ pkgs.ecryptfs ];
+      ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ];
+
+    boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
 
     security.wrappers = {
       unix_chkpwd = {
@@ -495,10 +496,7 @@ in
         owner = "root";
         setuid = true;
       };
-    } // (if config.security.pam.enableEcryptfs then {
-      "mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
-       "umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
-    } else {});
+    };
 
     environment.etc =
       mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
@@ -521,6 +519,7 @@ in
         ftp = {};
         i3lock = {};
         i3lock-color = {};
+        swaylock = {};
         screen = {};
         vlock = {};
         xlock = {};
diff --git a/nixos/modules/services/continuous-integration/buildkite-agent.nix b/nixos/modules/services/continuous-integration/buildkite-agent.nix
index 267bc16862c9..9c06e1d43bbe 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agent.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agent.nix
@@ -9,9 +9,8 @@ let
       token="${cfg.token}"
       name="${cfg.name}"
       meta-data="${cfg.meta-data}"
-      hooks-path="${pkgs.buildkite-agent}/share/hooks"
-      build-path="/var/lib/buildkite-agent/builds"
-      bootstrap-script="${pkgs.buildkite-agent}/share/bootstrap.sh"
+      hooks-path="${cfg.package}/share/hooks"
+      build-path="${cfg.dataDir}"
     '';
 in
 
@@ -20,6 +19,26 @@ in
     services.buildkite-agent = {
       enable = mkEnableOption "buildkite-agent";
 
+      package = mkOption {
+        default = pkgs.buildkite-agent;
+        defaultText = "pkgs.buildkite-agent";
+        description = "Which buildkite-agent derivation to use";
+        type = types.package;
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/buildkite-agent";
+        description = "The workdir for the agent";
+        type = types.str;
+      };
+
+      runtimePackages = mkOption {
+        default = [ pkgs.bash pkgs.nix ];
+        defaultText = "[ pkgs.bash pkgs.nix ]";
+        description = "Add programs to the buildkite-agent environment";
+        type = types.listOf types.package;
+      };
+
       token = mkOption {
         type = types.str;
         description = ''
@@ -62,27 +81,31 @@ in
   config = mkIf config.services.buildkite-agent.enable {
     users.extraUsers.buildkite-agent =
       { name = "buildkite-agent";
-        home = "/var/lib/buildkite-agent";
+        home = cfg.dataDir;
         createHome = true;
         description = "Buildkite agent user";
       };
 
-    environment.systemPackages = [ pkgs.buildkite-agent ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.buildkite-agent =
       { description = "Buildkite Agent";
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
-        environment.HOME = "/var/lib/buildkite-agent";
+        path = cfg.runtimePackages;
+        environment = config.networking.proxy.envVars // {
+          HOME = cfg.dataDir;
+          NIX_REMOTE = "daemon";
+        };
         preStart = ''
-            ${pkgs.coreutils}/bin/mkdir -m 0700 -p /var/lib/buildkite-agent/.ssh
+          ${pkgs.coreutils}/bin/mkdir -m 0700 -p ${cfg.dataDir}/.ssh
 
-            echo "${cfg.openssh.privateKey}" > /var/lib/buildkite-agent/.ssh/id_rsa
-            ${pkgs.coreutils}/bin/chmod 600 /var/lib/buildkite-agent/.ssh/id_rsa
+          echo "${cfg.openssh.privateKey}" > ${cfg.dataDir}/.ssh/id_rsa
+          ${pkgs.coreutils}/bin/chmod 600 ${cfg.dataDir}/.ssh/id_rsa
 
-            echo "${cfg.openssh.publicKey}" > /var/lib/buildkite-agent/.ssh/id_rsa.pub
-            ${pkgs.coreutils}/bin/chmod 600 /var/lib/buildkite-agent/.ssh/id_rsa.pub
-          '';
+          echo "${cfg.openssh.publicKey}" > ${cfg.dataDir}/.ssh/id_rsa.pub
+          ${pkgs.coreutils}/bin/chmod 600 ${cfg.dataDir}/.ssh/id_rsa.pub
+        '';
 
         serviceConfig =
           { ExecStart = "${pkgs.buildkite-agent}/bin/buildkite-agent start --config ${configFile}";
diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix
index c14aa4167231..0dd59e4fb444 100644
--- a/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -78,6 +78,13 @@ in {
         '';
       };
 
+      package = mkOption {
+        default = pkgs.jenkins;
+        defaultText = "pkgs.jenkins";
+        type = types.package;
+        description = "Jenkins package to use.";
+      };
+
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ];
         defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]";
@@ -194,7 +201,7 @@ in {
         '';
 
       script = ''
-        ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${pkgs.jenkins}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
+        ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
                                                   --httpPort=${toString cfg.port} \
                                                   --prefix=${cfg.prefix} \
                                                   ${concatStringsSep " " cfg.extraOptions}
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index 0e041fdbfbcf..c5a000d5c6a7 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -17,7 +17,7 @@ with lib;
         default = false;
         description = ''
           Whether to enable GeoClue 2 daemon, a DBus service
-          that provides location informationfor accessing.
+          that provides location information for accessing.
         '';
       };
 
diff --git a/nixos/modules/services/mail/opendkim.nix b/nixos/modules/services/mail/opendkim.nix
index f065208ddfc1..59a8373843a1 100644
--- a/nixos/modules/services/mail/opendkim.nix
+++ b/nixos/modules/services/mail/opendkim.nix
@@ -8,10 +8,12 @@ let
 
   defaultSock = "local:/run/opendkim/opendkim.sock";
 
+  keyFile = "${cfg.keyPath}/${cfg.selector}.private";
+
   args = [ "-f" "-l"
            "-p" cfg.socket
            "-d" cfg.domains
-           "-k" cfg.keyFile
+           "-k" keyFile
            "-s" cfg.selector
          ] ++ optionals (cfg.configFile != null) [ "-x" cfg.configFile ];
 
@@ -57,9 +59,13 @@ in {
         '';
       };
 
-      keyFile = mkOption {
+      keyPath = mkOption {
         type = types.path;
-        description = "Secret key file used for signing messages.";
+        description = ''
+          The path that opendkim should put its generated private keys into.
+          The DNS settings will be found in this directory with the name selector.txt.
+        '';
+        default = "/var/lib/opendkim/keys";
       };
 
       selector = mkOption {
@@ -100,11 +106,25 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
+      preStart = ''
+        mkdir -p "${cfg.keyPath}"
+        cd "${cfg.keyPath}"
+        if ! test -f ${cfg.selector}.private; then
+          ${pkgs.opendkim}/bin/opendkim-genkey -s ${cfg.selector} -d all-domains-generic-key
+          echo "Generated OpenDKIM key! Please update your DNS settings:\n"
+          echo "-------------------------------------------------------------"
+          cat ${cfg.selector}.txt
+          echo "-------------------------------------------------------------"
+        fi
+        chown ${cfg.user}:${cfg.group} ${cfg.selector}.private
+      '';
+
       serviceConfig = {
         ExecStart = "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}";
         User = cfg.user;
         Group = cfg.group;
         RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
+        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index ad4e58149847..1fef9ac6ec90 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -28,7 +28,7 @@ let
 
   mainCf = let
     escape = replaceStrings ["$"] ["$$"];
-    mkList = items: "\n  " + concatMapStringsSep "\n  " escape items;
+    mkList = items: "\n  " + concatStringsSep "\n  " items;
     mkVal = value:
       if isList value then mkList value
         else " " + (if value == true then "yes"
@@ -62,7 +62,9 @@ let
     shlib_directory      = false;
     relayhost            = if cfg.lookupMX || cfg.relayHost == ""
                              then cfg.relayHost
-                             else "[${cfg.relayHost}]";
+                             else
+			       "[${cfg.relayHost}]"
+			       + optionalString (cfg.relayPort != null) ":${toString cfg.relayPort}";
     mail_spool_directory = "/var/spool/mail/";
     setgid_group         = setgidGroup;
   }
@@ -458,6 +460,17 @@ in
         ";
       };
 
+      relayPort = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 587;
+        description = "
+          Specify an optional port for outbound mail relay. (Note:
+          only used if an explicit <option>relayHost</option> is
+          defined.)
+        ";
+      };
+
       lookupMX = mkOption {
         type = types.bool;
         default = false;
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
new file mode 100644
index 000000000000..f0b44b7bedeb
--- /dev/null
+++ b/nixos/modules/services/misc/gitea.nix
@@ -0,0 +1,270 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitea;
+  configFile = pkgs.writeText "app.ini" ''
+    APP_NAME = ${cfg.appName}
+    RUN_USER = ${cfg.user}
+    RUN_MODE = prod
+
+    [database]
+    DB_TYPE = ${cfg.database.type}
+    HOST = ${cfg.database.host}:${toString cfg.database.port}
+    NAME = ${cfg.database.name}
+    USER = ${cfg.database.user}
+    PASSWD = #dbpass#
+    PATH = ${cfg.database.path}
+
+    [repository]
+    ROOT = ${cfg.repositoryRoot}
+
+    [server]
+    DOMAIN = ${cfg.domain}
+    HTTP_ADDR = ${cfg.httpAddress}
+    HTTP_PORT = ${toString cfg.httpPort}
+    ROOT_URL = ${cfg.rootUrl}
+    STATIC_ROOT_PATH = ${cfg.staticRootPath}
+
+    [session]
+    COOKIE_NAME = session
+    COOKIE_SECURE = ${boolToString cfg.cookieSecure}
+
+    [security]
+    SECRET_KEY = #secretkey#
+    INSTALL_LOCK = true
+
+    ${cfg.extraConfig}
+  '';
+in
+
+{
+  options = {
+    services.gitea = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Enable Gitea Service.";
+      };
+
+      useWizard = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
+      };
+
+      stateDir = mkOption {
+        default = "/var/lib/gitea";
+        type = types.str;
+        description = "gitea data directory.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "gitea";
+        description = "User account under which gitea runs.";
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "sqlite3" "mysql" "postgres" ];
+          example = "mysql";
+          default = "sqlite3";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 3306;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "gitea";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "gitea";
+          description = "Database user.";
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            The password corresponding to <option>database.user</option>.
+            Warning: this is stored in cleartext in the Nix store!
+            Use <option>database.passwordFile</option> instead.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/gitea-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        path = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/data/gitea.db";
+          description = "Path to the sqlite3 database file.";
+        };
+      };
+
+      appName = mkOption {
+        type = types.str;
+        default = "gitea: Gitea Service";
+        description = "Application name.";
+      };
+
+      repositoryRoot = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/repositories";
+        description = "Path to the git repositories.";
+      };
+
+      domain = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Domain name of your server.";
+      };
+
+      rootUrl = mkOption {
+        type = types.str;
+        default = "http://localhost:3000/";
+        description = "Full public URL of gitea server.";
+      };
+
+      httpAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "HTTP listen address.";
+      };
+
+      httpPort = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "HTTP listen port.";
+      };
+
+      cookieSecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Marks session cookies as "secure" as a hint for browsers to only send
+          them via HTTPS. This option is recommend, if gitea is being served over HTTPS.
+        '';
+      };
+
+      staticRootPath = mkOption {
+        type = types.str;
+        default = "${pkgs.gitea.data}";
+        example = "/var/lib/gitea/data";
+        description = "Upper level of template and static files path.";
+      };
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = "Configuration lines appended to the generated gitea configuration file.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.gitea = {
+      description = "gitea";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.gitea.bin ];
+
+      preStart = let
+        runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+        secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+      in ''
+        mkdir -p ${cfg.stateDir}
+
+        # copy custom configuration and generate a random secret key if needed
+        ${optionalString (cfg.useWizard == false) ''
+          mkdir -p ${cfg.stateDir}/custom/conf
+          cp -f ${configFile} ${runConfig}
+
+          if [ ! -e ${secretKey} ]; then
+              head -c 16 /dev/urandom | base64 > ${secretKey}
+          fi
+
+          KEY=$(head -n1 ${secretKey})
+          DBPASS=$(head -n1 ${cfg.database.passwordFile})
+          sed -e "s,#secretkey#,$KEY,g" \
+              -e "s,#dbpass#,$DBPASS,g" \
+              -i ${runConfig}
+          chmod 640 ${runConfig} ${secretKey}
+        ''}
+
+        mkdir -p ${cfg.repositoryRoot}
+        # update all hooks' binary paths
+        HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*")
+        if [ "$HOOKS" ]
+        then
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${pkgs.gitea.bin}/bin/gitea,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
+        fi
+        if [ ! -d ${cfg.stateDir}/conf/locale ]
+        then
+          mkdir -p ${cfg.stateDir}/conf
+          cp -r ${pkgs.gitea.out}/locale ${cfg.stateDir}/conf/locale
+        fi
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        WorkingDirectory = cfg.stateDir;
+        ExecStart = "${pkgs.gitea.bin}/bin/gitea web";
+        Restart = "always";
+      };
+
+      environment = {
+        USER = cfg.user;
+        HOME = cfg.stateDir;
+        GITEA_WORK_DIR = cfg.stateDir;
+      };
+    };
+
+    users = mkIf (cfg.user == "gitea") {
+      extraUsers.gitea = {
+        description = "Gitea Service";
+        home = cfg.stateDir;
+        createHome = true;
+      };
+    };
+
+    warnings = optional (cfg.database.password != "")
+      ''config.services.gitea.database.password will be stored as plaintext
+        in the Nix store. Use database.passwordFile instead.'';
+
+    # Create database passwordFile default when password is configured.
+    services.gitea.database.passwordFile =
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "gitea-database-password";
+        text = cfg.database.password;
+      })));
+  };
+}
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 14c184c3d685..740cbc141b53 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -632,6 +632,11 @@ in {
           touch "${cfg.statePath}/db-seeded"
         fi
 
+        # The gitlab:shell:create_hooks task seems broken for fixing links
+        # so we instead delete all the hooks and create them anew
+        rm ${cfg.statePath}/repositories/**/*.git/hooks
+        ${gitlab-rake}/bin/gitlab-rake gitlab:shell:create_hooks RAILS_ENV=production
+
         # Change permissions in the last step because some of the
         # intermediary scripts like to create directories as root.
         chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}
@@ -641,7 +646,7 @@ in {
         chmod -R ug-s ${cfg.statePath}/repositories
         find ${cfg.statePath}/repositories -type d -print0 | xargs -0 chmod g+s
         chmod 770 ${cfg.statePath}/uploads
-        chown -R git ${cfg.statePath}/uploads
+        chown -R ${cfg.user} ${cfg.statePath}/uploads
         find ${cfg.statePath}/uploads -type f -exec chmod 0644 {} \;
         find ${cfg.statePath}/uploads -type d -not -path ${cfg.statePath}/uploads -exec chmod 0770 {} \;
       '';
diff --git a/nixos/modules/services/misc/tzupdate.nix b/nixos/modules/services/misc/tzupdate.nix
new file mode 100644
index 000000000000..570982ced29a
--- /dev/null
+++ b/nixos/modules/services/misc/tzupdate.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tzupdate;
+in {
+  options.services.tzupdate = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable the tzupdate timezone updating service. This provides
+        a one-shot service which can be activated with systemctl to 
+        update the timezone.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # We need to have imperative time zone management for this to work.
+    # This will give users an error if they have set an explicit time
+    # zone, which is better than silently overriding it.
+    time.timeZone = null; 
+
+    # We provide a one-shot service which can be manually run. We could
+    # provide a service that runs on startup, but it's tricky to get
+    # a service to run after you have *internet* access.
+    systemd.services.tzupdate = {
+      description = "tzupdate timezone update service";
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        # We could link directly into pkgs.tzdata, but at least timedatectl seems
+        # to expect the symlink to point directly to a file in etc.
+        # Setting the "debian timezone file" to point at /dev/null stops it doing anything.
+        ExecStart = "${pkgs.tzupdate}/bin/tzupdate -z /etc/zoneinfo -d /dev/null";
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.michaelpj ];
+}
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index 723b04dc0fe9..cc6d51f0ef1b 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -193,6 +193,9 @@ in
       };
     };
 
+    # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
+    systemd.tmpfiles.rules = [ "d /var/run/munin 0755 munin munin -" ];
+
   }) (mkIf cronCfg.enable {
 
     systemd.timers.munin-cron = {
@@ -212,9 +215,11 @@ in
       };
     };
 
-    system.activationScripts.munin-cron = stringAfter [ "users" "groups" ] ''
-      mkdir -p /var/{run,log,www,lib}/munin
-      chown -R munin:munin /var/{run,log,www,lib}/munin
-    '';
+    systemd.tmpfiles.rules = [
+      "d /var/run/munin 0755 munin munin -"
+      "d /var/log/munin 0755 munin munin -"
+      "d /var/www/munin 0755 munin munin -"
+      "d /var/lib/munin 0755 munin munin -"
+    ];
   })];
 }
diff --git a/nixos/modules/services/monitoring/prometheus/node-exporter.nix b/nixos/modules/services/monitoring/prometheus/node-exporter.nix
index b5b852438d77..bad4389ce799 100644
--- a/nixos/modules/services/monitoring/prometheus/node-exporter.nix
+++ b/nixos/modules/services/monitoring/prometheus/node-exporter.nix
@@ -4,9 +4,6 @@ with lib;
 
 let
   cfg = config.services.prometheus.nodeExporter;
-  cmdlineArgs = cfg.extraFlags ++ [
-    "-web.listen-address=${cfg.listenAddress}"
-  ];
 in {
   options = {
     services.prometheus.nodeExporter = {
@@ -33,7 +30,16 @@ in {
         default = [];
         example = ''[ "systemd" ]'';
         description = ''
-          Collectors to enable. Only collectors explicitly listed here will be enabled.
+          Collectors to enable. The collectors listed here are enabled in addition to the default ones.
+        '';
+      };
+
+      disabledCollectors = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = ''[ "timex" ]'';
+        description = ''
+          Collectors to disable which are enabled by default.
         '';
       };
 
@@ -64,14 +70,14 @@ in {
       wantedBy = [ "multi-user.target" ];
       script = ''
         exec ${pkgs.prometheus-node-exporter}/bin/node_exporter \
-          ${optionalString (cfg.enabledCollectors != [])
-            ''-collectors.enabled ${concatStringsSep "," cfg.enabledCollectors}''} \
-          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
+          ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
       serviceConfig = {
         User = "nobody";
-        Restart  = "always";
+        Restart = "always";
         PrivateTmp = true;
         WorkingDirectory = /tmp;
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index fcf5aa5f175b..91a3e54474ac 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -55,6 +55,14 @@ in
         '';
       };
 
+      alwaysKeepRunning = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
+        '';
+      };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
@@ -101,10 +109,12 @@ in
           BusName = "uk.org.thekelleys.dnsmasq";
           ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          PrivateTmp = true;
+          ProtectSystem = true;
+          ProtectHome = true;
+          Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
         };
         restartTriggers = [ config.environment.etc.hosts.source ];
     };
-
   };
-
 }
diff --git a/nixos/modules/services/networking/libreswan.nix b/nixos/modules/services/networking/libreswan.nix
index e7a6c565f4ff..280158b89f61 100644
--- a/nixos/modules/services/networking/libreswan.nix
+++ b/nixos/modules/services/networking/libreswan.nix
@@ -17,7 +17,7 @@ let
       else substring (head nonchars).ind (add 1 (sub (last nonchars).ind (head nonchars).ind)) str;
   indent = str: concatStrings (concatMap (s: ["  " (trim [" " "\t"] s) "\n"]) (splitString "\n" str));
   configText = indent (toString cfg.configSetup);
-  connectionText = concatStrings (mapAttrsToList (n: v: 
+  connectionText = concatStrings (mapAttrsToList (n: v:
     ''
       conn ${n}
       ${indent v}
@@ -27,7 +27,7 @@ let
     ''
       config setup
       ${configText}
-      
+
       ${connectionText}
     '';
 
@@ -93,6 +93,9 @@ in
         "${pkgs.libreswan}"
         "${pkgs.iproute}"
         "${pkgs.procps}"
+        "${pkgs.nssTools}"
+        "${pkgs.iptables}"
+        "${pkgs.nettools}"
       ];
 
       wants = [ "network-online.target" ];
diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix
index 2ea22a945940..c51dd5d94655 100644
--- a/nixos/modules/services/search/elasticsearch.nix
+++ b/nixos/modules/services/search/elasticsearch.nix
@@ -174,11 +174,13 @@ in {
         LimitNOFILE = "1024000";
       };
       preStart = ''
-        # Only set vm.max_map_count if lower than ES required minimum
-        # This avoids conflict if configured via boot.kernel.sysctl
-        if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
-          ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
-        fi
+        ${optionalString (!config.boot.isContainer) ''
+          # Only set vm.max_map_count if lower than ES required minimum
+          # This avoids conflict if configured via boot.kernel.sysctl
+          if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
+            ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
+          fi
+        ''}
 
         mkdir -m 0700 -p ${cfg.dataDir}
 
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 3e5087766b1c..ef48d52e7a94 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -385,7 +385,7 @@ in
 
     httpAddress = mkOption {
       type = types.str;
-      default = "127.0.0.1:4180";
+      default = "http://127.0.0.1:4180";
       description = ''
         HTTPS listening address.  This module does not expose the port by
         default. If you want this URL to be accessible to other machines, please
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index b54c91d7de92..2a5f08046382 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -142,7 +142,7 @@ in
           ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml
       '';
 
-      script = "${pkg}/start_crowd.sh";
+      script = "${pkg}/start_crowd.sh -fg";
 
       serviceConfig = {
         User = cfg.user;
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 8a04e07eeeac..801601aafd9d 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -142,10 +142,10 @@ with lib;
     globalRedirect = mkOption {
       type = types.nullOr types.str;
       default = null;
-      example = http://newserver.example.org/;
+      example = "newserver.example.org";
       description = ''
         If set, all requests for this host are redirected permanently to
-        the given URL.
+        the given hostname.
       '';
     };
 
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index 42ceeb819806..8701354b5285 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -7,12 +7,15 @@ let
 
   cfg = config.services.compton;
 
-  configFile = let
-    opacityRules = optionalString (length cfg.opacityRules != 0)
-      (concatStringsSep "\n"
-        (map (a: "opacity-rule = [ \"${a}\" ];") cfg.opacityRules)
-      );
-  in pkgs.writeText "compton.conf"
+  floatBetween = a: b: with lib; with types;
+    addCheck str (x: versionAtLeast x a && versionOlder x b);
+
+  pairOf = x: with types; addCheck (listOf x) (y: lib.length y == 2);
+
+  opacityRules = optionalString (length cfg.opacityRules != 0)
+    (concatMapStringsSep ",\n" (rule: ''"${rule}"'') cfg.opacityRules);
+
+  configFile = pkgs.writeText "compton.conf"
     (optionalString cfg.fade ''
       # fading
       fading = true;
@@ -36,7 +39,9 @@ let
       inactive-opacity = ${cfg.inactiveOpacity};
       menu-opacity     = ${cfg.menuOpacity};
 
-      ${opacityRules}
+      opacity-rule = [
+        ${opacityRules}
+      ];
 
       # other options
       backend = ${toJSON cfg.backend};
@@ -64,7 +69,7 @@ in {
     };
 
     fadeDelta = mkOption {
-      type = types.int;
+      type = types.addCheck types.int (x: x > 0);
       default = 10;
       example = 5;
       description = ''
@@ -73,11 +78,12 @@ in {
     };
 
     fadeSteps = mkOption {
-      type = types.listOf types.str;
+      type = pairOf (floatBetween "0.01" "1.01");
       default = [ "0.028" "0.03" ];
       example = [ "0.04" "0.04" ];
       description = ''
         Opacity change between fade steps (in and out).
+        (numbers in range 0.01 - 1.0)
       '';
     };
 
@@ -104,7 +110,7 @@ in {
     };
 
     shadowOffsets = mkOption {
-      type = types.listOf types.int;
+      type = pairOf types.int;
       default = [ (-15) (-15) ];
       example = [ (-10) (-15) ];
       description = ''
@@ -113,11 +119,11 @@ in {
     };
 
     shadowOpacity = mkOption {
-      type = types.str;
+      type = floatBetween "0.0" "1.01";
       default = "0.75";
       example = "0.8";
       description = ''
-        Window shadows opacity (number in range 0 - 1).
+        Window shadows opacity (number in range 0.0 - 1.0).
       '';
     };
 
@@ -136,60 +142,67 @@ in {
     };
 
     activeOpacity = mkOption {
-      type = types.str;
+      type = floatBetween "0.0" "1.01";
       default = "1.0";
       example = "0.8";
       description = ''
-        Opacity of active windows.
+        Opacity of active windows (number in range 0.0 - 1.0).
       '';
     };
 
     inactiveOpacity = mkOption {
-      type = types.str;
+      type = floatBetween "0.1" "1.01";
       default = "1.0";
       example = "0.8";
       description = ''
-        Opacity of inactive windows.
+        Opacity of inactive windows (number in range 0.1 - 1.0).
       '';
     };
 
     menuOpacity = mkOption {
-      type = types.str;
+      type = floatBetween "0.0" "1.01";
       default = "1.0";
       example = "0.8";
       description = ''
-        Opacity of dropdown and popup menu.
+        Opacity of dropdown and popup menu (number in range 0.0 - 1.0).
       '';
     };
 
     opacityRules = mkOption {
       type = types.listOf types.str;
       default = [];
+      example = [
+        "95:class_g = 'URxvt' && !_NET_WM_STATE@:32a"
+        "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
+      ];
       description = ''
-        Opacity rules to be handled by compton.
+        Rules that control the opacity of windows, in format PERCENT:PATTERN.
       '';
     };
 
     backend = mkOption {
-      type = types.str;
-      default = "glx";
+      type = types.enum [ "glx" "xrender" ];
+      default = "xrender";
       description = ''
         Backend to use: <literal>glx</literal> or <literal>xrender</literal>.
       '';
     };
 
     vSync = mkOption {
-     type = types.str;
-     default = "none";
-     example = "opengl-swc";
-     description = ''
-       Enable vertical synchronization using the specified method.
-       See <literal>compton(1)</literal> man page available methods.
-     '';
+      type = types.enum [
+        "none" "drm" "opengl"
+        "opengl-oml" "opengl-swc" "opengl-mswc"
+      ];
+      default = "none";
+      example = "opengl-swc";
+      description = ''
+        Enable vertical synchronization using the specified method.
+        See <literal>compton(1)</literal> man page an explanation.
+      '';
     };
 
     refreshRate = mkOption {
-      type = types.int;
+      type = types.addCheck types.int (x: x >= 0);
       default = 0;
       example = 60;
       description = ''
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index d7e72c4a7aec..685a93d952b1 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -47,7 +47,7 @@ in
             ${getBin config.hardware.pulseaudio.package}/bin/pactl load-module module-device-manager "do_routing=1"
           ''}
 
-          exec "${plasma5.startkde}"
+          exec "${getBin plasma5.plasma-workspace}/bin/startkde"
         '';
       };
 
@@ -175,7 +175,7 @@ in
         serif = [ "Noto Serif" ];
       };
 
-      programs.ssh.askPassword = "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
+      programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
 
       # Enable helpful DBus services.
       services.udisks2.enable = true;
@@ -190,7 +190,7 @@ in
       ];
 
       services.xserver.displayManager.sddm = {
-        theme = "breeze";
+        theme = mkDefault "breeze";
       };
 
       security.pam.services.kde = { allowNullPassword = true; };
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index e6cc02e4d491..facaea131ae5 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -19,6 +19,17 @@ let
 
   Xsetup = pkgs.writeScript "Xsetup" ''
     #!/bin/sh
+
+    # Prior to Qt 5.9.2, there is a QML cache invalidation bug which sometimes
+    # strikes new Plasma 5 releases. If the QML cache is not invalidated, SDDM
+    # will segfault without explanation. We really tore our hair out for awhile
+    # before finding the bug:
+    # https://bugreports.qt.io/browse/QTBUG-62302
+    # We work around the problem by deleting the QML cache before startup. It
+    # will be regenerated, causing a small but perceptible delay when SDDM
+    # starts.
+    rm -fr /var/lib/sddm/.cache/sddm-greeter/qmlcache
+
     ${cfg.setupScript}
   '';
 
diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix
index 992709ed0000..30d853841ea4 100644
--- a/nixos/modules/services/x11/redshift.nix
+++ b/nixos/modules/services/x11/redshift.nix
@@ -19,18 +19,31 @@ in {
     };
 
     latitude = mkOption {
-      type = types.str;
+      type = types.nullOr types.str;
+      default = null;
       description = ''
         Your current latitude, between
-        <literal>-90.0</literal> and <literal>90.0</literal>.
+        <literal>-90.0</literal> and <literal>90.0</literal>. Must be provided
+        along with longitude.
       '';
     };
 
     longitude = mkOption {
-      type = types.str;
+      type = types.nullOr types.str;
+      default = null;
       description = ''
         Your current longitude, between
-        between <literal>-180.0</literal> and <literal>180.0</literal>.
+        between <literal>-180.0</literal> and <literal>180.0</literal>. Must be
+        provided along with latitude.
+      '';
+    };
+
+    provider = mkOption {
+      type = types.enum [ "manual" "geoclue2" ];
+      default = "manual";
+      description = ''
+        The location provider to use for determining your location. If set to
+        <literal>manual</literal> you must also provide latitude/longitude.
       '';
     };
 
@@ -93,14 +106,33 @@ in {
   };
 
   config = mkIf cfg.enable {
-    systemd.user.services.redshift = {
+    assertions = [ 
+      {
+        assertion = 
+          if cfg.provider == "manual"
+          then (cfg.latitude != null && cfg.longitude != null) 
+          else (cfg.latitude == null && cfg.longitude == null);
+        message = "Latitude and longitude must be provided together, and with provider set to null.";
+      }
+    ];
+
+    services.geoclue2.enable = mkIf (cfg.provider == "geoclue2") true;
+
+    systemd.user.services.redshift = 
+    let
+      providerString = 
+        if cfg.provider == "manual"
+        then "${cfg.latitude}:${cfg.longitude}"
+        else cfg.provider;
+    in
+    {
       description = "Redshift colour temperature adjuster";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
       serviceConfig = {
         ExecStart = ''
           ${cfg.package}/bin/redshift \
-            -l ${cfg.latitude}:${cfg.longitude} \
+            -l ${providerString} \
             -t ${toString cfg.temperature.day}:${toString cfg.temperature.night} \
             -b ${toString cfg.brightness.day}:${toString cfg.brightness.night} \
             ${lib.strings.concatStringsSep " " cfg.extraOptions}
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index d45b1686c1ea..0938d22a45b8 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -72,7 +72,7 @@ in
     environment.systemPackages = [ plymouth ];
 
     environment.etc."plymouth/plymouthd.conf".source = configFile;
-    environment.etc."plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouth.defaults";
+    environment.etc."plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
     environment.etc."plymouth/logo.png".source = cfg.logo;
     environment.etc."plymouth/themes".source = "${themesEnv}/share/plymouth/themes";
     # XXX: Needed because we supply a different set of plugins in initrd.
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 6ceb36714b28..b3690fad1a6a 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -115,11 +115,18 @@ let
 
     };
 
-    config = {
+    config = let
+      defaultFormatOptions =
+        # -F needed to allow bare block device without partitions
+        if (builtins.substring 0 3 config.fsType) == "ext" then "-F"
+        # -q needed for non-interactive operations
+        else if config.fsType == "jfs" then "-q"
+        # (same here)
+        else if config.fsType == "reiserfs" then "-q"
+        else null;
+    in {
       options = mkIf config.autoResize [ "x-nixos.autoresize" ];
-
-      # -F needed to allow bare block device without partitions
-      formatOptions = mkIf ((builtins.substring 0 3 config.fsType) == "ext") (mkDefault "-F");
+      formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions);
     };
 
   };
diff --git a/nixos/modules/tasks/filesystems/ecryptfs.nix b/nixos/modules/tasks/filesystems/ecryptfs.nix
new file mode 100644
index 000000000000..12a407cabbfb
--- /dev/null
+++ b/nixos/modules/tasks/filesystems/ecryptfs.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+# TODO: make ecryptfs work in initramfs?
+
+with lib;
+
+{
+  config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
+    system.fsPackages = [ pkgs.ecryptfs ];
+    security.wrappers = {
+      "mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
+      "umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index a17e2c9ca471..3c89ca68113b 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -92,7 +92,7 @@ let
             -drive index=0,id=drive1,file=$NIX_DISK_IMAGE,if=${cfg.qemu.diskInterface},cache=writeback,werror=report \
             -kernel ${config.system.build.toplevel}/kernel \
             -initrd ${config.system.build.toplevel}/initrd \
-            -append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo} ${kernelConsole} $QEMU_KERNEL_PARAMS" \
+            -append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${kernelConsole} $QEMU_KERNEL_PARAMS" \
           ''} \
           $extraDisks \
           ${qemuGraphics} \
@@ -102,15 +102,7 @@ let
     '';
 
 
-  regInfo = pkgs.runCommand "reginfo"
-    { exportReferencesGraph =
-        map (x: [("closure-" + baseNameOf x) x]) config.virtualisation.pathsInNixDB;
-      buildInputs = [ pkgs.perl ];
-      preferLocalBuild = true;
-    }
-    ''
-      printRegistration=1 perl ${pkgs.pathsFromGraph} closure-* > $out
-    '';
+  regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.pathsInNixDB; };
 
 
   # Generate a hard disk image containing a /boot partition and GRUB
diff --git a/nixos/release.nix b/nixos/release.nix
index 3016b1ef9442..d5f59a524e04 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -256,7 +256,7 @@ in rec {
   tests.gocd-server = callTest tests/gocd-server.nix {};
   tests.gnome3 = callTest tests/gnome3.nix {};
   tests.gnome3-gdm = callTest tests/gnome3-gdm.nix {};
-  tests.grafama = callTest tests/grafana.nix {};
+  tests.grafana = callTest tests/grafana.nix {};
   tests.graphite = callTest tests/graphite.nix {};
   tests.hardened = callTest tests/hardened.nix { };
   tests.hibernate = callTest tests/hibernate.nix {};
diff --git a/nixos/tests/gitolite.nix b/nixos/tests/gitolite.nix
index 643ea4ff03ef..4b4e081acc5f 100644
--- a/nixos/tests/gitolite.nix
+++ b/nixos/tests/gitolite.nix
@@ -11,7 +11,7 @@ let
     -----END OPENSSH PRIVATE KEY-----
   '';
 
-  adminPublicKey = pkgs.writeText "id_ed25519.pub" ''
+  adminPublicKey = ''
     ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client
   '';
 
@@ -62,7 +62,7 @@ in
       {
         services.gitolite = {
           enable = true;
-          adminPubkey = builtins.readFile adminPublicKey;
+          adminPubkey = adminPublicKey;
         };
         services.openssh.enable = true;
       };