summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/default.nix71
-rw-r--r--nixos/modules/config/nsswitch.nix40
-rw-r--r--nixos/modules/misc/ids.nix2
-rw-r--r--nixos/modules/module-list.nix2
-rw-r--r--nixos/modules/programs/zsh/zsh.nix2
-rw-r--r--nixos/modules/security/wrappers/default.nix2
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix3
-rw-r--r--nixos/modules/services/databases/mysql.nix8
-rw-r--r--nixos/modules/services/databases/rethinkdb.nix110
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix9
-rw-r--r--nixos/modules/services/networking/networkmanager.nix2
-rw-r--r--nixos/modules/services/networking/toxvpn.nix16
-rw-r--r--nixos/modules/services/web-apps/piwik-doc.xml97
-rw-r--r--nixos/modules/services/web-apps/piwik.nix219
-rw-r--r--nixos/modules/services/web-servers/minio.nix69
-rw-r--r--nixos/modules/services/x11/xserver.nix10
-rw-r--r--nixos/release-combined.nix1
-rw-r--r--nixos/tests/installer.nix39
-rw-r--r--nixos/tests/minio.nix19
19 files changed, 657 insertions, 64 deletions
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index 40d49f1541b3..9413d71a34cf 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -65,7 +65,7 @@ let
       chmod -R u+w .
       ln -s ${modulesDoc} configuration/modules.xml
       ln -s ${optionsDocBook} options-db.xml
-      echo "${version}" > version
+      printf "%s" "${version}" > version
     '';
 
   toc = builtins.toFile "toc.xml"
@@ -94,25 +94,43 @@ let
     "--stringparam chunk.toc ${toc}"
   ];
 
-  olinkDB = runCommand "manual-olinkdb"
+  manual-combined = runCommand "nixos-manual-combined"
     { inherit sources;
       buildInputs = [ libxml2 libxslt ];
+      meta.description = "The NixOS manual as plain docbook XML";
     }
     ''
       ${copySources}
 
+      xmllint --xinclude --output ./manual-combined.xml ./manual.xml
+      xmllint --xinclude --noxincludenode \
+         --output ./man-pages-combined.xml ./man-pages.xml
+
+      xmllint --debug --noout --nonet \
+        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
+        manual-combined.xml
+      xmllint --debug --noout --nonet \
+        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
+        man-pages-combined.xml
+
+
+      mkdir $out
+      cp manual-combined.xml $out/
+      cp man-pages-combined.xml $out/
+    '';
+
+  olinkDB = runCommand "manual-olinkdb"
+    { inherit sources;
+      buildInputs = [ libxml2 libxslt ];
+    }
+    ''
       xsltproc \
         ${manualXsltprocOptions} \
         --stringparam collect.xref.targets only \
         --stringparam targets.filename "$out/manual.db" \
-        --nonet --xinclude \
+        --nonet \
         ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \
-        ./manual.xml
-
-      # Check the validity of the man pages sources.
-      xmllint --noout --nonet --xinclude --noxincludenode \
-        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
-        ./man-pages.xml
+        ${manual-combined}/manual-combined.xml
 
       cat > "$out/olinkdb.xml" <<EOF
       <?xml version="1.0" encoding="utf-8"?>
@@ -158,21 +176,15 @@ in rec {
       allowedReferences = ["out"];
     }
     ''
-      ${copySources}
-
-      # Check the validity of the manual sources.
-      xmllint --noout --nonet --xinclude --noxincludenode \
-        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
-        manual.xml
-
       # Generate the HTML manual.
       dst=$out/share/doc/nixos
       mkdir -p $dst
       xsltproc \
         ${manualXsltprocOptions} \
         --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
-        --nonet --xinclude --output $dst/ \
-        ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl ./manual.xml
+        --nonet --output $dst/ \
+        ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \
+        ${manual-combined}/manual-combined.xml
 
       mkdir -p $dst/images/callouts
       cp ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.gif $dst/images/callouts/
@@ -190,13 +202,6 @@ in rec {
       buildInputs = [ libxml2 libxslt zip ];
     }
     ''
-      ${copySources}
-
-      # Check the validity of the manual sources.
-      xmllint --noout --nonet --xinclude --noxincludenode \
-        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
-        manual.xml
-
       # Generate the epub manual.
       dst=$out/share/doc/nixos
 
