summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorFrederik Rietdijk <fridh@fridh.nl>2020-06-19 10:49:25 +0200
committerFrederik Rietdijk <fridh@fridh.nl>2020-06-19 10:49:25 +0200
commite4cd7a48f3a818deb45cdf135bc64fc29f1453d9 (patch)
tree0b310af4017670638860fcf380b1035bd0368c9c /nixos
parent3e0b8c5a709b75d54bd3d588d7f7f2d761104a35 (diff)
parent43f8fd5a4ecfe1423e7927a6a07aebf1033d33a1 (diff)
Merge staging-next into staging
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/config/fonts/fontconfig.nix9
-rw-r--r--nixos/modules/programs/hamster.nix2
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix10
-rw-r--r--nixos/modules/services/databases/openldap.nix2
-rw-r--r--nixos/modules/services/hardware/undervolt.nix79
-rw-r--r--nixos/modules/services/mail/mailman.nix455
-rw-r--r--nixos/modules/services/mail/mailman.xml59
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix10
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/redis.nix19
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix30
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py4
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix1
-rw-r--r--nixos/tests/ipfs.nix11
-rw-r--r--nixos/tests/prometheus-exporters.nix14
14 files changed, 434 insertions, 271 deletions
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index 6ac64b0ec9c6c..ac2a024eaa894 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -278,7 +278,14 @@ in
     (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
-  ];
+  ] ++ lib.forEach [ "enable" "substitutions" "preset" ]
+     (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
+       The fonts.fontconfig.ultimate module and configuration is obsolete.
+       The repository has since been archived and activity has ceased.
+       https://github.com/bohoomil/fontconfig-ultimate/issues/171.
+       No action should be needed for font configuration, as the fonts.fontconfig
+       module is already used by default.
+     '');
 
   options = {
 
diff --git a/nixos/modules/programs/hamster.nix b/nixos/modules/programs/hamster.nix
index ddf26a22fb536..b2f4a82b260e2 100644
--- a/nixos/modules/programs/hamster.nix
+++ b/nixos/modules/programs/hamster.nix
@@ -3,7 +3,7 @@
 with lib;
 
 {
-  meta.maintainers = maintainers.fabianhauser;
+  meta.maintainers = pkgs.hamster.meta.maintainers;
 
   options.programs.hamster.enable =
     mkEnableOption "Whether to enable hamster time tracking.";
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index 0185f490b0c2f..e1950b91382bd 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -25,7 +25,7 @@ let
      change_source = [ ${concatStringsSep "," cfg.changeSource} ],
      schedulers    = [ ${concatStringsSep "," cfg.schedulers} ],
      builders      = [ ${concatStringsSep "," cfg.builders} ],
-     status        = [ ${concatStringsSep "," cfg.status} ],
+     services      = [ ${concatStringsSep "," cfg.reporters} ],
     )
     for step in [ ${concatStringsSep "," cfg.factorySteps} ]:
       factory.addStep(step)
@@ -119,10 +119,10 @@ in {
         default = [ "worker.Worker('example-worker', 'pass')" ];
       };
 
-      status = mkOption {
+      reporters = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = "List of status notification endpoints.";
+        description = "List of reporter objects used to present build status to various users.";
       };
 
       user = mkOption {
@@ -276,6 +276,10 @@ in {
 
   imports = [
     (mkRenamedOptionModule [ "services" "buildbot-master" "bpPort" ] [ "services" "buildbot-master" "pbPort" ])
+    (mkRemovedOptionModule [ "services" "buildbot-master" "status" ] ''
+      Since Buildbot 0.9.0, status targets are deprecated and ignored.
+      Review your configuration and migrate to reporters (available at services.buildbot-master.reporters).
+    '')
   ];
 
   meta.maintainers = with lib.maintainers; [ nand0p mic92 ];
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index 8c2851c37ac23..9b4d9a98b745e 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -232,7 +232,7 @@ in
   };
 
   meta = {
-    maintainers = lib.maintainers.mic92;
+    maintainers = [ lib.maintainers.mic92 ];
   };
 
 
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index e5ef0601de3cd..da627af73bc68 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -1,18 +1,32 @@
 { config, pkgs, lib, ... }:
 
 with lib;
-
 let
   cfg = config.services.undervolt;
-in {
+  cliArgs = lib.cli.toGNUCommandLineShell {} {
+    inherit (cfg)
+      verbose
+      temp
+      ;
+    # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
+    #
+    #     Core or Cache offsets have no effect. It is not possible to set different offsets for
+    #     CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
+    #     both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
+    core = cfg.coreOffset;
+    cache = cfg.coreOffset;
+    gpu = cfg.gpuOffset;
+    uncore = cfg.uncoreOffset;
+    analogio = cfg.analogioOffset;
+
+    temp-bat = cfg.tempBat;
+    temp-ac = cfg.tempAc;
+  };
+in
+{
   options.services.undervolt = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to undervolt intel cpus.
-      '';
-    };
+    enable = mkEnableOption
+      "Intel CPU undervolting service (WARNING: may permanently damage your hardware!)";
 
     verbose = mkOption {
       type = types.bool;
@@ -32,58 +46,58 @@ in {
     };
 
     coreOffset = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The amount of voltage to offset the CPU cores by. Accepts a floating point number.
+        The amount of voltage in mV to offset the CPU cores by.
       '';
     };
 
     gpuOffset = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The amount of voltage to offset the GPU by. Accepts a floating point number.
+        The amount of voltage in mV to offset the GPU by.
       '';
     };
 
     uncoreOffset = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The amount of voltage to offset uncore by. Accepts a floating point number.
+        The amount of voltage in mV to offset uncore by.
       '';
     };
 
     analogioOffset = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The amount of voltage to offset analogio by. Accepts a floating point number.
+        The amount of voltage in mV to offset analogio by.
       '';
     };
 
     temp = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The temperature target. Accepts a floating point number.
