about summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/audio/mpd.nix1
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix3
-rw-r--r--nixos/modules/services/monitoring/netdata.nix2
-rw-r--r--nixos/modules/services/networking/jormungandr.nix102
-rw-r--r--nixos/modules/services/networking/nat.nix2
-rw-r--r--nixos/modules/services/security/vault.nix5
-rw-r--r--nixos/modules/services/web-apps/moinmoin.nix303
-rw-r--r--nixos/modules/services/web-apps/trac.nix79
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix133
-rw-r--r--nixos/modules/services/x11/hardware/digimend.nix43
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix2
11 files changed, 502 insertions, 173 deletions
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index 0df8f9688d25..56dc858b6405 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -181,6 +181,7 @@ in {
         ProtectKernelModules = true;
         RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
         RestrictNamespaces = true;
+        Restart = "always";
       };
     };
 
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 0f4eb2ccfcad..50661b873f64 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -407,6 +407,9 @@ in {
           "192.168.0.0/16"
           "100.64.0.0/10"
           "169.254.0.0/16"
+          "::1/128"
+          "fe80::/64"
+          "fc00::/7"
         ];
         description = ''
           List of IP address CIDR ranges that the URL preview spider is denied
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 7d976db96300..3ffde8e9bce2 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -138,7 +138,7 @@ in {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = (with pkgs; [ gawk curl ]) ++ lib.optional cfg.python.enable
+      path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable
         (pkgs.python3.withPackages cfg.python.extraPackages);
       serviceConfig = {
         Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules";
diff --git a/nixos/modules/services/networking/jormungandr.nix b/nixos/modules/services/networking/jormungandr.nix
deleted file mode 100644
index 152cceb4bf91..000000000000
--- a/nixos/modules/services/networking/jormungandr.nix
+++ /dev/null
@@ -1,102 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.jormungandr;
-
-  inherit (lib) mkEnableOption mkIf mkOption;
-  inherit (lib) optionalString types;
-
-  dataDir = "/var/lib/jormungandr";
-
-  # Default settings so far, as the service matures we will
-  # move these out as separate settings
-  configSettings = {
-    storage = dataDir;
-    p2p = {
-      public_address = "/ip4/127.0.0.1/tcp/8299";
-      topics_of_interest = {
-        messages = "high";
-        blocks = "high";
-      };
-    };
-    rest = {
-      listen = "127.0.0.1:8607";
-    };
-  };
-
-  configFile = if cfg.configFile == null then
-    pkgs.writeText "jormungandr.yaml" (builtins.toJSON configSettings)
-  else cfg.configFile;
-
-in {
-
-  options = {
-
-    services.jormungandr = {
-      enable = mkEnableOption "jormungandr service";
-
-      configFile = mkOption {
-       type = types.nullOr types.path;
-       default = null;
-       example = "/var/lib/jormungandr/node.yaml";
-       description = ''
-         The path of the jormungandr blockchain configuration file in YAML format.
-         If no file is specified, a file is generated using the other options.
-       '';
-     };
-
-      secretFile = mkOption {
-       type = types.nullOr types.path;
-       default = null;
-       example = "/etc/secret/jormungandr.yaml";
-       description = ''
-         The path of the jormungandr blockchain secret node configuration file in
-         YAML format. Do not store this in nix store!
-       '';
-     };
-
-      genesisBlockHash = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "d70495af81ae8600aca3e642b2427327cb6001ec4d7a0037e96a00dabed163f9";
-        description = ''
-          Set the genesis block hash (the hash of the block0) so we can retrieve
-          the genesis block (and the blockchain configuration) from the existing
-          storage or from the network.
-        '';
-      };
-
-      genesisBlockFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/var/lib/jormungandr/block-0.bin";
-        description = ''
-          The path of the genesis block file if we are hosting it locally.
-        '';
-      };
-
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    systemd.services.jormungandr = {
-      description = "jormungandr server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network-online.target" ];
-      environment = {
-        RUST_BACKTRACE = "full";
-      };
-      serviceConfig = {
-        DynamicUser = true;
-        StateDirectory = baseNameOf dataDir;
-        ExecStart = ''
-          ${pkgs.jormungandr}/bin/jormungandr --config ${configFile} \
-            ${optionalString (cfg.secretFile != null) " --secret ${cfg.secretFile}"} \
-            ${optionalString (cfg.genesisBlockHash != null) " --genesis-block-hash ${cfg.genesisBlockHash}"} \
-            ${optionalString (cfg.genesisBlockFile != null) " --genesis-block ${cfg.genesisBlockFile}"}
-        '';
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix
index 89d8590093dd..5681bda51cb4 100644
--- a/nixos/modules/services/networking/nat.nix
+++ b/nixos/modules/services/networking/nat.nix
@@ -29,7 +29,7 @@ let
     iptables -w -t nat -N nixos-nat-post
 
     # We can't match on incoming interface in POSTROUTING, so
-    # mark packets coming from the external interfaces.
+    # mark packets coming from the internal interfaces.
     ${concatMapStrings (iface: ''
       iptables -w -t nat -A nixos-nat-pre \
         -i '${iface}' -j MARK --set-mark 1
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index d5962ba9af90..b0ab8fadcbec 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -119,9 +119,8 @@ in
     };
     users.groups.vault.gid = config.ids.gids.vault;
 
-    systemd.tmpfiles.rules = optional (cfg.storagePath != null) [
-      "d '${cfg.storagePath}' 0700 vault vault - -"
-    ];
+    systemd.tmpfiles.rules = optional (cfg.storagePath != null)
+      "d '${cfg.storagePath}' 0700 vault vault - -";
 
     systemd.services.vault = {
       description = "Vault server daemon";
diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix
new file mode 100644
index 000000000000..0fee64be0bb2
--- /dev/null
+++ b/nixos/modules/services/web-apps/moinmoin.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.moinmoin;
+  python = pkgs.python27;
+  pkg = python.pkgs.moinmoin;
+  dataDir = "/var/lib/moin";
+  usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn";
+  usingNginx = cfg.webServer == "nginx-gunicorn";
+  user = "moin";
+  group = "moin";
+
+  uLit = s: ''u"${s}"'';
+  indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str);
+
+  moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" ''
+    ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user}
+  '';
+
+  wikiConfig = wikiIdent: w: ''
+    # -*- coding: utf-8 -*-
+
+    from MoinMoin.config import multiconfig, url_prefix_static
+
+    class Config(multiconfig.DefaultConfig):
+        ${optionalString (w.webLocation != "/") ''
+          url_prefix_static = '${w.webLocation}' + url_prefix_static
+        ''}
+
+        sitename = u'${w.siteName}'
+        page_front_page = u'${w.frontPage}'
+
+        data_dir = '${dataDir}/${wikiIdent}/data'
+        data_underlay_dir = '${dataDir}/${wikiIdent}/underlay'
+
+        language_default = u'${w.languageDefault}'
+        ${optionalString (w.superUsers != []) ''
+          superuser = [${concatMapStringsSep ", " uLit w.superUsers}]
+        ''}
+
+    ${indentLines 4 w.extraConfig}
+  '';
+  wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki);
+
+in
+{
+  options.services.moinmoin = with types; {
+    enable = mkEnableOption "MoinMoin Wiki Engine";
+
+    webServer = mkOption {
+      type = enum [ "nginx-gunicorn" "gunicorn" "none" ];
+      default = "nginx-gunicorn";
+      example = "none";
+      description = ''
+        Which web server to use to serve the wiki.
+        Use <literal>none</literal> if you want to configure this yourself.
+      '';
+    };
+
+    gunicorn.workers = mkOption {
+      type = ints.positive;
+      default = 3;
+      example = 10;
+      description = ''
+        The number of worker processes for handling requests.
+      '';
+    };
+
+    wikis = mkOption {
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          siteName = mkOption {
+            type = str;
+            default = "Untitled Wiki";
+            example = "ExampleWiki";
+            description = ''
+              Short description of your wiki site, displayed below the logo on each page, and
+              used in RSS documents as the channel title.
+            '';
+          };
+
+          webHost = mkOption {
+            type = str;
+            description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used.";
+            example = "wiki.example.org";
+          };
+
+          webLocation = mkOption {
+            type = str;
+            default = "/";
+            example = "/moin";
+            description = "Location part of the wiki URL.";
+          };
+
+          frontPage = mkOption {
+            type = str;
+            default = "LanguageSetup";
+            example = "FrontPage";
+            description = ''
+              Front page name. Set this to something like <literal>FrontPage</literal> once languages are
+              configured.
+            '';
+          };
+
+          superUsers = mkOption {
+            type = listOf str;
+            default = [];
+            example = [ "elvis" ];
+            description = ''
+              List of trusted user names with wiki system administration super powers.
+
+              Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.:
+              <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>.
+            '';
+          };
+
+          languageDefault = mkOption {
+            type = str;
+            default = "en";
+            example = "de";
+            description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored.";
+          };
+
+          extraConfig = mkOption {
+            type = lines;
+            default = "";
+            example = ''
+              show_hosts = True
+              search_results_per_page = 100
+              acl_rights_default = u"Known:read,write,delete,revert All:read"
+              logo_string = u"<h2>\U0001f639</h2>"
+              theme_default = u"modernized"
+
+              user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0}
+              navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar
+              actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount']
+
+              mail_smarthost = "mail.example.org"
+              mail_from = u"Example.Org Wiki <wiki@example.org>"
+            '';
+            description = ''
+              Additional configuration to be appended verbatim to this wiki's config.
+
+              See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation.
+            '';
+          };
+
+        };
+        config = {
+          webHost = mkDefault name;
+        };
+      }));
+      example = literalExample ''
+        {
+          "mywiki" = {
+            siteName = "Example Wiki";
+            webHost = "wiki.example.org";
+            superUsers = [ "admin" ];
+            frontPage = "Index";
+            extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'"
+          };
+        }
+      '';
+      description = ''
+        Configurations of the individual wikis. Attribute names must be valid Python
+        identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>.
+
+        For every attribute <replaceable>WIKINAME</replaceable>, a helper script
+        moin-<replaceable>WIKINAME</replaceable> is created which runs the
+        <command>moin</command> command under the <literal>moin</literal> user (to avoid
+        file ownership issues) and with the right configuration directory passed to it.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = forEach (attrNames cfg.wikis) (wname:
+      { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null;
+        message = "${wname} is not valid Python identifier";
+      }
+    );
+
+    users.users = {
+      moin = {
+        description = "MoinMoin wiki";
+        home = dataDir;
+        group = group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = {
+      moin = {
+        members = mkIf usingNginx [ config.services.nginx.user ];
+      };
+    };
+
+    environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis);
+
+    systemd.services = mkIf usingGunicorn
+      (flip mapAttrs' cfg.wikis (wikiIdent: wiki:
+        nameValuePair "moin-${wikiIdent}"
+          {
+            description = "MoinMoin wiki ${wikiIdent} - gunicorn process";
+            wantedBy = [ "multi-user.target" ];
+            after = [ "network.target" ];
+            restartIfChanged = true;
+            restartTriggers = [ (wikiConfigFile wikiIdent wiki) ];
+
+            environment = let
+              penv = python.buildEnv.override {
+                # setuptools: https://github.com/benoitc/gunicorn/issues/1716
+                extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ];
+              };
+            in {
+              PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
+            };
+
+            preStart = ''
+              umask 0007
+              rm -rf ${dataDir}/${wikiIdent}/underlay
+              cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/
+              chmod -R u+w ${dataDir}/${wikiIdent}/underlay
+            '';
+
+            serviceConfig = {
+              User = user;
+              Group = group;
+              WorkingDirectory = "${dataDir}/${wikiIdent}";
+              ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
+                --name gunicorn-${wikiIdent} \
+                --workers ${toString cfg.gunicorn.workers} \
+                --worker-class gevent \
+                --bind unix:/run/moin/${wikiIdent}/gunicorn.sock
+              '';
+
+              Restart = "on-failure";
+              RestartSec = "2s";
+              StartLimitIntervalSec = "30s";
+
+              StateDirectory = "moin/${wikiIdent}";
+              StateDirectoryMode = "0750";
+              RuntimeDirectory = "moin/${wikiIdent}";
+              RuntimeDirectoryMode = "0750";
+
+              NoNewPrivileges = true;
+              ProtectSystem = "strict";
+              ProtectHome = true;
+              PrivateTmp = true;
+              PrivateDevices = true;
+              PrivateNetwork = true;
+              ProtectKernelTunables = true;
+              ProtectKernelModules = true;
+              ProtectControlGroups = true;
+              RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+              RestrictNamespaces = true;
+              LockPersonality = true;
+              MemoryDenyWriteExecute = true;
+              RestrictRealtime = true;
+            };
+          }
+      ));
+
+    services.nginx = mkIf usingNginx {
+      enable = true;
+      virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost {
+        forceSSL = mkDefault true;
+        enableACME = mkDefault true;
+        locations."${w.webLocation}" = {
+          extraConfig = ''
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            proxy_set_header X-Forwarded-Host $host;
+            proxy_set_header X-Forwarded-Server $host;
+
+            proxy_pass http://unix:/run/moin/${name}/gunicorn.sock;
+          '';
+        };
+      });
+    };
+
+    systemd.tmpfiles.rules = [
+      "d  /run/moin            0750 ${user} ${group} - -"
+      "d  ${dataDir}           0550 ${user} ${group} - -"
+    ]
+    ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [
+      "d  ${dataDir}/${wikiIdent}                      0750 ${user} ${group} - -"
+      "d  ${dataDir}/${wikiIdent}/config               0550 ${user} ${group} - -"
+      "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py -    -       -        - ${wikiConfigFile wikiIdent wiki}"
+      # needed in order to pass module name to gunicorn
+      "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py  -    -       -        - ${pkg}/share/moin/server/moin.wsgi"
+      # seed data files
+      "C  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - ${pkg}/share/moin/data"
+      # fix nix store permissions
+      "Z  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - -"
+    ])));
+  };
+
+  meta.maintainers = with lib.maintainers; [ b42 ];
+}
diff --git a/nixos/modules/services/web-apps/trac.nix b/nixos/modules/services/web-apps/trac.nix
new file mode 100644
index 000000000000..207fb857438a
--- /dev/null
+++ b/nixos/modules/services/web-apps/trac.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.trac;
+
+  inherit (lib) mkEnableOption mkIf mkOption types;
+
+in {
+
+  options = {
+
+    services.trac = {
+      enable = mkEnableOption "Trac service";
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            IP address that Trac should listen on.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8000;
+          description = ''
+            Listen port for Trac.
+          '';
+        };
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/trac";
+        type = types.path;
+        description = ''
+            The directory for storing the Trac data.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for Trac.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.trac = {
+      description = "Trac server";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf cfg.dataDir;
+        ExecStart = ''
+          ${pkgs.trac}/bin/tracd -s \
+            -b ${toString cfg.listen.ip} \
+            -p ${toString cfg.listen.port} \
+            ${cfg.dataDir}
+        '';
+      };
+      preStart = ''
+        if [ ! -e ${cfg.dataDir}/VERSION ]; then
+          ${pkgs.trac}/bin/trac-admin ${cfg.dataDir} initenv Trac "sqlite:db/trac.db"
+        fi
+      '';
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+  };
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 99304d0e48ae..3c5918baa533 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -6,6 +6,8 @@ let
 
   mainCfg = config.services.httpd;
 
+  runtimeDir = "/run/httpd";
+
   httpd = mainCfg.package.out;
 
   httpdConf = mainCfg.configFile;
@@ -27,41 +29,26 @@ let
 
   listenToString = l: "${l.ip}:${toString l.port}";
 
-  extraModules = attrByPath ["extraModules"] [] mainCfg;
-  extraForeignModules = filter isAttrs extraModules;
-  extraApacheModules = filter isString extraModules;
-
   allHosts = [mainCfg] ++ mainCfg.virtualHosts;
 
   enableSSL = any (vhost: vhost.enableSSL) allHosts;
 
-
-  # Names of modules from ${httpd}/modules that we want to load.
-  apacheModules =
-    [ # HTTP authentication mechanisms: basic and digest.
-      "auth_basic" "auth_digest"
-
-      # Authentication: is the user who he claims to be?
-      "authn_file" "authn_dbm" "authn_anon" "authn_core"
-
-      # Authorization: is the user allowed access?
-      "authz_user" "authz_groupfile" "authz_host" "authz_core"
-
-      # Other modules.
-      "ext_filter" "include" "log_config" "env" "mime_magic"
-      "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
-      "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
-      "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
-      "userdir" "alias" "rewrite" "proxy" "proxy_http"
-      "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
+  # NOTE: generally speaking order of modules is very important
+  modules =
+    [ # required apache modules our httpd service cannot run without
+      "authn_core" "authz_core"
+      "log_config"
+      "mime" "autoindex" "negotiation" "dir"
+      "alias" "rewrite"
+      "unixd" "slotmem_shm" "socache_shmcb"
       "mpm_${mainCfg.multiProcessingModule}"
-
-      # For compatibility with old configurations, the new module mod_access_compat is provided.
-      "access_compat"
     ]
     ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
     ++ optional enableSSL "ssl"
-    ++ extraApacheModules;
+    ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+    ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+    ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+    ++ mainCfg.extraModules;
 
 
   allDenied = "Require all denied";
@@ -85,20 +72,22 @@ let
 
 
   browserHacks = ''
-    BrowserMatch "Mozilla/2" nokeepalive
-    BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
-    BrowserMatch "RealPlayer 4\.0" force-response-1.0
-    BrowserMatch "Java/1\.0" force-response-1.0
-    BrowserMatch "JDK/1\.0" force-response-1.0
-    BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
-    BrowserMatch "^WebDrive" redirect-carefully
-    BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
-    BrowserMatch "^gnome-vfs" redirect-carefully
+    <IfModule mod_setenvif.c>
+        BrowserMatch "Mozilla/2" nokeepalive
+        BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+        BrowserMatch "RealPlayer 4\.0" force-response-1.0
+        BrowserMatch "Java/1\.0" force-response-1.0
+        BrowserMatch "JDK/1\.0" force-response-1.0
+        BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+        BrowserMatch "^WebDrive" redirect-carefully
+        BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+        BrowserMatch "^gnome-vfs" redirect-carefully
+    </IfModule>
   '';
 
 
   sslConf = ''
-    SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
+    SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
 
     Mutex posixsem
 
@@ -239,13 +228,13 @@ let
 
     ServerRoot ${httpd}
 
-    DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+    DefaultRuntimeDir ${runtimeDir}/runtime
 
-    PidFile ${mainCfg.stateDir}/httpd.pid
+    PidFile ${runtimeDir}/httpd.pid
 
     ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
       # mod_cgid requires this.
-      ScriptSock ${mainCfg.stateDir}/cgisock
+      ScriptSock ${runtimeDir}/cgisock
     ''}
 
     <IfModule prefork.c>
@@ -264,13 +253,12 @@ let
     Group ${mainCfg.group}
 
     ${let
-        load = {name, path}: "LoadModule ${name}_module ${path}\n";
-        allModules = map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
-          ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
-          ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
-          ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
-          ++ extraForeignModules;
-      in concatMapStrings load (unique allModules)
+        mkModule = module:
+          if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; }
+          else if isAttrs module then { inherit (module) name path; }
+          else throw "Expecting either a string or attribute set including a name and path.";
+      in
+        concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
     }
 
     AddHandler type-map var