@@ -204,10 +209,11 @@ in rec {
         ${manualXsltprocOptions} \
         --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
         --nonet --xinclude --output $dst/epub/ \
-        ${docbook5_xsl}/xml/xsl/docbook/epub/docbook.xsl ./manual.xml
+        ${docbook5_xsl}/xml/xsl/docbook/epub/docbook.xsl \
+        ${manual-combined}/manual-combined.xml
 
       mkdir -p $dst/epub/OEBPS/images/callouts
-      cp -r ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.gif $dst/epub/OEBPS/images/callouts
+      cp -r ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.gif $dst/epub/OEBPS/images/callouts # */
       echo "application/epub+zip" > mimetype
       manual="$dst/nixos-manual.epub"
       zip -0Xq "$manual" mimetype
@@ -227,23 +233,16 @@ in rec {
       allowedReferences = ["out"];
     }
     ''
-      ${copySources}
-
-      # Check the validity of the man pages sources.
-      xmllint --noout --nonet --xinclude --noxincludenode \
-        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
-        ./man-pages.xml
-
       # Generate manpages.
       mkdir -p $out/share/man
-      xsltproc --nonet --xinclude \
+      xsltproc --nonet \
         --param man.output.in.separate.dir 1 \
         --param man.output.base.dir "'$out/share/man/'" \
         --param man.endnotes.are.numbered 0 \
         --param man.break.after.slash 1 \
         --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
         ${docbook5_xsl}/xml/xsl/docbook/manpages/docbook.xsl \
-        ./man-pages.xml
+        ${manual-combined}/man-pages-combined.xml
     '';
 
 }
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index d541fff140eb..16c43a99ad56 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -6,24 +6,29 @@ with lib;
 
 let
 
-  inherit (config.services.avahi) nssmdns;
-  inherit (config.services.samba) nsswins;
-  ldap = (config.users.ldap.enable && config.users.ldap.nsswitch);
-  sssd = config.services.sssd.enable;
-  resolved = config.services.resolved.enable;
-
-  hostArray = [ "files" "mymachines" ]
+  # only with nscd up and running we can load NSS modules that are not integrated in NSS
+  canLoadExternalModules = config.services.nscd.enable;
+  myhostname = canLoadExternalModules;
+  mymachines = canLoadExternalModules;
+  nssmdns = canLoadExternalModules && config.services.avahi.nssmdns;
+  nsswins = canLoadExternalModules && config.services.samba.nsswins;
+  ldap = canLoadExternalModules && (config.users.ldap.enable && config.users.ldap.nsswitch);
+  sssd = canLoadExternalModules && config.services.sssd.enable;
+  resolved = canLoadExternalModules && config.services.resolved.enable;
+
+  hostArray = [ "files" ]
+    ++ optionals mymachines [ "mymachines" ]
     ++ optionals nssmdns [ "mdns_minimal [!UNAVAIL=return]" ]
     ++ optionals nsswins [ "wins" ]
-    ++ optionals resolved ["resolv [!UNAVAIL=return]"]
+    ++ optionals resolved ["resolve [!UNAVAIL=return]"]
     ++ [ "dns" ]
     ++ optionals nssmdns [ "mdns" ]
-    ++ ["myhostname" ];
+    ++ optionals myhostname ["myhostname" ];
 
   passwdArray = [ "files" ]
     ++ optional sssd "sss"
     ++ optionals ldap [ "ldap" ]
-    ++ [ "mymachines" ];
+    ++ optionals mymachines [ "mymachines" ];
 
   shadowArray = [ "files" ]
     ++ optional sssd "sss"