+        The temperature target in Celsius degrees.
       '';
     };
 
     tempAc = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The temperature target on AC power. Accepts a floating point number.
+        The temperature target on AC power in Celsius degrees.
       '';
     };
 
     tempBat = mkOption {
-      type = types.nullOr types.str;
+      type = types.nullOr types.int;
       default = null;
       description = ''
-        The temperature target on battery power. Accepts a floating point number.
+        The temperature target on battery power in Celsius degrees.
       '';
     };
   };
@@ -100,24 +114,7 @@ in {
       serviceConfig = {
         Type = "oneshot";
         Restart = "no";
-
-        # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
-        #
-        #     Core or Cache offsets have no effect. It is not possible to set different offsets for
-        #     CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
-        #     both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
-        ExecStart = ''
-          ${pkgs.undervolt}/bin/undervolt \
-            ${optionalString cfg.verbose "--verbose"} \
-            ${optionalString (cfg.coreOffset != null) "--core ${cfg.coreOffset}"} \
-            ${optionalString (cfg.coreOffset != null) "--cache ${cfg.coreOffset}"} \
-            ${optionalString (cfg.gpuOffset != null) "--gpu ${cfg.gpuOffset}"} \
-            ${optionalString (cfg.uncoreOffset != null) "--uncore ${cfg.uncoreOffset}"} \
-            ${optionalString (cfg.analogioOffset != null) "--analogio ${cfg.analogioOffset}"} \
-            ${optionalString (cfg.temp != null) "--temp ${cfg.temp}"} \
-            ${optionalString (cfg.tempAc != null) "--temp-ac ${cfg.tempAc}"} \
-            ${optionalString (cfg.tempBat != null) "--temp-bat ${cfg.tempBat}"}
-        '';
+        ExecStart = "${pkgs.undervolt}/bin/undervolt ${cliArgs}";
       };
     };
 
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index f5e78b1829338..5c61cfbebf6cd 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -6,42 +6,46 @@ let
 
   cfg = config.services.mailman;
 
+  pythonEnv = pkgs.python3.withPackages (ps:
+    [ps.mailman ps.mailman-web]
+    ++ lib.optional cfg.hyperkitty.enable ps.mailman-hyperkitty
+    ++ cfg.extraPythonPackages);
+
   # This deliberately doesn't use recursiveUpdate so users can
   # override the defaults.
