summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorFrederik Rietdijk <fridh@fridh.nl>2019-04-29 13:46:20 +0200
committerFrederik Rietdijk <fridh@fridh.nl>2019-04-29 13:46:20 +0200
commit2f936f85d8e1b06de4998fa5ed4b112fd96559b1 (patch)
treea17510968c4241bb8c6566433d4cf8ddabfa83b3 /nixos
parentee5f4636b09abe2dda532d2f79789f7bb3ae0292 (diff)
parent6dabc28cd0713f93d97fd86362d176f660f145af (diff)
downloadnixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar
nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.gz
nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.bz2
nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.lz
nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.xz
nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.zst
nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.zip
Merge master into staging-next
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/x-windows.xml26
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.xml8
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/programs/browserpass.nix10
-rw-r--r--nixos/modules/programs/sway.nix6
-rw-r--r--nixos/modules/rename.nix3
-rw-r--r--nixos/modules/services/logging/journalwatch.nix42
-rw-r--r--nixos/modules/services/misc/gitea.nix15
-rw-r--r--nixos/modules/services/misc/jellyfin.nix60
-rw-r--r--nixos/modules/services/misc/nzbget.nix96
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/networking/ejabberd.nix28
-rw-r--r--nixos/modules/services/networking/zeronet.nix13
-rw-r--r--nixos/modules/services/security/sks.nix24
-rw-r--r--nixos/modules/services/system/earlyoom.nix15
-rw-r--r--nixos/modules/services/web-apps/documize.nix171
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix5
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl18
-rw-r--r--nixos/modules/system/boot/luksroot.nix3
-rw-r--r--nixos/tests/all-tests.nix2
-rw-r--r--nixos/tests/ejabberd.nix262
-rw-r--r--nixos/tests/jellyfin.nix16
-rw-r--r--nixos/tests/nzbget.nix26
-rw-r--r--nixos/tests/prosody.nix68
-rw-r--r--nixos/tests/xmpp-sendmessage.nix46
25 files changed, 721 insertions, 245 deletions
diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml
index 3bcb288b5eb..798d1fbdfd8 100644
--- a/nixos/doc/manual/configuration/x-windows.xml
+++ b/nixos/doc/manual/configuration/x-windows.xml
@@ -67,6 +67,32 @@
 <xref linkend="opt-hardware.opengl.driSupport32Bit"/> = true;
 </programlisting>
  </para>
+ <simplesect xml:id="sec-x11-auto-login">
+  <title>Auto-login</title>
+  <para>
+  The x11 login screen can be skipped entirely, automatically logging you into
+  your window manager and desktop environment when you boot your computer.
+  </para>
+  <para>
+  This is especially helpful if you have disk encryption enabled. Since you
+  already have to provide a password to decrypt your disk, entering a second
+  password to login can be redundant.
+  </para>
+  <para>
+  To enable auto-login, you need to define your default window manager and
+  desktop environment. If you wanted no desktop environment and i3 as your your
+  window manager, you'd define:
+<programlisting>
+<xref linkend="opt-services.xserver.desktopManager.default"/> = "none";
+<xref linkend="opt-services.xserver.windowManager.default"/> = "i3";
+</programlisting>
+  And, finally, to enable auto-login for a user <literal>johndoe</literal>:
+<programlisting>
+<xref linkend="opt-services.xserver.displayManager.auto.enable"/> = true;
+<xref linkend="opt-services.xserver.displayManager.auto.user"/> = "johndoe";
+</programlisting>
+  </para>
+ </simplesect>
  <simplesect xml:id="sec-x11-graphics-cards-nvidia">
   <title>Proprietary NVIDIA drivers</title>
   <para>
diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml
index 2dc2dc41ff2..b16297da7ce 100644
--- a/nixos/doc/manual/release-notes/rl-1909.xml
+++ b/nixos/doc/manual/release-notes/rl-1909.xml
@@ -83,6 +83,14 @@
       The same applies to ModemManager where modem-manager.service is now called ModemManager.service again.
     </para>
    </listitem>
+   <listitem>
+    <para>
+      The <option>services.nzbget.configFile</option> and <option>services.nzbget.openFirewall</option>
+      options were removed as they are managed internally by the nzbget. The
+      <option>services.nzbget.dataDir</option> option hadn't actually been used by
+      the module for some time and so was removed as cleanup.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index fca4a20eee6..56c44a43c6e 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -414,6 +414,7 @@
   ./services/misc/ihaskell.nix
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
+  ./services/misc/jellyfin.nix
   ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
   ./services/misc/lidarr.nix
diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
index 47b9d1ccb1f..e1456d3c184 100644
--- a/nixos/modules/programs/browserpass.nix
+++ b/nixos/modules/programs/browserpass.nix
@@ -26,16 +26,6 @@ with lib;
       # brave
       "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium";
       "opt/brave/policies/managed/${appId}".source = source "policies/chromium";