@@ -36,6 +41,7 @@ in {
   options = {
 
     # NSS modules.  Hacky!
+    # Only works with nscd!
     system.nssModules = mkOption {
       type = types.listOf types.path;
       internal = true;
@@ -55,6 +61,18 @@ in {
   };
 
   config = {
+    assertions = [
+      {
+        # generic catch if the NixOS module adding to nssModules does not prevent it with specific message.
+        assertion = config.system.nssModules.path != "" -> canLoadExternalModules;
+        message = "Loading NSS modules from path ${config.system.nssModules.path} requires nscd being enabled.";
+      }
+      {
+        # resolved does not need to add to nssModules, therefore needs an extra assertion
+        assertion = resolved -> canLoadExternalModules;
+        message = "Loading systemd-resolved's nss-resolve NSS module requires nscd being enabled.";
+      }
+    ];
 
     # Name Service Switch configuration file.  Required by the C
     # library.  !!! Factor out the mdns stuff.  The avahi module
@@ -78,7 +96,7 @@ in {
     # configured IP addresses, or ::1 and 127.0.0.2 as
     # fallbacks. Systemd also provides nss-mymachines to return IP
     # addresses of local containers.
-    system.nssModules = [ config.systemd.package.out ];
+    system.nssModules = optionals canLoadExternalModules [ config.systemd.package.out ];
 
   };
 }
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index d7459e3fe91c..22059bb7fbbb 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -295,6 +295,7 @@
       aria2 = 277;
       clickhouse = 278;
       rslsync = 279;
+      minio = 280;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -559,6 +560,7 @@
       aria2 = 277;
       clickhouse = 278;
       rslsync = 279;
+      minio = 280;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 3aa483c848c9..9f8d876704c9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -577,6 +577,7 @@
   ./services/web-apps/frab.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/nixbot.nix
+  ./services/web-apps/piwik.nix
   ./services/web-apps/pump.io.nix
   ./services/web-apps/tt-rss.nix
   ./services/web-apps/selfoss.nix
@@ -589,6 +590,7 @@
   ./services/web-servers/lighttpd/default.nix
   ./services/web-servers/lighttpd/gitweb.nix
   ./services/web-servers/lighttpd/inginious.nix
+  ./services/web-servers/minio.nix
   ./services/web-servers/nginx/default.nix
   ./services/web-servers/phpfpm/default.nix
   ./services/web-servers/shellinabox.nix
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index acb3e987aee6..b276bf9bb73c 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -117,7 +117,7 @@ in
 
         # Tell zsh how to find installed completions
         for p in ''${(z)NIX_PROFILES}; do
-          fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions)
+          fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
         done
 
         ${if cfg.enableCompletion then "autoload -U compinit && compinit" else ""}
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index dc1227c5628d..a6dc8faaae98 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -171,7 +171,7 @@ in
 
     ###### setcap activation script
     system.activationScripts.wrappers =
-      lib.stringAfter [ "users" ]
+      lib.stringAfter [ "specialfs" "users" ]
         ''
           # Look in the system path and in the default profile for
           # programs to be wrapped.
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 6d39affee480..fcc0f58637c4 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -308,6 +308,7 @@ in
         requires = [ "hydra-init.service" ];
         after = [ "hydra-init.service" ];
         environment = serverEnv;
+        restartTriggers = [ hydraConf ];
         serviceConfig =
           { ExecStart =
               "@${cfg.package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' "
@@ -324,6 +325,7 @@ in
         requires = [ "hydra-init.service" ];
         after = [ "hydra-init.service" "network.target" ];
         path = [ cfg.package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ];
+        restartTriggers = [ hydraConf ];
         environment = env // {
           PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
           IN_SYSTEMD = "1"; # to get log severity levels
@@ -345,6 +347,7 @@ in
         requires = [ "hydra-init.service" ];
         after = [ "hydra-init.service" "network.target" ];
         path = with pkgs; [ cfg.package nettools jq ];
+        restartTriggers = [ hydraConf ];
         environment = env;
         serviceConfig =
           { ExecStart = "@${cfg.package}/bin/hydra-evaluator hydra-evaluator";
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index bae6b170c472..6027f109285a 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -20,6 +20,7 @@ let
   ''
     [mysqld]
     port = ${toString cfg.port}
+    ${optionalString (cfg.bind != null) "bind-address = ${cfg.bind}" }
     ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "log-bin=mysql-bin"}
     ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "server-id = ${toString cfg.replication.serverId}"}
     ${optionalString (cfg.replication.role == "slave" && !atLeast55)
@@ -58,6 +59,13 @@ in
         ";
       };
 
+      bind = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = literalExample "0.0.0.0";
+        description = "Address to bind to. The default it to bind to all addresses";
+      };
+
       port = mkOption {
         type = types.int;
         default = 3306;
diff --git a/nixos/modules/services/databases/rethinkdb.nix b/nixos/modules/services/databases/rethinkdb.nix
new file mode 100644
index 000000000000..cd8c386b08db
--- /dev/null
+++ b/nixos/modules/services/databases/rethinkdb.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rethinkdb;
+  rethinkdb = cfg.package;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.rethinkdb = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable the RethinkDB server.";
+      };
+
+      #package = mkOption {
+      #  default = pkgs.rethinkdb;
+      #  description = "Which RethinkDB derivation to use.";
+      #};
+
+      user = mkOption {
+        default = "rethinkdb";
+        description = "User account under which RethinkDB runs.";
+      };
+
+      group = mkOption {
+        default = "rethinkdb";
+        description = "Group which rethinkdb user belongs to.";
+      };
+
+      dbpath = mkOption {
+        default = "/var/db/rethinkdb";
+        description = "Location where RethinkDB stores its data, 1 data directory per instance.";
+      };
+
+      pidpath = mkOption {
+        default = "/var/run/rethinkdb";
+        description = "Location where each instance's pid file is located.";
+      };
+
+      #cfgpath = mkOption {
+      #  default = "/etc/rethinkdb/instances.d";
+      #  description = "Location where RethinkDB stores it config files, 1 config file per instance.";
+      #};
+
+      # TODO: currently not used by our implementation.
+      #instances = mkOption {
+      #  type = types.attrsOf types.str;
+      #  default = {};
+      #  description = "List of named RethinkDB instances in our cluster.";
+      #};
+
+    };
+
+  };
+
+  ###### implementation
+  config = mkIf config.services.rethinkdb.enable {
+
+    environment.systemPackages = [ rethinkdb ];
+
+    systemd.services.rethinkdb = {
+      description = "RethinkDB server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        # TODO: abstract away 'default', which is a per-instance directory name
+        #       allowing end user of this nix module to provide multiple instances,
+        #       and associated directory per instance
+        ExecStart = "${rethinkdb}/bin/rethinkdb -d ${cfg.dbpath}/default";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = cfg.user;
+        Group = cfg.group;
+        PIDFile = "${cfg.pidpath}/default.pid";
+        PermissionsStartOnly = true;
+      };
+
+      preStart = ''
+        if ! test -e ${cfg.dbpath}; then
+            install -d -m0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dbpath}
+            install -d -m0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dbpath}/default
+            chown -R ${cfg.user}:${cfg.group} ${cfg.dbpath}
+        fi
+        if ! test -e "${cfg.pidpath}/default.pid"; then
+            install -D -o ${cfg.user} -g ${cfg.group} /dev/null "${cfg.pidpath}/default.pid"
+        fi
+      '';
+    };
+
+    users.extraUsers.rethinkdb = mkIf (cfg.user == "rethinkdb")
+      { name = "rethinkdb";
+        description = "RethinkDB server user";
+      };
+
+    users.extraGroups = optionalAttrs (cfg.group == "rethinkdb") (singleton
+      { name = "rethinkdb";
+      });
+
+  };
+
+}
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index b9435c02b1de..c12b5f35dea3 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -66,15 +66,6 @@ let
           How frequently to evaluate rules by default.
         '';
       };