-  settings = {
+  webSettings = {
     DEFAULT_FROM_EMAIL = cfg.siteOwner;
     SERVER_EMAIL = cfg.siteOwner;
     ALLOWED_HOSTS = [ "localhost" "127.0.0.1" ] ++ cfg.webHosts;
     COMPRESS_OFFLINE = true;
-    STATIC_ROOT = "/var/lib/mailman-web/static";
+    STATIC_ROOT = "/var/lib/mailman-web-static";
     MEDIA_ROOT = "/var/lib/mailman-web/media";
+    LOGGING = {
+      version = 1;
+      disable_existing_loggers = true;
+      handlers.console.class = "logging.StreamHandler";
+      loggers.django = {
+        handlers = [ "console" ];
+        level = "INFO";
+      };
+    };
+    HAYSTACK_CONNECTIONS.default = {
+      ENGINE = "haystack.backends.whoosh_backend.WhooshEngine";
+      PATH = "/var/lib/mailman-web/fulltext-index";
+    };
   } // cfg.webSettings;
 
-  settingsJSON = pkgs.writeText "settings.json" (builtins.toJSON settings);
-
-  mailmanCfg = ''
-    [mailman]
-    site_owner: ${cfg.siteOwner}
-    layout: fhs
-
-    [paths.fhs]
-    bin_dir: ${pkgs.python3Packages.mailman}/bin
-    var_dir: /var/lib/mailman
-    queue_dir: $var_dir/queue
-    template_dir: $var_dir/templates
-    log_dir: $var_dir/log
-    lock_dir: $var_dir/lock
-    etc_dir: /etc
-    ext_dir: $etc_dir/mailman.d
-    pid_file: /run/mailman/master.pid
-  '' + optionalString cfg.hyperkitty.enable ''
-
-    [archiver.hyperkitty]
-    class: mailman_hyperkitty.Archiver
-    enable: yes
-    configuration: /var/lib/mailman/mailman-hyperkitty.cfg
+  webSettingsJSON = pkgs.writeText "settings.json" (builtins.toJSON webSettings);
+
+  # TODO: Should this be RFC42-ised so that users can set additional options without modifying the module?
+  mtaConfig = pkgs.writeText "mailman-postfix.cfg" ''
+    [postfix]
+    postmap_command: ${pkgs.postfix}/bin/postmap
+    transport_file_type: hash
   '';
 
+  mailmanCfg = lib.generators.toINI {} cfg.settings;
+
   mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" ''
     [general]
     # This is your HyperKitty installation, preferably on the localhost. This
@@ -84,7 +88,7 @@ in {
         type = types.package;
         default = pkgs.mailman;
         defaultText = "pkgs.mailman";
-        example = "pkgs.mailman.override { archivers = []; }";
+        example = literalExample "pkgs.mailman.override { archivers = []; }";
         description = "Mailman package to use";
       };
 
@@ -98,18 +102,6 @@ in {
         '';
       };
 
-      webRoot = mkOption {
-        type = types.path;
-        default = "${pkgs.mailman-web}/${pkgs.python3.sitePackages}";
-        defaultText = "\${pkgs.mailman-web}/\${pkgs.python3.sitePackages}";
-        description = ''
-          The web root for the Hyperkity + Postorius apps provided by Mailman.
-          This variable can be set, of course, but it mainly exists so that site
-          admins can refer to it in their own hand-written web server
-          configuration files.
-        '';
-      };
-
       webHosts = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -124,7 +116,7 @@ in {
 
       webUser = mkOption {
         type = types.str;
-        default = config.services.httpd.user;
+        default = "mailman-web";
         description = ''
           User to run mailman-web as
         '';
@@ -138,6 +130,22 @@ in {
         '';
       };
 
+      serve = {
+        enable = mkEnableOption "Automatic nginx and uwsgi setup for mailman-web";
+      };
+
+      extraPythonPackages = mkOption {
+        description = "Packages to add to the python environment used by mailman and mailman-web";
+        type = types.listOf types.package;
+        default = [];
+      };
+
+      settings = mkOption {
+        description = "Settings for mailman.cfg";
+        type = types.attrsOf (types.attrsOf types.str);
+        default = {};
+      };
+
       hyperkitty = {
         enable = mkEnableOption "the Hyperkitty archiver for Mailman";
 
@@ -158,6 +166,35 @@ in {
 
   config = mkIf cfg.enable {
 
+    services.mailman.settings = {
+      mailman.site_owner = lib.mkDefault cfg.siteOwner;
+      mailman.layout = "fhs";
+
+      "paths.fhs" = {
+        bin_dir = "${pkgs.python3Packages.mailman}/bin";
+        var_dir = "/var/lib/mailman";
+        queue_dir = "$var_dir/queue";
+        template_dir = "$var_dir/templates";
+        log_dir = "/var/log/mailman";
+        lock_dir = "$var_dir/lock";
+        etc_dir = "/etc";
+        ext_dir = "$etc_dir/mailman.d";
+        pid_file = "/run/mailman/master.pid";
+      };
+
+      mta.configuration = lib.mkDefault "${mtaConfig}";
+
+      "archiver.hyperkitty" = lib.mkIf cfg.hyperkitty.enable {
+        class = "mailman_hyperkitty.Archiver";
+        enable = "yes";
+        configuration = "/var/lib/mailman/mailman-hyperkitty.cfg";
+      };
+    } // (let
+      loggerNames = ["root" "archiver" "bounce" "config" "database" "debug" "error" "fromusenet" "http" "locks" "mischief" "plugins" "runner" "smtp"];
+      loggerSectionNames = map (n: "logging.${n}") loggerNames;
+      in lib.genAttrs loggerSectionNames(name: { handler = "stderr"; })
+    );
+
     assertions = let
       inherit (config.services) postfix;
 
@@ -183,7 +220,17 @@ in {
       (requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
     ];
 
-    users.users.mailman = { description = "GNU Mailman"; isSystemUser = true; };
+    users.users.mailman = {
+      description = "GNU Mailman";
+      isSystemUser = true;
+      group = "mailman";
+    };
+    users.users.mailman-web = lib.mkIf (cfg.webUser == "mailman-web") {
+      description = "GNU Mailman web interface";
+      isSystemUser = true;
+      group = "mailman";
+    };
+    users.groups.mailman = {};
 
     environment.etc."mailman.cfg".text = mailmanCfg;
 
@@ -198,197 +245,193 @@ in {
 
       import json
 
-      with open('${settingsJSON}') as f:
+      with open('${webSettingsJSON}') as f:
           globals().update(json.load(f))
 
       with open('/var/lib/mailman-web/settings_local.json') as f:
           globals().update(json.load(f))
     '';
 
-    environment.systemPackages = [ cfg.package ] ++ (with pkgs; [ mailman-web ]);
-
-    services.postfix = {
-      recipientDelimiter = "+";         # bake recipient addresses in mail envelopes via VERP
-      config = {
-        owner_request_special = "no";   # Mailman handles -owner addresses on its own
-      };
-    };
-
-    systemd.services.mailman = {
-      description = "GNU Mailman Master Process";
-      after = [ "network.target" ];
-      restartTriggers = [ config.environment.etc."mailman.cfg".source ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/mailman start";
-        ExecStop = "${cfg.package}/bin/mailman stop";
-        User = "mailman";
-        Type = "forking";
-        RuntimeDirectory = "mailman";
-        PIDFile = "/run/mailman/master.pid";
-      };
-    };
-
-    systemd.services.mailman-settings = {
-      description = "Generate settings files (including secrets) for Mailman";
-      before = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
-      requiredBy = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
-      path = with pkgs; [ jq ];
-      script = ''
-        mailmanDir=/var/lib/mailman
-        mailmanWebDir=/var/lib/mailman-web
-
-        mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
-        mailmanWebCfg=$mailmanWebDir/settings_local.json
-
-        install -m 0700 -o mailman -g nogroup -d $mailmanDir
-        install -m 0700 -o ${cfg.webUser} -g nogroup -d $mailmanWebDir
-
-        if [ ! -e $mailmanWebCfg ]; then
-            hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
-            secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
-
-            mailmanWebCfgTmp=$(mktemp)
-            jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
-                --arg archiver_key "$hyperkittyApiKey" \
-                --arg secret_key "$secretKey" \
-                >"$mailmanWebCfgTmp"
-            chown ${cfg.webUser} "$mailmanWebCfgTmp"
-            mv -n "$mailmanWebCfgTmp" $mailmanWebCfg
-        fi
-
-        hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY $mailmanWebCfg)"
-        mailmanCfgTmp=$(mktemp)
-        sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
-        chown mailman "$mailmanCfgTmp"
-        mv "$mailmanCfgTmp" $mailmanCfg
-      '';
-      serviceConfig = {
-        Type = "oneshot";
-        # RemainAfterExit makes restartIfChanged work for this service, so
-        # downstream services will get updated automatically when things like
-        # services.mailman.hyperkitty.baseUrl change.  Otherwise users have to
-        # restart things manually, which is confusing.
-        RemainAfterExit = "yes";
+    services.nginx = mkIf cfg.serve.enable {
+      enable = mkDefault true;
+      virtualHosts."${lib.head cfg.webHosts}" = {
+        serverAliases = cfg.webHosts;
+        locations = {
+          "/".extraConfig = "uwsgi_pass unix:/run/mailman-web.socket;";
+          "/static/".alias = webSettings.STATIC_ROOT + "/";
+        };
       };
     };
 
-    systemd.services.mailman-web = {
-      description = "Init Postorius DB";
-      before = [ "httpd.service" "uwsgi.service" ];
-      requiredBy = [ "httpd.service" "uwsgi.service" ];
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      script = ''
-        ${pkgs.mailman-web}/bin/mailman-web migrate
-        rm -rf static
-        ${pkgs.mailman-web}/bin/mailman-web collectstatic
-        ${pkgs.mailman-web}/bin/mailman-web compress
+    environment.systemPackages = [ (pkgs.buildEnv {
+      name = "mailman-tools";
+      # We don't want to pollute the system PATH with a python
+      # interpreter etc. so let's pick only the stuff we actually
+      # want from pythonEnv
+      pathsToLink = ["/bin"];
+      paths = [pythonEnv];
+      postBuild = ''
+        find $out/bin/ -mindepth 1 -not -name "mailman*" -delete
       '';
-      serviceConfig = {
-        User = cfg.webUser;
-        Type = "oneshot";
-        # Similar to mailman-settings.service, this makes restartTriggers work
-        # properly for this service.
-        RemainAfterExit = "yes";
-        WorkingDirectory = "/var/lib/mailman-web";
-      };
-    };
+    }) ];
 
-    systemd.services.mailman-daily = {
-      description = "Trigger daily Mailman events";
-      startAt = "daily";
-      restartTriggers = [ config.environment.etc."mailman.cfg".source ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/mailman digests --send";
-        User = "mailman";
-      };
-    };
-
-    systemd.services.hyperkitty = {
-      inherit (cfg.hyperkitty) enable;
-      description = "GNU Hyperkitty QCluster Process";
-      after = [ "network.target" ];
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      wantedBy = [ "mailman.service" "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web qcluster";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
+    services.postfix = {
+      recipientDelimiter = "+";         # bake recipient addresses in mail envelopes via VERP
+      config = {
+        owner_request_special = "no";   # Mailman handles -owner addresses on its own
       };
     };
 
-    systemd.services.hyperkitty-minutely = {
-      inherit (cfg.hyperkitty) enable;
-      description = "Trigger minutely Hyperkitty events";
-      startAt = "minutely";
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs minutely";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
-      };
+    systemd.sockets.mailman-uwsgi = lib.mkIf cfg.serve.enable {
+      wantedBy = ["sockets.target"];
+      before = ["nginx.service"];
+      socketConfig.ListenStream = "/run/mailman-web.socket";
     };
-
-    systemd.services.hyperkitty-quarter-hourly = {
-      inherit (cfg.hyperkitty) enable;
-      description = "Trigger quarter-hourly Hyperkitty events";
-      startAt = "*:00/15";
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs quarter_hourly";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
+    systemd.services = {
+      mailman = {
+        description = "GNU Mailman Master Process";
+        after = [ "network.target" ];
+        restartTriggers = [ config.environment.etc."mailman.cfg".source ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${pythonEnv}/bin/mailman start";
+          ExecStop = "${pythonEnv}/bin/mailman stop";
+          User = "mailman";
+          Group = "mailman";
+          Type = "forking";
+          RuntimeDirectory = "mailman";
+          LogsDirectory = "mailman";
+          PIDFile = "/run/mailman/master.pid";
+        };
       };
-    };
 
-    systemd.services.hyperkitty-hourly = {
-      inherit (cfg.hyperkitty) enable;
-      description = "Trigger hourly Hyperkitty events";
-      startAt = "hourly";
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs hourly";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
+      mailman-settings = {
+        description = "Generate settings files (including secrets) for Mailman";
+        before = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
+        requiredBy = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
+        path = with pkgs; [ jq ];
+        script = ''
+          mailmanDir=/var/lib/mailman
+          mailmanWebDir=/var/lib/mailman-web
+
+          mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
+          mailmanWebCfg=$mailmanWebDir/settings_local.json
+
+          install -m 0775 -o mailman -g mailman -d /var/lib/mailman-web-static
+          install -m 0770 -o mailman -g mailman -d $mailmanDir
+          install -m 0770 -o ${cfg.webUser} -g mailman -d $mailmanWebDir
+
+          if [ ! -e $mailmanWebCfg ]; then
+              hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
+              secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
+
+              mailmanWebCfgTmp=$(mktemp)
+              jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
+                  --arg archiver_key "$hyperkittyApiKey" \
+                  --arg secret_key "$secretKey" \
+                  >"$mailmanWebCfgTmp"
+              chown root:mailman "$mailmanWebCfgTmp"
+              chmod 440 "$mailmanWebCfgTmp"
+              mv -n "$mailmanWebCfgTmp" "$mailmanWebCfg"
+          fi
+
+          hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY "$mailmanWebCfg")"
+          mailmanCfgTmp=$(mktemp)
+          sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
+          chown mailman:mailman "$mailmanCfgTmp"
+          mv "$mailmanCfgTmp" "$mailmanCfg"
+        '';
       };
-    };
 
-    systemd.services.hyperkitty-daily = {
-      inherit (cfg.hyperkitty) enable;
-      description = "Trigger daily Hyperkitty events";
-      startAt = "daily";
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs daily";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
+      mailman-web-setup = {
+        description = "Prepare mailman-web files and database";
+        before = [ "uwsgi.service" "mailman-uwsgi.service" ];
+        requiredBy = [ "mailman-uwsgi.service" ];
+        restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+        script = ''
+          [[ -e "${webSettings.STATIC_ROOT}" ]] && find "${webSettings.STATIC_ROOT}/" -mindepth 1 -delete
+          ${pythonEnv}/bin/mailman-web migrate
+          ${pythonEnv}/bin/mailman-web collectstatic
+          ${pythonEnv}/bin/mailman-web compress
+        '';
+        serviceConfig = {
+          User = cfg.webUser;
+          Group = "mailman";
+          Type = "oneshot";
+          WorkingDirectory = "/var/lib/mailman-web";
+        };
       };
-    };
 
-    systemd.services.hyperkitty-weekly = {
-      inherit (cfg.hyperkitty) enable;
-      description = "Trigger weekly Hyperkitty events";
-      startAt = "weekly";
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs weekly";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
+      mailman-uwsgi = mkIf cfg.serve.enable (let
+        uwsgiConfig.uwsgi = {
+          type = "normal";
+          plugins = ["python3"];
+          home = pythonEnv;
+          module = "mailman_web.wsgi";
+        };
+        uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig);
+      in {
+        wantedBy = ["multi-user.target"];
+        requires = ["mailman-uwsgi.socket" "mailman-web-setup.service"];
+        restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+        serviceConfig = {
+          # Since the mailman-web settings.py obstinately creates a logs
+          # dir in the cwd, change to the (writable) runtime directory before
+          # starting uwsgi.
+          ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; }}/bin/uwsgi --json ${uwsgiConfigFile}";
+          User = cfg.webUser;
+          Group = "mailman";
+          RuntimeDirectory = "mailman-uwsgi";
+        };
+      });
+
+      mailman-daily = {
+        description = "Trigger daily Mailman events";
+        startAt = "daily";
+        restartTriggers = [ config.environment.etc."mailman.cfg".source ];
+        serviceConfig = {
+          ExecStart = "${pythonEnv}/bin/mailman digests --send";
+          User = "mailman";
+          Group = "mailman";
+        };
       };
-    };
 
-    systemd.services.hyperkitty-yearly = {
-      inherit (cfg.hyperkitty) enable;
-      description = "Trigger yearly Hyperkitty events";
-      startAt = "yearly";
-      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
-      serviceConfig = {
-        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs yearly";
-        User = cfg.webUser;
-        WorkingDirectory = "/var/lib/mailman-web";
+      hyperkitty = lib.mkIf cfg.hyperkitty.enable {
+        description = "GNU Hyperkitty QCluster Process";
+        after = [ "network.target" ];
+        restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+        wantedBy = [ "mailman.service" "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${pythonEnv}/bin/mailman-web qcluster";
+          User = cfg.webUser;
+          Group = "mailman";
+          WorkingDirectory = "/var/lib/mailman-web";
+        };
       };
-    };
+    } // flip lib.mapAttrs' {
+      "minutely" = "minutely";
+      "quarter_hourly" = "*:00/15";
+      "hourly" = "hourly";
+      "daily" = "daily";
+      "weekly" = "weekly";
+      "yearly" = "yearly";
+    } (name: startAt:
+      lib.nameValuePair "hyperkitty-${name}" (lib.mkIf cfg.hyperkitty.enable {
+        description = "Trigger ${name} Hyperkitty events";
+        inherit startAt;
+        restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+        serviceConfig = {
+          ExecStart = "${pythonEnv}/bin/mailman-web runjobs minutely";
+          User = cfg.webUser;
+          Group = "mailman";
+          WorkingDirectory = "/var/lib/mailman-web";
+        };
+      }));
+  };
 
+  meta = {
+    maintainers = with lib.maintainers; [ lheckemann ];
+    doc = ./mailman.xml;
   };
 
 }
diff --git a/nixos/modules/services/mail/mailman.xml b/nixos/modules/services/mail/mailman.xml
new file mode 100644
index 0000000000000..cbe50ed0b9179
--- /dev/null
+++ b/nixos/modules/services/mail/mailman.xml
@@ -0,0 +1,59 @@
+<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-mailman">
+  <title>Mailman</title>
+  <para>
+    <link xlink:href="https://www.list.org">Mailman</link> is free
+    software for managing electronic mail discussion and e-newsletter
+    lists. Mailman and its web interface can be configured using the
+    corresponding NixOS module. Note that this service is best used with
+    an existing, securely configured Postfix setup, as it does not automatically configure this.
+  </para>
+
+  <section xml:id="module-services-mailman-basic-usage">
+    <title>Basic usage</title>
+    <para>
+      For a basic configuration, the following settings are suggested:
+      <programlisting>{ config, ... }: {
+  services.postfix = {
+    enable = true;
+    relayDomains = ["hash:/var/lib/mailman/data/postfix_domains"];
+    sslCert = config.security.acme.certs."lists.example.org".directory + "/full.pem";
+    sslKey = config.security.acme.certs."lists.example.org".directory + "/key.pem";
+    config = {
+      transport_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
+      local_recipient_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
+    };
+  };
+  services.mailman = {
+    <link linkend="opt-services.mailman.enable">enable</link> = true;
+    <link linkend="opt-services.mailman.serve.enable">serve.enable</link> = true;
+    <link linkend="opt-services.mailman.hyperkitty.enable">hyperkitty.enable</link> = true;
+    <link linkend="opt-services.mailman.hyperkitty.enable">webHosts</link> = ["lists.example.org"];
+    <link linkend="opt-services.mailman.hyperkitty.enable">siteOwner</link> = "mailman@example.org";
+  };
+  <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">services.nginx.virtualHosts."lists.example.org".enableACME</link> = true;
+  <link linkend="opt-services.mailman.hyperkitty.enable">networking.firewall.allowedTCPPorts</link> = [ 25 80 443 ];
+}</programlisting>
+    </para>
+    <para>
+      DNS records will also be required:
+      <itemizedlist>
+        <listitem><para><literal>AAAA</literal> and <literal>A</literal> records pointing to the host in question, in order for browsers to be able to discover the address of the web server;</para></listitem>
+        <listitem><para>An <literal>MX</literal> record pointing to a domain name at which the host is reachable, in order for other mail servers to be able to deliver emails to the mailing lists it hosts.</para></listitem>
+      </itemizedlist>
+    </para>
+    <para>
+      After this has been done and appropriate DNS records have been
+      set up, the Postorius mailing list manager and the Hyperkitty
+      archive browser will be available at
+      https://lists.example.org/. Note that this setup is not
+      sufficient to deliver emails to most email providers nor to
+      avoid spam -- a number of additional measures for authenticating
+      incoming and outgoing mails, such as SPF, DMARC and DKIM are
+      necessary, but outside the scope of the Mailman module.
+    </para>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 0318acae50f70..c4aedd7e23b20 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -39,6 +39,7 @@ let
     "node"
     "postfix"
     "postgres"
+    "redis"
     "rspamd"
     "snmp"
     "surfboard"
@@ -171,15 +172,6 @@ in
        (opt: lib.mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
          The prometheus exporters are now configured using `services.prometheus.exporters'.
          See the 18.03 release notes for more information.
-       '' ))
-
-    ++ (lib.forEach [ "enable" "substitutions" "preset" ]
-       (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
-         The fonts.fontconfig.ultimate module and configuration is obsolete.
-         The repository has since been archived and activity has ceased.
-         https://github.com/bohoomil/fontconfig-ultimate/issues/171.
-         No action should be needed for font configuration, as the fonts.fontconfig
-         module is already used by default.
        '' ));
 
   options.services.prometheus.exporters = mkOption {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/redis.nix b/nixos/modules/services/monitoring/prometheus/exporters/redis.nix
new file mode 100644
index 0000000000000..befbcb21f7664
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/redis.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.redis;
+in
+{
+  port = 9121;
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-redis-exporter}/bin/redis_exporter \
+          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index a3bd40135d193..7d18410ff0a45 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -12,6 +12,19 @@ let
     (optionalString (cfg.defaultMode == "norouting") "--routing=none")
   ] ++ cfg.extraFlags);
 
+  splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
+
+  multiaddrToListenStream = addrRaw: let
+      addr = splitMulitaddr addrRaw;
+      s = builtins.elemAt addr;
+    in if s 0 == "ip4" && s 2 == "tcp"
+      then "${s 1}:${s 3}"
+    else if s 0 == "ip6" && s 2 == "tcp"
+      then "[${s 1}]:${s 3}"
+    else if s 0 == "unix"
+      then "/${lib.concatStringsSep "/" (lib.tail addr)}"
+    else null; # not valid for listen stream, skip
+
 in {
 
   ###### interface
@@ -80,7 +93,10 @@ in {
 
       swarmAddress = mkOption {
         type = types.listOf types.str;
-        default = [ "/ip4/0.0.0.0/tcp/4001" "/ip6/::/tcp/4001" ];
+        default = [
+          "/ip4/0.0.0.0/tcp/4001"
+          "/ip6/::/tcp/4001"
+        ];
         description = "Where IPFS listens for incoming p2p connections";
       };
 
@@ -250,14 +266,18 @@ in {
 
     systemd.sockets.ipfs-gateway = {
       wantedBy = [ "sockets.target" ];
-      socketConfig.ListenStream = [ "" ]
-        ++ lib.optional (cfg.gatewayAddress == opt.gatewayAddress.default) [ "127.0.0.1:8080" "[::1]:8080" ];
+      socketConfig.ListenStream = let
+          fromCfg = multiaddrToListenStream cfg.gatewayAddress;
+        in [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
     };
 
     systemd.sockets.ipfs-api = {
       wantedBy = [ "sockets.target" ];
-      socketConfig.ListenStream = [ "" "%t/ipfs.sock" ]
-        ++ lib.optional (cfg.apiAddress == opt.apiAddress.default) [ "127.0.0.1:5001" "[::1]:5001" ];
+      # We also include "%t/ipfs.sock" because tere is no way to put the "%t"
+      # in the multiaddr.
+      socketConfig.ListenStream = let
+          fromCfg = multiaddrToListenStream cfg.apiAddress;
+        in [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg;
     };
 
   };
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index d8baed65c6df3..97e824fe629ce 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -47,9 +47,9 @@ def write_loader_conf(profile, generation):
         if "@timeout@" != "":
             f.write("timeout @timeout@\n")
         if profile:
-            f.write("default nixos-%s-generation-%d.conf\n".format(profile, generation))
+            f.write("default nixos-%s-generation-%d.conf\n" % (profile, generation))
         else:
-            f.write("default nixos-generation-%d.conf\n".format(generation))
+            f.write("default nixos-generation-%d.conf\n" % (generation))
         if not @editor@:
             f.write("editor 0\n");
         f.write("console-mode @consoleMode@\n");
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index bee21f1a8f363..c6dbb96951ae0 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -233,6 +233,7 @@ in rec {
 
     path = mkOption {
       default = [];
+      type = with types; listOf (oneOf [ package str ]);
       apply = ps: "${makeBinPath ps}:${makeSearchPathOutput "bin" "sbin" ps}";
       description = ''
         Packages added to the service's <envar>PATH</envar>
diff --git a/nixos/tests/ipfs.nix b/nixos/tests/ipfs.nix
index 82234f969226c..9c0ff5306e06a 100644
--- a/nixos/tests/ipfs.nix
+++ b/nixos/tests/ipfs.nix
@@ -7,21 +7,28 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   nodes.machine = { ... }: {
     services.ipfs = {
       enable = true;
+      # Also will add a unix domain socket socket API address, see module.
+      startWhenNeeded = true;
       apiAddress = "/ip4/127.0.0.1/tcp/2324";
     };
   };
 
   testScript = ''
     start_all()
-    machine.wait_for_unit("ipfs")
 
-    machine.wait_until_succeeds("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
+    # IPv4 activation
+
+    machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
     ipfs_hash = machine.succeed(
         "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | awk '{ print $2 }'"
     )
 
     machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")
 
+    # Unix domain socket activation
+
+    machine.stop_job("ipfs")
+
     ipfs_hash = machine.succeed(
         "echo fnord2 | ipfs --api /unix/run/ipfs.sock add | awk '{ print $2 }'"
     )
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 4dbd64312224e..75c4ca12db24d 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -475,6 +475,20 @@ let
       '';
     };
 
+    redis = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider.services.redis.enable = true;
+      exporterTest = ''
+        wait_for_unit("redis.service")
+        wait_for_unit("prometheus-redis-exporter.service")
+        wait_for_open_port(6379)
+        wait_for_open_port(9121)
+        wait_until_succeeds("curl -sSf localhost:9121/metrics | grep -q 'redis_up 1'")
+      '';
+    };
+
     rspamd = {
       exporterConfig = {
         enable = true;