-    }
-    # As with the v2 backwards compatibility in the pkgs.browserpass
-    # declaration, this part can be removed once the browser extension
-    # auto-updates to v3 (planned 2019-04-13, see
-    # https://github.com/browserpass/browserpass-native/issues/31)
-    // {
-      "chromium/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
-      "chromium/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json";
-      "opt/chrome/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
-      "opt/chrome/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json";
     };
     nixpkgs.config.firefox.enableBrowserpass = true;
   };
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index 457faaa3c10..b4f03151cdc 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -78,9 +78,9 @@ in {
     environment = {
       systemPackages = [ swayJoined ] ++ cfg.extraPackages;
       etc = {
-        "sway/config".source = "${swayPackage}/etc/sway/config";
-        #"sway/security.d".source = "${swayPackage}/etc/sway/security.d/";
-        #"sway/config.d".source = "${swayPackage}/etc/sway/config.d/";
+        "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config";
+        #"sway/security.d".source = mkOptionDefault "${swayPackage}/etc/sway/security.d/";
+        #"sway/config.d".source = mkOptionDefault "${swayPackage}/etc/sway/config.d/";
       };
     };
     security.pam.services.swaylock = {};
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index f6c112d9cfa..70807ccf7cd 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -45,6 +45,9 @@ with lib;
     (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.")
     (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.")
     (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.")
     (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.")
     (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.")
     (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
diff --git a/nixos/modules/services/logging/journalwatch.nix b/nixos/modules/services/logging/journalwatch.nix
index d0824df38ae..576c646c0f5 100644
--- a/nixos/modules/services/logging/journalwatch.nix
+++ b/nixos/modules/services/logging/journalwatch.nix
@@ -4,6 +4,8 @@ with lib;
 let
   cfg = config.services.journalwatch;
   user = "journalwatch";
+  # for journal access
+  group = "systemd-journal";
   dataDir = "/var/lib/${user}";
 
   journalwatchConfig = pkgs.writeText "config" (''
@@ -31,6 +33,17 @@ let
 
   '') filterBlocks);
 
+  # can't use joinSymlinks directly, because when we point $XDG_CONFIG_HOME
+  # to the /nix/store path, we still need the subdirectory "journalwatch" inside that
+  # to match journalwatch's expectations
+  journalwatchConfigDir = pkgs.runCommand "journalwatch-config"
+    { preferLocalBuild = true; allowSubstitutes = false; }
+    ''
+      mkdir -p $out/journalwatch
+      ln -sf ${journalwatchConfig} $out/journalwatch/config
+      ln -sf ${journalwatchPatterns} $out/journalwatch/patterns
+    '';
+
 
 in {
   options = {
@@ -199,33 +212,38 @@ in {
 
     users.users.${user} = {
       isSystemUser = true;
-      createHome = true;
       home = dataDir;
-      # for journal access
-      group = "systemd-journal";
+      group = group;
     };
 
+    systemd.tmpfiles.rules = [
+      # present since NixOS 19.09: remove old stateful symlink join directory,
+      # which has been replaced with the journalwatchConfigDir store path
+      "R ${dataDir}/config"
+    ];
+
     systemd.services.journalwatch = {
+
       environment = {
+        # journalwatch stores the last processed timpestamp here
+        # the share subdirectory is historic now that config home lives in /nix/store,
+        # but moving this in a backwards-compatible way is much more work than what's justified
+        # for cleaning that up.
         XDG_DATA_HOME = "${dataDir}/share";
-        XDG_CONFIG_HOME = "${dataDir}/config";
+        XDG_CONFIG_HOME = journalwatchConfigDir;
       };
       serviceConfig = {
         User = user;
+        Group = group;
         Type = "oneshot";
-        PermissionsStartOnly = true;
+        # requires a relative directory name to create beneath /var/lib
+        StateDirectory = user;
+        StateDirectoryMode = 0750;
         ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
         # lowest CPU and IO priority, but both still in best-effort class to prevent starvation
         Nice=19;
         IOSchedulingPriority=7;
       };
-      preStart = ''
-        chown -R ${user}:systemd-journal ${dataDir}
-        chmod -R u+rwX,go-w ${dataDir}
-        mkdir -p ${dataDir}/config/journalwatch
-        ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config
-        ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns
-      '';
     };
 
     systemd.timers.journalwatch = {
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index be4d3871978..6fd4183bd6b 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -8,6 +8,7 @@ let
   pg = config.services.postgresql;
   useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
+  useSqlite = cfg.database.type == "sqlite3";
   configFile = pkgs.writeText "app.ini" ''
     APP_NAME = ${cfg.appName}
     RUN_USER = ${cfg.user}
@@ -15,11 +16,15 @@ let
 
     [database]
     DB_TYPE = ${cfg.database.type}
-    HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
-    NAME = ${cfg.database.name}
-    USER = ${cfg.database.user}
-    PASSWD = #dbpass#
-    PATH = ${cfg.database.path}
+    ${optionalString (usePostgresql || useMysql) ''
+      HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
+      NAME = ${cfg.database.name}
+      USER = ${cfg.database.user}
+      PASSWD = #dbpass#
+    ''}
+    ${optionalString useSqlite ''
+      PATH = ${cfg.database.path}
+    ''}
     ${optionalString usePostgresql ''
       SSL_MODE = disable
     ''}
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
new file mode 100644
index 00000000000..7f38dd0ff23
--- /dev/null
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -0,0 +1,60 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jellyfin;
+in
+{
+  options = {
+    services.jellyfin = {
+      enable = mkEnableOption "Jellyfin Media Server";
+
+      user = mkOption {
+        type = types.str;
+        default = "jellyfin";
+        description = "User account under which Jellyfin runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "jellyfin";
+        description = "Group under which jellyfin runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.jellyfin = {
+      description = "Jellyfin Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = rec {
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = "jellyfin";
+        CacheDirectory = "jellyfin";
+        ExecStart = "${pkgs.jellyfin}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "jellyfin") {
+      jellyfin.group = cfg.group;
+    };
+
+    users.groups = mkIf (cfg.group == "jellyfin") {
+      jellyfin = {};
+    };
+
+    assertions = [
+      {
+        assertion = !config.services.emby.enable;
+        message = "Emby and Jellyfin are incompatible, you cannot enable both";
+      }
+    ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ minijackson ];
+}
diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix
index 6ab98751c57..eb7b4c05d82 100644
--- a/nixos/modules/services/misc/nzbget.nix
+++ b/nixos/modules/services/misc/nzbget.nix
@@ -4,32 +4,34 @@ with lib;
 
 let
   cfg = config.services.nzbget;
-  dataDir = builtins.dirOf cfg.configFile;
-in {
-  options = {
-    services.nzbget = {
-      enable = mkEnableOption "NZBGet";
+  pkg = pkgs.nzbget;
+  stateDir = "/var/lib/nzbget";
+  configFile = "${stateDir}/nzbget.conf";
+  configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.nzbget;
-        defaultText = "pkgs.nzbget";
-        description = "The NZBGet package to use";
-      };
+  nixosOpts = {
+    # allows nzbget to run as a "simple" service
+    OutputMode = "loggable";
+    # use journald for logging
+    WriteLog = "none";
+    ErrorTarget = "screen";
+    WarningTarget = "screen";
+    InfoTarget = "screen";
+    DetailTarget = "screen";
+    # required paths
+    ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
+    WebDir = "${pkg}/share/nzbget/webui";
+    # nixos handles package updates
+    UpdateCheck = "none";
+  };
 
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/nzbget";
-        description = "The directory where NZBGet stores its configuration files.";
-      };
+in
+{
+  # interface
 
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Open ports in the firewall for the NZBGet web interface
-        '';
-      };
+  options = {
+    services.nzbget = {
+      enable = mkEnableOption "NZBGet";
 
       user = mkOption {
         type = types.str;
@@ -42,15 +44,11 @@ in {
         default = "nzbget";
         description = "Group under which NZBGet runs";
       };
-
-      configFile = mkOption {
-        type = types.str;
-        default = "/var/lib/nzbget/nzbget.conf";
-        description = "Path for NZBGet's config file. (If this doesn't exist, the default config template is copied here.)";
-      };
     };
   };
 
+  # implementation
+
   config = mkIf cfg.enable {
     systemd.services.nzbget = {
       description = "NZBGet Daemon";
@@ -61,50 +59,26 @@ in {
         p7zip
       ];
       preStart = ''
-        cfgtemplate=${cfg.package}/share/nzbget/nzbget.conf
-        if [ ! -f ${cfg.configFile} ]; then
-          echo "${cfg.configFile} not found. Copying default config $cfgtemplate to ${cfg.configFile}"
-          install -m 0700 $cfgtemplate ${cfg.configFile}
-          echo "Setting temporary \$MAINDIR variable in default config required in order to allow nzbget to complete initial start"
-          echo "Remember to change this to a proper value once NZBGet startup has been completed"
-          sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' ${cfg.configFile}
+        if [ ! -f ${configFile} ]; then
+          ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
         fi
       '';
 
-      script = ''
-        args="--daemon --configfile ${cfg.configFile}"
-        # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time.
-        # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to
-        # the currently installed nzbget derivation.
-        cfgfallback () {
-          local hit=`grep -Po "(?<=^$1=).*+" "${cfg.configFile}" | sed 's/[ \t]*$//'` # Strip trailing whitespace
-          ( test $hit && test -e $hit ) || {
-            echo "In ${cfg.configFile}, valid $1 not found; falling back to $1=$2"
-            args+=" -o $1=$2"
-          }
-        }
-        cfgfallback ConfigTemplate ${cfg.package}/share/nzbget/nzbget.conf
-        cfgfallback WebDir ${cfg.package}/share/nzbget/webui
-        ${cfg.package}/bin/nzbget $args
-      '';
-
       serviceConfig = {
-        StateDirectory = dataDir;
-        StateDirectoryMode = "0700";
-        Type = "forking";
+        StateDirectory = "nzbget";
+        StateDirectoryMode = "0750";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
+        UMask = "0002";
         Restart = "on-failure";
+        ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
+        ExecStop = "${pkg}/bin/nzbget --quit";
       };
     };
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 8989 ];
-    };
-
     users.users = mkIf (cfg.user == "nzbget") {
       nzbget = {
+        home = stateDir;
         group = cfg.group;
         uid = config.ids.uids.nzbget;
       };
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 85879cfe0b3..5d3f2e6ac28 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -177,7 +177,7 @@ let
       folder = mkOption {
         type = types.str;
         default = "";
-        description = "Add dashboards to the speciied folder";
+        description = "Add dashboards to the specified folder";
       };
       type = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index ef5e2cee6f2..6a38f85c48a 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -11,7 +11,7 @@ let
     ${cfg.ctlConfig}
   '';
 
-  ectl = ''${cfg.package}/bin/ejabberdctl ${if cfg.configFile == null then "" else "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
+  ectl = ''${cfg.package}/bin/ejabberdctl ${optionalString (cfg.configFile != null) "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
 
   dumps = lib.escapeShellArgs cfg.loadDumps;
 
@@ -111,28 +111,17 @@ in {
       description = "ejabberd server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      path = [ pkgs.findutils pkgs.coreutils pkgs.runit ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
+      path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
 
       serviceConfig = {
-        ExecStart = ''${ectl} foreground'';
-        # FIXME: runit is used for `chpst` -- can we get rid of this?
-        ExecStop = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} stop'';
-        ExecReload = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} reload_config'';
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = true;
+        ExecStart = "${ectl} foreground";
+        ExecStop = "${ectl} stop";
+        ExecReload = "${ectl} reload_config";
       };
 
       preStart = ''
-        mkdir -p -m750 "${cfg.logsDir}"
-        chown "${cfg.user}:${cfg.group}" "${cfg.logsDir}"
-
-        mkdir -p -m750 "/var/lock/ejabberdctl"
-        chown "${cfg.user}:${cfg.group}" "/var/lock/ejabberdctl"
-
-        mkdir -p -m750 "${cfg.spoolDir}"
-        chown -R "${cfg.user}:${cfg.group}" "${cfg.spoolDir}"
-
         if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
           touch "${cfg.spoolDir}/.firstRun"
         fi
@@ -149,13 +138,18 @@ in {
           for src in ${dumps}; do
             find "$src" -type f | while read dump; do
               echo "Loading configuration dump at $dump"
-              chpst -u "${cfg.user}:${cfg.group}" ${ectl} load "$dump"
+              ${ectl} load "$dump"
             done
           done
         fi
       '';
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logsDir}' 0750 ${cfg.user} ${cfg.group} -"
+      "d '${cfg.spoolDir}' 0700 ${cfg.user} ${cfg.group} -"
+    ];
+
     security.pam.services.ejabberd = {};
 
   };
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 8b60799891c..611a51c74ce 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -5,13 +5,15 @@ let
 
   zConfFile = pkgs.writeTextFile {
     name = "zeronet.conf";
-    
+
     text = ''
       [global]
       data_dir = ${cfg.dataDir}
       log_dir = ${cfg.logDir}
     '' + lib.optionalString (cfg.port != null) ''
       ui_port = ${toString cfg.port}
+    '' + lib.optionalString (cfg.fileserverPort != null) ''
+      fileserver_port = ${toString cfg.fileserverPort}
     '' + lib.optionalString (cfg.torAlways) ''
       tor = always
     '' + cfg.extraConfig;
@@ -41,6 +43,15 @@ in with lib; {
       description = "Optional zeronet web UI port.";
     };
 
+    fileserverPort = mkOption {
+      # Not optional: when absent zeronet tries to write one to the
+      # read-only config file and crashes
+      type = types.int;
+      default = 12261;
+      example = 12261;
+      description = "Zeronet fileserver port.";
+    };
+
     tor = mkOption {
       type = types.bool;
       default = false;
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 8136a5c763a..1b7a2ad1398 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -116,20 +116,22 @@ in {
           ${lib.optionalString (cfg.webroot != null)
             "ln -sfT \"${cfg.webroot}\" web"}
           mkdir -p dump
-          # Check that both database configs are symlinks before overwriting them
-          if [ -e KDB/DB_CONFIG ] && [ ! -L KBD/DB_CONFIG ]; then
-            echo "KDB/DB_CONFIG exists but is not a symlink." >&2
-            exit 1
-          fi
-          if [ -e PTree/DB_CONFIG ] && [ ! -L PTree/DB_CONFIG ]; then
-            echo "PTree/DB_CONFIG exists but is not a symlink." >&2
-            exit 1
-          fi
-          ln -sf ${dbConfig} KDB/DB_CONFIG
-          ln -sf ${dbConfig} PTree/DB_CONFIG
           ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
           ${sksPkg}/bin/sks cleandb || true
           ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+          # Check that both database configs are symlinks before overwriting them
+          # TODO: The initial build will be without DB_CONFIG, but this will
+          # hopefully not cause any significant problems. It might be better to
+          # create both directories manually but we have to check that this does
+          # not affect the initial build of the DB.
+          for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do
+            if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then
+              echo "$CONFIG_FILE exists but is not a symlink." >&2
+              echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2
+              exit 1
+            fi
+            ln -sf ${dbConfig} $CONFIG_FILE
+          done
         '';
         serviceConfig = {
           WorkingDirectory = "~";
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index daa46838bfa..39d1bf274bd 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -63,6 +63,17 @@ in
           Enable debugging messages.
         '';
       };
+
+      notificationsCommand = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "sudo -u example_user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send";
+        description = ''
+          Command used to send notifications.
+
+          See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
+        '';
+      };
     };
   };
 
@@ -88,7 +99,9 @@ in
           -s ${toString ecfg.freeSwapThreshold} \
           ${optionalString ecfg.useKernelOOMKiller "-k"} \
           ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \
-          ${optionalString ecfg.enableDebugInfo "-d"}
+          ${optionalString ecfg.enableDebugInfo "-d"} \
+          ${optionalString (ecfg.notificationsCommand != null)
+            "-N ${escapeShellArg ecfg.notificationsCommand}"}
         '';
       };
     };
diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
index 206617b0e5a..37359869cb6 100644
--- a/nixos/modules/services/web-apps/documize.nix
+++ b/nixos/modules/services/web-apps/documize.nix
@@ -3,65 +3,136 @@
 with lib;
 
 let
-
   cfg = config.services.documize;
 
-in
+  mkParams = optional: concatMapStrings (name: let
+    predicate = optional -> cfg.${name} != null;
+    template = " -${name} '${toString cfg.${name}}'";
+  in optionalString predicate template);
 
-  {
-    options.services.documize = {
-      enable = mkEnableOption "Documize Wiki";
+in {
+  options.services.documize = {
+    enable = mkEnableOption "Documize Wiki";
 
-      offline = mkEnableOption "Documize offline mode";
+    package = mkOption {
+      type = types.package;
+      default = pkgs.documize-community;
+      description = ''
+        Which package to use for documize.
+      '';
+    };
 
-      package = mkOption {
-        default = pkgs.documize-community;
-        type = types.package;
-        description = ''
-          Which package to use for documize.
-        '';
-      };
+    salt = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "3edIYV6c8B28b19fh";
+      description = ''
+        The salt string used to encode JWT tokens, if not set a random value will be generated.
+      '';
+    };
 
-      db = mkOption {
-        type = types.str;
-        example = "host=localhost port=5432 sslmode=disable user=admin password=secret dbname=documize";
-        description = ''
-          The DB connection string to use for the database.
-        '';
-      };
+    cert = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>cert.pem</filename> file used for https.
+      '';
+    };
 
-      dbtype = mkOption {
-        type = types.enum [ "postgresql" "percona" "mariadb" "mysql" ];
-        description = ''
-          Which database to use for storage.
-        '';
-      };
+    key = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>key.pem</filename> file used for https.
+      '';
+    };
 
-      port = mkOption {
-        type = types.port;
-        example = 3000;
-        description = ''
-          Which TCP port to serve.
-        '';
-      };
+    port = mkOption {
+      type = types.port;
+      default = 5001;
+      description = ''
+        The http/https port number.
+      '';
+    };
+
+    forcesslport = mkOption {
+      type = types.nullOr types.port;
+      default = null;
+      description = ''
+        Redirect given http port number to TLS.
+      '';
+    };
+
+    offline = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Set <literal>true</literal> for offline mode.
+      '';
+      apply = v: if true == v then 1 else 0;
+    };
+
+    dbtype = mkOption {
+      type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ];
+      default = "postgresql";
+      description = ''
+        Specify the database provider:
+        <simplelist type='inline'>
+          <member><literal>mysql</literal></member>
+          <member><literal>percona</literal></member>
+          <member><literal>mariadb</literal></member>
+          <member><literal>postgresql</literal></member>
+          <member><literal>sqlserver</literal></member>
+        </simplelist>
+      '';
     };
 
-    config = mkIf cfg.enable {
-      systemd.services.documize-server = {
-        wantedBy = [ "multi-user.target" ];
-
-        script = ''
-          ${cfg.package}/bin/documize \
-            -db "${cfg.db}" \
-            -dbtype ${cfg.dbtype} \
-            -port ${toString cfg.port} \
-            -offline ${if cfg.offline then "1" else "0"}
-        '';
-
-        serviceConfig = {
-          Restart = "always";
-          DynamicUser = "yes";
-        };
+    db = mkOption {
+      type = types.str;
+      description = ''
+        Database specific connection string for example:
+        <itemizedlist>
+        <listitem><para>MySQL/Percona/MariaDB:
+          <literal>user:password@tcp(host:3306)/documize</literal>
+        </para></listitem>
+        <listitem><para>MySQLv8+:
+          <literal>user:password@tcp(host:3306)/documize?allowNativePasswords=true</literal>
+        </para></listitem>
+        <listitem><para>PostgreSQL:
+          <literal>host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable</literal>
+        </para></listitem>
+        <listitem><para>MSSQL:
+          <literal>sqlserver://username:password@localhost:1433?database=Documize</literal> or
+          <literal>sqlserver://sa@localhost/SQLExpress?database=Documize</literal>
+        </para></listitem>
+        </itemizedlist>
+      '';
+    };
+
+    location = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        reserved
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.documize-server = {
+      description = "Documize Wiki";
+      documentation = [ https://documize.com/ ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = concatStringsSep " " [
+          "${cfg.package}/bin/documize"
+          (mkParams false [ "db" "dbtype" "port" ])
+          (mkParams true [ "offline" "location" "forcesslport" "key" "cert" "salt" ])
+        ];
+        Restart = "always";
+        DynamicUser = "yes";
       };
     };
-  }
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index c9b0669e7ba..0ef55d5f2c0 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -60,12 +60,15 @@ in
         ${cfg.extraSessionCommands}
 
         ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
-          "-c \"${cfg.configFile}\""
+          "-c /etc/i3/config"
         } &
         waitPID=$!
       '';
     }];
     environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+    environment.etc."i3/config" = mkIf (cfg.configFile != null) {
+      source = cfg.configFile;
+    };
   };
 
   imports = [
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 397b308b731..8ff00fa11dc 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -166,24 +166,6 @@ while (my ($unit, $state) = each %{$activePrev}) {
 
     if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
         if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
-            # Ignore (i.e. never stop) these units:
-            if ($unit eq "system.slice") {
-                # TODO: This can be removed a few months after 18.09 is out
-                # (i.e. after everyone switched away from 18.03).
-                # Problem: Restarting (stopping) system.slice would not only
-                # stop X11 but also most system units/services. We obviously
-                # don't want this happening to users when they switch from 18.03
-                # to 18.09 or nixos-unstable.
-                # Reason: The following change in systemd:
-                # https://github.com/systemd/systemd/commit/d8e5a9338278d6602a0c552f01f298771a384798
-                # The commit adds system.slice to the perpetual units, which
-                # means removing the unit file and adding it to the source code.
-                # This is done so that system.slice can't be stopped anymore but
-                # in our case it ironically would cause this script to stop
-                # system.slice because the unit was removed (and an older
-                # systemd version is still running).
-                next;
-            }
             my $unitInfo = parseUnit($prevUnitFile);
             $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes");
         }
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index aa4a5f8abcc..3841074f043 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -87,6 +87,9 @@ let
     mkdir -p /crypt-ramfs
     mount -t ramfs none /crypt-ramfs
 
+    # Cryptsetup locking directory
+    mkdir -p /run/cryptsetup
+
     # For Yubikey salt storage
     mkdir -p /crypt-storage
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 016e695b3ad..5639b2668c3 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -72,6 +72,7 @@ in
   #ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
   ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
   ecryptfs = handleTest ./ecryptfs.nix {};
+  ejabberd = handleTest ./ejabberd.nix {};
   elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
   env = handleTest ./env.nix {};
   etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
@@ -172,6 +173,7 @@ in
   nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
   nsd = handleTest ./nsd.nix {};
+  nzbget = handleTest ./nzbget.nix {};
   openldap = handleTest ./openldap.nix {};
   opensmtpd = handleTest ./opensmtpd.nix {};
   openssh = handleTest ./openssh.nix {};
diff --git a/nixos/tests/ejabberd.nix b/nixos/tests/ejabberd.nix
new file mode 100644
index 00000000000..0691dfe780d
--- /dev/null
+++ b/nixos/tests/ejabberd.nix
@@ -0,0 +1,262 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "ejabberd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ajs124 ];
+  };
+  nodes = {
+    client = { nodes, pkgs, ... }: {
+      environment.systemPackages = [
+        (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; })
+      ];
+    };
+    server = { config, pkgs, ... }: {
+      networking.extraHosts = ''
+        ${config.networking.primaryIPAddress} example.com
+      '';
+
+      services.ejabberd = {
+        enable = true;
+        configFile = "/etc/ejabberd.yml";
+      };
+
+      environment.etc."ejabberd.yml" = {
+        user = "ejabberd";
+        mode = "0600";
+        text = ''
+          loglevel: 3
+
+          hosts:
+            - "example.com"
+
+          listen:
+            -
+              port: 5222
+              module: ejabberd_c2s
+              zlib: false
+              max_stanza_size: 65536
+              shaper: c2s_shaper
+              access: c2s
+            -
+              port: 5269
+              ip: "::"
+              module: ejabberd_s2s_in
+            -
+              port: 5347
+              ip: "127.0.0.1"
+              module: ejabberd_service
+              access: local
+              shaper_rule: fast
+              ip: "127.0.0.1"
+
+          ## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text
+          ## password storage (see auth_password_format option).
+          disable_sasl_mechanisms: "digest-md5"
+
+          ## Outgoing S2S options
+          ## Preferred address families (which to try first) and connect timeout
+          ## in seconds.
+          outgoing_s2s_families:
+             - ipv4
+             - ipv6
+
+          ## auth_method: Method used to authenticate the users.
+          ## The default method is the internal.
+          ## If you want to use a different method,
+          ## comment this line and enable the correct ones.
+          auth_method: internal
+
+          ## Store the plain passwords or hashed for SCRAM:
+          ## auth_password_format: plain
+          auth_password_format: scram
+
+          ###'  TRAFFIC SHAPERS
+          shaper:
+            # in B/s
+            normal: 1000000
+            fast: 50000000
+
+          ## This option specifies the maximum number of elements in the queue
+          ## of the FSM. Refer to the documentation for details.
+          max_fsm_queue: 1000
+
+          ###'   ACCESS CONTROL LISTS
+          acl:
+            ## The 'admin' ACL grants administrative privileges to XMPP accounts.
+            ## You can put here as many accounts as you want.
+            admin:
+               user:
+                 - "root": "example.com"
+
+            ## Local users: don't modify this.
+            local:
+              user_regexp: ""
+
+            ## Loopback network
+            loopback:
+              ip:
+                - "127.0.0.0/8"
+                - "::1/128"
+                - "::FFFF:127.0.0.1/128"
+
+          ###'  SHAPER RULES
+          shaper_rules:
+            ## Maximum number of simultaneous sessions allowed for a single user:
+            max_user_sessions: 10
+            ## Maximum number of offline messages that users can have:
+            max_user_offline_messages:
+              - 5000: admin
+              - 1024
+            ## For C2S connections, all users except admins use the "normal" shaper
+            c2s_shaper:
+              - none: admin
+              - normal
+            ## All S2S connections use the "fast" shaper
+            s2s_shaper: fast
+
+          ###'  ACCESS RULES
+          access_rules:
+            ## This rule allows access only for local users:
+            local:
+              - allow: local
+            ## Only non-blocked users can use c2s connections:
+            c2s:
+              - deny: blocked
+              - allow
+            ## Only admins can send announcement messages:
+            announce:
+              - allow: admin
+            ## Only admins can use the configuration interface:
+            configure:
+              - allow: admin
+            ## Only accounts of the local ejabberd server can create rooms:
+            muc_create:
+              - allow: local
+            ## Only accounts on the local ejabberd server can create Pubsub nodes:
+            pubsub_createnode:
+              - allow: local
+            ## In-band registration allows registration of any possible username.
+            ## To disable in-band registration, replace 'allow' with 'deny'.
+            register:
+              - allow
+            ## Only allow to register from localhost
+            trusted_network:
+              - allow: loopback
+
+          ## ===============
+          ## API PERMISSIONS
+          ## ===============
+          ##
+          ## This section allows you to define who and using what method
+          ## can execute commands offered by ejabberd.
+          ##
+          ## By default "console commands" section allow executing all commands
+          ## issued using ejabberdctl command, and "admin access" section allows
+          ## users in admin acl that connect from 127.0.0.1 to  execute all
+          ## commands except start and stop with any available access method
+          ## (ejabberdctl, http-api, xmlrpc depending what is enabled on server).
+          ##
+          ## If you remove "console commands" there will be one added by
+          ## default allowing executing all commands, but if you just change
+          ## permissions in it, version from config file will be used instead
+          ## of default one.
+          ##
+          api_permissions:
+            "console commands":
+              from:
+                - ejabberd_ctl
+              who: all
+              what: "*"
+
+          language: "en"
+
+          ###'  MODULES
+          ## Modules enabled in all ejabberd virtual hosts.
+          modules:
+            mod_adhoc: {}
+            mod_announce: # recommends mod_adhoc
+              access: announce
+            mod_blocking: {} # requires mod_privacy
+            mod_caps: {}
+            mod_carboncopy: {}
+            mod_client_state: {}
+            mod_configure: {} # requires mod_adhoc
+            ## mod_delegation: {} # for xep0356
+            mod_echo: {}
+            #mod_irc:
+            #  host: "irc.@HOST@"
+            #  default_encoding: "utf-8"
+            ## mod_bosh: {}
+            ## mod_http_fileserver:
+            ##   docroot: "/var/www"
+            ##   accesslog: "/var/log/ejabberd/access.log"
+            #mod_http_upload:
+            #  thumbnail: false # otherwise needs the identify command from ImageMagick installed
+            #  put_url: "https://@HOST@:5444"
+            ##   # docroot: "@HOME@/upload"
+            #mod_http_upload_quota:
+            #  max_days: 14
+            mod_last: {}
+            ## XEP-0313: Message Archive Management
+            ## You might want to setup a SQL backend for MAM because the mnesia database is
+            ## limited to 2GB which might be exceeded on large servers
+            mod_mam: {}
+            mod_muc:
+              host: "muc.@HOST@"
+              access:
+                - allow
+              access_admin:
+                - allow: admin
+              access_create: muc_create
+              access_persistent: muc_create
+            mod_muc_admin: {}
+            mod_muc_log: {}
+            mod_offline:
+              access_max_user_messages: max_user_offline_messages
+            mod_ping: {}
+            ## mod_pres_counter:
+            ##   count: 5
+            ##   interval: 60
+            mod_privacy: {}
+            mod_private: {}
+            mod_roster:
+                versioning: true
+            mod_shared_roster: {}
+            mod_stats: {}
+            mod_time: {}
+            mod_vcard:
+              search: false
+            mod_vcard_xupdate: {}
+            ## Convert all avatars posted by Android clients from WebP to JPEG
+            mod_avatar: {}
+            #  convert:
+            #    webp: jpeg
+            mod_version: {}
+            mod_stream_mgmt: {}
+            ##   The module for S2S dialback (XEP-0220). Please note that you cannot
+            ##   rely solely on dialback if you want to federate with other servers,
+            ##   because a lot of servers have dialback disabled and instead rely on
+            ##   PKIX authentication. Make sure you have proper certificates installed
+            ##   and check your accessibility at https://check.messaging.one/
+            mod_s2s_dialback: {}
+            mod_pubsub:
+              plugins:
+                - "pep"
+            mod_push: {}
+        '';
+      };
+
+      networking.firewall.enable = false;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    $server->waitForUnit('ejabberd.service');
+    $server->succeed('su ejabberd -s $(which ejabberdctl) status|grep started') =~ /ejabberd is running/;
+    $server->succeed('su ejabberd -s $(which ejabberdctl) register azurediamond example.com hunter2');
+    $server->succeed('su ejabberd -s $(which ejabberdctl) register cthon98 example.com nothunter2');
+    $server->fail('su ejabberd -s $(which ejabberdctl) register asdf wrong.domain');
+    $client->succeed('send-message');
+    $server->succeed('su ejabberd -s $(which ejabberdctl) unregister cthon98 example.com');
+    $server->succeed('su ejabberd -s $(which ejabberdctl) unregister azurediamond example.com');
+  '';
+})
diff --git a/nixos/tests/jellyfin.nix b/nixos/tests/jellyfin.nix
new file mode 100644
index 00000000000..b60c6eb94f4
--- /dev/null
+++ b/nixos/tests/jellyfin.nix
@@ -0,0 +1,16 @@
+import ./make-test.nix ({ lib, ...}:
+
+{
+  name = "jellyfin";
+  meta.maintainers = with lib.maintainers; [ minijackson ];
+
+  machine =
+    { ... }:
+    { services.jellyfin.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('jellyfin.service');
+    $machine->waitForOpenPort('8096');
+    $machine->succeed("curl --fail http://localhost:8096/");
+  '';
+})
diff --git a/nixos/tests/nzbget.nix b/nixos/tests/nzbget.nix
new file mode 100644
index 00000000000..042ccec98cf
--- /dev/null
+++ b/nixos/tests/nzbget.nix
@@ -0,0 +1,26 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "nzbget";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aanderse flokli ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.nzbget.enable = true;
+
+      # hack, don't add (unfree) unrar to nzbget's path,
+      # so we can run this test in CI
+      systemd.services.nzbget.path = pkgs.stdenv.lib.mkForce [ pkgs.p7zip ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("nzbget.service");
+    $server->waitForUnit("network.target");
+    $server->waitForOpenPort(6789);
+    $server->succeed("curl -s -u nzbget:tegbzn6789 http://127.0.0.1:6789 | grep -q 'This file is part of nzbget'");
+    $server->succeed("${pkgs.nzbget}/bin/nzbget -n -o ControlIP=127.0.0.1 -o ControlPort=6789 -o ControlPassword=tegbzn6789 -V");
+  '';
+})
diff --git a/nixos/tests/prosody.nix b/nixos/tests/prosody.nix
index 61ae5bb38ed..a39bae7898d 100644
--- a/nixos/tests/prosody.nix
+++ b/nixos/tests/prosody.nix
@@ -9,70 +9,30 @@ import ./make-test.nix {
       extraConfig = ''
         storage = "sql"
       '';
+      virtualHosts.test = {
+        domain = "example.com";
+        enabled = true;
+      };
     };
-    environment.systemPackages = let
-      sendMessage = pkgs.writeScriptBin "send-message" ''
-        #!/usr/bin/env python3
-        # Based on the sleekxmpp send_client example, look there for more details:
-        # https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py
-        import sleekxmpp
-
-        class SendMsgBot(sleekxmpp.ClientXMPP):
-            """
-            A basic SleekXMPP bot that will log in, send a message,
-            and then log out.
-            """
-            def __init__(self, jid, password, recipient, message):
-                sleekxmpp.ClientXMPP.__init__(self, jid, password)
-
-                self.recipient = recipient
-                self.msg = message
-
-                self.add_event_handler("session_start", self.start, threaded=True)
-
-            def start(self, event):
-                self.send_presence()
-                self.get_roster()
-
-                self.send_message(mto=self.recipient,
-                                  mbody=self.msg,
-                                  mtype='chat')
-
-                self.disconnect(wait=True)
-
-
-        if __name__ == '__main__':
-            xmpp = SendMsgBot("test1@localhost", "test1", "test2@localhost", "Hello World!")
-            xmpp.register_plugin('xep_0030') # Service Discovery
-            xmpp.register_plugin('xep_0199') # XMPP Ping
-
-            # TODO: verify certificate
-            # If you want to verify the SSL certificates offered by a server:
-            # xmpp.ca_certs = "path/to/ca/cert"
-
-            if xmpp.connect(('localhost', 5222)):
-                xmpp.process(block=True)
-            else:
-                print("Unable to connect.")
-                sys.exit(1)
-      '';
-    in [ (pkgs.python3.withPackages (ps: [ ps.sleekxmpp ])) sendMessage ];
+    environment.systemPackages = [
+      (pkgs.callPackage ./xmpp-sendmessage.nix {})
+    ];
   };
 
   testScript = ''
     $machine->waitForUnit('prosody.service');
     $machine->succeed('prosodyctl status') =~ /Prosody is running/;
 
-    # set password to 'test' (it's asked twice)
-    $machine->succeed('yes test1 | prosodyctl adduser test1@localhost');
+    # set password to 'nothunter2' (it's asked twice)
+    $machine->succeed('yes nothunter2 | prosodyctl adduser cthon98@example.com');
     # set password to 'y'
-    $machine->succeed('yes | prosodyctl adduser test2@localhost');
-    # correct password to 'test2'
-    $machine->succeed('yes test2 | prosodyctl passwd test2@localhost');
+    $machine->succeed('yes | prosodyctl adduser azurediamond@example.com');
+    # correct password to 'hunter2'
+    $machine->succeed('yes hunter2 | prosodyctl passwd azurediamond@example.com');
 
     $machine->succeed("send-message");
 
-    $machine->succeed('prosodyctl deluser test1@localhost');
-    $machine->succeed('prosodyctl deluser test2@localhost');
+    $machine->succeed('prosodyctl deluser cthon98@example.com');
+    $machine->succeed('prosodyctl deluser azurediamond@example.com');
   '';
 }
diff --git a/nixos/tests/xmpp-sendmessage.nix b/nixos/tests/xmpp-sendmessage.nix
new file mode 100644
index 00000000000..2a075a01813
--- /dev/null
+++ b/nixos/tests/xmpp-sendmessage.nix
@@ -0,0 +1,46 @@
+{ writeScriptBin, python3, connectTo ? "localhost" }:
+writeScriptBin "send-message" ''
+  #!${(python3.withPackages (ps: [ ps.sleekxmpp ])).interpreter}
+  # Based on the sleekxmpp send_client example, look there for more details:
+  # https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py
+  import sleekxmpp
+
+  class SendMsgBot(sleekxmpp.ClientXMPP):
+      """
+      A basic SleekXMPP bot that will log in, send a message,
+      and then log out.
+      """
+      def __init__(self, jid, password, recipient, message):
+          sleekxmpp.ClientXMPP.__init__(self, jid, password)
+
+          self.recipient = recipient
+          self.msg = message
+
+          self.add_event_handler("session_start", self.start, threaded=True)
+
+      def start(self, event):
+          self.send_presence()
+          self.get_roster()
+
+          self.send_message(mto=self.recipient,
+                            mbody=self.msg,
+                            mtype='chat')
+
+          self.disconnect(wait=True)
+
+
+  if __name__ == '__main__':
+      xmpp = SendMsgBot("cthon98@example.com", "nothunter2", "azurediamond@example.com", "hey, if you type in your pw, it will show as stars")
+      xmpp.register_plugin('xep_0030') # Service Discovery
+      xmpp.register_plugin('xep_0199') # XMPP Ping
+
+      # TODO: verify certificate
+      # If you want to verify the SSL certificates offered by a server:
+      # xmpp.ca_certs = "path/to/ca/cert"
+
+      if xmpp.connect(('${connectTo}', 5222)):
+          xmpp.process(block=True)
+      else:
+          print("Unable to connect.")
+          sys.exit(1)
+''