-
-      labels = mkOption {
-        type = types.attrsOf types.str;
-        default = {};
-        description = ''
-          The labels to add to any timeseries that this Prometheus instance
-          scrapes.
-        '';
-      };
     };
   };
 
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index f8b7f26f5f2f..58c93d8e2ac3 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -16,8 +16,6 @@ let
     dns=${if cfg.useDnsmasq then "dnsmasq" else "default"}
 
     [keyfile]
-    ${optionalString (config.networking.hostName != "")
-      ''hostname=${config.networking.hostName}''}
     ${optionalString (cfg.unmanaged != [])
       ''unmanaged-devices=${lib.concatStringsSep ";" cfg.unmanaged}''}
 
diff --git a/nixos/modules/services/networking/toxvpn.nix b/nixos/modules/services/networking/toxvpn.nix
index 911836fdee42..5e13402d7645 100644
--- a/nixos/modules/services/networking/toxvpn.nix
+++ b/nixos/modules/services/networking/toxvpn.nix
@@ -18,6 +18,13 @@ with lib;
         default     = 33445;
         description = "udp port for toxcore, port-forward to help with connectivity if you run many nodes behind one NAT";
       };
+
+      auto_add_peers = mkOption {
+        type        = types.listOf types.string;
+        default     = [];
+        example     = ''[ "toxid1" "toxid2" ]'';
+        description = "peers to automacally connect to on startup";
+      };
     };
   };
 