@@ -337,6 +325,7 @@ in
 
   imports = [
     (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
+    (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
   ];
 
   ###### interface
@@ -384,7 +373,12 @@ in
       extraModules = mkOption {
         type = types.listOf types.unspecified;
         default = [];
-        example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+        example = literalExample ''
+          [
+            "proxy_connect"
+            { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
+          ]
+        '';
         description = ''
           Additional Apache modules to be used.  These can be
           specified as a string in the case of modules distributed
@@ -431,16 +425,6 @@ in
         '';
       };
 
-      stateDir = mkOption {
-        type = types.path;
-        default = "/run/httpd";
-        description = ''
-          Directory for Apache's transient runtime state (such as PID
-          files).  It is created automatically.  Note that the default,
-          <filename>/run/httpd</filename>, is deleted at boot time.
-        '';
-      };
-
       virtualHosts = mkOption {
         type = types.listOf (types.submodule (
           { options = import ./per-server-options.nix {
@@ -595,6 +579,28 @@ in
         date.timezone = "${config.time.timeZone}"
       '';
 
+    services.httpd.extraModules = mkBefore [
+      # HTTP authentication mechanisms: basic and digest.
+      "auth_basic" "auth_digest"
+
+      # Authentication: is the user who he claims to be?
+      "authn_file" "authn_dbm" "authn_anon"
+
+      # Authorization: is the user allowed access?
+      "authz_user" "authz_groupfile" "authz_host"
+
+      # Other modules.
+      "ext_filter" "include" "env" "mime_magic"
+      "cern_meta" "expires" "headers" "usertrack" "setenvif"
+      "dav" "status" "asis" "info" "dav_fs"
+      "vhost_alias" "imagemap" "actions" "speling"
+      "proxy" "proxy_http"
+      "cache" "cache_disk"
+
+      # For compatibility with old configurations, the new module mod_access_compat is provided.
+      "access_compat"
+    ];
+
     systemd.services.httpd =
       { description = "Apache HTTPD";
 
@@ -611,12 +617,6 @@ in
 
         preStart =
           ''
-            mkdir -m 0750 -p ${mainCfg.stateDir}
-            [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
-
-            mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
-            [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
-
             mkdir -m 0700 -p ${mainCfg.logDir}
 
             # Get rid of old semaphores.  These tend to accumulate across
@@ -630,10 +630,13 @@ in
         serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
         serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
         serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+        serviceConfig.Group = mainCfg.group;
         serviceConfig.Type = "forking";
-        serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+        serviceConfig.PIDFile = "${runtimeDir}/httpd.pid";
         serviceConfig.Restart = "always";
         serviceConfig.RestartSec = "5s";
+        serviceConfig.RuntimeDirectory = "httpd httpd/runtime";
+        serviceConfig.RuntimeDirectoryMode = "0750";
       };
 
   };
diff --git a/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix
new file mode 100644
index 000000000000..a9f5640905aa
--- /dev/null
+++ b/nixos/modules/services/x11/hardware/digimend.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.digimend;
+
+  pkg = config.boot.kernelPackages.digimend;
+
+in
+
+{
+
+  options = {
+
+    services.xserver.digimend = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable the digimend drivers for Huion/XP-Pen/etc. tablets.
+        '';
+      };
+
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    # digimend drivers use xsetwacom and wacom X11 drivers
+    services.xserver.wacom.enable = true;
+
+    boot.extraModulePackages = [ pkg ];
+
+    environment.etc."X11/xorg.conf.d/50-digimend.conf".source =
+      "${pkg}/usr/share/X11/xorg.conf.d/50-digimend.conf";
+
+  };
+
+}
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index bd289976532b..4a25232383d3 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -122,7 +122,7 @@ in {
         description =
           ''
             Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>,
-            or <literal>none</literal>
+            <literal>button</literal>, or <literal>none</literal>
           '';
       };