@@ -33,8 +40,13 @@ with lib;
         chown toxvpn /run/toxvpn
       '';
 
+      path = [ pkgs.toxvpn ];
+
+      script = ''
+        exec toxvpn -i ${config.services.toxvpn.localip} -l /run/toxvpn/control -u toxvpn -p ${toString config.services.toxvpn.port} ${lib.concatMapStringsSep " " (x: "-a ${x}") config.services.toxvpn.auto_add_peers}
+      '';
+
       serviceConfig = {
-        ExecStart = "${pkgs.toxvpn}/bin/toxvpn -i ${config.services.toxvpn.localip} -l /run/toxvpn/control -u toxvpn -p ${toString config.services.toxvpn.port}";
         KillMode  = "process";
         Restart   = "on-success";
         Type      = "notify";
@@ -43,6 +55,8 @@ with lib;
       restartIfChanged = false; # Likely to be used for remote admin
     };
 
+    environment.systemPackages = [ pkgs.toxvpn ];
+
     users.extraUsers = {
       toxvpn = {
         uid        = config.ids.uids.toxvpn;
diff --git a/nixos/modules/services/web-apps/piwik-doc.xml b/nixos/modules/services/web-apps/piwik-doc.xml
new file mode 100644
index 000000000000..a1d8a5b7556a
--- /dev/null
+++ b/nixos/modules/services/web-apps/piwik-doc.xml
@@ -0,0 +1,97 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-piwik">
+
+  <title>Piwik</title>
+  <para>
+    Piwik is a real-time web analytics application.
+    This module configures php-fpm as backend for piwik, optionally configuring an nginx vhost as well.
+  </para>
+
+  <para>
+    An automatic setup is not suported by piwik, so you need to configure piwik itself in the browser-based piwik setup.
+  </para>
+
+
+  <section>
+    <title>Database Setup</title>
+
+    <para>
+      You also need to configure a MariaDB or MySQL database and -user for piwik yourself,
+      and enter those credentials in your browser.
+      You can use passwordless database authentication via the UNIX_SOCKET authentication plugin
+      with the following SQL commands:
+      <programlisting>
+        INSTALL PLUGIN unix_socket SONAME 'auth_socket';
+        ALTER USER root IDENTIFIED VIA unix_socket;
+        CREATE DATABASE piwik;
+        CREATE USER 'piwik'@'localhost' IDENTIFIED VIA unix_socket;
+        GRANT ALL PRIVILEGES ON piwik.* TO 'piwik'@'localhost';
+      </programlisting>
+      Then fill in <literal>piwik</literal> as database user and database name, and leave the password field blank.
+      This works with MariaDB and MySQL. This authentication works by allowing only the <literal>piwik</literal> unix
+      user to authenticate as <literal>piwik</literal> database (without needing a password), but no other users.
+      For more information on passwordless login, see
+      <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
+    </para>
+
+    <para>
+      Of course, you can use password based authentication as well, e.g. when the database is not on the same host.
+    </para>
+  </section>
+
+
+  <section>
+    <title>Backup</title>
+    <para>
+      You only need to take backups of your MySQL database and the
+      <filename>/var/lib/piwik/config/config.ini.php</filename> file.
+      Use a user in the <literal>piwik</literal> group or root to access the file.
+      For more information, see <link xlink:href="https://piwik.org/faq/how-to-install/faq_138/" />.
+    </para>
+  </section>
+
+
+  <section>
+    <title>Issues</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          Piwik's file integrity check will warn you.
+          This is due to the patches necessary for NixOS, you can safely ignore this.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+          Piwik will warn you that the JavaScript tracker is not writable.
+          This is because it's located in the read-only nix store.
+          You can safely ignore this, unless you need a plugin that needs JavaScript tracker access.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+          Sending mail from piwik, e.g. for the password reset function, might not work out of the box:
+          There's a problem with using <command>sendmail</command> from <literal>php-fpm</literal> that is
+          being investigated at <link xlink:href="https://github.com/NixOS/nixpkgs/issues/26611" />.
+          If you have (or don't have) this problem as well, please report it. You can enable SMTP as method
+          to send mail in piwik's <quote>General Settings</quote> > <quote>Mail Server Settings</quote> instead.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+
+
+  <section>
+    <title>Using other Web Servers than nginx</title>
+
+    <para>
+      You can use other web servers by forwarding calls for <filename>index.php</filename> and
+      <filename>piwik.php</filename> to the <literal>/run/phpfpm-piwik.sock</literal> fastcgi unix socket.
+      You can use the nginx configuration in the module code as a reference to what else should be configured.
+    </para>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/web-apps/piwik.nix b/nixos/modules/services/web-apps/piwik.nix
new file mode 100644
index 000000000000..26342a9c5f00
--- /dev/null
+++ b/nixos/modules/services/web-apps/piwik.nix
@@ -0,0 +1,219 @@
+{ config, lib, pkgs, services, ... }:
+with lib;
+let
+  cfg = config.services.piwik;
+
+  user = "piwik";
+  dataDir = "/var/lib/${user}";
+
+  pool = user;
+  # it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770,
+  # and therefore is not accessible by the web server.
+  phpSocket = "/run/phpfpm-${pool}.sock";
+  phpExecutionUnit = "phpfpm-${pool}";
+  databaseService = "mysql.service";
+
+in {
+  options = {
+    services.piwik = {
+      # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963
+      # piwik issue for automatic piwik setup: https://github.com/piwik/piwik/issues/10257
+      # TODO: find a nice way to do this when more NixOS MySQL and / or piwik automatic setup stuff is implemented.
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable piwik web analytics with php-fpm backend.
+        '';
+      };
+
+      webServerUser = mkOption {
+        type = types.str;
+        example = "nginx";
+        description = ''
+          Name of the owner of the ${phpSocket} fastcgi socket for piwik.
+          If you want to use another webserver than nginx, you need to set this to that server's user
+          and pass fastcgi requests to `index.php` and `piwik.php` to this socket.
+        '';
+      };
+
+      phpfpmProcessManagerConfig = mkOption {
+        type = types.str;
+        default = ''
+          ; default phpfpm process manager settings
+          pm = dynamic
+          pm.max_children = 75
+          pm.start_servers = 10
+          pm.min_spare_servers = 5
+          pm.max_spare_servers = 20
+          pm.max_requests = 500
+
+          ; log worker's stdout, but this has a performance hit
+          catch_workers_output = yes
+        '';
+        description = ''
+          Settings for phpfpm's process manager. You might need to change this depending on the load for piwik.
+        '';
+      };
+
+      nginx = mkOption {
+        # TODO: for maximum flexibility, it would be nice to use nginx's vhost_options module
+        #       but this only makes sense if we can somehow specify defaults suitable for piwik.
+        #       But users can always copy the piwik nginx config to their configuration.nix and customize it.
+        type = types.nullOr (types.submodule {
+          options = {
+            virtualHost = mkOption {
+              type = types.str;
+              default = "piwik.${config.networking.hostName}";
+              example = "piwik.$\{config.networking.hostName\}";
+              description = ''
+                  Name of the nginx virtualhost to use and set up.
+              '';
+            };
+            enableSSL = mkOption {
+              type = types.bool;
+              default = true;
+              description = "Whether to enable https.";
+            };
+            forceSSL = mkOption {
+              type = types.bool;
+              default = true;
+              description = "Whether to always redirect to https.";
+            };
+            enableACME = mkOption {
+              type = types.bool;
+              default = true;
+              description = "Whether to ask Let's Encrypt to sign a certificate for this vhost.";
+            };
+          };
+        });
+        default = null;
+        example = { virtualHost = "stats.$\{config.networking.hostName\}"; };
+        description = ''
+            The options to use to configure an nginx virtualHost.
+            If null (the default), no nginx virtualHost will be configured.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.extraUsers.${user} = {
+      isSystemUser = true;
+      createHome = true;
+      home = dataDir;
+      group  = user;
+    };
+    users.extraGroups.${user} = {};
+
+    systemd.services.piwik_setup_update = {
+      # everything needs to set up and up to date before piwik php files are executed
+      requiredBy = [ "${phpExecutionUnit}.service" ];
+      before = [ "${phpExecutionUnit}.service" ];
+      # the update part of the script can only work if the database is already up and running
+      requires = [ databaseService ];
+      after = [ databaseService ];
+      path = [ pkgs.piwik ];
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        # hide especially config.ini.php from other
+        UMask = "0007";
+        Environment = "PIWIK_USER_PATH=${dataDir}";
+        # chown + chmod in preStart needs root
+        PermissionsStartOnly = true;
+      };
+      # correct ownership and permissions in case they're not correct anymore,
+      # e.g. after restoring from backup or moving from another system.
+      # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
+      preStart = ''
+        chown -R ${user}:${user} ${dataDir}
+        chmod -R ug+rwX,o-rwx ${dataDir}
+        '';
+      script = ''
+            # Use User-Private Group scheme to protect piwik data, but allow administration / backup via piwik group
+            # Copy config folder
+            chmod g+s "${dataDir}"
+            cp -r "${pkgs.piwik}/config" "${dataDir}/"
+            chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
+
+            # check whether user setup has already been done
+            if test -f "${dataDir}/config/config.ini.php"; then
+              # then execute possibly pending database upgrade
+              piwik-console core:update --yes
+            fi
+      '';
+    };
+
+    systemd.services.${phpExecutionUnit} = {
+      # stop phpfpm on package upgrade, do database upgrade via piwik_setup_update, and then restart
+      restartTriggers = [ pkgs.piwik ];
+      # stop config.ini.php from getting written with read permission for others
+      serviceConfig.UMask = "0007";
+    };
+
+    services.phpfpm.poolConfigs = {
+      ${pool} = ''
+        listen = "${phpSocket}"
+        listen.owner = ${cfg.webServerUser}
+        listen.group = root
+        listen.mode = 0600
+        user = ${user}
+        env[PIWIK_USER_PATH] = ${dataDir}
+        ${cfg.phpfpmProcessManagerConfig}
+      '';
+    };
+
+
+    services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
+      # References:
+      # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
+      # https://github.com/perusio/piwik-nginx
+      ${cfg.nginx.virtualHost} = {
+        root = "${pkgs.piwik}/share";
+        enableSSL  = cfg.nginx.enableSSL;
+        enableACME = cfg.nginx.enableACME;
+        forceSSL   = cfg.nginx.forceSSL;
+
+        locations."/" = {
+          index = "index.php";
+        };
+        # allow index.php for webinterface
+        locations."= /index.php".extraConfig = ''
+          fastcgi_pass unix:${phpSocket};
+        '';
+        # allow piwik.php for tracking
+        locations."= /piwik.php".extraConfig = ''
+          fastcgi_pass unix:${phpSocket};
+        '';
+        # Any other attempt to access any php files is forbidden
+        locations."~* ^.+\.php$".extraConfig = ''
+          return 403;
+        '';
+        # Disallow access to unneeded directories
+        # config and tmp are already removed
+        locations."~ ^/(?:core|lang|misc)/".extraConfig = ''
+          return 403;
+        '';
+        # Disallow access to several helper files
+        locations."~* \.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
+          return 403;
+        '';
+        # No crawling of this site for bots that obey robots.txt - no useful information here.
+        locations."= /robots.txt".extraConfig = ''
+          return 200 "User-agent: *\nDisallow: /\n";
+        '';
+        # let browsers cache piwik.js
+        locations."= /piwik.js".extraConfig = ''
+          expires 1M;
+        '';
+      };
+    };
+  };
+
+  meta = {
+    doc = ./piwik-doc.xml;
+    maintainers = with stdenv.lib.maintainers; [ florianjacob ];
+  };
+}
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
new file mode 100644
index 000000000000..1893edf3a776
--- /dev/null
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.minio;
+in
+{
+  meta.maintainers = [ maintainers.bachp ];
+
+  options.services.minio = {
+    enable = mkEnableOption "Minio Object Storage";
+
+    listenAddress = mkOption {
+      default = ":9000";
+      type = types.str;
+      description = "Listen on a specific IP address and port.";
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/minio/data";
+      type = types.path;
+      description = "The data directory, for storing the objects.";
+    };
+
+    configDir = mkOption {
+      default = "/var/lib/minio/config";
+      type = types.path;
+      description = "The config directory, for the access keys and other settings.";
+    };
+
+    package = mkOption {
+      default = pkgs.minio;
+      defaultText = "pkgs.minio";
+      type = types.package;
+      description = "Minio package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.minio = {
+      description = "Minio Object Storage";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        # Make sure directories exist with correct owner
+        mkdir -p ${cfg.configDir}
+        chown -R minio:minio ${cfg.configDir}
+        mkdir -p ${cfg.dataDir}
+        chown minio:minio ${cfg.dataDir}
+      '';
+      serviceConfig = {
+        PermissionsStartOnly = true;
+        ExecStart = "${cfg.package}/bin/minio server --address ${cfg.listenAddress} --config-dir=${cfg.configDir} ${cfg.dataDir}";
+        Type = "simple";
+        User = "minio";
+        Group = "minio";
+        LimitNOFILE = 65536;
+      };
+    };
+
+    users.extraUsers.minio = {
+      group = "minio";
+      uid = config.ids.uids.minio;
+    };
+
+    users.extraGroups.minio.gid = config.ids.uids.minio;
+  };
+}
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 01e1659b30bb..01bab8fccadb 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -651,9 +651,13 @@ in
     system.extraDependencies = singleton (pkgs.runCommand "xkb-layouts-exist" {
       inherit (cfg) layout xkbDir;
     } ''
-      sed -n -e ':i /^! \(layout\|variant\) *$/ {
-        :l; n; /^!/bi; s/^ *\([^ ]\+\).*/\1/p; tl
-      }' "$xkbDir/rules/base.lst" | grep -qxF "$layout" && exec touch "$out"
+      if sed -n -e ':i /^! \(layout\|variant\) *$/ {
+          :l; n; /^!/bi; s/^ *\([^ ]\+\).*/\1/p; tl
+         }' "$xkbDir/rules/base.lst" | grep -qxF "$layout"
+      then
+        touch "$out"
+        exit 0
+      fi
 
       cat >&2 <<-EOF
 
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index eca2d281342f..49ddd95887be 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -52,6 +52,7 @@ in rec {
         (all nixos.tests.firefox)
         (all nixos.tests.firewall)
         nixos.tests.gnome3.x86_64-linux # FIXME: i686-linux
+        (all nixos.tests.installer.zfsroot)
         (all nixos.tests.installer.lvm)
         (all nixos.tests.installer.luksroot)
         (all nixos.tests.installer.separateBoot)
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 85d31334d6bf..a6afe8a4b72a 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -171,6 +171,7 @@ let
 
   makeInstallerTest = name:
     { createPartitions, preBootCommands ? "", extraConfig ? ""
+    , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
     , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid"
     , enableOCR ? false, meta ? {}
@@ -192,6 +193,7 @@ let
           { imports =
               [ ../modules/profiles/installation-device.nix
                 ../modules/profiles/base.nix
+                extraInstallerConfig
               ];
 
             virtualisation.diskSize = 8 * 1024;
@@ -332,6 +334,43 @@ in {
         '';
     };
 
+  # zfs on / with swap
+  zfsroot = makeInstallerTest "zfs-root"
+    {
+      extraInstallerConfig = {
+        boot.supportedFilesystems = [ "zfs" ];
+      };
+
+      extraConfig = ''
+        boot.supportedFilesystems = [ "zfs" ];
+
+        # Using by-uuid overrides the default of by-id, and is unique
+        # to the qemu disks, as they don't produce by-id paths for
+        # some reason.
+        boot.zfs.devNodes = "/dev/disk/by-uuid/";
+        networking.hostId = "00000000";
+      '';
+
+      createPartitions =
+        ''
+          $machine->succeed(
+              "parted /dev/vda mklabel msdos",
+              "parted /dev/vda -- mkpart primary linux-swap 1M 1024M",
+              "parted /dev/vda -- mkpart primary 1024M -1s",
+              "udevadm settle",
+
+              "mkswap /dev/vda1 -L swap",
+              "swapon -L swap",
+
+              "zpool create rpool /dev/vda2",
+              "zfs create -o mountpoint=legacy rpool/root",
+              "mount -t zfs rpool/root /mnt",
+
+              "udevadm settle"
+          );
+        '';
+    };
+
   # Create two physical LVM partitions combined into one volume group
   # that contains the logical swap and root partitions.
   lvm = makeInstallerTest "lvm"
diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix
new file mode 100644
index 000000000000..462a3bc4768c
--- /dev/null
+++ b/nixos/tests/minio.nix
@@ -0,0 +1,19 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "minio";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bachp ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    services.minio.enable = true;
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("minio.service");
+      $machine->waitForOpenPort(9000);
+      $machine->succeed("curl --fail http://localhost:9000/minio/index.html");
+      $machine->shutdown;
+    '';
+})