summary refs log tree commit diff
diff options
context:
space:
mode:
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2021-04-08 18:14:17 +0000
committerGitHub <noreply@github.com>2021-04-08 18:14:17 +0000
commit85b57e4446562377e8a81d1818a8fa13d99e4f42 (patch)
tree08768f255584c1a01b911a27f783c8c39d8887d9
parentbf6abedefbebbec8f54cda97bd33bc06c5f46ed5 (diff)
parentc6bd90d48fbb60297c9406edc1693b7aa66cd33b (diff)
downloadnixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.tar
nixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.tar.gz
nixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.tar.bz2
nixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.tar.lz
nixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.tar.xz
nixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.tar.zst
nixpkgs-85b57e4446562377e8a81d1818a8fa13d99e4f42.zip
Merge master into staging-next
-rw-r--r--.github/CODEOWNERS6
-rw-r--r--nixos/doc/manual/release-notes/rl-2105.xml10
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix2
-rw-r--r--nixos/modules/services/misc/home-assistant.nix19
-rw-r--r--nixos/modules/services/networking/gvpe.nix2
-rw-r--r--nixos/modules/services/networking/libreswan.nix6
-rw-r--r--nixos/modules/services/networking/mullvad-vpn.nix2
-rw-r--r--nixos/modules/services/networking/nomad.nix2
-rw-r--r--nixos/modules/services/networking/quagga.nix2
-rw-r--r--nixos/modules/services/networking/rxe.nix4
-rw-r--r--nixos/modules/services/networking/wg-quick.nix8
-rw-r--r--nixos/modules/services/system/cloud-init.nix2
-rw-r--r--nixos/modules/services/web-apps/discourse.nix1035
-rw-r--r--nixos/modules/services/web-apps/discourse.xml323
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix14
-rw-r--r--nixos/modules/system/boot/initrd-openvpn.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces.nix2
-rw-r--r--nixos/tests/all-tests.nix5
-rw-r--r--nixos/tests/discourse.nix197
-rw-r--r--nixos/tests/kernel-generic.nix37
-rw-r--r--nixos/tests/kernel-latest.nix17
-rw-r--r--nixos/tests/kernel-lts.nix17
-rw-r--r--nixos/tests/kernel-testing.nix17
-rw-r--r--nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix2
-rw-r--r--pkgs/applications/misc/blucontrol/wrapper.nix32
-rw-r--r--pkgs/applications/misc/free42/default.nix27
-rw-r--r--pkgs/applications/misc/metadata-cleaner/default.nix4
-rw-r--r--pkgs/applications/networking/browsers/chromium/upstream-info.json6
-rw-r--r--pkgs/applications/networking/browsers/palemoon/default.nix4
-rw-r--r--pkgs/applications/search/recoll/default.nix10
-rw-r--r--pkgs/applications/window-managers/dwl/default.nix21
-rw-r--r--pkgs/applications/window-managers/labwc/default.nix12
-rw-r--r--pkgs/applications/window-managers/sway/default.nix9
-rw-r--r--pkgs/applications/window-managers/sway/load-configuration-from-etc.patch38
-rw-r--r--pkgs/applications/window-managers/wayfire/applications.nix29
-rw-r--r--pkgs/development/compilers/llvm/12/clang/default.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/compiler-rt.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/default.nix4
-rw-r--r--pkgs/development/compilers/llvm/12/libc++/default.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/libc++abi.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/libunwind.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/lld.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/lldb.nix2
-rw-r--r--pkgs/development/compilers/llvm/12/llvm.nix4
-rw-r--r--pkgs/development/compilers/llvm/12/openmp.nix2
-rw-r--r--pkgs/development/libraries/openzwave/default.nix51
-rw-r--r--pkgs/development/libraries/tracker/default.nix10
-rw-r--r--pkgs/development/libraries/v8/default.nix34
-rw-r--r--pkgs/development/libraries/v8/gcc_arm.patch31
-rw-r--r--pkgs/development/libraries/wlroots/0.12.nix57
-rw-r--r--pkgs/development/libraries/wlroots/default.nix10
-rw-r--r--pkgs/development/python-modules/aiodiscover/default.nix12
-rw-r--r--pkgs/development/python-modules/asyncio-nats-client/default.nix50
-rw-r--r--pkgs/development/python-modules/homeassistant-pyozw/default.nix17
-rw-r--r--pkgs/development/python-modules/nats-python/default.nix46
-rw-r--r--pkgs/development/python-modules/smartypants/default.nix14
-rw-r--r--pkgs/development/python-modules/smartypants/hgtags17
-rw-r--r--pkgs/development/python-modules/ytmusicapi/default.nix4
-rw-r--r--pkgs/development/ruby-modules/bundix/default.nix2
-rw-r--r--pkgs/development/tools/analysis/radare2/default.nix17
-rw-r--r--pkgs/development/tools/analysis/rizin/cutter.nix2
-rw-r--r--pkgs/development/tools/analysis/rizin/default.nix2
-rw-r--r--pkgs/development/tools/rust/cargo-deny/default.nix6
-rw-r--r--pkgs/os-specific/darwin/macfuse/default.nix6
-rw-r--r--pkgs/os-specific/linux/kernel/generic.nix5
-rw-r--r--pkgs/os-specific/linux/kernel/linux-4.14.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-4.19.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-4.4.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-4.9.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-5.10.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-5.11.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-5.4.nix4
-rw-r--r--pkgs/os-specific/linux/kernel/linux-testing.nix4
-rw-r--r--pkgs/servers/home-assistant/component-packages.nix21
-rw-r--r--pkgs/servers/home-assistant/default.nix49
-rw-r--r--pkgs/servers/home-assistant/frontend.nix4
-rw-r--r--pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch12
-rw-r--r--pkgs/servers/web-apps/discourse/admin_create.patch48
-rw-r--r--pkgs/servers/web-apps/discourse/default.nix234
-rw-r--r--pkgs/servers/web-apps/discourse/disable_jhead.patch12
-rw-r--r--pkgs/servers/web-apps/discourse/mail_receiver/default.nix39
-rw-r--r--pkgs/servers/web-apps/discourse/nixos_defaults.patch13
-rw-r--r--pkgs/servers/web-apps/discourse/rubyEnv/Gemfile248
-rw-r--r--pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock561
-rw-r--r--pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix2272
-rw-r--r--pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch25
-rwxr-xr-xpkgs/servers/web-apps/discourse/update.py164
-rw-r--r--pkgs/tools/filesystems/sshfs-fuse/common.nix61
-rw-r--r--pkgs/tools/filesystems/sshfs-fuse/default.nix74
-rw-r--r--pkgs/tools/filesystems/sshfs-fuse/fix-fuse-darwin-h.patch14
-rw-r--r--pkgs/tools/networking/openssh/copyid.nix11
-rw-r--r--pkgs/top-level/aliases.nix2
-rw-r--r--pkgs/top-level/all-packages.nix57
-rw-r--r--pkgs/top-level/php-packages.nix8
-rw-r--r--pkgs/top-level/python-packages.nix8
96 files changed, 5971 insertions, 341 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b68e8436fe3..b15d89219f4 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -94,6 +94,10 @@
 /pkgs/applications/science/math/R   @peti
 /pkgs/development/r-modules         @peti
 
+# Ruby
+/pkgs/development/interpreters/ruby @marsam
+/pkgs/development/ruby-modules      @marsam
+
 # Rust
 /pkgs/development/compilers/rust @Mic92 @LnL7 @zowoq
 /pkgs/build-support/rust @andir @danieldk @zowoq
@@ -135,7 +139,7 @@
 /pkgs/development/libraries/qt-5 @ttuegel
 
 # PostgreSQL and related stuff
-/pkgs/servers/sql/postgresql @thoughtpolice
+/pkgs/servers/sql/postgresql @thoughtpolice @marsam
 /nixos/modules/services/databases/postgresql.xml @thoughtpolice
 /nixos/modules/services/databases/postgresql.nix @thoughtpolice
 /nixos/tests/postgresql.nix @thoughtpolice
diff --git a/nixos/doc/manual/release-notes/rl-2105.xml b/nixos/doc/manual/release-notes/rl-2105.xml
index ba42601096b..b5290b374f9 100644
--- a/nixos/doc/manual/release-notes/rl-2105.xml
+++ b/nixos/doc/manual/release-notes/rl-2105.xml
@@ -118,6 +118,16 @@
        <xref linkend="opt-services.samba-wsdd.enable" /> Web Services Dynamic Discovery host daemon
      </para>
    </listitem>
+   <listitem>
+     <para>
+       <link xlink:href="https://www.discourse.org/">Discourse</link>, a
+       modern and open source discussion platform.
+     </para>
+     <para>
+       See the <link linkend="module-services-discourse">Discourse
+       section of the NixOS manual</link> for more information.
+     </para>
+   </listitem>
   </itemizedlist>
 
  </section>
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 6f172720558..6f600a608dc 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -897,6 +897,7 @@
   ./services/web-apps/calibre-web.nix
   ./services/web-apps/convos.nix
   ./services/web-apps/cryptpad.nix
+  ./services/web-apps/discourse.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/dokuwiki.nix
   ./services/web-apps/engelsystem.nix
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 7efcf8ac6c5..b5346b1cd44 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -266,7 +266,7 @@ in
           gitMinimal
           openssh
           util-linux
-          iproute
+          iproute2
           ethtool
           thin-provisioning-tools
           iptables
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index f6398c08397..2787c975b35 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -50,10 +50,15 @@ let
   # List of components used in config
   extraComponents = filter useComponent availableComponents;
 
-  package = if (cfg.autoExtraComponents && cfg.config != null)
+  testedPackage = if (cfg.autoExtraComponents && cfg.config != null)
     then (cfg.package.override { inherit extraComponents; })
     else cfg.package;
 
+  # overridePythonAttrs has to be applied after override
+  package = testedPackage.overridePythonAttrs (oldAttrs: {
+    doCheck = false;
+  });
+
   # If you are changing this, please update the description in applyDefaultConfig
   defaultConfig = {
     homeassistant.time_zone = config.time.timeZone;
@@ -183,13 +188,9 @@ in {
     };
 
     package = mkOption {
-      default = pkgs.home-assistant.overridePythonAttrs (oldAttrs: {
-        doCheck = false;
-      });
+      default = pkgs.home-assistant;
       defaultText = literalExample ''
-        pkgs.home-assistant.overridePythonAttrs (oldAttrs: {
-          doCheck = false;
-        })
+        pkgs.home-assistant
       '';
       type = types.package;
       example = literalExample ''
@@ -198,10 +199,12 @@ in {
         }
       '';
       description = ''
-        Home Assistant package to use. By default the tests are disabled, as they take a considerable amout of time to complete.
+        Home Assistant package to use. Tests are automatically disabled, as they take a considerable amout of time to complete.
         Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
         If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
         to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
+        Avoid <literal>home-assistant.overridePythonAttrs</literal> if you use
+        <literal>autoExtraComponents</literal>.
       '';
     };
 
diff --git a/nixos/modules/services/networking/gvpe.nix b/nixos/modules/services/networking/gvpe.nix
index b851facf1e3..4fad37ba15e 100644
--- a/nixos/modules/services/networking/gvpe.nix
+++ b/nixos/modules/services/networking/gvpe.nix
@@ -27,7 +27,7 @@ let
     text = ''
       #! /bin/sh
 
-      export PATH=$PATH:${pkgs.iproute}/sbin
+      export PATH=$PATH:${pkgs.iproute2}/sbin
 
       ip link set $IFNAME up
       ip address add ${cfg.ipAddress} dev $IFNAME
diff --git a/nixos/modules/services/networking/libreswan.nix b/nixos/modules/services/networking/libreswan.nix
index 7a25769e067..81bc4e1cf95 100644
--- a/nixos/modules/services/networking/libreswan.nix
+++ b/nixos/modules/services/networking/libreswan.nix
@@ -91,7 +91,7 @@ in
       description = "Internet Key Exchange (IKE) Protocol Daemon for IPsec";
       path = [
         "${pkgs.libreswan}"
-        "${pkgs.iproute}"
+        "${pkgs.iproute2}"
         "${pkgs.procps}"
         "${pkgs.nssTools}"
         "${pkgs.iptables}"
@@ -115,8 +115,8 @@ in
         ExecStart = "${libexec}/pluto --config ${configFile} --nofork \$PLUTO_OPTIONS";
         ExecStop = "${libexec}/whack --shutdown";
         ExecStopPost = [
-          "${pkgs.iproute}/bin/ip xfrm policy flush"
-          "${pkgs.iproute}/bin/ip xfrm state flush"
+          "${pkgs.iproute2}/bin/ip xfrm policy flush"
+          "${pkgs.iproute2}/bin/ip xfrm state flush"
           "${ipsec} --stopnflog"
         ];
         ExecReload = "${libexec}/whack --listen";
diff --git a/nixos/modules/services/networking/mullvad-vpn.nix b/nixos/modules/services/networking/mullvad-vpn.nix
index 6f595ca4be2..8ce71f26b3e 100644
--- a/nixos/modules/services/networking/mullvad-vpn.nix
+++ b/nixos/modules/services/networking/mullvad-vpn.nix
@@ -28,7 +28,7 @@ with lib;
         "systemd-resolved.service"
       ];
       path = [
-        pkgs.iproute
+        pkgs.iproute2
         # Needed for ping
         "/run/wrappers"
       ];
diff --git a/nixos/modules/services/networking/nomad.nix b/nixos/modules/services/networking/nomad.nix
index 9f1b443b89b..48689f1195c 100644
--- a/nixos/modules/services/networking/nomad.nix
+++ b/nixos/modules/services/networking/nomad.nix
@@ -119,7 +119,7 @@ in
       path = cfg.extraPackages ++ (with pkgs; [
         # Client mode requires at least the following:
         coreutils
-        iproute
+        iproute2
         iptables
       ]);
 
diff --git a/nixos/modules/services/networking/quagga.nix b/nixos/modules/services/networking/quagga.nix
index 5acdd5af8f8..7c169fe62d8 100644
--- a/nixos/modules/services/networking/quagga.nix
+++ b/nixos/modules/services/networking/quagga.nix
@@ -164,7 +164,7 @@ in
                   preStart = ''
                     install -m 0755 -o quagga -g quagga -d /run/quagga
 
-                    ${pkgs.iproute}/bin/ip route flush proto zebra
+                    ${pkgs.iproute2}/bin/ip route flush proto zebra
                   '';
                 }
               else
diff --git a/nixos/modules/services/networking/rxe.nix b/nixos/modules/services/networking/rxe.nix
index c7d174a00de..868e2c81ccb 100644
--- a/nixos/modules/services/networking/rxe.nix
+++ b/nixos/modules/services/networking/rxe.nix
@@ -39,11 +39,11 @@ in {
         Type = "oneshot";
         RemainAfterExit = true;
         ExecStart = map ( x:
-          "${pkgs.iproute}/bin/rdma link add rxe_${x} type rxe netdev ${x}"
+          "${pkgs.iproute2}/bin/rdma link add rxe_${x} type rxe netdev ${x}"
           ) cfg.interfaces;
 
         ExecStop = map ( x:
-          "${pkgs.iproute}/bin/rdma link delete rxe_${x}"
+          "${pkgs.iproute2}/bin/rdma link delete rxe_${x}"
           ) cfg.interfaces;
       };
     };
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 02fe40a22a1..3b76de58548 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -57,7 +57,7 @@ let
 
       preUp = mkOption {
         example = literalExample ''
-          ${pkgs.iproute}/bin/ip netns add foo
+          ${pkgs.iproute2}/bin/ip netns add foo
         '';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
@@ -68,7 +68,7 @@ let
 
       preDown = mkOption {
         example = literalExample ''
-          ${pkgs.iproute}/bin/ip netns del foo
+          ${pkgs.iproute2}/bin/ip netns del foo
         '';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
@@ -79,7 +79,7 @@ let
 
       postUp = mkOption {
         example = literalExample ''
-          ${pkgs.iproute}/bin/ip netns add foo
+          ${pkgs.iproute2}/bin/ip netns add foo
         '';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
@@ -90,7 +90,7 @@ let
 
       postDown = mkOption {
         example = literalExample ''
-          ${pkgs.iproute}/bin/ip netns del foo
+          ${pkgs.iproute2}/bin/ip netns del foo
         '';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index f83db30c1f0..eb82b738e49 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -5,7 +5,7 @@ with lib;
 let cfg = config.services.cloud-init;
     path = with pkgs; [
       cloud-init
-      iproute
+      iproute2
       nettools
       openssh
       shadow
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
new file mode 100644
index 00000000000..03ea002c9de
--- /dev/null
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -0,0 +1,1035 @@
+{ config, options, lib, pkgs, utils, ... }:
+
+let
+  json = pkgs.formats.json {};
+
+  cfg = config.services.discourse;
+
+  postgresqlPackage = if config.services.postgresql.enable then
+                        config.services.postgresql.package
+                      else
+                        pkgs.postgresql;
+
+  # We only want to create a database if we're actually going to connect to it.
+  databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == null;
+
+  tlsEnabled = (cfg.enableACME
+                || cfg.sslCertificate != null
+                || cfg.sslCertificateKey != null);
+in
+{
+  options = {
+    services.discourse = {
+      enable = lib.mkEnableOption "Discourse, an open source discussion platform";
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.discourse;
+        defaultText = "pkgs.discourse";
+        description = ''
+          The discourse package to use.
+        '';
+      };
+
+      hostname = lib.mkOption {
+        type = lib.types.str;
+        default = if config.networking.domain != null then
+                    config.networking.fqdn
+                  else
+                    config.networking.hostName;
+        defaultText = "config.networking.fqdn";
+        example = "discourse.example.com";
+        description = ''
+          The hostname to serve Discourse on.
+        '';
+      };
+
+      secretKeyBaseFile = lib.mkOption {
+        type = with lib.types; nullOr path;
+        default = null;
+        example = "/run/keys/secret_key_base";
+        description = ''
+          The path to a file containing the
+          <literal>secret_key_base</literal> secret.
+
+          Discourse uses <literal>secret_key_base</literal> to encrypt
+          the cookie store, which contains session data, and to digest
+          user auth tokens.
+
+          Needs to be a 64 byte long string of hexadecimal
+          characters. You can generate one by running
+
+          <screen>
+          <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file
+          </screen>
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      sslCertificate = lib.mkOption {
+        type = with lib.types; nullOr path;
+        default = null;
+        example = "/run/keys/ssl.cert";
+        description = ''
+          The path to the server SSL certificate. Set this to enable
+          SSL.
+        '';
+      };
+
+      sslCertificateKey = lib.mkOption {
+        type = with lib.types; nullOr path;
+        default = null;
+        example = "/run/keys/ssl.key";
+        description = ''
+          The path to the server SSL certificate key. Set this to
+          enable SSL.
+        '';
+      };
+
+      enableACME = lib.mkOption {
+        type = lib.types.bool;
+        default = cfg.sslCertificate == null && cfg.sslCertificateKey == null;
+        defaultText = "true, unless services.discourse.sslCertificate and services.discourse.sslCertificateKey are set.";
+        description = ''
+          Whether an ACME certificate should be used to secure
+          connections to the server.
+        '';
+      };
+
+      backendSettings = lib.mkOption {
+        type = with lib.types; attrsOf (nullOr (oneOf [ str int bool float ]));
+        default = {};
+        example = lib.literalExample ''
+          {
+            max_reqs_per_ip_per_minute = 300;
+            max_reqs_per_ip_per_10_seconds = 60;
+            max_asset_reqs_per_ip_per_10_seconds = 250;
+            max_reqs_per_ip_mode = "warn+block";
+          };
+        '';
+        description = ''
+          Additional settings to put in the
+          <filename>discourse.conf</filename> file.
+
+          Look in the
+          <link xlink:href="https://github.com/discourse/discourse/blob/master/config/discourse_defaults.conf">discourse_defaults.conf</link>
+          file in the upstream distribution to find available options.
+
+          Setting an option to <literal>null</literal> means
+          <quote>define variable, but leave right-hand side
+          empty</quote>.
+        '';
+      };
+
+      siteSettings = lib.mkOption {
+        type = json.type;
+        default = {};
+        example = lib.literalExample ''
+          {
+            required = {
+              title = "My Cats";
+              site_description = "Discuss My Cats (and be nice plz)";
+            };
+            login = {
+              enable_github_logins = true;
+              github_client_id = "a2f6dfe838cb3206ce20";
+              github_client_secret._secret = /run/keys/discourse_github_client_secret;
+            };
+          };
+        '';
+        description = ''
+          Discourse site settings. These are the settings that can be
+          changed from the UI. This only defines their default values:
+          they can still be overridden from the UI.
+
+          Available settings can be found by looking in the
+          <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">site_settings.yml</link>
+          file of the upstream distribution. To find a setting's path,
+          you only need to care about the first two levels; i.e. its
+          category and name. See the example.
+
+          Settings containing secret data should be set to an
+          attribute set containing the attribute
+          <literal>_secret</literal> - a string pointing to a file
+          containing the value the option should be set to. See the
+          example to get a better picture of this: in the resulting
+          <filename>config/nixos_site_settings.json</filename> file,
+          the <literal>login.github_client_secret</literal> key will
+          be set to the contents of the
+          <filename>/run/keys/discourse_github_client_secret</filename>
+          file.
+        '';
+      };
+
+      admin = {
+        email = lib.mkOption {
+          type = lib.types.str;
+          example = "admin@example.com";
+          description = ''
+            The admin user email address.
+          '';
+        };
+
+        username = lib.mkOption {
+          type = lib.types.str;
+          example = "admin";
+          description = ''
+            The admin user username.
+          '';
+        };
+
+        fullName = lib.mkOption {
+          type = lib.types.str;
+          description = ''
+            The admin user's full name.
+          '';
+        };
+
+        passwordFile = lib.mkOption {
+          type = lib.types.path;
+          description = ''
+            A path to a file containing the admin user's password.
+
+            This should be a string, not a nix path, since nix paths are
+            copied into the world-readable nix store.
+          '';
+        };
+      };
+
+      nginx.enable = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Whether an <literal>nginx</literal> virtual host should be
+          set up to serve Discourse. Only disable if you're planning
+          to use a different web server, which is not recommended.
+        '';
+      };
+
+      database = {
+        pool = lib.mkOption {
+          type = lib.types.int;
+          default = 8;
+          description = ''
+            Database connection pool size.
+          '';
+        };
+
+        host = lib.mkOption {
+          type = with lib.types; nullOr str;
+          default = null;
+          description = ''
+            Discourse database hostname. <literal>null</literal> means <quote>prefer
+            local unix socket connection</quote>.
+          '';
+        };
+
+        passwordFile = lib.mkOption {
+          type = with lib.types; nullOr path;
+          default = null;
+          description = ''
+            File containing the Discourse database user password.
+
+            This should be a string, not a nix path, since nix paths are
+            copied into the world-readable nix store.
+          '';
+        };
+
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = ''
+            Whether a database should be automatically created on the
+            local host. Set this to <literal>false</literal> if you plan
+            on provisioning a local database yourself. This has no effect
+            if <option>services.discourse.database.host</option> is customized.
+          '';
+        };
+
+        name = lib.mkOption {
+          type = lib.types.str;
+          default = "discourse";
+          description = ''
+            Discourse database name.
+          '';
+        };
+
+        username = lib.mkOption {
+          type = lib.types.str;
+          default = "discourse";
+          description = ''
+            Discourse database user.
+          '';
+        };
+      };
+
+      redis = {
+        host = lib.mkOption {
+          type = lib.types.str;
+          default = "localhost";
+          description = ''
+            Redis server hostname.
+          '';
+        };
+
+        passwordFile = lib.mkOption {
+          type = with lib.types; nullOr path;
+          default = null;
+          description = ''
+            File containing the Redis password.
+
+            This should be a string, not a nix path, since nix paths are
+            copied into the world-readable nix store.
+          '';
+        };
+
+        dbNumber = lib.mkOption {
+          type = lib.types.int;
+          default = 0;
+          description = ''
+            Redis database number.
+          '';
+        };
+
+        useSSL = lib.mkOption {
+          type = lib.types.bool;
+          default = cfg.redis.host != "localhost";
+          description = ''
+            Connect to Redis with SSL.
+          '';
+        };
+      };
+
+      mail = {
+        notificationEmailAddress = lib.mkOption {
+          type = lib.types.str;
+          default = "${if cfg.mail.incoming.enable then "notifications" else "noreply"}@${cfg.hostname}";
+          defaultText = ''
+            "notifications@`config.services.discourse.hostname`" if
+            config.services.discourse.mail.incoming.enable is "true",
+            otherwise "noreply`config.services.discourse.hostname`"
+          '';
+          description = ''
+            The <literal>from:</literal> email address used when
+            sending all essential system emails. The domain specified
+            here must have SPF, DKIM and reverse PTR records set
+            correctly for email to arrive.
+          '';
+        };
+
+        contactEmailAddress = lib.mkOption {
+          type = lib.types.str;
+          default = "";
+          description = ''
+            Email address of key contact responsible for this
+            site. Used for critical notifications, as well as on the
+            <literal>/about</literal> contact form for urgent matters.
+          '';
+        };
+
+        outgoing = {
+          serverAddress = lib.mkOption {
+            type = lib.types.str;
+            default = "localhost";
+            description = ''
+              The address of the SMTP server Discourse should use to
+              send email.
+            '';
+          };
+
+          port = lib.mkOption {
+            type = lib.types.int;
+            default = 25;
+            description = ''
+              The port of the SMTP server Discourse should use to
+              send email.
+            '';
+          };
+
+          username = lib.mkOption {
+            type = with lib.types; nullOr str;
+            default = null;
+            description = ''
+              The username of the SMTP server.
+            '';
+          };
+
+          passwordFile = lib.mkOption {
+            type = lib.types.nullOr lib.types.path;
+            default = null;
+            description = ''
+              A file containing the password of the SMTP server account.
+
+              This should be a string, not a nix path, since nix paths
+              are copied into the world-readable nix store.
+            '';
+          };
+
+          domain = lib.mkOption {
+            type = lib.types.str;
+            default = cfg.hostname;
+            description = ''
+              HELO domain to use for outgoing mail.
+            '';
+          };
+
+          authentication = lib.mkOption {
+            type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
+            default = null;
+            description = ''
+              Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+            '';
+          };
+
+          enableStartTLSAuto = lib.mkOption {
+            type = lib.types.bool;
+            default = true;
+            description = ''
+              Whether to try to use StartTLS.
+            '';
+          };
+
+          opensslVerifyMode = lib.mkOption {
+            type = lib.types.str;
+            default = "peer";
+            description = ''
+              How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+            '';
+          };
+        };
+
+        incoming = {
+          enable = lib.mkOption {
+            type = lib.types.bool;
+            default = false;
+            description = ''
+              Whether to set up Postfix to receive incoming mail.
+            '';
+          };
+
+          replyEmailAddress = lib.mkOption {
+            type = lib.types.str;
+            default = "%{reply_key}@${cfg.hostname}";
+            defaultText = "%{reply_key}@`config.services.discourse.hostname`";
+            description = ''
+              Template for reply by email incoming email address, for
+              example: %{reply_key}@reply.example.com or
+              replies+%{reply_key}@example.com
+            '';
+          };
+
+          mailReceiverPackage = lib.mkOption {
+            type = lib.types.package;
+            default = pkgs.discourse-mail-receiver;
+            defaultText = "pkgs.discourse-mail-receiver";
+            description = ''
+              The discourse-mail-receiver package to use.
+            '';
+          };
+
+          apiKeyFile = lib.mkOption {
+            type = lib.types.nullOr lib.types.path;
+            default = null;
+            description = ''
+              A file containing the Discourse API key used to add
+              posts and messages from mail. If left at its default
+              value <literal>null</literal>, one will be automatically
+              generated.
+
+              This should be a string, not a nix path, since nix paths
+              are copied into the world-readable nix store.
+            '';
+          };
+        };
+      };
+
+      plugins = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [];
+        example = ''
+          [
+            (pkgs.fetchFromGitHub {
+              owner = "discourse";
+              repo = "discourse-spoiler-alert";
+              rev = "e200cfa571d252cab63f3d30d619b370986e4cee";
+              sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33";
+            })
+          ];
+        '';
+        description = ''
+          <productname>Discourse</productname> plugins to install as a
+          list of derivations. As long as a plugin supports the
+          standard install method, packaging it should only require
+          fetching its source with an appropriate fetcher.
+        '';
+      };
+
+      sidekiqProcesses = lib.mkOption {
+        type = lib.types.int;
+        default = 1;
+        description = ''
+          How many Sidekiq processes should be spawned.
+        '';
+      };
+
+      unicornTimeout = lib.mkOption {
+        type = lib.types.int;
+        default = 30;
+        description = ''
+          Time in seconds before a request to Unicorn times out.
+
+          This can be raised if the system Discourse is running on is
+          too slow to handle many requests within 30 seconds.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.database.host != null) -> (cfg.database.passwordFile != null);
+        message = "When services.gitlab.database.host is customized, services.discourse.database.passwordFile must be set!";
+      }
+      {
+        assertion = cfg.hostname != "";
+        message = "Could not automatically determine hostname, set service.discourse.hostname manually.";
+      }
+    ];
+
+
+    # Default config values are from `config/discourse_defaults.conf`
+    # upstream.
+    services.discourse.backendSettings = lib.mapAttrs (_: lib.mkDefault) {
+      db_pool = cfg.database.pool;
+      db_timeout = 5000;
+      db_connect_timeout = 5;
+      db_socket = null;
+      db_host = cfg.database.host;
+      db_backup_host = null;
+      db_port = null;
+      db_backup_port = 5432;
+      db_name = cfg.database.name;
+      db_username = if databaseActuallyCreateLocally then "discourse" else cfg.database.username;
+      db_password = cfg.database.passwordFile;
+      db_prepared_statements = false;
+      db_replica_host = null;
+      db_replica_port = null;
+      db_advisory_locks = true;
+
+      inherit (cfg) hostname;
+      backup_hostname = null;
+
+      smtp_address = cfg.mail.outgoing.serverAddress;
+      smtp_port = cfg.mail.outgoing.port;
+      smtp_domain = cfg.mail.outgoing.domain;
+      smtp_user_name = cfg.mail.outgoing.username;
+      smtp_password = cfg.mail.outgoing.passwordFile;
+      smtp_authentication = cfg.mail.outgoing.authentication;
+      smtp_enable_start_tls = cfg.mail.outgoing.enableStartTLSAuto;
+      smtp_openssl_verify_mode = cfg.mail.outgoing.opensslVerifyMode;
+
+      load_mini_profiler = true;
+      mini_profiler_snapshots_period = 0;
+      mini_profiler_snapshots_transport_url = null;
+      mini_profiler_snapshots_transport_auth_key = null;
+
+      cdn_url = null;
+      cdn_origin_hostname = null;
+      developer_emails = null;
+
+      redis_host = cfg.redis.host;
+      redis_port = 6379;
+      redis_slave_host = null;
+      redis_slave_port = 6379;
+      redis_db = cfg.redis.dbNumber;
+      redis_password = cfg.redis.passwordFile;
+      redis_skip_client_commands = false;
+      redis_use_ssl = cfg.redis.useSSL;
+
+      message_bus_redis_enabled = false;
+      message_bus_redis_host = "localhost";
+      message_bus_redis_port = 6379;
+      message_bus_redis_slave_host = null;
+      message_bus_redis_slave_port = 6379;
+      message_bus_redis_db = 0;
+      message_bus_redis_password = null;
+      message_bus_redis_skip_client_commands = false;
+
+      enable_cors = false;
+      cors_origin = "";
+      serve_static_assets = false;
+      sidekiq_workers = 5;
+      rtl_css = false;
+      connection_reaper_age = 30;
+      connection_reaper_interval = 30;
+      relative_url_root = null;
+      message_bus_max_backlog_size = 100;
+      secret_key_base = cfg.secretKeyBaseFile;
+      fallback_assets_path = null;
+
+      s3_bucket = null;
+      s3_region = null;
+      s3_access_key_id = null;
+      s3_secret_access_key = null;
+      s3_use_iam_profile = null;
+      s3_cdn_url = null;
+      s3_endpoint = null;
+      s3_http_continue_timeout = null;
+      s3_install_cors_rule = null;
+
+      max_user_api_reqs_per_minute = 20;
+      max_user_api_reqs_per_day = 2880;
+      max_admin_api_reqs_per_key_per_minute = 60;
+      max_reqs_per_ip_per_minute = 200;
+      max_reqs_per_ip_per_10_seconds = 50;
+      max_asset_reqs_per_ip_per_10_seconds = 200;
+      max_reqs_per_ip_mode = "block";
+      max_reqs_rate_limit_on_private = false;
+      force_anonymous_min_queue_seconds = 1;
+      force_anonymous_min_per_10_seconds = 3;
+      background_requests_max_queue_length = 0.5;
+      reject_message_bus_queue_seconds = 0.1;
+      disable_search_queue_threshold = 1;
+      max_old_rebakes_per_15_minutes = 300;
+      max_logster_logs = 1000;
+      refresh_maxmind_db_during_precompile_days = 2;
+      maxmind_backup_path = null;
+      maxmind_license_key = null;
+      enable_performance_http_headers = false;
+      enable_js_error_reporting = true;
+      mini_scheduler_workers = 5;
+      compress_anon_cache = false;
+      anon_cache_store_threshold = 2;
+      allowed_theme_repos = null;
+      enable_email_sync_demon = false;
+      max_digests_enqueued_per_30_mins_per_site = 10000;
+    };
+
+    services.redis.enable = lib.mkDefault (cfg.redis.host == "localhost");
+
+    services.postgresql = lib.mkIf databaseActuallyCreateLocally {
+      enable = true;
+      ensureUsers = [{ name = "discourse"; }];
+    };
+
+    # The postgresql module doesn't currently support concepts like
+    # objects owners and extensions; for now we tack on what's needed
+    # here.
+    systemd.services.discourse-postgresql =
+      let
+        pgsql = config.services.postgresql;
+      in
+        lib.mkIf databaseActuallyCreateLocally {
+          after = [ "postgresql.service" ];
+          bindsTo = [ "postgresql.service" ];
+          wantedBy = [ "discourse.service" ];
+          partOf = [ "discourse.service" ];
+          path = [
+            pgsql.package
+          ];
+          script = ''
+            set -o errexit -o pipefail -o nounset -o errtrace
+            shopt -s inherit_errexit
+
+            psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'discourse'" | grep -q 1 || psql -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
+            psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+            psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
+          '';
+
+          serviceConfig = {
+            User = pgsql.superUser;
+            Type = "oneshot";
+            RemainAfterExit = true;
+          };
+        };
+
+    systemd.services.discourse = {
+      wantedBy = [ "multi-user.target" ];
+      after = [
+        "redis.service"
+        "postgresql.service"
+        "discourse-postgresql.service"
+      ];
+      bindsTo = [
+        "redis.service"
+      ] ++ lib.optionals (cfg.database.host == null) [
+        "postgresql.service"
+        "discourse-postgresql.service"
+      ];
+      path = cfg.package.runtimeDeps ++ [
+        postgresqlPackage
+        pkgs.replace
+        cfg.package.rake
+      ];
+      environment = cfg.package.runtimeEnv // {
+        UNICORN_TIMEOUT = builtins.toString cfg.unicornTimeout;
+        UNICORN_SIDEKIQS = builtins.toString cfg.sidekiqProcesses;
+      };
+
+      preStart =
+        let
+          discourseKeyValue = lib.generators.toKeyValue {
+            mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " = " {
+              mkValueString = v: with builtins;
+                if isInt           v then toString v
+                else if isString   v then ''"${v}"''
+                else if true  ==   v then "true"
+                else if false ==   v then "false"
+                else if null  ==   v then ""
+                else if isFloat    v then lib.strings.floatToString v
+                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+            };
+          };
+
+          discourseConf = pkgs.writeText "discourse.conf" (discourseKeyValue cfg.backendSettings);
+
+          mkSecretReplacement = file:
+            lib.optionalString (file != null) ''
+              (
+                  password=$(<'${file}')
+                  replace-literal -fe '${file}' "$password" /run/discourse/config/discourse.conf
+              )
+            '';
+        in ''
+          set -o errexit -o pipefail -o nounset -o errtrace
+          shopt -s inherit_errexit
+
+          umask u=rwx,g=rx,o=
+
+          cp -r ${cfg.package}/share/discourse/config.dist/* /run/discourse/config/
+          cp -r ${cfg.package}/share/discourse/public.dist/* /run/discourse/public/
+          cp -r ${cfg.package}/share/discourse/plugins.dist/* /run/discourse/plugins/
+          ${lib.concatMapStrings (p: "ln -sf ${p} /run/discourse/plugins/") cfg.plugins}
+          ln -sf /var/lib/discourse/uploads /run/discourse/public/uploads
+          ln -sf /var/lib/discourse/backups /run/discourse/public/backups
+
+          (
+              umask u=rwx,g=,o=
+
+              ${utils.genJqSecretsReplacementSnippet
+                  cfg.siteSettings
+                  "/run/discourse/config/nixos_site_settings.json"
+              }
+              install -T -m 0400 -o discourse ${discourseConf} /run/discourse/config/discourse.conf
+              ${mkSecretReplacement cfg.database.passwordFile}
+              ${mkSecretReplacement cfg.mail.outgoing.passwordFile}
+              ${mkSecretReplacement cfg.redis.passwordFile}
+              ${mkSecretReplacement cfg.secretKeyBaseFile}
+          )
+
+          discourse-rake db:migrate >>/var/log/discourse/db_migration.log
+          chmod -R u+w /run/discourse/tmp/
+
+          export ADMIN_EMAIL="${cfg.admin.email}"
+          export ADMIN_NAME="${cfg.admin.fullName}"
+          export ADMIN_USERNAME="${cfg.admin.username}"
+          export ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})"
+          discourse-rake admin:create_noninteractively
+
+          discourse-rake themes:update
+          discourse-rake uploads:regenerate_missing_optimized
+        '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = "discourse";
+        Group = "discourse";
+        RuntimeDirectory = map (p: "discourse/" + p) [
+          "config"
+          "home"
+          "tmp"
+          "assets/javascripts/plugins"
+          "public"
+          "plugins"
+          "sockets"
+        ];
+        RuntimeDirectoryMode = 0750;
+        StateDirectory = map (p: "discourse/" + p) [
+          "uploads"
+          "backups"
+        ];
+        StateDirectoryMode = 0750;
+        LogsDirectory = "discourse";
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = "${cfg.package}/share/discourse";
+
+        RemoveIPC = true;
+        PrivateTmp = true;
+        NoNewPrivileges = true;
+        RestrictSUIDSGID = true;
+        ProtectSystem = "strict";
+        ProtectHome = "read-only";
+
+        ExecStart = "${cfg.package.rubyEnv}/bin/bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb";
+      };
+    };
+
+    services.nginx = lib.mkIf cfg.nginx.enable {
+      enable = true;
+      additionalModules = [ pkgs.nginxModules.brotli ];
+
+      recommendedTlsSettings = true;
+      recommendedOptimisation = true;
+      recommendedGzipSettings = true;
+      recommendedProxySettings = true;
+
+      upstreams.discourse.servers."unix:/run/discourse/sockets/unicorn.sock" = {};
+
+      appendHttpConfig = ''
+        # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week)
+        # levels means it is a 2 deep heirarchy cause we can have lots of files
+        # max_size limits the size of the cache
+        proxy_cache_path /var/cache/nginx inactive=1440m levels=1:2 keys_zone=discourse:10m max_size=600m;
+
+        # see: https://meta.discourse.org/t/x/74060
+        proxy_buffer_size 8k;
+      '';
+
+      virtualHosts.${cfg.hostname} = {
+        inherit (cfg) sslCertificate sslCertificateKey enableACME;
+        forceSSL = lib.mkDefault tlsEnabled;
+
+        root = "/run/discourse/public";
+
+        locations =
+          let
+            proxy = { extraConfig ? "" }: {
+              proxyPass = "http://discourse";
+              extraConfig = extraConfig + ''
+                proxy_set_header X-Request-Start "t=''${msec}";
+              '';
+            };
+            cache = time: ''
+              expires ${time};
+              add_header Cache-Control public,immutable;
+            '';
+            cache_1y = cache "1y";
+            cache_1d = cache "1d";
+          in
+            {
+              "/".tryFiles = "$uri @discourse";
+              "@discourse" = proxy {};
+              "^~ /backups/".extraConfig = ''
+                internal;
+              '';
+              "/favicon.ico" = {
+                return = "204";
+                extraConfig = ''
+                  access_log off;
+                  log_not_found off;
+                '';
+              };
+              "~ ^/uploads/short-url/" = proxy {};
+              "~ ^/secure-media-uploads/" = proxy {};
+              "~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$".extraConfig = cache_1y + ''
+                add_header Access-Control-Allow-Origin *;
+              '';
+              "/srv/status" = proxy {
+                extraConfig = ''
+                  access_log off;
+                  log_not_found off;
+                '';
+              };
+              "~ ^/javascripts/".extraConfig = cache_1d;
+              "~ ^/assets/(?<asset_path>.+)$".extraConfig = cache_1y + ''
+                # asset pipeline enables this
+                brotli_static on;
+                gzip_static on;
+              '';
+              "~ ^/plugins/".extraConfig = cache_1y;
+              "~ /images/emoji/".extraConfig = cache_1y;
+              "~ ^/uploads/" = proxy {
+                extraConfig = cache_1y + ''
+                  proxy_set_header X-Sendfile-Type X-Accel-Redirect;
+                  proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/;
+
+                  # custom CSS
+                  location ~ /stylesheet-cache/ {
+                      try_files $uri =404;
+                  }
+                  # this allows us to bypass rails
+                  location ~* \.(gif|png|jpg|jpeg|bmp|tif|tiff|ico|webp)$ {
+                      try_files $uri =404;
+                  }
+                  # SVG needs an extra header attached
+                  location ~* \.(svg)$ {
+                  }
+                  # thumbnails & optimized images
+                  location ~ /_?optimized/ {
+                      try_files $uri =404;
+                  }
+                '';
+              };
+              "~ ^/admin/backups/" = proxy {
+                extraConfig = ''
+                  proxy_set_header X-Sendfile-Type X-Accel-Redirect;
+                  proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/;
+                '';
+              };
+              "~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker)" = proxy {
+                extraConfig = ''
+                  # if Set-Cookie is in the response nothing gets cached
+                  # this is double bad cause we are not passing last modified in
+                  proxy_ignore_headers "Set-Cookie";
+                  proxy_hide_header "Set-Cookie";
+                  proxy_hide_header "X-Discourse-Username";
+                  proxy_hide_header "X-Runtime";
+
+                  # note x-accel-redirect can not be used with proxy_cache
+                  proxy_cache discourse;
+                  proxy_cache_key "$scheme,$host,$request_uri";
+                  proxy_cache_valid 200 301 302 7d;
+                  proxy_cache_valid any 1m;
+                '';
+              };
+              "/message-bus/" = proxy {
+                extraConfig = ''
+                  proxy_http_version 1.1;
+                  proxy_buffering off;
+                '';
+              };
+              "/downloads/".extraConfig = ''
+                internal;
+                alias /run/discourse/public/;
+              '';
+            };
+      };
+    };
+
+    systemd.services.discourse-mail-receiver-setup = lib.mkIf cfg.mail.incoming.enable (
+      let
+        mail-receiver-environment = {
+          MAIL_DOMAIN = cfg.hostname;
+          DISCOURSE_BASE_URL = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
+          DISCOURSE_API_KEY = "@api-key@";
+          DISCOURSE_API_USERNAME = "system";
+        };
+        mail-receiver-json = json.generate "mail-receiver.json" mail-receiver-environment;
+      in
+        {
+          before = [ "postfix.service" ];
+          after = [ "discourse.service" ];
+          wantedBy = [ "discourse.service" ];
+          partOf = [ "discourse.service" ];
+          path = [
+            cfg.package.rake
+            pkgs.jq
+          ];
+          preStart = lib.optionalString (cfg.mail.incoming.apiKeyFile == null) ''
+            set -o errexit -o pipefail -o nounset -o errtrace
+            shopt -s inherit_errexit
+
+            if [[ ! -e /var/lib/discourse-mail-receiver/api_key ]]; then
+                discourse-rake api_key:create_master[email-receiver] >/var/lib/discourse-mail-receiver/api_key
+            fi
+          '';
+          script =
+            let
+              apiKeyPath =
+                if cfg.mail.incoming.apiKeyFile == null then
+                  "/var/lib/discourse-mail-receiver/api_key"
+                else
+                  cfg.mail.incoming.apiKeyFile;
+            in ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+
+              export api_key=$(<'${apiKeyPath}')
+
+              jq <${mail-receiver-json} \
+                 '.DISCOURSE_API_KEY = $ENV.api_key' \
+                 >'/run/discourse-mail-receiver/mail-receiver-environment.json'
+            '';
+
+          serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+            RuntimeDirectory = "discourse-mail-receiver";
+            RuntimeDirectoryMode = "0700";
+            StateDirectory = "discourse-mail-receiver";
+            User = "discourse";
+            Group = "discourse";
+          };
+        });
+
+    services.discourse.siteSettings = {
+      required = {
+        notification_email = cfg.mail.notificationEmailAddress;
+        contact_email = cfg.mail.contactEmailAddress;
+      };
+      email = {
+        manual_polling_enabled = cfg.mail.incoming.enable;
+        reply_by_email_enabled = cfg.mail.incoming.enable;
+        reply_by_email_address = cfg.mail.incoming.replyEmailAddress;
+      };
+    };
+
+    services.postfix = lib.mkIf cfg.mail.incoming.enable {
+      enable = true;
+      sslCert = if cfg.sslCertificate != null then cfg.sslCertificate else "";
+      sslKey = if cfg.sslCertificateKey != null then cfg.sslCertificateKey else "";
+
+      origin = cfg.hostname;
+      relayDomains = [ cfg.hostname ];
+      config = {
+        smtpd_recipient_restrictions = "check_policy_service unix:private/discourse-policy";
+        append_dot_mydomain = lib.mkDefault false;
+        compatibility_level = "2";
+        smtputf8_enable = false;
+        smtpd_banner = lib.mkDefault "ESMTP server";
+        myhostname = lib.mkDefault cfg.hostname;
+        mydestination = lib.mkDefault "localhost";
+      };
+      transport = ''
+        ${cfg.hostname} discourse-mail-receiver:
+      '';
+      masterConfig = {
+        "discourse-mail-receiver" = {
+          type = "unix";
+          privileged = true;
+          chroot = false;
+          command = "pipe";
+          args = [
+            "user=discourse"
+            "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/receive-mail"
+            "\${recipient}"
+          ];
+        };
+        "discourse-policy" = {
+          type = "unix";
+          privileged = true;
+          chroot = false;
+          command = "spawn";
+          args = [
+            "user=discourse"
+            "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/discourse-smtp-fast-rejection"
+          ];
+        };
+      };
+    };
+
+    users.users = {
+      discourse = {
+        group = "discourse";
+        isSystemUser = true;
+      };
+    } // (lib.optionalAttrs cfg.nginx.enable {
+      ${config.services.nginx.user}.extraGroups = [ "discourse" ];
+    });
+
+    users.groups = {
+      discourse = {};
+    };
+
+    environment.systemPackages = [
+      cfg.package.rake
+    ];
+  };
+
+  meta.doc = ./discourse.xml;
+  meta.maintainers = [ lib.maintainers.talyz ];
+}
diff --git a/nixos/modules/services/web-apps/discourse.xml b/nixos/modules/services/web-apps/discourse.xml
new file mode 100644
index 00000000000..bae56242321
--- /dev/null
+++ b/nixos/modules/services/web-apps/discourse.xml
@@ -0,0 +1,323 @@
+<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-discourse">
+ <title>Discourse</title>
+ <para>
+   <link xlink:href="https://www.discourse.org/">Discourse</link> is a
+   modern and open source discussion platform.
+ </para>
+
+ <section xml:id="module-services-discourse-basic-usage">
+   <title>Basic usage</title>
+   <para>
+     A minimal configuration using Let's Encrypt for TLS certificates looks like this:
+<programlisting>
+services.discourse = {
+  <link linkend="opt-services.discourse.enable">enable</link> = true;
+  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
+  admin = {
+    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
+    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
+    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
+    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
+  };
+  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
+};
+<link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com";
+<link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
+</programlisting>
+   </para>
+
+   <para>
+     Provided a proper DNS setup, you'll be able to connect to the
+     instance at <literal>discourse.example.com</literal> and log in
+     using the credentials provided in
+     <literal>services.discourse.admin</literal>.
+   </para>
+ </section>
+
+ <section xml:id="module-services-discourse-tls">
+   <title>Using a regular TLS certificate</title>
+   <para>
+     To set up TLS using a regular certificate and key on file, use
+     the <xref linkend="opt-services.discourse.sslCertificate" />
+     and <xref linkend="opt-services.discourse.sslCertificateKey" />
+     options:
+
+<programlisting>
+services.discourse = {
+  <link linkend="opt-services.discourse.enable">enable</link> = true;
+  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
+  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
+  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
+  admin = {
+    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
+    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
+    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
+    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
+  };
+  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
+};
+</programlisting>
+
+   </para>
+ </section>
+
+ <section xml:id="module-services-discourse-database">
+   <title>Database access</title>
+   <para>
+     <productname>Discourse</productname> uses
+     <productname>PostgreSQL</productname> to store most of its
+     data. A database will automatically be enabled and a database
+     and role created unless <xref
+     linkend="opt-services.discourse.database.host" /> is changed from
+     its default of <literal>null</literal> or <xref
+     linkend="opt-services.discourse.database.createLocally" /> is set
+     to <literal>false</literal>.
+   </para>
+
+   <para>
+     External database access can also be configured by setting
+     <xref linkend="opt-services.discourse.database.host" />, <xref
+     linkend="opt-services.discourse.database.username" /> and <xref
+     linkend="opt-services.discourse.database.passwordFile" /> as
+     appropriate. Note that you need to manually create a database
+     called <literal>discourse</literal> (or the name you chose in
+     <xref linkend="opt-services.discourse.database.name" />) and
+     allow the configured database user full access to it.
+   </para>
+ </section>
+
+ <section xml:id="module-services-discourse-mail">
+   <title>Email</title>
+   <para>
+     In addition to the basic setup, you'll want to configure an SMTP
+     server <productname>Discourse</productname> can use to send user
+     registration and password reset emails, among others. You can
+     also optionally let <productname>Discourse</productname> receive
+     email, which enables people to reply to threads and conversations
+     via email.
+   </para>
+
+   <para>
+     A basic setup which assumes you want to use your configured <link
+     linkend="opt-services.discourse.hostname">hostname</link> as
+     email domain can be done like this:
+
+<programlisting>
+services.discourse = {
+  <link linkend="opt-services.discourse.enable">enable</link> = true;
+  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
+  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
+  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
+  admin = {
+    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
+    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
+    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
+    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
+  };
+  mail.outgoing = {
+    <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
+    <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
+    <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
+    <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
+  };
+  <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
+  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
+};
+</programlisting>
+
+     This assumes you have set up an MX record for the address you've
+     set in <link linkend="opt-services.discourse.hostname">hostname</link> and
+     requires proper SPF, DKIM and DMARC configuration to be done for
+     the domain you're sending from, in order for email to be reliably delivered.
+   </para>
+
+   <para>
+     If you want to use a different domain for your outgoing email
+     (for example <literal>example.com</literal> instead of
+     <literal>discourse.example.com</literal>) you should set
+     <xref linkend="opt-services.discourse.mail.notificationEmailAddress" /> and
+     <xref linkend="opt-services.discourse.mail.contactEmailAddress" /> manually.
+   </para>
+
+   <note>
+     <para>
+       Setup of TLS for incoming email is currently only configured
+       automatically when a regular TLS certificate is used, i.e. when
+       <xref linkend="opt-services.discourse.sslCertificate" /> and
+       <xref linkend="opt-services.discourse.sslCertificateKey" /> are
+       set.
+     </para>
+   </note>
+
+ </section>
+
+ <section xml:id="module-services-discourse-settings">
+   <title>Additional settings</title>
+   <para>
+     Additional site settings and backend settings, for which no
+     explicit <productname>NixOS</productname> options are provided,
+     can be set in <xref linkend="opt-services.discourse.siteSettings" /> and
+     <xref linkend="opt-services.discourse.backendSettings" /> respectively.
+   </para>
+
+   <section xml:id="module-services-discourse-site-settings">
+     <title>Site settings</title>
+     <para>
+       <quote>Site settings</quote> are the settings that can be
+       changed through the <productname>Discourse</productname>
+       UI. Their <emphasis>default</emphasis> values can be set using
+       <xref linkend="opt-services.discourse.siteSettings" />.
+     </para>
+
+     <para>
+       Settings are expressed as a Nix attribute set which matches the
+       structure of the configuration in
+       <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">config/site_settings.yml</link>.
+       To find a setting's path, you only need to care about the first
+       two levels; i.e. its category (e.g. <literal>login</literal>)
+       and name (e.g. <literal>invite_only</literal>).
+     </para>
+
+     <para>
+       Settings containing secret data should be set to an attribute
+       set containing the attribute <literal>_secret</literal> - a
+       string pointing to a file containing the value the option
+       should be set to. See the example.
+     </para>
+   </section>
+
+   <section xml:id="module-services-discourse-backend-settings">
+     <title>Backend settings</title>
+     <para>
+       Settings are expressed as a Nix attribute set which matches the
+       structure of the configuration in
+       <link xlink:href="https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf">config/discourse.conf</link>.
+       Empty parameters can be defined by setting them to
+       <literal>null</literal>.
+     </para>
+   </section>
+
+   <section xml:id="module-services-discourse-settings-example">
+     <title>Example</title>
+     <para>
+       The following example sets the title and description of the
+       <productname>Discourse</productname> instance and enables
+       <productname>GitHub</productname> login in the site settings,
+       and changes a few request limits in the backend settings:
+<programlisting>
+services.discourse = {
+  <link linkend="opt-services.discourse.enable">enable</link> = true;
+  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
+  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
+  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
+  admin = {
+    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
+    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
+    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
+    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
+  };
+  mail.outgoing = {
+    <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
+    <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
+    <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
+    <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
+  };
+  <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
+  <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = {
+    required = {
+      title = "My Cats";
+      site_description = "Discuss My Cats (and be nice plz)";
+    };
+    login = {
+      enable_github_logins = true;
+      github_client_id = "a2f6dfe838cb3206ce20";
+      github_client_secret._secret = /run/keys/discourse_github_client_secret;
+    };
+  };
+  <link linkend="opt-services.discourse.backendSettings">backendSettings</link> = {
+    max_reqs_per_ip_per_minute = 300;
+    max_reqs_per_ip_per_10_seconds = 60;
+    max_asset_reqs_per_ip_per_10_seconds = 250;
+    max_reqs_per_ip_mode = "warn+block";
+  };
+  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
+};
+</programlisting>
+     </para>
+     <para>
+       In the resulting site settings file, the
+       <literal>login.github_client_secret</literal> key will be set
+       to the contents of the
+       <filename>/run/keys/discourse_github_client_secret</filename>
+       file.
+     </para>
+   </section>
+ </section>
+  <section xml:id="module-services-discourse-plugins">
+    <title>Plugins</title>
+    <para>
+      You can install <productname>Discourse</productname> plugins
+      using the <xref linkend="opt-services.discourse.plugins" />
+      option. As long as a plugin supports the standard install
+      method, packaging it should only require fetching its source
+      with an appropriate fetcher.
+    </para>
+
+    <para>
+      Some plugins provide <link
+      linkend="module-services-discourse-site-settings">site
+      settings</link>. Their defaults can be configured using <xref
+      linkend="opt-services.discourse.siteSettings" />, just like
+      regular site settings. To find the names of these settings, look
+      in the <literal>config/settings.yml</literal> file of the plugin
+      repo.
+    </para>
+
+    <para>
+      For example, to add the <link
+      xlink:href="https://github.com/discourse/discourse-spoiler-alert">discourse-spoiler-alert</link>
+      plugin and disable it by default:
+
+<programlisting>
+services.discourse = {
+  <link linkend="opt-services.discourse.enable">enable</link> = true;
+  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
+  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
+  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
+  admin = {
+    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
+    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
+    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
+    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
+  };
+  mail.outgoing = {
+    <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
+    <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
+    <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
+    <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
+  };
+  <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
+  <link linkend="opt-services.discourse.mail.incoming.enable">plugins</link> = [
+    (pkgs.fetchFromGitHub {
+      owner = "discourse";
+      repo = "discourse-spoiler-alert";
+      rev = "e200cfa571d252cab63f3d30d619b370986e4cee";
+      sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33";
+    })
+  ];
+  <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = {
+    plugins = {
+      spoiler_enabled = false;
+    };
+  };
+  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
+};
+</programlisting>
+
+    </para>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index f011b527238..52fcce6d17b 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -397,6 +397,9 @@ in
         default = pkgs.nginxStable;
         defaultText = "pkgs.nginxStable";
         type = types.package;
+        apply = p: p.override {
+          modules = p.modules ++ cfg.additionalModules;
+        };
         description = "
           Nginx package to use. This defaults to the stable version. Note
           that the nginx team recommends to use the mainline version which
@@ -404,6 +407,17 @@ in
         ";
       };
 
+      additionalModules = mkOption {
+        default = [];
+        type = types.listOf (types.attrsOf types.anything);
+        example = literalExample "[ pkgs.nginxModules.brotli ]";
+        description = ''
+          Additional <link xlink:href="https://www.nginx.com/resources/wiki/modules/">third-party nginx modules</link>
+          to install. Packaged modules are available in
+          <literal>pkgs.nginxModules</literal>.
+        '';
+      };
+
       logError = mkOption {
         default = "stderr";
         type = types.str;
diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix
index e59bc7b6678..b35fb0b57c0 100644
--- a/nixos/modules/system/boot/initrd-openvpn.nix
+++ b/nixos/modules/system/boot/initrd-openvpn.nix
@@ -55,7 +55,7 @@ in
     # The shared libraries are required for DNS resolution
     boot.initrd.extraUtilsCommands = ''
       copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn
-      copy_bin_and_libs ${pkgs.iproute}/bin/ip
+      copy_bin_and_libs ${pkgs.iproute2}/bin/ip
 
       cp -pv ${pkgs.glibc}/lib/libresolv.so.2 $out/lib
       cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index b5d97849658..f501f85b2a9 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -1144,7 +1144,7 @@ in
 
     environment.systemPackages =
       [ pkgs.host
-        pkgs.iproute
+        pkgs.iproute2
         pkgs.iputils
         pkgs.nettools
       ]
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 62188ddf9e8..58b2ba7fa51 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -88,6 +88,7 @@ in
   croc = handleTest ./croc.nix {};
   deluge = handleTest ./deluge.nix {};
   dhparams = handleTest ./dhparams.nix {};
+  discourse = handleTest ./discourse.nix {};
   dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
   dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {};
   doas = handleTest ./doas.nix {};
@@ -193,9 +194,7 @@ in
   keepalived = handleTest ./keepalived.nix {};
   keepassxc = handleTest ./keepassxc.nix {};
   kerberos = handleTest ./kerberos/default.nix {};
-  kernel-latest = handleTest ./kernel-latest.nix {};
-  kernel-lts = handleTest ./kernel-lts.nix {};
-  kernel-testing = handleTest ./kernel-testing.nix {};
+  kernel-generic = handleTest ./kernel-generic.nix {};
   kernel-latest-ath-user-regd = handleTest ./kernel-latest-ath-user-regd.nix {};
   keycloak = discoverTests (import ./keycloak.nix);
   keymap = handleTest ./keymap.nix {};
diff --git a/nixos/tests/discourse.nix b/nixos/tests/discourse.nix
new file mode 100644
index 00000000000..3c965550fe0
--- /dev/null
+++ b/nixos/tests/discourse.nix
@@ -0,0 +1,197 @@
+# This tests Discourse by:
+#  1. logging in as the admin user
+#  2. sending a private message to the admin user through the API
+#  3. replying to that message via email.
+
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    clientDomain = "client.fake.domain";
+    discourseDomain = certs.domain;
+    adminPassword = "eYAX85qmMJ5GZIHLaXGDAoszD7HSZp5d";
+    secretKeyBase = "381f4ac6d8f5e49d804dae72aa9c046431d2f34c656a705c41cd52fed9b4f6f76f51549f0b55db3b8b0dded7a00d6a381ebe9a4367d2d44f5e743af6628b4d42";
+    admin = {
+      email = "alice@${clientDomain}";
+      username = "alice";
+      fullName = "Alice Admin";
+      passwordFile = "${pkgs.writeText "admin-pass" adminPassword}";
+    };
+  in
+  {
+    name = "discourse";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ talyz ];
+    };
+
+    nodes.discourse =
+      { nodes, ... }:
+      {
+        virtualisation.memorySize = 2048;
+
+        imports = [ common/user-account.nix ];
+
+        security.pki.certificateFiles = [
+          certs.ca.cert
+        ];
+
+        networking.extraHosts = ''
+          127.0.0.1 ${discourseDomain}
+          ${nodes.client.config.networking.primaryIPAddress} ${clientDomain}
+        '';
+
+        services.postfix = {
+          enableSubmission = true;
+          enableSubmissions = true;
+          submissionsOptions = {
+            smtpd_sasl_auth_enable = "yes";
+            smtpd_client_restrictions = "permit";
+          };
+        };
+
+        environment.systemPackages = [ pkgs.jq ];
+
+        services.discourse = {
+          enable = true;
+          inherit admin;
+          hostname = discourseDomain;
+          sslCertificate = "${certs.${discourseDomain}.cert}";
+          sslCertificateKey = "${certs.${discourseDomain}.key}";
+          secretKeyBaseFile = "${pkgs.writeText "secret-key-base" secretKeyBase}";
+          enableACME = false;
+          mail.outgoing.serverAddress = clientDomain;
+          mail.incoming.enable = true;
+          siteSettings = {
+            posting = {
+              min_post_length = 5;
+              min_first_post_length = 5;
+              min_personal_message_post_length = 5;
+            };
+          };
+          unicornTimeout = 900;
+        };
+
+        networking.firewall.allowedTCPPorts = [ 25 465 ];
+      };
+
+    nodes.client =
+      { nodes, ... }:
+      {
+        imports = [ common/user-account.nix ];
+
+        security.pki.certificateFiles = [
+          certs.ca.cert
+        ];
+
+        networking.extraHosts = ''
+          127.0.0.1 ${clientDomain}
+          ${nodes.discourse.config.networking.primaryIPAddress} ${discourseDomain}
+        '';
+
+        services.dovecot2 = {
+          enable = true;
+          protocols = [ "imap" ];
+          modules = [ pkgs.dovecot_pigeonhole ];
+        };
+
+        services.postfix = {
+          enable = true;
+          origin = clientDomain;
+          relayDomains = [ clientDomain ];
+          config = {
+            compatibility_level = "2";
+            smtpd_banner = "ESMTP server";
+            myhostname = clientDomain;
+            mydestination = clientDomain;
+          };
+        };
+
+        environment.systemPackages =
+          let
+            replyToEmail = pkgs.writeScriptBin "reply-to-email" ''
+              #!${pkgs.python3.interpreter}
+              import imaplib
+              import smtplib
+              import ssl
+              import email.header
+              from email import message_from_bytes
+              from email.message import EmailMessage
+
+              with imaplib.IMAP4('localhost') as imap:
+                  imap.login('alice', 'foobar')
+                  imap.select()
+                  status, data = imap.search(None, 'ALL')
+                  assert status == 'OK'
+
+                  nums = data[0].split()
+                  assert len(nums) == 1
+
+                  status, msg_data = imap.fetch(nums[0], '(RFC822)')
+                  assert status == 'OK'
+
+              msg = email.message_from_bytes(msg_data[0][1])
+              subject = str(email.header.make_header(email.header.decode_header(msg['Subject'])))
+              reply_to = email.header.decode_header(msg['Reply-To'])[0][0]
+              message_id = email.header.decode_header(msg['Message-ID'])[0][0]
+              date = email.header.decode_header(msg['Date'])[0][0]
+
+              ctx = ssl.create_default_context()
+              with smtplib.SMTP_SSL(host='${discourseDomain}', context=ctx) as smtp:
+                  reply = EmailMessage()
+                  reply['Subject'] = 'Re: ' + subject
+                  reply['To'] = reply_to
+                  reply['From'] = 'alice@${clientDomain}'
+                  reply['In-Reply-To'] = message_id
+                  reply['References'] = message_id
+                  reply['Date'] = date
+                  reply.set_content("Test reply.")
+
+                  smtp.send_message(reply)
+                  smtp.quit()
+            '';
+          in
+            [ replyToEmail ];
+
+        networking.firewall.allowedTCPPorts = [ 25 ];
+      };
+
+
+    testScript = { nodes }:
+      let
+        request = builtins.toJSON {
+          title = "Private message";
+          raw = "This is a test message.";
+          target_usernames = admin.username;
+          archetype = "private_message";
+        };
+      in ''
+        discourse.start()
+        client.start()
+
+        discourse.wait_for_unit("discourse.service")
+        discourse.wait_for_file("/run/discourse/sockets/unicorn.sock")
+        discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}")
+        discourse.succeed(
+            "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token",
+            "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.config.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.config.services.discourse.admin.username}\"'",
+            "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.config.services.discourse.admin.username}",
+        )
+
+        client.wait_for_unit("postfix.service")
+        client.wait_for_unit("dovecot2.service")
+
+        discourse.succeed(
+            "sudo -u discourse discourse-rake api_key:create_master[master] >api_key",
+            'curl -sS -f https://${discourseDomain}/posts -X POST -H "Content-Type: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" -d \'${request}\' ',
+        )
+
+        client.wait_until_succeeds("reply-to-email")
+
+        discourse.wait_until_succeeds(
+            'curl -sS -f https://${discourseDomain}/topics/private-messages/system -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .topic_list.topics[0].id != null then .topic_list.topics[0].id else null end\' >topic_id'
+        )
+        discourse.succeed(
+            'curl -sS -f https://${discourseDomain}/t/$(<topic_id) -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .post_stream.posts[1].cooked == "<p>Test reply.</p>" then true else null end\' '
+        )
+      '';
+  })
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
new file mode 100644
index 00000000000..fbead1dc23b
--- /dev/null
+++ b/nixos/tests/kernel-generic.nix
@@ -0,0 +1,37 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with pkgs.lib;
+
+let
+  makeKernelTest = version: linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: {
+    name = "kernel-${version}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nequissimus ];
+    };
+
+    machine = { ... }:
+      {
+        boot.kernelPackages = linuxPackages;
+      };
+
+    testScript =
+      ''
+        assert "Linux" in machine.succeed("uname -s")
+        assert "${linuxPackages.kernel.modDirVersion}" in machine.succeed("uname -a")
+      '';
+  }));
+in
+with pkgs; {
+  linux_4_4 = makeKernelTest "4.4" linuxPackages_4_4;
+  linux_4_9 = makeKernelTest "4.9" linuxPackages_4_9;
+  linux_4_14 = makeKernelTest "4.14" linuxPackages_4_14;
+  linux_4_19 = makeKernelTest "4.19" linuxPackages_4_19;
+  linux_5_4 = makeKernelTest "5.4" linuxPackages_5_4;
+  linux_5_10 = makeKernelTest "5.10" linuxPackages_5_10;
+  linux_5_11 = makeKernelTest "5.11" linuxPackages_5_11;
+
+  linux_testing = makeKernelTest "testing" linuxPackages_testing;
+}
diff --git a/nixos/tests/kernel-latest.nix b/nixos/tests/kernel-latest.nix
deleted file mode 100644
index 323dde267a4..00000000000
--- a/nixos/tests/kernel-latest.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "kernel-latest";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      boot.kernelPackages = pkgs.linuxPackages_latest;
-    };
-
-  testScript =
-    ''
-      assert "Linux" in machine.succeed("uname -s")
-      assert "${pkgs.linuxPackages_latest.kernel.version}" in machine.succeed("uname -a")
-    '';
-})
diff --git a/nixos/tests/kernel-lts.nix b/nixos/tests/kernel-lts.nix
deleted file mode 100644
index 9b03e9db6d8..00000000000
--- a/nixos/tests/kernel-lts.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "kernel-lts";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      boot.kernelPackages = pkgs.linuxPackages;
-    };
-
-  testScript =
-    ''
-      assert "Linux" in machine.succeed("uname -s")
-      assert "${pkgs.linuxPackages.kernel.version}" in machine.succeed("uname -a")
-    '';
-})
diff --git a/nixos/tests/kernel-testing.nix b/nixos/tests/kernel-testing.nix
deleted file mode 100644
index 017007c0aec..00000000000
--- a/nixos/tests/kernel-testing.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "kernel-testing";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      boot.kernelPackages = pkgs.linuxPackages_testing;
-    };
-
-  testScript =
-    ''
-      assert "Linux" in machine.succeed("uname -s")
-      assert "${pkgs.linuxPackages_testing.kernel.modDirVersion}" in machine.succeed("uname -a")
-    '';
-})
diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index 5831c8692f6..94f17605e00 100644
--- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -43,7 +43,7 @@ import ./make-test-python.nix ({pkgs, ...}: {
       # Everyone on the "isp" machine will be able to add routes to the kernel.
       security.wrappers.add-dhcpd-lease = {
         source = pkgs.writeShellScript "add-dhcpd-lease" ''
-          exec ${pkgs.iproute}/bin/ip -6 route replace "$1" via "$2"
+          exec ${pkgs.iproute2}/bin/ip -6 route replace "$1" via "$2"
         '';
         capabilities = "cap_net_admin+ep";
       };
diff --git a/pkgs/applications/misc/blucontrol/wrapper.nix b/pkgs/applications/misc/blucontrol/wrapper.nix
new file mode 100644
index 00000000000..c0c76b4ef49
--- /dev/null
+++ b/pkgs/applications/misc/blucontrol/wrapper.nix
@@ -0,0 +1,32 @@
+{ stdenv, lib, makeWrapper, ghcWithPackages, packages ? (_:[]) }:
+let
+  blucontrolEnv = ghcWithPackages (self: [ self.blucontrol ] ++ packages self);
+in
+  stdenv.mkDerivation {
+    pname = "blucontrol-with-packages";
+    version = blucontrolEnv.version;
+
+    nativeBuildInputs = [ makeWrapper ];
+
+    buildCommand = ''
+      makeWrapper ${blucontrolEnv}/bin/blucontrol $out/bin/blucontrol \
+        --prefix PATH : ${lib.makeBinPath [ blucontrolEnv ]}
+    '';
+
+    # trivial derivation
+    preferLocalBuild = true;
+    allowSubstitues = false;
+
+    meta = with lib; {
+      description = "Configurable blue light filter";
+      longDescription = ''
+        This application is a blue light filter, with the main focus on configurability.
+        Configuration is done in Haskell in the style of xmonad.
+        Blucontrol makes use of monad transformers and allows monadic calculation of gamma values and recoloring. The user chooses, what will be captured in the monadic state.
+      '';
+      license = licenses.bsd3;
+      homepage = "https://github.com/jumper149/blucontrol";
+      platforms = platforms.unix;
+      maintainers = with maintainers; [ jumper149 ];
+    };
+  }
diff --git a/pkgs/applications/misc/free42/default.nix b/pkgs/applications/misc/free42/default.nix
index c48e151e8ae..51867a6acee 100644
--- a/pkgs/applications/misc/free42/default.nix
+++ b/pkgs/applications/misc/free42/default.nix
@@ -1,6 +1,8 @@
 { lib
 , stdenv
 , fetchFromGitHub
+, makeDesktopItem
+, copyDesktopItems
 , pkg-config
 , gtk3
 , alsaLib
@@ -17,7 +19,7 @@ stdenv.mkDerivation rec {
     sha256 = "sha256-Htk2NHgYVL622URx67BUtounAUopLTahaSqfAqd3+ZI=";
   };
 
-  nativeBuildInputs = [ pkg-config ];
+  nativeBuildInputs = [ copyDesktopItems pkg-config ];
   buildInputs = [ gtk3 alsaLib ];
 
   postPatch = ''
@@ -55,6 +57,29 @@ stdenv.mkDerivation rec {
     runHook postInstall
   '';
 
+  desktopItems = [
+    (makeDesktopItem {
+      name = "com.thomasokken.free42bin";
+      desktopName = "Free42Bin";
+      genericName = "Calculator";
+      exec = "free42bin";
+      type = "Application";
+      comment = meta.description;
+      categories = "Utility;Calculator;";
+      terminal = "false";
+    })
+    (makeDesktopItem {
+      name = "com.thomasokken.free42dec";
+      desktopName = "Free42Dec";
+      genericName = "Calculator";
+      exec = "free42dec";
+      type = "Application";
+      comment = meta.description;
+      categories = "Utility;Calculator;";
+      terminal = "false";
+    })
+  ];
+
   meta = with lib; {
     homepage = "https://github.com/thomasokken/free42";
     description = "A software clone of HP-42S Calculator";
diff --git a/pkgs/applications/misc/metadata-cleaner/default.nix b/pkgs/applications/misc/metadata-cleaner/default.nix
index de715bf315f..b1b77a53ea0 100644
--- a/pkgs/applications/misc/metadata-cleaner/default.nix
+++ b/pkgs/applications/misc/metadata-cleaner/default.nix
@@ -17,7 +17,7 @@
 
 python3.pkgs.buildPythonApplication rec {
   pname = "metadata-cleaner";
-  version = "1.0.3";
+  version = "1.0.4";
 
   format = "other";
 
@@ -25,7 +25,7 @@ python3.pkgs.buildPythonApplication rec {
     owner = "rmnvgr";
     repo = "metadata-cleaner";
     rev = "v${version}";
-    sha256 = "06dzfcnjb1xd8lk0r7bi4i784gfj8r7habbjbk2c4vn2847v71lf";
+    sha256 = "sha256-F/xh4dFX7W50kFzpWpGKyMUhxOlDO3WDXBzXVsDViY8=";
   };
 
   nativeBuildInputs = [
diff --git a/pkgs/applications/networking/browsers/chromium/upstream-info.json b/pkgs/applications/networking/browsers/chromium/upstream-info.json
index 7660f2f9e69..94c45120cc6 100644
--- a/pkgs/applications/networking/browsers/chromium/upstream-info.json
+++ b/pkgs/applications/networking/browsers/chromium/upstream-info.json
@@ -18,9 +18,9 @@
     }
   },
   "beta": {
-    "version": "90.0.4430.51",
-    "sha256": "1k87fw0pv0d2zlxm0il9b5p60gdz6l44jssmsns4zy2fmd9316wr",
-    "sha256bin64": "0q5yx7bc266azs3nl29ksz4yafvy2nmzn09ifcgr69fjkvsr1qh7",
+    "version": "90.0.4430.61",
+    "sha256": "01vssy3q64pv9rw4cdxv5rdg7yrxmhyc03a5r75fhxc95fj66iac",
+    "sha256bin64": "07l8dzyv0hav1gls3xw91q9ay2l8xxmsf7yagg940cya9ncl0lhi",
     "deps": {
       "gn": {
         "version": "2021-02-09",
diff --git a/pkgs/applications/networking/browsers/palemoon/default.nix b/pkgs/applications/networking/browsers/palemoon/default.nix
index cf5f13fa745..554167c3574 100644
--- a/pkgs/applications/networking/browsers/palemoon/default.nix
+++ b/pkgs/applications/networking/browsers/palemoon/default.nix
@@ -16,14 +16,14 @@ let
 
 in stdenv.mkDerivation rec {
   pname = "palemoon";
-  version = "29.1.0";
+  version = "29.1.1";
 
   src = fetchFromGitHub {
     githubBase = "repo.palemoon.org";
     owner = "MoonchildProductions";
     repo = "Pale-Moon";
     rev = "${version}_Release";
-    sha256 = "02blhk3v7gpnicd7s5l5fpqvdvj2279g3rq8xyhcd4sw6qnms8m6";
+    sha256 = "1ppdmj816zwccb0l0mgpq14ckdwg785wmqz41wran0nl63fg6i1x";
     fetchSubmodules = true;
   };
 
diff --git a/pkgs/applications/search/recoll/default.nix b/pkgs/applications/search/recoll/default.nix
index 48671582fba..a07340469fe 100644
--- a/pkgs/applications/search/recoll/default.nix
+++ b/pkgs/applications/search/recoll/default.nix
@@ -46,10 +46,12 @@ mkDerivation rec {
     ++ lib.optionals (!withGui) [ "--disable-qtgui" "--disable-x11mon" ]
     ++ (if stdenv.isLinux then [ "--with-inotify" ] else [ "--without-inotify" ]);
 
-  nativeBuildInputs = [ pkg-config ];
+  nativeBuildInputs = [
+    file pkg-config python3Packages.setuptools which
+  ];
 
-  buildInputs = with python3Packages; [
-    bison chmlib file python setuptools which xapian zlib
+  buildInputs = [
+    bison chmlib python3Packages.python xapian zlib
   ] ++ lib.optional withGui qtbase
     ++ lib.optional stdenv.isDarwin libiconv;
 
@@ -98,6 +100,6 @@ mkDerivation rec {
     homepage = "https://www.lesbonscomptes.com/recoll/";
     license = licenses.gpl2;
     platforms = platforms.unix;
-    maintainers = [ maintainers.jcumming ];
+    maintainers = with maintainers; [ jcumming kiyengar ];
   };
 }
diff --git a/pkgs/applications/window-managers/dwl/default.nix b/pkgs/applications/window-managers/dwl/default.nix
index 52c0a6ae04d..d8f102ed767 100644
--- a/pkgs/applications/window-managers/dwl/default.nix
+++ b/pkgs/applications/window-managers/dwl/default.nix
@@ -12,8 +12,27 @@
 , patches ? [ ]
 , conf ? null
 , writeText
+, fetchpatch
 }:
 
+let
+  # Add two patches to fix compile errors with wlroots 0.13:
+  totalPatches = patches ++ [
+    # Fix the renamed constant WLR_KEY_PRESSED => WL_KEYBOARD_KEY_STATE_PRESSED
+    # https://github.com/djpohly/dwl/pull/66
+    (fetchpatch {
+      url = "https://github.com/djpohly/dwl/commit/a42613db9d9f6debfa4fb2363d75af9457d238ed.patch";
+      sha256 = "0h76hx1fhazi07gqg7sljh13f91v6bvjy7m9qqmimhvqgfwdcc0j";
+    })
+    # Use the new signature for wlr_backend_autocreate, which removes an argument:
+    # https://github.com/djpohly/dwl/pull/76
+    (fetchpatch {
+      url = "https://github.com/djpohly/dwl/commit/0ff13cf216056a36a261f4eed53c6a864989a9fb.patch";
+      sha256 = "18clpdb4il1vxf1b0cx0qrwild68s9dism8ab66zpmvxs5qag2dm";
+    })
+  ];
+in
+
 stdenv.mkDerivation rec {
   pname = "dwl";
   version = "0.2";
@@ -39,7 +58,7 @@ stdenv.mkDerivation rec {
   ];
 
   # Allow users to set their own list of patches
-  inherit patches;
+  patches = totalPatches;
 
   # Last line of config.mk enables XWayland
   prePatch = lib.optionalString enable-xwayland ''
diff --git a/pkgs/applications/window-managers/labwc/default.nix b/pkgs/applications/window-managers/labwc/default.nix
index 9d39bd537fd..c82eb76f6bf 100644
--- a/pkgs/applications/window-managers/labwc/default.nix
+++ b/pkgs/applications/window-managers/labwc/default.nix
@@ -8,7 +8,6 @@
 , glib
 , libinput
 , libxml2
-, pandoc
 , pango
 , wayland
 , wayland-protocols
@@ -16,20 +15,22 @@
 , libxcb
 , libxkbcommon
 , xwayland
+, libdrm
+, scdoc
 }:
 
 stdenv.mkDerivation rec {
   pname = "labwc";
-  version = "unstable-2021-02-06";
+  version = "unstable-2021-03-15";
 
   src = fetchFromGitHub {
     owner = "johanmalm";
     repo = pname;
-    rev = "4a8fcf5c6d0b730b1e2e17e544ce7d7d3c72cd13";
-    sha256 = "g1ba8dchUN393eis0VAu1bIjQfthDGLaSijSavz4lfU=";
+    rev = "fddeb74527e5b860d9c1a91a237d390041c758b6";
+    sha256 = "0rhniv5j4bypqxxj0nbpa3hclmn8znal9rldv0mrgbizn3wsbs54";
   };
 
-  nativeBuildInputs = [ pkg-config meson ninja pandoc ];
+  nativeBuildInputs = [ pkg-config meson ninja scdoc ];
   buildInputs = [
     cairo
     glib
@@ -42,6 +43,7 @@ stdenv.mkDerivation rec {
     libxcb
     libxkbcommon
     xwayland
+    libdrm
   ];
 
   mesonFlags = [ "-Dxwayland=enabled" ];
diff --git a/pkgs/applications/window-managers/sway/default.nix b/pkgs/applications/window-managers/sway/default.nix
index 1798c8235d0..d8a1679bbed 100644
--- a/pkgs/applications/window-managers/sway/default.nix
+++ b/pkgs/applications/window-managers/sway/default.nix
@@ -2,18 +2,18 @@
 , meson, ninja, pkg-config, wayland, scdoc
 , libxkbcommon, pcre, json_c, dbus, libevdev
 , pango, cairo, libinput, libcap, pam, gdk-pixbuf, librsvg
-, wlroots, wayland-protocols
+, wlroots, wayland-protocols, libdrm
 }:
 
 stdenv.mkDerivation rec {
   pname = "sway-unwrapped";
-  version = "1.5.1";
+  version = "1.6";
 
   src = fetchFromGitHub {
     owner = "swaywm";
     repo = "sway";
     rev = version;
-    sha256 = "1xsa3h8zhf29p0mi90baxpr76jkd9pd1gr97ky8cnjbcs4isj9j0";
+    sha256 = "0vnplva11yafhbijrk68wy7pw0psn9jm0caaymswq1s951xsn1c8";
   };
 
   patches = [
@@ -33,11 +33,12 @@ stdenv.mkDerivation rec {
   buildInputs = [
     wayland libxkbcommon pcre json_c dbus libevdev
     pango cairo libinput libcap pam gdk-pixbuf librsvg
-    wlroots wayland-protocols
+    wlroots wayland-protocols libdrm
   ];
 
   mesonFlags = [
     "-Ddefault-wallpaper=false"
+    "-Dsd-bus-provider=libsystemd"
   ];
 
   meta = with lib; {
diff --git a/pkgs/applications/window-managers/sway/load-configuration-from-etc.patch b/pkgs/applications/window-managers/sway/load-configuration-from-etc.patch
index 26a3d40d66c..46a170abc04 100644
--- a/pkgs/applications/window-managers/sway/load-configuration-from-etc.patch
+++ b/pkgs/applications/window-managers/sway/load-configuration-from-etc.patch
@@ -1,22 +1,26 @@
-From 26f9c65ef037892977a824f0d7d7111066856b53 Mon Sep 17 00:00:00 2001
-From: Michael Weiss <dev.primeos@gmail.com>
-Date: Sat, 27 Apr 2019 14:26:16 +0200
+From 92283df3acbffa5c1bb21f23cdd686113d905114 Mon Sep 17 00:00:00 2001
+From: Patrick Hilhorst <git@hilhorst.be>
+Date: Wed, 31 Mar 2021 21:14:13 +0200
 Subject: [PATCH] Load configs from /etc but fallback to /nix/store
 
 This change will load all configuration files from /etc, to make it easy
 to override them, but fallback to /nix/store/.../etc/sway/config to make
 Sway work out-of-the-box with the default configuration on non NixOS
 systems.
+
+Original patch by Michael Weiss, updated for Sway 1.6 by Patrick Hilhorst
+
+Co-authored-by: Michael Weiss <dev.primeos@gmail.com>
 ---
  meson.build   | 3 ++-
- sway/config.c | 1 +
- 2 files changed, 3 insertions(+), 1 deletion(-)
+ sway/config.c | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
 
 diff --git a/meson.build b/meson.build
-index 02b5d606..c03a9c0f 100644
+index b7a29660..8ae8ceb3 100644
 --- a/meson.build
 +++ b/meson.build
-@@ -129,7 +129,8 @@ if scdoc.found()
+@@ -164,7 +164,8 @@ if scdoc.found()
  	endforeach
  endif
  
@@ -25,18 +29,20 @@ index 02b5d606..c03a9c0f 100644
 +add_project_arguments('-DNIX_SYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c')
  
  version = '"@0@"'.format(meson.project_version())
- if git.found()
+ git = find_program('git', native: true, required: false)
 diff --git a/sway/config.c b/sway/config.c
-index 4cd21bbc..dd855753 100644
+index 76b9ec08..fb5b51aa 100644
 --- a/sway/config.c
 +++ b/sway/config.c
-@@ -317,6 +317,7 @@ static char *get_config_path(void) {
- 		"$XDG_CONFIG_HOME/i3/config",
- 		SYSCONFDIR "/sway/config",
- 		SYSCONFDIR "/i3/config",
-+		NIX_SYSCONFDIR "/sway/config",
+@@ -374,7 +374,8 @@ static char *get_config_path(void) {
+ 		{ .prefix = home, .config_folder = ".i3"},
+ 		{ .prefix = config_home, .config_folder = "i3"},
+ 		{ .prefix = SYSCONFDIR, .config_folder = "sway"},
+-		{ .prefix = SYSCONFDIR, .config_folder = "i3"}
++		{ .prefix = SYSCONFDIR, .config_folder = "i3"},
++		{ .prefix = NIX_SYSCONFDIR, .config_folder = "sway"},
  	};
  
- 	char *config_home = getenv("XDG_CONFIG_HOME");
+ 	size_t num_config_paths = sizeof(config_paths)/sizeof(config_paths[0]);
 -- 
-2.19.2
+2.30.1
diff --git a/pkgs/applications/window-managers/wayfire/applications.nix b/pkgs/applications/window-managers/wayfire/applications.nix
index 6c22227c0c5..a77d3f8bf5d 100644
--- a/pkgs/applications/window-managers/wayfire/applications.nix
+++ b/pkgs/applications/window-managers/wayfire/applications.nix
@@ -1,23 +1,20 @@
-{ newScope, wayfirePlugins }:
+{ lib, newScope, wayfirePlugins }:
 
-let
-  self = with self; {
-    inherit wayfirePlugins;
+lib.makeExtensible (self: with self; {
+  inherit wayfirePlugins;
 
-    callPackage = newScope self;
+  callPackage = newScope self;
 
-    wayfire = callPackage ./. { };
+  wayfire = callPackage ./. { };
 
-    wcm = callPackage ./wcm.nix {
-      inherit (wayfirePlugins) wf-shell;
-    };
+  wcm = callPackage ./wcm.nix {
+    inherit (wayfirePlugins) wf-shell;
+  };
 
-    wrapWayfireApplication = callPackage ./wrapper.nix { };
+  wrapWayfireApplication = callPackage ./wrapper.nix { };
 
-    withPlugins = selector: self // {
-      wayfire = wrapWayfireApplication wayfire selector;
-      wcm = wrapWayfireApplication wcm selector;
-    };
+  withPlugins = selector: self // {
+    wayfire = wrapWayfireApplication wayfire selector;
+    wcm = wrapWayfireApplication wcm selector;
   };
-in
-self
+})
diff --git a/pkgs/development/compilers/llvm/12/clang/default.nix b/pkgs/development/compilers/llvm/12/clang/default.nix
index d4d0ddf7954..d90d019e6d5 100644
--- a/pkgs/development/compilers/llvm/12/clang/default.nix
+++ b/pkgs/development/compilers/llvm/12/clang/default.nix
@@ -8,7 +8,7 @@ let
     pname = "clang";
     inherit version;
 
-    src = fetch "clang" "11ay72f81ffygil5ficq7mzplck4gffm77p0yj4ib3dgiqbb1qbw";
+    src = fetch "clang" "185r9rr254v75ja33nmm53j85lcnkj7bzsl18wvnd37jmz2nfxa5";
     inherit clang-tools-extra_src;
 
     unpackPhase = ''
diff --git a/pkgs/development/compilers/llvm/12/compiler-rt.nix b/pkgs/development/compilers/llvm/12/compiler-rt.nix
index 9721879d762..e6ac77b7d26 100644
--- a/pkgs/development/compilers/llvm/12/compiler-rt.nix
+++ b/pkgs/development/compilers/llvm/12/compiler-rt.nix
@@ -11,7 +11,7 @@ in
 stdenv.mkDerivation rec {
   pname = "compiler-rt";
   inherit version;
-  src = fetch pname "01dvir3858qkjmqhw2h6jjagq0la0kasnwzqbyv91yixnwx8369z";
+  src = fetch pname "1x0z875nbdpzhr4qb7linm6r9swvdf6dvwqy1s22pbn4wdcw0cvf";
 
   nativeBuildInputs = [ cmake python3 llvm ];
   buildInputs = lib.optional stdenv.hostPlatform.isDarwin libcxxabi;
diff --git a/pkgs/development/compilers/llvm/12/default.nix b/pkgs/development/compilers/llvm/12/default.nix
index 901e9c82eee..593db716ac6 100644
--- a/pkgs/development/compilers/llvm/12/default.nix
+++ b/pkgs/development/compilers/llvm/12/default.nix
@@ -8,7 +8,7 @@
 
 let
   release_version = "12.0.0";
-  candidate = "rc4"; # empty or "rcN"
+  candidate = "rc5"; # empty or "rcN"
   dash-candidate = lib.optionalString (candidate != "") "-${candidate}";
   version = "${release_version}${dash-candidate}"; # differentiating these (variables) is important for RCs
   targetConfig = stdenv.targetPlatform.config;
@@ -18,7 +18,7 @@ let
     inherit sha256;
   };
 
-  clang-tools-extra_src = fetch "clang-tools-extra" "1m1qga8m967bzqkxwx9xqkw1lkxi9dhlrn6km2k7g2yqyb6k14ag";
+  clang-tools-extra_src = fetch "clang-tools-extra" "1hga9k5m60ywmr7m69jf1v6vj1ra1n6ybv1abzlz94f5q22i1a02";
 
   tools = lib.makeExtensible (tools: let
     callPackage = newScope (tools // { inherit stdenv cmake libxml2 python3 isl release_version version fetch; });
diff --git a/pkgs/development/compilers/llvm/12/libc++/default.nix b/pkgs/development/compilers/llvm/12/libc++/default.nix
index e910d2c96b6..757651c0f4c 100644
--- a/pkgs/development/compilers/llvm/12/libc++/default.nix
+++ b/pkgs/development/compilers/llvm/12/libc++/default.nix
@@ -6,7 +6,7 @@ stdenv.mkDerivation {
   pname = "libc++";
   inherit version;
 
-  src = fetch "libcxx" "0ai91zls1738502c3b2frhawmjpi73rm3m677hh540wrjp3xv0ql";
+  src = fetch "libcxx" "01abh553dvjgk5cjzzp0ghmg00laqbr4ar4frdhyhpbwhhmwc880";
 
   postUnpack = ''
     unpackFile ${libcxxabi.src}
diff --git a/pkgs/development/compilers/llvm/12/libc++abi.nix b/pkgs/development/compilers/llvm/12/libc++abi.nix
index 70dd5af7277..e35480c7bf2 100644
--- a/pkgs/development/compilers/llvm/12/libc++abi.nix
+++ b/pkgs/development/compilers/llvm/12/libc++abi.nix
@@ -6,7 +6,7 @@ stdenv.mkDerivation {
   pname = "libc++abi";
   inherit version;
 
-  src = fetch "libcxxabi" "02qp6ndagq7n48p53z93d1rrx0v0v4rsahd4vkv5frid0vm4ah9h";
+  src = fetch "libcxxabi" "0mjj4f63ix4j1b72bgzpcki7mzf3qszrq7snqhiq0c5s73skkwx0";
 
   nativeBuildInputs = [ cmake python3 ];
   buildInputs = lib.optional (!stdenv.isDarwin && !stdenv.isFreeBSD && !stdenv.hostPlatform.isWasm) libunwind;
diff --git a/pkgs/development/compilers/llvm/12/libunwind.nix b/pkgs/development/compilers/llvm/12/libunwind.nix
index ddfcf508fd8..83e76c0c56c 100644
--- a/pkgs/development/compilers/llvm/12/libunwind.nix
+++ b/pkgs/development/compilers/llvm/12/libunwind.nix
@@ -6,7 +6,7 @@ stdenv.mkDerivation rec {
   pname = "libunwind";
   inherit version;
 
-  src = fetch pname "1a5db1lxw98a430b8mnaclc0w98y6cc8k587kgjhn0nghl40l40i";
+  src = fetch pname "0kaq75ygzv9dqfsx27pi5a0clipdjq6a9vghhb89d8k1rf20lslh";
 
   postUnpack = ''
     unpackFile ${libcxx.src}
diff --git a/pkgs/development/compilers/llvm/12/lld.nix b/pkgs/development/compilers/llvm/12/lld.nix
index 00e30e16b6b..a5e4ab834ec 100644
--- a/pkgs/development/compilers/llvm/12/lld.nix
+++ b/pkgs/development/compilers/llvm/12/lld.nix
@@ -11,7 +11,7 @@ stdenv.mkDerivation rec {
   pname = "lld";
   inherit version;
 
-  src = fetch pname "0r9pxhvinipirv9s5k8fnsnqd30zfniwqjkvw5sac3lq29rn2lp1";
+  src = fetch pname "044lv1d9am2xmbc3pvssxkkiyxyv72n2xkgk8z3p9k72h3ay00q3";
 
   nativeBuildInputs = [ cmake ];
   buildInputs = [ llvm libxml2 ];
diff --git a/pkgs/development/compilers/llvm/12/lldb.nix b/pkgs/development/compilers/llvm/12/lldb.nix
index f9f978c60b1..67de0c18741 100644
--- a/pkgs/development/compilers/llvm/12/lldb.nix
+++ b/pkgs/development/compilers/llvm/12/lldb.nix
@@ -25,7 +25,7 @@ stdenv.mkDerivation (rec {
   pname = "lldb";
   inherit version;
 
-  src = fetch pname "0943gan83mldizwbhksd07w4h90z4djjpv5f8v49caz8y9113svg";
+  src = fetch pname "0q4p4s5ws1zszs3i4da5w5fnxkpny0q3fr1s1sh7jp9wcwxbxiqq";
 
   patches = [ ./lldb-procfs.patch ];
 
diff --git a/pkgs/development/compilers/llvm/12/llvm.nix b/pkgs/development/compilers/llvm/12/llvm.nix
index f70c4379c1a..6e6127ba949 100644
--- a/pkgs/development/compilers/llvm/12/llvm.nix
+++ b/pkgs/development/compilers/llvm/12/llvm.nix
@@ -32,8 +32,8 @@ in stdenv.mkDerivation (rec {
   pname = "llvm";
   inherit version;
 
-  src = fetch pname "1jif65i165h41cfcsfvfjy5k1yrnikg61assj5vs0f25pv1vbyvf";
-  polly_src = fetch "polly" "0hk6j6rsal3zsp1f9fla71yzbwmzz9007m63x22hy7qfiwyplvf2";
+  src = fetch pname "088dyv7hppidl3rqfsjdibvn4d3a74896fg2sz4dwaxlg19way93";
+  polly_src = fetch "polly" "1qj7gkfr1yrsrz6j086l9p6d2kyyln15fmfiab4isn96g1dhsfb5";
 
   unpackPhase = ''
     unpackFile $src
diff --git a/pkgs/development/compilers/llvm/12/openmp.nix b/pkgs/development/compilers/llvm/12/openmp.nix
index fcb91574d17..1e4618e4a1b 100644
--- a/pkgs/development/compilers/llvm/12/openmp.nix
+++ b/pkgs/development/compilers/llvm/12/openmp.nix
@@ -11,7 +11,7 @@ stdenv.mkDerivation rec {
   pname = "openmp";
   inherit version;
 
-  src = fetch pname "07g2rsfhli3szv3chzy6y37p2176ywdb6w3k2fv9g2r416cpxjdz";
+  src = fetch pname "1d16r5whjb2n4n28rg8wn2g9krlc92q6nb0qmnnbzhqhx0rbkjfb";
 
   nativeBuildInputs = [ cmake perl ];
   buildInputs = [ llvm ];
diff --git a/pkgs/development/libraries/openzwave/default.nix b/pkgs/development/libraries/openzwave/default.nix
index 0a617561a89..723288ab4a9 100644
--- a/pkgs/development/libraries/openzwave/default.nix
+++ b/pkgs/development/libraries/openzwave/default.nix
@@ -1,23 +1,33 @@
-{ lib, stdenv, fetchFromGitHub
+{ lib, stdenv, fetchFromGitHub, fetchpatch
 , doxygen, fontconfig, graphviz-nox, libxml2, pkg-config, which
 , systemd }:
 
-let
-  version = "2019-12-08";
-
-in stdenv.mkDerivation {
+stdenv.mkDerivation rec {
   pname = "openzwave";
-  inherit version;
+  version = "1.6";
 
-  # Use fork by Home Assistant because this package is mainly used for python.pkgs.homeassistant-pyozw.
-  # See https://github.com/OpenZWave/open-zwave/compare/master...home-assistant:hass for the difference.
   src = fetchFromGitHub {
-    owner = "home-assistant";
+    owner = "OpenZWave";
     repo = "open-zwave";
-    rev = "2cd2137025c529835e4893a7b87c3d56605b2681";
-    sha256 = "04g8fb4f4ihakvvsmzcnncgfdd2ikmki7s22i9c6layzdwavbwf1";
+    rev = "v${version}";
+    sha256 = "0xgs4mmr0480c269wx9xkk67ikjzxkh8xcssrdx0f5xcl1lyd333";
   };
 
+  patches = [
+    (fetchpatch {
+      name = "fix-strncat-build-failure.patch";
+      url = "https://github.com/OpenZWave/open-zwave/commit/601e5fb16232a7984885e67fdddaf5b9c9dd8105.patch";
+      sha256 = "1n1k5arwk1dyc12xz6xl4n8yw28vghzhv27j65z1nca4zqsxgza1";
+    })
+    (fetchpatch {
+      name = "fix-text-uninitialized.patch";
+      url = "https://github.com/OpenZWave/open-zwave/commit/3b029a467e83bc7f0054e4dbba1e77e6eac7bc7f.patch";
+      sha256 = "183mrzjh1zx2b2wzkj4jisiw8br7g7bbs167afls4li0fm01d638";
+    })
+  ];
+
+  outputs = [ "out" "doc" ];
+
   nativeBuildInputs = [ doxygen fontconfig graphviz-nox libxml2 pkg-config which ];
 
   buildInputs = [ systemd ];
@@ -26,13 +36,9 @@ in stdenv.mkDerivation {
 
   enableParallelBuilding = true;
 
-  installPhase = ''
-    runHook preInstall
-
-    DESTDIR=$out PREFIX= pkgconfigdir=lib/pkgconfig make install $installFlags
-
-    runHook postInstall
-  '';
+  makeFlags = [
+    "PREFIX=${placeholder "out"}"
+  ];
 
   FONTCONFIG_FILE="${fontconfig.out}/etc/fonts/fonts.conf";
   FONTCONFIG_PATH="${fontconfig.out}/etc/fonts/";
@@ -42,15 +48,6 @@ in stdenv.mkDerivation {
       --replace /etc/openzwave $out/etc/openzwave
   '';
 
-  fixupPhase = ''
-    substituteInPlace $out/lib/pkgconfig/libopenzwave.pc \
-      --replace prefix= prefix=$out \
-      --replace dir=    dir=$out
-
-    substituteInPlace $out/bin/ozw_config \
-      --replace pcfile=${pkg-config} pcfile=$out
-  '';
-
   meta = with lib; {
     description = "C++ library to control Z-Wave Networks via a USB Z-Wave Controller";
     homepage = "http://www.openzwave.net/";
diff --git a/pkgs/development/libraries/tracker/default.nix b/pkgs/development/libraries/tracker/default.nix
index 1da2f34aa5b..fae10f2b1b4 100644
--- a/pkgs/development/libraries/tracker/default.nix
+++ b/pkgs/development/libraries/tracker/default.nix
@@ -27,7 +27,7 @@
 , substituteAll
 }:
 
-stdenv.mkDerivation rec {
+stdenv.mkDerivation (rec {
   pname = "tracker";
   version = "3.0.3";
 
@@ -82,7 +82,8 @@ stdenv.mkDerivation rec {
     "-Ddocs=true"
   ];
 
-  doCheck = true;
+  # https://gitlab.gnome.org/GNOME/tracker/-/issues/292#note_1075369
+  doCheck = !stdenv.isi686;
 
   postPatch = ''
     patchShebangs utils/g-ir-merge/g-ir-merge
@@ -133,3 +134,8 @@ stdenv.mkDerivation rec {
     platforms = platforms.linux;
   };
 }
+  // lib.optionalAttrs stdenv.isi686 {
+    # TMP: fatal error: libtracker-sparql/tracker-sparql-enum-types.h: No such file or directory
+    enableParallelBuilding = false;
+  }
+)
diff --git a/pkgs/development/libraries/v8/default.nix b/pkgs/development/libraries/v8/default.nix
index c79073f04e4..d513e01b7a3 100644
--- a/pkgs/development/libraries/v8/default.nix
+++ b/pkgs/development/libraries/v8/default.nix
@@ -11,23 +11,23 @@ let
   deps = {
     "base/trace_event/common" = fetchgit {
       url    = "${git_url}/chromium/src/base/trace_event/common.git";
-      rev    = "936ba8a963284a6b3737cf2f0474a7131073abee";
-      sha256 = "14nr22fqdpxma1kzjflj6a865vr3hfnnm2gs4vcixyq4kmfzfcy2";
+      rev    = "dab187b372fc17e51f5b9fad8201813d0aed5129";
+      sha256 = "0dmpj9hj4xv3xb0fl1kb9hm4bhpbs2s5csx3z8cgjd5vwvhdzig4";
     };
     build = fetchgit {
       url    = "${git_url}/chromium/src/build.git";
-      rev    = "325e95d6dae64f35b160b3dc7d73218cee5ec079";
-      sha256 = "0dddyxa76p2xpjhmxif05v63i5ar6h5v684fdl667sg84f5bhhxf";
+      rev    = "26e9d485d01d6e0eb9dadd21df767a63494c8fea";
+      sha256 = "1jjvsgj0cs97d26i3ba531ic1f9gqan8x7z4aya8yl8jx02l342q";
     };
     "third_party/googletest/src" = fetchgit {
       url    = "${git_url}/external/github.com/google/googletest.git";
-      rev    = "5ec7f0c4a113e2f18ac2c6cc7df51ad6afc24081";
-      sha256 = "0gmr10042c0xybxnn6g7ndj1na1mmd3l9w7449qlcv4s8gmfs7k6";
+      rev    = "e3f0319d89f4cbf32993de595d984183b1a9fc57";
+      sha256 = "18xz71l2xjrqsc0q317whgw4xi1i5db24zcj7v04f5g6r1hyf1a5";
     };
     "third_party/icu" = fetchgit {
       url    = "${git_url}/chromium/deps/icu.git";
-      rev    = "960f195aa87acaec46e6104ec93a596da7ae0843";
-      sha256 = "073kh6gpcairgjxf3hlhpqljc13gwl2aj8fz91fv220xibwqs834";
+      rev    = "f2223961702f00a8833874b0560d615a2cc42738";
+      sha256 = "0z5p53kbrjfkjn0i12dpk55cp8976j2zk7a4wk88423s2c5w87zl";
     };
     "third_party/jinja2" = fetchgit {
       url    = "${git_url}/chromium/src/third_party/jinja2.git";
@@ -39,29 +39,31 @@ let
       rev    = "8f45f5cfa0009d2a70589bcda0349b8cb2b72783";
       sha256 = "168ppjmicfdh4i1l0l25s86mdbrz9fgxmiq1rx33x79mph41scfz";
     };
+    "third_party/zlib" = fetchgit {
+      url    = "${git_url}/chromium/src/third_party/zlib.git";
+      rev    = "156be8c52f80cde343088b4a69a80579101b6e67";
+      sha256 = "0hxbkkzmlv714fjq2jlp5dd2jc339xyh6gkjx1sz3srwv33mlk92";
+    };
   };
 
 in
 
 stdenv.mkDerivation rec {
   pname = "v8";
-  version = "7.4.255";
+  version = "8.4.255";
 
   doCheck = true;
 
   patches = [
-    (fetchpatch {
-      url = "https://raw.githubusercontent.com/RPi-Distro/chromium-browser/master/debian/patches/revert-Xclang-instcombine-lower-dbg-declare.patch";
-      sha256 = "02hczcg43m36q8j1kv5j3hq9czj9niiil9w13w22vzv2f3c67dvn";
-    })
     ./darwin.patch
+    ./gcc_arm.patch  # Fix building zlib with gcc on aarch64, from https://gist.github.com/Adenilson/d973b6fd96c7709d33ddf08cf1dcb149
   ];
 
   src = fetchFromGitHub {
     owner = "v8";
     repo = "v8";
     rev = version;
-    sha256 = "14i0c71hmffzqnq9n73dh9dnabdxhbjhzkhqpk5yv9y90bwrzi2n";
+    sha256 = "07ymw4kqbz7kv311gpk5bs5q90wj73n2q7jkyfhqk4hvhs1q5bw7";
   };
 
   postUnpack = ''
@@ -97,9 +99,7 @@ stdenv.mkDerivation rec {
     ''v8_snapshot_toolchain="//build/toolchain/linux/unbundle:default"''
   ] ++ lib.optional stdenv.cc.isClang ''clang_base_path="${stdenv.cc}"'';
 
-  # with gcc8, -Wclass-memaccess became part of -Wall and causes logging limit
-  # to be exceeded
-  NIX_CFLAGS_COMPILE = lib.optionalString stdenv.cc.isGNU "-Wno-class-memaccess";
+  NIX_CFLAGS_COMPILE = "-O2";
 
   nativeBuildInputs = [ gn ninja pkg-config python ]
     ++ lib.optionals stdenv.isDarwin [ xcbuild darwin.DarwinTools ];
diff --git a/pkgs/development/libraries/v8/gcc_arm.patch b/pkgs/development/libraries/v8/gcc_arm.patch
new file mode 100644
index 00000000000..09579561fd8
--- /dev/null
+++ b/pkgs/development/libraries/v8/gcc_arm.patch
@@ -0,0 +1,31 @@
+diff --git a/third_party/zlib/contrib/optimizations/insert_string.h b/third_party/zlib/contrib/optimizations/insert_string.h
+index 1826601..d123305 100644
+--- a/third_party/zlib/contrib/optimizations/insert_string.h
++++ b/third_party/zlib/contrib/optimizations/insert_string.h
+@@ -26,15 +26,23 @@
+   #define _cpu_crc32_u32 _mm_crc32_u32
+ 
+ #elif defined(CRC32_ARMV8_CRC32)
+-  #if defined(__clang__)
++  #if defined(__GNUC__) || defined(__clang__)
+     #undef TARGET_CPU_WITH_CRC
+-    #define __crc32cw __builtin_arm_crc32cw
++    #if defined(__clang__)
++      #define __crc32cw __builtin_arm_crc32cw
++    #elif defined(__GNUC__)
++      #define __crc32cw __builtin_aarch64_crc32cw
++    #endif
+   #endif
+ 
+   #define _cpu_crc32_u32 __crc32cw
+ 
+   #if defined(__aarch64__)
+-    #define TARGET_CPU_WITH_CRC __attribute__((target("crc")))
++    #if defined(__clang__)
++      #define TARGET_CPU_WITH_CRC __attribute__((target("crc")))
++    #elif defined(__GNUC__)
++      #define TARGET_CPU_WITH_CRC __attribute__((target("+crc")))
++    #endif
+   #else  // !defined(__aarch64__)
+     #define TARGET_CPU_WITH_CRC __attribute__((target("armv8-a,crc")))
+   #endif  // defined(__aarch64__)
diff --git a/pkgs/development/libraries/wlroots/0.12.nix b/pkgs/development/libraries/wlroots/0.12.nix
new file mode 100644
index 00000000000..66d4fab2bf9
--- /dev/null
+++ b/pkgs/development/libraries/wlroots/0.12.nix
@@ -0,0 +1,57 @@
+{ lib, stdenv, fetchFromGitHub, meson, ninja, pkg-config, wayland
+, libGL, wayland-protocols, libinput, libxkbcommon, pixman
+, xcbutilwm, libX11, libcap, xcbutilimage, xcbutilerrors, mesa
+, libpng, ffmpeg
+}:
+
+# Fixed version derivation.
+# nixpkgs-update: no auto update
+stdenv.mkDerivation rec {
+  pname = "wlroots";
+  version = "0.12.0";
+
+  src = fetchFromGitHub {
+    owner = "swaywm";
+    repo = "wlroots";
+    rev = version;
+    sha256 = "01j38lmgs2c6fq68v8b75pkilia2wsgzgp46ivfbi9hhx47kgcfn";
+  };
+
+  # $out for the library and $examples for the example programs (in examples):
+  outputs = [ "out" "examples" ];
+
+  nativeBuildInputs = [ meson ninja pkg-config wayland ];
+
+  buildInputs = [
+    libGL wayland wayland-protocols libinput libxkbcommon pixman
+    xcbutilwm libX11 libcap xcbutilimage xcbutilerrors mesa
+    libpng ffmpeg
+  ];
+
+  mesonFlags = [ "-Dlogind-provider=systemd" "-Dlibseat=disabled" ];
+
+  postFixup = ''
+    # Install ALL example programs to $examples:
+    # screencopy dmabuf-capture input-inhibitor layer-shell idle-inhibit idle
+    # screenshot output-layout multi-pointer rotation tablet touch pointer
+    # simple
+    mkdir -p $examples/bin
+    cd ./examples
+    for binary in $(find . -executable -type f -printf '%P\n' | grep -vE '\.so'); do
+      cp "$binary" "$examples/bin/wlroots-$binary"
+    done
+  '';
+
+  meta = with lib; {
+    description = "A modular Wayland compositor library";
+    longDescription = ''
+      Pluggable, composable, unopinionated modules for building a Wayland
+      compositor; or about 50,000 lines of code you were going to write anyway.
+    '';
+    inherit (src.meta) homepage;
+    changelog = "https://github.com/swaywm/wlroots/releases/tag/${version}";
+    license     = licenses.mit;
+    platforms   = platforms.linux;
+    maintainers = with maintainers; [ primeos synthetica ];
+  };
+}
diff --git a/pkgs/development/libraries/wlroots/default.nix b/pkgs/development/libraries/wlroots/default.nix
index a21179e2025..a51de90d196 100644
--- a/pkgs/development/libraries/wlroots/default.nix
+++ b/pkgs/development/libraries/wlroots/default.nix
@@ -1,18 +1,18 @@
 { lib, stdenv, fetchFromGitHub, meson, ninja, pkg-config, wayland
 , libGL, wayland-protocols, libinput, libxkbcommon, pixman
 , xcbutilwm, libX11, libcap, xcbutilimage, xcbutilerrors, mesa
-, libpng, ffmpeg
+, libpng, ffmpeg, libuuid, xcbutilrenderutil, xwayland
 }:
 
 stdenv.mkDerivation rec {
   pname = "wlroots";
-  version = "0.12.0";
+  version = "0.13.0";
 
   src = fetchFromGitHub {
     owner = "swaywm";
     repo = "wlroots";
     rev = version;
-    sha256 = "01j38lmgs2c6fq68v8b75pkilia2wsgzgp46ivfbi9hhx47kgcfn";
+    sha256 = "01plhbnsp5yg18arz0v8fr0pr9l4w4pdzwkg9px486qdvb3s1vgy";
   };
 
   # $out for the library and $examples for the example programs (in examples):
@@ -23,7 +23,7 @@ stdenv.mkDerivation rec {
   buildInputs = [
     libGL wayland wayland-protocols libinput libxkbcommon pixman
     xcbutilwm libX11 libcap xcbutilimage xcbutilerrors mesa
-    libpng ffmpeg
+    libpng ffmpeg libuuid xcbutilrenderutil xwayland
   ];
 
   mesonFlags = [ "-Dlogind-provider=systemd" "-Dlibseat=disabled" ];
@@ -50,6 +50,6 @@ stdenv.mkDerivation rec {
     changelog = "https://github.com/swaywm/wlroots/releases/tag/${version}";
     license     = licenses.mit;
     platforms   = platforms.linux;
-    maintainers = with maintainers; [ primeos ];
+    maintainers = with maintainers; [ primeos synthetica ];
   };
 }
diff --git a/pkgs/development/python-modules/aiodiscover/default.nix b/pkgs/development/python-modules/aiodiscover/default.nix
index 923be510c45..e724d53bfe7 100644
--- a/pkgs/development/python-modules/aiodiscover/default.nix
+++ b/pkgs/development/python-modules/aiodiscover/default.nix
@@ -10,24 +10,16 @@
 
 buildPythonPackage rec {
   pname = "aiodiscover";
-  version = "1.3.2";
+  version = "1.3.3";
   disabled = pythonOlder "3.7";
 
   src = fetchFromGitHub {
     owner = "bdraco";
     repo = pname;
     rev = "v${version}";
-    sha256 = "0qg2wm6ddsfai788chylr5ynrvakwg91q3dszz7dxzbkfdcxixj3";
+    sha256 = "186agrjx818vn00d3pqlka5ir48rgpbfyn1cifkn9ylsxg9cz3ph";
   };
 
-  patches = [
-    (fetchpatch {
-      name = "remove-entry_point.patch";
-      url = "https://github.com/bdraco/aiodiscover/commit/4c497fb7d4c8685a78209c710e92e0bd17f46bb2.patch";
-      sha256 = "0py9alhg6qdncbn6a04mrnjhs4j19kg759dv69knpqzryikcfa63";
-    })
-  ];
-
   propagatedBuildInputs = [
     async-dns
     pyroute2
diff --git a/pkgs/development/python-modules/asyncio-nats-client/default.nix b/pkgs/development/python-modules/asyncio-nats-client/default.nix
new file mode 100644
index 00000000000..a7feac7312f
--- /dev/null
+++ b/pkgs/development/python-modules/asyncio-nats-client/default.nix
@@ -0,0 +1,50 @@
+{ lib
+, buildPythonPackage
+, ed25519
+, fetchFromGitHub
+, nats-server
+, pytestCheckHook
+, pythonOlder
+}:
+
+buildPythonPackage rec {
+  pname = "asyncio-nats-client";
+  version = "0.11.4";
+  disabled = pythonOlder "3.6";
+
+  src = fetchFromGitHub {
+    owner = "nats-io";
+    repo = "nats.py";
+    rev = "v${version}";
+    sha256 = "1aj57xi2rj1xswq8air13xdsll1ybpi0nmz5f6jq01azm0zy9xyd";
+  };
+
+  propagatedBuildInputs = [
+    ed25519
+  ];
+
+  checkInputs = [
+    nats-server
+    pytestCheckHook
+  ];
+
+  postPatch = ''
+    substituteInPlace setup.cfg \
+      --replace "--cov=nats --cov-report html" ""
+  '';
+
+  disabledTests = [
+    # RuntimeError: Event loop is closed
+    "test_subscribe_no_echo"
+    "test_reconnect_to_new_server_with_auth"
+  ];
+
+  pythonImportsCheck = [ "nats.aio" ];
+
+  meta = with lib; {
+    description = "Python client for NATS.io";
+    homepage = "https://github.com/nats-io/nats.py";
+    license = with licenses; [ asl20 ];
+    maintainers = with maintainers; [ fab ];
+  };
+}
diff --git a/pkgs/development/python-modules/homeassistant-pyozw/default.nix b/pkgs/development/python-modules/homeassistant-pyozw/default.nix
index a4ecb0d7ba3..271059e4851 100644
--- a/pkgs/development/python-modules/homeassistant-pyozw/default.nix
+++ b/pkgs/development/python-modules/homeassistant-pyozw/default.nix
@@ -1,6 +1,19 @@
-{ python_openzwave, fetchPypi }:
+{ python_openzwave, fetchPypi, openzwave, fetchFromGitHub }:
 
-python_openzwave.overridePythonAttrs (oldAttrs: rec {
+(python_openzwave.override {
+  openzwave = openzwave.overrideAttrs (oldAttrs: {
+    version = "unstable-2020-03-24";
+
+    src = fetchFromGitHub {
+      owner = "home-assistant";
+      repo = "open-zwave";
+      rev = "94267fa298c1882f0dc73c0fd08f1f755ba83e83";
+      sha256 = "0p2869fwidz1wcqzfm52cwm9ab96pmwkna3d4yvvh21nh09cvmwk";
+    };
+
+    patches = [ ];
+  });
+}).overridePythonAttrs (oldAttrs: rec {
   pname = "homeassistant_pyozw";
   version = "0.1.10";
 
diff --git a/pkgs/development/python-modules/nats-python/default.nix b/pkgs/development/python-modules/nats-python/default.nix
new file mode 100644
index 00000000000..5bf5b2a6d21
--- /dev/null
+++ b/pkgs/development/python-modules/nats-python/default.nix
@@ -0,0 +1,46 @@
+{ lib
+, buildPythonPackage
+, fetchFromGitHub
+, fetchpatch
+, poetry-core
+, pythonOlder
+}:
+
+buildPythonPackage rec {
+  pname = "nats-python";
+  version = "0.8.0";
+  disabled = pythonOlder "3.6";
+  format = "pyproject";
+
+  src = fetchFromGitHub {
+    owner = "Gr1N";
+    repo = "nats-python";
+    rev = version;
+    sha256 = "1j7skyxldir3mphvnsyhjxmf3cimv4h7n5v58jl2gff4yd0hdw7g";
+  };
+
+  nativeBuildInputs = [
+    poetry-core
+  ];
+
+  patches = [
+    # Switch to poetry-core, https://github.com/Gr1N/nats-python/pull/19
+    (fetchpatch {
+      name = "use-poetry-core.patch";
+      url = "https://github.com/Gr1N/nats-python/commit/71b25b324212dccd7fc06ba3914491adba22e83f.patch";
+      sha256 = "1fip1qpzk2ka7qgkrdpdr6vnrnb1p8cwapa51xp0h26nm7yis1gl";
+    })
+  ];
+
+  # Tests require a running NATS server
+  doCheck = false;
+
+  pythonImportsCheck = [ "pynats" ];
+
+  meta = with lib; {
+    description = "Python client for NATS messaging system";
+    homepage = "https://github.com/Gr1N/nats-python";
+    license = with licenses; [ mit ];
+    maintainers = with maintainers; [ fab ];
+  };
+}
diff --git a/pkgs/development/python-modules/smartypants/default.nix b/pkgs/development/python-modules/smartypants/default.nix
index d1e11e30d61..ec41c8a32aa 100644
--- a/pkgs/development/python-modules/smartypants/default.nix
+++ b/pkgs/development/python-modules/smartypants/default.nix
@@ -1,6 +1,6 @@
 { lib
 , buildPythonPackage
-, fetchhg
+, fetchFromGitHub
 , isPyPy
 }:
 
@@ -9,15 +9,21 @@ buildPythonPackage rec {
   pname = "smartypants";
   disabled = isPyPy;
 
-  src = fetchhg {
-    url = "https://bitbucket.org/livibetter/smartypants.py";
+  src = fetchFromGitHub {
+    owner = "leohemsted";
+    repo = "smartypants.py";
     rev = "v${version}";
     sha256 = "1cmzz44d2hm6y8jj2xcq1wfr26760gi7iq92ha8xbhb1axzd7nq6";
+    # remove this file and the name on the next version update
+    extraPostFetch = ''
+      cp ${./hgtags} "$out"/.hgtags
+    '';
+    name = "hg-archive";
   };
 
   meta = with lib; {
     description = "Python with the SmartyPants";
-    homepage = "https://bitbucket.org/livibetter/smartypants.py";
+    homepage = "https://github.com/leohemsted/smartypants.py";
     license = licenses.bsd3;
     maintainers = with maintainers; [ ];
   };
diff --git a/pkgs/development/python-modules/smartypants/hgtags b/pkgs/development/python-modules/smartypants/hgtags
new file mode 100644
index 00000000000..522ca8d2c01
--- /dev/null
+++ b/pkgs/development/python-modules/smartypants/hgtags
@@ -0,0 +1,17 @@
+47e996532eff9d3bb2c7048aca37d5fd1028e706 v1.5_1.2
+4ff48eba1d3d37d708005e465cf701b63eb68fd3 v1.5_1.1
+6ba7f3a14ff77e4e9bf9918413b1710c33deae4d v1.5_1.3.1
+71006a014216defb21e4db6e03434d289564ea60 v1.5_1.6
+90950ff693122f80710974abc0f2be64d4105e84 v1.5_1.3
+a6ecae6541d64f5b12c7b788c65362b0c012278d v1.5_1.0
+eed4a8a16f116f98e8280dc79128845020bbe766 v1.5_1.5
+f9a62f541f19ead9be4c3be896b64d1caa0b524c v1.5_1.4
+fc0bee49a07daf05f034560cfef81a8a8d034d1f v1.5_1.7
+096ed5f806b6dbc473fae1848643cf45005b9bf1 v1.7.0
+aaeb8099a24ad7db3f36ebe71ef326d6377730aa v1.7.1
+fd8ccc937af7280db4e581b2eb1354245f4672ab v1.8.0
+7839b0eab3e9daf5b346edfa5c54f3cc46fc202a v1.8.1
+6140b78317beabb6e49cd91b35a779ccb0af7327 v1.8.2
+c3b1c83c5ddada685b421b8f82f7e92c794bf2f6 v1.8.3
+460c1add9b9f89831e1ab965f1e1c31325f6e72d v1.8.4
+78165f4976299c37d6e3dd5463adcd61f9cb2b75 v1.8.5
diff --git a/pkgs/development/python-modules/ytmusicapi/default.nix b/pkgs/development/python-modules/ytmusicapi/default.nix
index f84db4ff624..1f211cd2872 100644
--- a/pkgs/development/python-modules/ytmusicapi/default.nix
+++ b/pkgs/development/python-modules/ytmusicapi/default.nix
@@ -7,13 +7,13 @@
 
 buildPythonPackage rec {
   pname = "ytmusicapi";
-  version = "0.15.0";
+  version = "0.15.1";
 
   disabled = isPy27;
 
   src = fetchPypi {
     inherit pname version;
-    sha256 = "sha256-pVQqoMvuuFc/1QNG5z/AspGlgIGPi9aqjZ3/3eVNhis=";
+    sha256 = "sha256-W/eZubJ/SNLBya1S6wLUwTwZCUD+wCQ5FAuNcSpl+9Y=";
   };
 
   propagatedBuildInputs = [
diff --git a/pkgs/development/ruby-modules/bundix/default.nix b/pkgs/development/ruby-modules/bundix/default.nix
index 4eb06f76aba..17f5d6553f6 100644
--- a/pkgs/development/ruby-modules/bundix/default.nix
+++ b/pkgs/development/ruby-modules/bundix/default.nix
@@ -38,7 +38,7 @@ buildRubyGem rec {
     '';
     homepage = "https://github.com/manveru/bundix";
     license = "MIT";
-    maintainers = with lib.maintainers; [ manveru zimbatm ];
+    maintainers = with lib.maintainers; [ manveru marsam zimbatm ];
     platforms = lib.platforms.all;
   };
 }
diff --git a/pkgs/development/tools/analysis/radare2/default.nix b/pkgs/development/tools/analysis/radare2/default.nix
index f5ce1bbde76..1622c616860 100644
--- a/pkgs/development/tools/analysis/radare2/default.nix
+++ b/pkgs/development/tools/analysis/radare2/default.nix
@@ -13,23 +13,18 @@
 , file
 , libzip
 , xxHash
-, gtk2 ? null
-, vte ? null
-, gtkdialog ? null
-, python3 ? null
-, ruby ? null
-, lua ? null
+, gtk2
+, vte
+, gtkdialog
+, python3
+, ruby
+, lua
 , useX11 ? false
 , rubyBindings ? false
 , pythonBindings ? false
 , luaBindings ? false
 }:
 
-assert useX11 -> (gtk2 != null && vte != null && gtkdialog != null);
-assert rubyBindings -> ruby != null;
-assert pythonBindings -> python3 != null;
-
-
 let
   inherit (lib) optional;
 
diff --git a/pkgs/development/tools/analysis/rizin/cutter.nix b/pkgs/development/tools/analysis/rizin/cutter.nix
index 14d815f04b1..55795b9830c 100644
--- a/pkgs/development/tools/analysis/rizin/cutter.nix
+++ b/pkgs/development/tools/analysis/rizin/cutter.nix
@@ -35,8 +35,6 @@ mkDerivation rec {
     qtWrapperArgs+=(--prefix PYTHONPATH : "$PYTHONPATH")
   '';
 
-  enableParallelBuilding = true;
-
   meta = with lib; {
     description = "Free and Open Source Reverse Engineering Platform powered by rizin";
     homepage = src.meta.homepage;
diff --git a/pkgs/development/tools/analysis/rizin/default.nix b/pkgs/development/tools/analysis/rizin/default.nix
index bbcc432ea99..fdc8da7b5f8 100644
--- a/pkgs/development/tools/analysis/rizin/default.nix
+++ b/pkgs/development/tools/analysis/rizin/default.nix
@@ -40,8 +40,6 @@ stdenv.mkDerivation rec {
     "-Duse_sys_tree_sitter=true"
   ];
 
-  enableParallelBuilding = true;
-
   nativeBuildInputs = [ pkg-config meson ninja cmake ];
 
   buildInputs = [
diff --git a/pkgs/development/tools/rust/cargo-deny/default.nix b/pkgs/development/tools/rust/cargo-deny/default.nix
index 1225d78d71e..73e3cfd3c4c 100644
--- a/pkgs/development/tools/rust/cargo-deny/default.nix
+++ b/pkgs/development/tools/rust/cargo-deny/default.nix
@@ -7,16 +7,16 @@
 
 rustPlatform.buildRustPackage rec {
   pname = "cargo-deny";
-  version = "0.8.9";
+  version = "0.9.0";
 
   src = fetchFromGitHub {
     owner = "EmbarkStudios";
     repo = pname;
     rev = version;
-    sha256 = "sha256-K8lNo2XmHzgbaVCMNvwDwr86hrXBPws9v3HD8ku+D6w=";
+    sha256 = "sha256-ZjXAZN93ij42WVYSOgvKAzFZ/cZ2RTFKT2sr44j7TVc=";
   };
 
-  cargoSha256 = "sha256-spTy9vzldzqu66904wRVwAeH1rNOQ3WeC6miJkRiAGg=";
+  cargoSha256 = "sha256-eQv9pFegHTjjjFURiD/yN/srtONAwAH3vwfrSY/LM/Q=";
 
   doCheck = false;
 
diff --git a/pkgs/os-specific/darwin/macfuse/default.nix b/pkgs/os-specific/darwin/macfuse/default.nix
index 7e1b4ed7763..4fd92a15562 100644
--- a/pkgs/os-specific/darwin/macfuse/default.nix
+++ b/pkgs/os-specific/darwin/macfuse/default.nix
@@ -56,4 +56,10 @@ stdenv.mkDerivation rec {
       lgpl2Plus # libfuse
     ];
   };
+
+  passthru.warning = ''
+    macFUSE is required for this package to work on macOS. To install macFUSE,
+    use the installer from the <link xlink:href="https://osxfuse.github.io/">
+    project website</link>.
+  '';
 }
diff --git a/pkgs/os-specific/linux/kernel/generic.nix b/pkgs/os-specific/linux/kernel/generic.nix
index ac9d6fbb2b5..0a9c2d11fef 100644
--- a/pkgs/os-specific/linux/kernel/generic.nix
+++ b/pkgs/os-specific/linux/kernel/generic.nix
@@ -55,7 +55,7 @@
 , autoModules ? stdenv.hostPlatform.linux-kernel.autoModules
 , preferBuiltin ? stdenv.hostPlatform.linux-kernel.preferBuiltin or false
 , kernelArch ? stdenv.hostPlatform.linuxArch
-
+, kernelTests ? []
 , ...
 }:
 
@@ -178,10 +178,11 @@ let
 
   passthru = {
     features = kernelFeatures;
-    inherit commonStructuredConfig isXen isZen isHardened isLibre;
+    inherit commonStructuredConfig isXen isZen isHardened isLibre modDirVersion;
     kernelOlder = lib.versionOlder version;
     kernelAtLeast = lib.versionAtLeast version;
     passthru = kernel.passthru // (removeAttrs passthru [ "passthru" ]);
+    tests = kernelTests;
   };
 
 in lib.extendDerivation true passthru kernel
diff --git a/pkgs/os-specific/linux/kernel/linux-4.14.nix b/pkgs/os-specific/linux/kernel/linux-4.14.nix
index 729cf3a6019..fc647d10959 100644
--- a/pkgs/os-specific/linux/kernel/linux-4.14.nix
+++ b/pkgs/os-specific/linux/kernel/linux-4.14.nix
@@ -1,4 +1,4 @@
-{ lib, buildPackages, fetchurl, perl, buildLinux, modDirVersionArg ? null, ... } @ args:
+{ lib, buildPackages, fetchurl, perl, buildLinux, nixosTests, modDirVersionArg ? null, ... } @ args:
 
 with lib;
 
@@ -15,4 +15,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v4.x/linux-${version}.tar.xz";
     sha256 = "0nw1jf6x5a990n69aw2da4s4lc1c7mnwiwcda40bl2rkmd24s1qm";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_4_14 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-4.19.nix b/pkgs/os-specific/linux/kernel/linux-4.19.nix
index dbd0f9e7f58..b0e5a865724 100644
--- a/pkgs/os-specific/linux/kernel/linux-4.19.nix
+++ b/pkgs/os-specific/linux/kernel/linux-4.19.nix
@@ -1,4 +1,4 @@
-{ lib, buildPackages, fetchurl, perl, buildLinux, modDirVersionArg ? null, ... } @ args:
+{ lib, buildPackages, fetchurl, perl, buildLinux, nixosTests, modDirVersionArg ? null, ... } @ args:
 
 with lib;
 
@@ -15,4 +15,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v4.x/linux-${version}.tar.xz";
     sha256 = "0z5pgal8775rf7pvpxq47dnghr42al2k9py0s9jl3js2wamgdyix";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_4_19 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-4.4.nix b/pkgs/os-specific/linux/kernel/linux-4.4.nix
index d90489922a4..3a5f86db3a1 100644
--- a/pkgs/os-specific/linux/kernel/linux-4.4.nix
+++ b/pkgs/os-specific/linux/kernel/linux-4.4.nix
@@ -1,4 +1,4 @@
-{ buildPackages, fetchurl, perl, buildLinux, ... } @ args:
+{ buildPackages, fetchurl, perl, buildLinux, nixosTests, ... } @ args:
 
 buildLinux (args // rec {
   version = "4.4.264";
@@ -8,4 +8,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v4.x/linux-${version}.tar.xz";
     sha256 = "1b0d735qnk0bcqn9gdsjqxhk8pkb3597ya9f34lv1vjfaqkkxk7l";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_4_4 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-4.9.nix b/pkgs/os-specific/linux/kernel/linux-4.9.nix
index e0caeb138da..798a16d7a30 100644
--- a/pkgs/os-specific/linux/kernel/linux-4.9.nix
+++ b/pkgs/os-specific/linux/kernel/linux-4.9.nix
@@ -1,4 +1,4 @@
-{ buildPackages, fetchurl, perl, buildLinux, ... } @ args:
+{ buildPackages, fetchurl, perl, buildLinux, nixosTests, ... } @ args:
 
 buildLinux (args // rec {
   version = "4.9.264";
@@ -8,4 +8,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v4.x/linux-${version}.tar.xz";
     sha256 = "1df2dv26c9z6zsdlqzbcc60f2pszh0hx1n94v65jswlb72a2mipc";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_4_9 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-5.10.nix b/pkgs/os-specific/linux/kernel/linux-5.10.nix
index 911cb326dd7..f35b0e432be 100644
--- a/pkgs/os-specific/linux/kernel/linux-5.10.nix
+++ b/pkgs/os-specific/linux/kernel/linux-5.10.nix
@@ -1,4 +1,4 @@
-{ lib, buildPackages, fetchurl, perl, buildLinux, modDirVersionArg ? null, ... } @ args:
+{ lib, buildPackages, fetchurl, perl, buildLinux, nixosTests, modDirVersionArg ? null, ... } @ args:
 
 with lib;
 
@@ -15,4 +15,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v5.x/linux-${version}.tar.xz";
     sha256 = "1nb95ll66kxiz702gs903n3gy5ialz8cin58l19rqaai55kck7fr";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_5_10 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-5.11.nix b/pkgs/os-specific/linux/kernel/linux-5.11.nix
index 16d0e5c1d4c..1c8540d8934 100644
--- a/pkgs/os-specific/linux/kernel/linux-5.11.nix
+++ b/pkgs/os-specific/linux/kernel/linux-5.11.nix
@@ -1,4 +1,4 @@
-{ lib, buildPackages, fetchurl, perl, buildLinux, modDirVersionArg ? null, ... } @ args:
+{ lib, buildPackages, fetchurl, perl, buildLinux, nixosTests, modDirVersionArg ? null, ... } @ args:
 
 with lib;
 
@@ -15,4 +15,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v5.x/linux-${version}.tar.xz";
     sha256 = "1fc3yl4srzla3cbihgnry0pqmgcc17zv0zlkk9zpx99371hpay0a";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_5_11 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-5.4.nix b/pkgs/os-specific/linux/kernel/linux-5.4.nix
index ac867e78768..e9d72200eda 100644
--- a/pkgs/os-specific/linux/kernel/linux-5.4.nix
+++ b/pkgs/os-specific/linux/kernel/linux-5.4.nix
@@ -1,4 +1,4 @@
-{ lib, buildPackages, fetchurl, perl, buildLinux, modDirVersionArg ? null, ... } @ args:
+{ lib, buildPackages, fetchurl, perl, buildLinux, nixosTests, modDirVersionArg ? null, ... } @ args:
 
 with lib;
 
@@ -15,4 +15,6 @@ buildLinux (args // rec {
     url = "mirror://kernel/linux/kernel/v5.x/linux-${version}.tar.xz";
     sha256 = "1vmpc6yrr2zm4m3naflwik5111jr8hy0mnyddwk31l0p4xbg8smc";
   };
+
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_5_4 ];
 } // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/kernel/linux-testing.nix b/pkgs/os-specific/linux/kernel/linux-testing.nix
index eec306f2892..f0d41869346 100644
--- a/pkgs/os-specific/linux/kernel/linux-testing.nix
+++ b/pkgs/os-specific/linux/kernel/linux-testing.nix
@@ -1,4 +1,4 @@
-{ lib, buildPackages, fetchurl, perl, buildLinux, modDirVersionArg ? null, ... } @ args:
+{ lib, buildPackages, fetchurl, perl, buildLinux, nixosTests, modDirVersionArg ? null, ... } @ args:
 
 with lib;
 
@@ -14,6 +14,8 @@ buildLinux (args // rec {
     sha256 = "0w0zk2byimdbcvn8myqaq0ab6lyd43493fnkv9a1407dimpxb03d";
   };
 
+  kernelTests = args.kernelTests or [ nixosTests.kernel-generic.linux_testing ];
+
   # Should the testing kernels ever be built on Hydra?
   extraMeta.hydraPlatforms = [];
 
diff --git a/pkgs/servers/home-assistant/component-packages.nix b/pkgs/servers/home-assistant/component-packages.nix
index 91f995d6cca..7d5c6f29e02 100644
--- a/pkgs/servers/home-assistant/component-packages.nix
+++ b/pkgs/servers/home-assistant/component-packages.nix
@@ -2,7 +2,7 @@
 # Do not edit!
 
 {
-  version = "2021.3.4";
+  version = "2021.4.0";
   components = {
     "abode" = ps: with ps; [ abodepy ];
     "accuweather" = ps: with ps; [ accuweather ];
@@ -31,6 +31,7 @@
     "ambient_station" = ps: with ps; [ aioambient ];
     "amcrest" = ps: with ps; [ amcrest ha-ffmpeg ];
     "ampio" = ps: with ps; [ ]; # missing inputs: asmog
+    "analytics" = ps: with ps; [ aiohttp-cors ];
     "android_ip_webcam" = ps: with ps; [ ]; # missing inputs: pydroid-ipcam
     "androidtv" = ps: with ps; [ adb-shell androidtv pure-python-adb ];
     "anel_pwrctrl" = ps: with ps; [ ]; # missing inputs: anel_pwrctrl-homeassistant
@@ -57,7 +58,7 @@
     "atag" = ps: with ps; [ ]; # missing inputs: pyatag
     "aten_pe" = ps: with ps; [ atenpdu ];
     "atome" = ps: with ps; [ ]; # missing inputs: pyatome
-    "august" = ps: with ps; [ ]; # missing inputs: py-august
+    "august" = ps: with ps; [ yalexs ];
     "aurora" = ps: with ps; [ auroranoaa ];
     "aurora_abb_powerone" = ps: with ps; [ ]; # missing inputs: aurorapy
     "auth" = ps: with ps; [ aiohttp-cors ];
@@ -154,7 +155,7 @@
     "deconz" = ps: with ps; [ pydeconz ];
     "decora" = ps: with ps; [ bluepy ]; # missing inputs: decora
     "decora_wifi" = ps: with ps; [ ]; # missing inputs: decora_wifi
-    "default_config" = ps: with ps; [ pynacl aiohttp-cors async-upnp-client defusedxml distro emoji hass-nabucasa netdisco pillow scapy sqlalchemy zeroconf ];
+    "default_config" = ps: with ps; [ pynacl aiodiscover aiohttp-cors async-upnp-client defusedxml distro emoji hass-nabucasa netdisco pillow scapy sqlalchemy zeroconf ];
     "delijn" = ps: with ps; [ ]; # missing inputs: pydelijn
     "deluge" = ps: with ps; [ deluge-client ];
     "demo" = ps: with ps; [ aiohttp-cors ];
@@ -167,7 +168,7 @@
     "device_tracker" = ps: with ps; [ ];
     "devolo_home_control" = ps: with ps; [ aiohttp-cors devolo-home-control-api zeroconf ];
     "dexcom" = ps: with ps; [ pydexcom ];
-    "dhcp" = ps: with ps; [ scapy ];
+    "dhcp" = ps: with ps; [ aiodiscover scapy ];
     "dht" = ps: with ps; [ ]; # missing inputs: Adafruit-DHT
     "dialogflow" = ps: with ps; [ aiohttp-cors ];
     "digital_ocean" = ps: with ps; [ digital-ocean ];
@@ -277,7 +278,7 @@
     "foscam" = ps: with ps; [ ]; # missing inputs: libpyfoscam
     "foursquare" = ps: with ps; [ aiohttp-cors ];
     "free_mobile" = ps: with ps; [ ]; # missing inputs: freesms
-    "freebox" = ps: with ps; [ aiohttp-cors freebox-api netdisco zeroconf ];
+    "freebox" = ps: with ps; [ freebox-api ];
     "freedns" = ps: with ps; [ ];
     "fritz" = ps: with ps; [ fritzconnection ];
     "fritzbox" = ps: with ps; [ pyfritzhome ];
@@ -326,7 +327,6 @@
     "gree" = ps: with ps; [ ]; # missing inputs: greeclimate
     "greeneye_monitor" = ps: with ps; [ ]; # missing inputs: greeneye_monitor
     "greenwave" = ps: with ps; [ ]; # missing inputs: greenwavereality
-    "griddy" = ps: with ps; [ ]; # missing inputs: griddypower
     "group" = ps: with ps; [ ];
     "growatt_server" = ps: with ps; [ ]; # missing inputs: growattServer
     "gstreamer" = ps: with ps; [ ]; # missing inputs: gstreamer-player
@@ -352,6 +352,7 @@
     "hive" = ps: with ps; [ ]; # missing inputs: pyhiveapi
     "hlk_sw16" = ps: with ps; [ ]; # missing inputs: hlk-sw16
     "home_connect" = ps: with ps; [ aiohttp-cors ]; # missing inputs: homeconnect
+    "home_plus_control" = ps: with ps; [ aiohttp-cors homepluscontrol ];
     "homeassistant" = ps: with ps; [ ];
     "homekit" = ps: with ps; [ HAP-python pyqrcode pyturbojpeg aiohttp-cors base36 fnvhash ha-ffmpeg zeroconf ];
     "homekit_controller" = ps: with ps; [ aiohomekit aiohttp-cors zeroconf ];
@@ -448,7 +449,7 @@
     "linux_battery" = ps: with ps; [ batinfo ];
     "lirc" = ps: with ps; [ ]; # missing inputs: python-lirc
     "litejet" = ps: with ps; [ ]; # missing inputs: pylitejet
-    "litterrobot" = ps: with ps; [ ]; # missing inputs: pylitterbot
+    "litterrobot" = ps: with ps; [ pylitterbot ];
     "llamalab_automate" = ps: with ps; [ ];
     "local_file" = ps: with ps; [ ];
     "local_ip" = ps: with ps; [ ];
@@ -715,6 +716,7 @@
     "scene" = ps: with ps; [ ];
     "schluter" = ps: with ps; [ ]; # missing inputs: py-schluter
     "scrape" = ps: with ps; [ beautifulsoup4 jsonpath xmltodict ];
+    "screenlogic" = ps: with ps; [ screenlogicpy ];
     "script" = ps: with ps; [ ];
     "scsgate" = ps: with ps; [ ]; # missing inputs: scsgate
     "search" = ps: with ps; [ aiohttp-cors ];
@@ -864,6 +866,7 @@
     "tplink" = ps: with ps; [ pyhs100 ];
     "tplink_lte" = ps: with ps; [ ]; # missing inputs: tp-connected
     "traccar" = ps: with ps; [ aiohttp-cors stringcase ]; # missing inputs: pytraccar
+    "trace" = ps: with ps; [ ];
     "trackr" = ps: with ps; [ ]; # missing inputs: pytrackr
     "tradfri" = ps: with ps; [ ]; # missing inputs: pytradfri[async]
     "trafikverket_train" = ps: with ps; [ pytrafikverket ];
@@ -906,7 +909,7 @@
     "velux" = ps: with ps; [ pyvlx ];
     "venstar" = ps: with ps; [ venstarcolortouch ];
     "vera" = ps: with ps; [ pyvera ];
-    "verisure" = ps: with ps; [ jsonpath vsure ];
+    "verisure" = ps: with ps; [ vsure ];
     "versasense" = ps: with ps; [ ]; # missing inputs: pyversasense
     "version" = ps: with ps; [ pyhaversion ];
     "vesync" = ps: with ps; [ pyvesync ];
@@ -974,7 +977,7 @@
     "zeroconf" = ps: with ps; [ aiohttp-cors zeroconf ];
     "zerproc" = ps: with ps; [ pyzerproc ];
     "zestimate" = ps: with ps; [ xmltodict ];
-    "zha" = ps: with ps; [ bellows pyserial-asyncio pyserial zha-quirks zigpy-cc zigpy-deconz zigpy-xbee zigpy-zigate zigpy-znp zigpy ];
+    "zha" = ps: with ps; [ aiohttp-cors bellows pyserial-asyncio pyserial zeroconf zha-quirks zigpy-cc zigpy-deconz zigpy-xbee zigpy-zigate zigpy-znp zigpy ];
     "zhong_hong" = ps: with ps; [ ]; # missing inputs: zhong_hong_hvac
     "ziggo_mediabox_xl" = ps: with ps; [ ]; # missing inputs: ziggo-mediabox-xl
     "zodiac" = ps: with ps; [ ];
diff --git a/pkgs/servers/home-assistant/default.nix b/pkgs/servers/home-assistant/default.nix
index aac3a2ee795..0c58caa2922 100644
--- a/pkgs/servers/home-assistant/default.nix
+++ b/pkgs/servers/home-assistant/default.nix
@@ -1,7 +1,6 @@
 { stdenv
 , lib
 , fetchFromGitHub
-, fetchpatch
 , python3
 , nixosTests
 
@@ -43,6 +42,21 @@ let
       });
     })
 
+    # Pinned due to API changes in pylilterbot>=2021.3.0
+    (self: super: {
+      pylitterbot = super.pylitterbot.overridePythonAttrs (oldAttrs: rec {
+        version = "2021.2.8";
+        src = fetchFromGitHub {
+          owner = "natekspencer";
+          repo = "pylitterbot";
+          rev = version;
+          sha256 = "142lhijm51v11cd0lhcfdnjdd143jxi2hjsrqdq0rrbbnmj6mymp";
+        };
+        # had no tests before 2021.3.0
+        doCheck = false;
+      });
+    })
+
     # Pinned due to bug in ring-doorbell 0.7.0
     # https://github.com/tchellomello/python-ring-doorbell/issues/240
     (mkOverride "ring-doorbell" "0.6.2"
@@ -81,7 +95,7 @@ let
   extraBuildInputs = extraPackages py.pkgs;
 
   # Don't forget to run parse-requirements.py after updating
-  hassVersion = "2021.3.4";
+  hassVersion = "2021.4.0";
 
 in with py.pkgs; buildPythonApplication rec {
   pname = "homeassistant";
@@ -100,32 +114,22 @@ in with py.pkgs; buildPythonApplication rec {
     owner = "home-assistant";
     repo = "core";
     rev = version;
-    sha256 = "110pvin39lr40zd3lhb8zvh2wafl0k0dy3nbmc483yafy31xa4kw";
+    sha256 = "1gkbkyxqsw3isdyskzi0ib07fgqvirnr20jkhrz86vl0k9ix8hwf";
   };
 
   # leave this in, so users don't have to constantly update their downstream patch handling
   patches = [
-    (fetchpatch {
-      # Fix I-frame interval in stream test video
-      # https://github.com/home-assistant/core/pull/47638
-      url = "https://github.com/home-assistant/core/commit/d9bf63103fde44ddd38fb6b9a510d82609802b36.patch";
-      sha256 = "1y34cmw9zqb2lxyzm0q7vxlm05wwz76mhysgnh1jn39484fn9f9m";
-    })
   ];
 
   postPatch = ''
     substituteInPlace setup.py \
-      --replace "aiohttp==3.7.4" "aiohttp>=3.7.3" \
-      --replace "attrs==19.3.0" "attrs>=19.3.0" \
-      --replace "awesomeversion==21.2.3" "awesomeversion>=21.2.3" \
-      --replace "bcrypt==3.1.7" "bcrypt>=3.1.7" \
+      --replace "awesomeversion==21.2.3" "awesomeversion" \
+      --replace "bcrypt==3.1.7" "bcrypt" \
       --replace "cryptography==3.3.2" "cryptography" \
-      --replace "httpx==0.16.1" "httpx>=0.16.1" \
-      --replace "jinja2>=2.11.3" "jinja2>=2.11.2" \
       --replace "pip>=8.0.3,<20.3" "pip" \
-      --replace "pytz>=2021.1" "pytz>=2020.5" \
+      --replace "pytz>=2021.1" "pytz" \
       --replace "pyyaml==5.4.1" "pyyaml" \
-      --replace "ruamel.yaml==0.15.100" "ruamel.yaml>=0.15.100"
+      --replace "ruamel.yaml==0.15.100" "ruamel.yaml"
     substituteInPlace tests/test_config.py --replace '"/usr"' '"/build/media"'
   '';
 
@@ -179,6 +183,7 @@ in with py.pkgs; buildPythonApplication rec {
   componentTests = [
     "accuweather"
     "airly"
+    "analytics"
     "alert"
     "api"
     "auth"
@@ -231,6 +236,7 @@ in with py.pkgs; buildPythonApplication rec {
     "hddtemp"
     "history"
     "history_stats"
+    "home_plus_control"
     "homekit"
     "homekit_controller"
     "homeassistant"
@@ -253,6 +259,7 @@ in with py.pkgs; buildPythonApplication rec {
     "ipp"
     "kmtronic"
     "light"
+    "litterrobot"
     "local_file"
     "local_ip"
     "lock"
@@ -298,6 +305,7 @@ in with py.pkgs; buildPythonApplication rec {
     "rss_feed_template"
     "safe_mode"
     "scene"
+    "screenlogic"
     "script"
     "search"
     "shell_command"
@@ -328,6 +336,7 @@ in with py.pkgs; buildPythonApplication rec {
     "time_date"
     "timer"
     "tod"
+    "trace"
     "tts"
     "universal"
     "updater"
@@ -357,6 +366,8 @@ in with py.pkgs; buildPythonApplication rec {
     "--dist loadfile"
     # tests are located in tests/
     "tests"
+    # screenlogic/test_config_flow.py: Tries to send out UDP broadcasts
+    "--deselect tests/components/screenlogic/test_config_flow.py::test_form_cannot_connect"
     # dynamically add packages required for component tests
   ] ++ map (component: "tests/components/" + component) componentTests;
 
@@ -379,9 +390,13 @@ in with py.pkgs; buildPythonApplication rec {
     # generic/test_camera.py: AssertionError: 500 == 200
     "test_fetching_without_verify_ssl"
     "test_fetching_url_with_verify_ssl"
+    # util/test_package.py: AssertionError on package.is_installed('homeassistant>=999.999.999')
+    "test_check_package_version_does_not_match"
   ];
 
   preCheck = ''
+    export HOME="$TEMPDIR"
+
     # the tests require the existance of a media dir
     mkdir /build/media
 
diff --git a/pkgs/servers/home-assistant/frontend.nix b/pkgs/servers/home-assistant/frontend.nix
index e62ee9b42b6..72a1ea13e0d 100644
--- a/pkgs/servers/home-assistant/frontend.nix
+++ b/pkgs/servers/home-assistant/frontend.nix
@@ -4,11 +4,11 @@ buildPythonPackage rec {
   # the frontend version corresponding to a specific home-assistant version can be found here
   # https://github.com/home-assistant/home-assistant/blob/master/homeassistant/components/frontend/manifest.json
   pname = "home-assistant-frontend";
-  version = "20210302.6";
+  version = "20210407.1";
 
   src = fetchPypi {
     inherit pname version;
-    sha256 = "sha256-h3jCqfAPg+z6vsdLm5Pdr+7PCEWW58GCG9viIz3Mi64=";
+    sha256 = "sha256-7kgL6Ixlc1OZ+3sUAuvJd7vgY6FBgPFEKi6xhq7fiBc=";
   };
 
   # there is nothing to strip in this package
diff --git a/pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch b/pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch
new file mode 100644
index 00000000000..83c44a466fa
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/action_mailer_ca_cert.patch
@@ -0,0 +1,12 @@
+diff --git a/config/environments/production.rb b/config/environments/production.rb
+index 75c3a69512..7fc374cd9d 100644
+--- a/config/environments/production.rb
++++ b/config/environments/production.rb
+@@ -32,6 +32,7 @@ Discourse::Application.configure do
+       user_name: GlobalSetting.smtp_user_name,
+       password: GlobalSetting.smtp_password,
+       authentication: GlobalSetting.smtp_authentication,
++      ca_file: "/etc/ssl/certs/ca-certificates.crt",
+       enable_starttls_auto: GlobalSetting.smtp_enable_start_tls
+     }
+ 
diff --git a/pkgs/servers/web-apps/discourse/admin_create.patch b/pkgs/servers/web-apps/discourse/admin_create.patch
new file mode 100644
index 00000000000..651e8ce81dc
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/admin_create.patch
@@ -0,0 +1,48 @@
+diff --git a/lib/tasks/admin.rake b/lib/tasks/admin.rake
+index 80c403616d..cba01202ac 100644
+--- a/lib/tasks/admin.rake
++++ b/lib/tasks/admin.rake
+@@ -107,3 +107,43 @@ task "admin:create" => :environment do
+   end
+ 
+ end
++
++desc "Creates a forum administrator noninteractively"
++task "admin:create_noninteractively" => :environment do
++  email = ENV["ADMIN_EMAIL"]
++  existing_user = User.find_by_email(email)
++
++  # check if user account already exixts
++  if existing_user
++    admin = existing_user
++  else
++    # create new user
++    admin = User.new
++  end
++
++  admin.email = email
++  admin.name = ENV["ADMIN_NAME"]
++  admin.username = ENV["ADMIN_USERNAME"]
++
++  password = ENV["ADMIN_PASSWORD"]
++  unless admin.confirm_password?(password)
++    admin.password = password
++    puts "Admin password set!"
++  end
++
++  admin.active = true
++
++  # save/update user account
++  saved = admin.save
++  raise admin.errors.full_messages.join("\n") unless saved
++
++  puts "Account created successfully with username #{admin.username}" unless existing_user
++
++  # grant admin privileges
++  admin.grant_admin!
++  if admin.trust_level < 1
++    admin.change_trust_level!(1)
++  end
++  admin.email_tokens.update_all confirmed: true
++  admin.activate
++end
diff --git a/pkgs/servers/web-apps/discourse/default.nix b/pkgs/servers/web-apps/discourse/default.nix
new file mode 100644
index 00000000000..900d6921092
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/default.nix
@@ -0,0 +1,234 @@
+{ stdenv, makeWrapper, runCommandNoCC, lib, nixosTests
+, fetchFromGitHub, bundlerEnv, ruby, replace, gzip, gnutar, git
+, util-linux, gawk, imagemagick, optipng, pngquant, libjpeg, jpegoptim
+, gifsicle, libpsl, redis, postgresql, which, brotli, procps
+, nodePackages, v8
+}:
+
+let
+  version = "2.6.3";
+
+  src = fetchFromGitHub {
+    owner = "discourse";
+    repo = "discourse";
+    rev = "v${version}";
+    sha256 = "sha256-lAIhVxvmjxEiru1KNxbFV+eDMLUGza/Dma3WU0ex0xs=";
+  };
+
+  runtimeDeps = [
+    # For backups, themes and assets
+    rubyEnv.wrappedRuby
+    gzip
+    gnutar
+    git
+    brotli
+
+    # Misc required system utils
+    which
+    procps       # For ps and kill
+    util-linux   # For renice
+    gawk
+
+    # Image optimization
+    imagemagick
+    optipng
+    pngquant
+    libjpeg
+    jpegoptim
+    gifsicle
+    nodePackages.svgo
+  ];
+
+  runtimeEnv = {
+    HOME = "/run/discourse/home";
+    RAILS_ENV = "production";
+    UNICORN_LISTENER = "/run/discourse/sockets/unicorn.sock";
+  };
+
+  rake = runCommandNoCC "discourse-rake" {
+    nativeBuildInputs = [ makeWrapper ];
+  } ''
+    mkdir -p $out/bin
+    makeWrapper ${rubyEnv}/bin/rake $out/bin/discourse-rake \
+        ${lib.concatStrings (lib.mapAttrsToList (name: value: "--set ${name} '${value}' ") runtimeEnv)} \
+        --prefix PATH : ${lib.makeBinPath runtimeDeps} \
+        --set RAKEOPT '-f ${discourse}/share/discourse/Rakefile' \
+        --run 'cd ${discourse}/share/discourse'
+  '';
+
+  rubyEnv = bundlerEnv {
+    name = "discourse-ruby-env-${version}";
+    inherit version ruby;
+    gemdir = ./rubyEnv;
+    gemset =
+      let
+        gems = import ./rubyEnv/gemset.nix;
+      in
+        gems // {
+          mini_racer = gems.mini_racer // {
+            buildInputs = [ v8 ];
+            dontBuild = false;
+            # The Ruby extension makefile generator assumes the source
+            # is C, when it's actually C++ ¯\_(ツ)_/¯
+            postPatch = ''
+              substituteInPlace ext/mini_racer_extension/extconf.rb \
+                --replace '" -std=c++0x"' \
+                          '" -x c++ -std=c++0x"'
+            '';
+          };
+          mini_suffix = gems.mini_suffix // {
+            propagatedBuildInputs = [ libpsl ];
+            dontBuild = false;
+            # Use our libpsl instead of the vendored one, which isn't
+            # available for aarch64
+            postPatch = ''
+              cp $(readlink -f ${libpsl}/lib/libpsl.so) vendor/libpsl.so
+            '';
+          };
+        };
+
+    groups = [
+      "default" "assets" "development" "test"
+    ];
+  };
+
+  assets = stdenv.mkDerivation {
+    pname = "discourse-assets";
+    inherit version src;
+
+    nativeBuildInputs = [
+      rubyEnv.wrappedRuby
+      postgresql
+      redis
+      which
+      brotli
+      procps
+      nodePackages.uglify-js
+    ];
+
+    # We have to set up an environment that is close enough to
+    # production ready or the assets:precompile task refuses to
+    # run. This means that Redis and PostgreSQL has to be running and
+    # database migrations performed.
+    preBuild = ''
+      redis-server >/dev/null &
+
+      initdb -A trust $NIX_BUILD_TOP/postgres >/dev/null
+      postgres -D $NIX_BUILD_TOP/postgres -k $NIX_BUILD_TOP >/dev/null &
+      export PGHOST=$NIX_BUILD_TOP
+
+      echo "Waiting for Redis and PostgreSQL to be ready.."
+      while ! redis-cli --scan >/dev/null || ! psql -l >/dev/null; do
+        sleep 0.1
+      done
+
+      psql -d postgres -tAc 'CREATE USER "discourse"'
+      psql -d postgres -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
+      psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+      psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
+
+      # Create a temporary home dir to stop bundler from complaining
+      mkdir $NIX_BUILD_TOP/tmp_home
+      export HOME=$NIX_BUILD_TOP/tmp_home
+
+      export RAILS_ENV=production
+
+      bundle exec rake db:migrate >/dev/null
+      rm -r tmp/*
+    '';
+
+    buildPhase = ''
+      runHook preBuild
+
+      bundle exec rake assets:precompile
+
+      runHook postBuild
+    '';
+
+    installPhase = ''
+      runHook preInstall
+
+      mv public/assets $out
+
+      runHook postInstall
+    '';
+  };
+
+  discourse = stdenv.mkDerivation {
+    pname = "discourse";
+    inherit version src;
+
+    buildInputs = [
+      rubyEnv rubyEnv.wrappedRuby rubyEnv.bundler
+    ];
+
+    patches = [
+      # Load a separate NixOS site settings file
+      ./nixos_defaults.patch
+
+      # Add a noninteractive admin creation task
+      ./admin_create.patch
+
+      # Disable jhead, which is currently marked as vulnerable
+      ./disable_jhead.patch
+
+      # Add the path to the CA cert bundle to make TLS work
+      ./action_mailer_ca_cert.patch
+
+      # Log Unicorn messages to the journal and make request timeout
+      # configurable
+      ./unicorn_logging_and_timeout.patch
+    ];
+
+    postPatch = ''
+      # Always require lib-files and application.rb through their store
+      # path, not their relative state directory path. This gets rid of
+      # warnings and means we don't have to link back to lib from the
+      # state directory.
+      find config -type f -execdir sed -Ei "s,(\.\./)+(lib|app)/,$out/share/discourse/\2/," {} \;
+
+      ${replace}/bin/replace-literal -f -r -e 'File.rename(temp_destination, destination)' "FileUtils.mv(temp_destination, destination)" .
+    '';
+
+    buildPhase = ''
+      runHook preBuild
+
+      mv config config.dist
+      mv public public.dist
+      mv plugins plugins.dist
+
+      runHook postBuild
+    '';
+
+    installPhase = ''
+      runHook preInstall
+
+      mkdir -p $out/share
+      cp -r . $out/share/discourse
+      rm -r $out/share/discourse/log
+      ln -sf /var/log/discourse $out/share/discourse/log
+      ln -sf /run/discourse/tmp $out/share/discourse/tmp
+      ln -sf /run/discourse/config $out/share/discourse/config
+      ln -sf /run/discourse/assets/javascripts/plugins $out/share/discourse/app/assets/javascripts/plugins
+      ln -sf /run/discourse/public $out/share/discourse/public
+      ln -sf /run/discourse/plugins $out/share/discourse/plugins
+      ln -sf ${assets} $out/share/discourse/public.dist/assets
+
+      runHook postInstall
+    '';
+
+    meta = with lib; {
+      homepage = "https://www.discourse.org/";
+      platforms = platforms.linux;
+      maintainers = with maintainers; [ talyz ];
+      license = licenses.gpl2Plus;
+      description = "Discourse is an open source discussion platform";
+    };
+
+    passthru = {
+      inherit rubyEnv runtimeEnv runtimeDeps rake;
+      ruby = rubyEnv.wrappedRuby;
+      tests = nixosTests.discourse;
+    };
+  };
+in discourse
diff --git a/pkgs/servers/web-apps/discourse/disable_jhead.patch b/pkgs/servers/web-apps/discourse/disable_jhead.patch
new file mode 100644
index 00000000000..709a1959d63
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/disable_jhead.patch
@@ -0,0 +1,12 @@
+diff --git a/lib/file_helper.rb b/lib/file_helper.rb
+index 162de9a40b..9ac8807e9d 100644
+--- a/lib/file_helper.rb
++++ b/lib/file_helper.rb
+@@ -124,6 +124,7 @@ class FileHelper
+         jpegoptim: { strip: strip_image_metadata ? "all" : "none" },
+         jpegtran: false,
+         jpegrecompress: false,
++        jhead: false,
+       )
+     end
+   end
diff --git a/pkgs/servers/web-apps/discourse/mail_receiver/default.nix b/pkgs/servers/web-apps/discourse/mail_receiver/default.nix
new file mode 100644
index 00000000000..c1a3a2df106
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/mail_receiver/default.nix
@@ -0,0 +1,39 @@
+{ stdenv, lib, fetchFromGitHub, ruby, makeWrapper, replace }:
+
+stdenv.mkDerivation rec {
+  pname = "discourse-mail-receiver";
+  version = "4.0.7";
+
+  src = fetchFromGitHub {
+    owner = "discourse";
+    repo = "mail-receiver";
+    rev = "v${version}";
+    sha256 = "0grifm5qyqazq63va3w26xjqnxwmfixhx0fx0zy7kd39378wwa6i";
+  };
+
+  nativeBuildInputs = [ replace ];
+  buildInputs = [ ruby makeWrapper ];
+
+  dontBuild = true;
+
+  installPhase = ''
+    mkdir -p $out/bin
+
+    replace-literal -f -r -e /etc/postfix /run/discourse-mail-receiver .
+
+    cp -r receive-mail discourse-smtp-fast-rejection $out/bin/
+    cp -r lib $out/
+
+    wrapProgram $out/bin/receive-mail --set RUBYLIB $out/lib
+    wrapProgram $out/bin/discourse-smtp-fast-rejection --set RUBYLIB $out/lib
+  '';
+
+  meta = with lib; {
+    homepage = "https://www.discourse.org/";
+    platforms = platforms.linux;
+    maintainers = with maintainers; [ talyz ];
+    license = licenses.mit;
+    description = "A helper program which receives incoming mail for Discourse";
+  };
+
+}
diff --git a/pkgs/servers/web-apps/discourse/nixos_defaults.patch b/pkgs/servers/web-apps/discourse/nixos_defaults.patch
new file mode 100644
index 00000000000..3efca97e62c
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/nixos_defaults.patch
@@ -0,0 +1,13 @@
+diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
+index 89a5e923fc..b60754f50a 100644
+--- a/app/models/site_setting.rb
++++ b/app/models/site_setting.rb
+@@ -26,6 +26,8 @@ class SiteSetting < ActiveRecord::Base
+     end
+   end
+ 
++  load_settings(File.join(Rails.root, 'config', 'nixos_site_settings.json'))
++
+   setup_deprecated_methods
+   client_settings << :available_locales
+ 
diff --git a/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile
new file mode 100644
index 00000000000..cb86d7e4bac
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile
@@ -0,0 +1,248 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+# if there is a super emergency and rubygems is playing up, try
+#source 'http://production.cf.rubygems.org'
+
+gem 'bootsnap', require: false, platform: :mri
+
+def rails_master?
+  ENV["RAILS_MASTER"] == '1'
+end
+
+if rails_master?
+  gem 'arel', git: 'https://github.com/rails/arel.git'
+  gem 'rails', git: 'https://github.com/rails/rails.git'
+else
+  # NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit
+  # this allows us to include the bits of rails we use without pieces we do not.
+  #
+  # To issue a rails update bump the version number here
+  gem 'actionmailer', '6.0.3.3'
+  gem 'actionpack', '6.0.3.3'
+  gem 'actionview', '6.0.3.3'
+  gem 'activemodel', '6.0.3.3'
+  gem 'activerecord', '6.0.3.3'
+  gem 'activesupport', '6.0.3.3'
+  gem 'railties', '6.0.3.3'
+  gem 'sprockets-rails'
+end
+
+gem 'json'
+
+# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
+# This is a desired upgrade we should get to.
+gem 'sprockets', '3.7.2'
+
+# this will eventually be added to rails,
+# allows us to precompile all our templates in the unicorn master
+gem 'actionview_precompiler', require: false
+
+gem 'seed-fu'
+
+gem 'mail', require: false
+gem 'mini_mime'
+gem 'mini_suffix'
+
+gem 'redis'
+
+# This is explicitly used by Sidekiq and is an optional dependency.
+# We tell Sidekiq to use the namespace "sidekiq" which triggers this
+# gem to be used. There is no explicit dependency in sidekiq cause
+# redis namespace support is optional
+# We already namespace stuff in DiscourseRedis, so we should consider
+# just using a single implementation in core vs having 2 namespace implementations
+gem 'redis-namespace'
+
+# NOTE: AM serializer gets a lot slower with recent updates
+# we used an old branch which is the fastest one out there
+# are long term goal here is to fork this gem so we have a
+# better maintained living fork
+gem 'active_model_serializers', '~> 0.8.3'
+
+gem 'onebox'
+
+gem 'http_accept_language', require: false
+
+# Ember related gems need to be pinned cause they control client side
+# behavior, we will push these versions up when upgrading ember
+gem 'discourse-ember-rails', '0.18.6', require: 'ember-rails'
+gem 'discourse-ember-source', '~> 3.12.2'
+gem 'ember-handlebars-template', '0.8.0'
+gem 'discourse-fonts'
+
+gem 'barber'
+
+gem 'message_bus'
+
+gem 'rails_multisite'
+
+gem 'fast_xs', platform: :ruby
+
+gem 'xorcist'
+
+gem 'fastimage'
+
+gem 'aws-sdk-s3', require: false
+gem 'aws-sdk-sns', require: false
+gem 'excon', require: false
+gem 'unf', require: false
+
+gem 'email_reply_trimmer'
+
+# Forked until https://github.com/toy/image_optim/pull/162 is merged
+# https://github.com/discourse/image_optim
+gem 'discourse_image_optim', require: 'image_optim'
+gem 'multi_json'
+gem 'mustache'
+gem 'nokogiri'
+gem 'css_parser', require: false
+
+gem 'omniauth'
+gem 'omniauth-facebook'
+gem 'omniauth-twitter'
+gem 'omniauth-github'
+
+gem 'omniauth-oauth2', require: false
+
+gem 'omniauth-google-oauth2'
+
+gem 'oj'
+gem 'pg'
+gem 'mini_sql'
+gem 'pry-rails', require: false
+gem 'pry-byebug', require: false
+gem 'r2', require: false
+gem 'rake'
+
+gem 'thor', require: false
+gem 'diffy', require: false
+gem 'rinku'
+gem 'sidekiq'
+gem 'mini_scheduler'
+
+gem 'execjs', require: false
+gem 'mini_racer'
+
+gem 'highline', require: false
+
+gem 'rack'
+
+gem 'rack-protection' # security
+gem 'cbor', require: false
+gem 'cose', require: false
+gem 'addressable'
+
+# Gems used only for assets and not required in production environments by default.
+# Allow everywhere for now cause we are allowing asset debugging in production
+group :assets do
+  gem 'uglifier'
+  gem 'rtlit', require: false # for css rtling
+end
+
+group :test do
+  gem 'webmock', require: false
+  gem 'fakeweb', require: false
+  gem 'minitest', require: false
+  gem 'simplecov', require: false
+  gem "test-prof"
+end
+
+group :test, :development do
+  gem 'rspec'
+  gem 'mock_redis'
+  gem 'listen', require: false
+  gem 'certified', require: false
+  gem 'fabrication', require: false
+  gem 'mocha', require: false
+
+  gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
+
+  gem 'rspec-rails'
+
+  gem 'shoulda-matchers', require: false
+  gem 'rspec-html-matchers'
+  gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri
+  gem "rubocop-discourse", require: false
+  gem 'parallel_tests'
+
+  gem 'rswag-specs'
+end
+
+group :development do
+  gem 'ruby-prof', require: false, platform: :mri
+  gem 'bullet', require: !!ENV['BULLET']
+  gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS']
+  gem 'binding_of_caller'
+  gem 'yaml-lint'
+  gem 'annotate'
+end
+
+# this is an optional gem, it provides a high performance replacement
+# to String#blank? a method that is called quite frequently in current
+# ActiveRecord, this may change in the future
+gem 'fast_blank', platform: :ruby
+
+# this provides a very efficient lru cache
+gem 'lru_redux'
+
+gem 'htmlentities', require: false
+
+# IMPORTANT: mini profiler monkey patches, so it better be required last
+#  If you want to amend mini profiler to do the monkey patches in the railties
+#  we are open to it. by deferring require to the initializer we can configure discourse installs without it
+
+gem 'flamegraph', require: false
+gem 'rack-mini-profiler', require: ['enable_rails_patches']
+
+gem 'unicorn', require: false, platform: :ruby
+gem 'puma', require: false
+gem 'rbtrace', require: false, platform: :mri
+gem 'gc_tracer', require: false, platform: :mri
+
+# required for feed importing and embedding
+gem 'ruby-readability', require: false
+
+gem 'stackprof', require: false, platform: :mri
+gem 'memory_profiler', require: false, platform: :mri
+
+gem 'cppjieba_rb', require: false
+
+gem 'lograge', require: false
+gem 'logstash-event', require: false
+gem 'logstash-logger', require: false
+gem 'logster'
+
+# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
+# and until resolved should be locked at 2.0.1
+gem 'sassc', '2.0.1', require: false
+gem "sassc-rails"
+
+gem 'rotp', require: false
+
+gem 'rqrcode'
+
+gem 'rubyzip', require: false
+
+gem 'sshkey', require: false
+
+gem 'rchardet', require: false
+gem 'lz4-ruby', require: false, platform: :ruby
+
+if ENV["IMPORT"] == "1"
+  gem 'mysql2'
+  gem 'redcarpet'
+
+  # NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one
+  gem 'sqlite3', '~> 1.3', '>= 1.3.13'
+  gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md'
+  gem 'reverse_markdown'
+  gem 'tiny_tds'
+  gem 'csv'
+end
+
+gem 'webpush', require: false
+gem 'colored2', require: false
+gem 'maxminddb'
+
+gem 'rails_failover', require: false
diff --git a/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock
new file mode 100644
index 00000000000..4f067493227
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/rubyEnv/Gemfile.lock
@@ -0,0 +1,561 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    actionmailer (6.0.3.3)
+      actionpack (= 6.0.3.3)
+      actionview (= 6.0.3.3)
+      activejob (= 6.0.3.3)
+      mail (~> 2.5, >= 2.5.4)
+      rails-dom-testing (~> 2.0)
+    actionpack (6.0.3.3)
+      actionview (= 6.0.3.3)
+      activesupport (= 6.0.3.3)
+      rack (~> 2.0, >= 2.0.8)
+      rack-test (>= 0.6.3)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.2.0)
+    actionview (6.0.3.3)
+      activesupport (= 6.0.3.3)
+      builder (~> 3.1)
+      erubi (~> 1.4)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.1, >= 1.2.0)
+    actionview_precompiler (0.2.3)
+      actionview (>= 6.0.a)
+    active_model_serializers (0.8.4)
+      activemodel (>= 3.0)
+    activejob (6.0.3.3)
+      activesupport (= 6.0.3.3)
+      globalid (>= 0.3.6)
+    activemodel (6.0.3.3)
+      activesupport (= 6.0.3.3)
+    activerecord (6.0.3.3)
+      activemodel (= 6.0.3.3)
+      activesupport (= 6.0.3.3)
+    activesupport (6.0.3.3)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (>= 0.7, < 2)
+      minitest (~> 5.1)
+      tzinfo (~> 1.1)
+      zeitwerk (~> 2.2, >= 2.2.2)
+    addressable (2.7.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    annotate (3.1.1)
+      activerecord (>= 3.2, < 7.0)
+      rake (>= 10.4, < 14.0)
+    ast (2.4.1)
+    aws-eventstream (1.1.0)
+    aws-partitions (1.390.0)
+    aws-sdk-core (3.109.2)
+      aws-eventstream (~> 1, >= 1.0.2)
+      aws-partitions (~> 1, >= 1.239.0)
+      aws-sigv4 (~> 1.1)
+      jmespath (~> 1.0)
+    aws-sdk-kms (1.39.0)
+      aws-sdk-core (~> 3, >= 3.109.0)
+      aws-sigv4 (~> 1.1)
+    aws-sdk-s3 (1.83.2)
+      aws-sdk-core (~> 3, >= 3.109.0)
+      aws-sdk-kms (~> 1)
+      aws-sigv4 (~> 1.1)
+    aws-sdk-sns (1.35.0)
+      aws-sdk-core (~> 3, >= 3.109.0)
+      aws-sigv4 (~> 1.1)
+    aws-sigv4 (1.2.2)
+      aws-eventstream (~> 1, >= 1.0.2)
+    barber (0.12.2)
+      ember-source (>= 1.0, < 3.1)
+      execjs (>= 1.2, < 3)
+    better_errors (2.9.1)
+      coderay (>= 1.0.0)
+      erubi (>= 1.0.0)
+      rack (>= 0.9.0)
+    binding_of_caller (0.8.0)
+      debug_inspector (>= 0.0.1)
+    bootsnap (1.5.1)
+      msgpack (~> 1.0)
+    builder (3.2.4)
+    bullet (6.1.0)
+      activesupport (>= 3.0.0)
+      uniform_notifier (~> 1.11)
+    byebug (11.1.3)
+    cbor (0.5.9.6)
+    certified (1.0.0)
+    chunky_png (1.3.14)
+    coderay (1.1.3)
+    colored2 (3.1.2)
+    concurrent-ruby (1.1.7)
+    connection_pool (2.2.3)
+    cose (1.2.0)
+      cbor (~> 0.5.9)
+      openssl-signature_algorithm (~> 1.0)
+    cppjieba_rb (0.3.3)
+    crack (0.4.4)
+    crass (1.0.6)
+    css_parser (1.7.1)
+      addressable
+    debug_inspector (0.0.3)
+    diff-lcs (1.4.4)
+    diffy (3.4.0)
+    discourse-ember-rails (0.18.6)
+      active_model_serializers
+      ember-data-source (>= 1.0.0.beta.5)
+      ember-handlebars-template (>= 0.1.1, < 1.0)
+      ember-source (>= 1.1.0)
+      jquery-rails (>= 1.0.17)
+      railties (>= 3.1)
+    discourse-ember-source (3.12.2.2)
+    discourse-fonts (0.0.5)
+    discourse_image_optim (0.26.2)
+      exifr (~> 1.2, >= 1.2.2)
+      fspath (~> 3.0)
+      image_size (~> 1.5)
+      in_threads (~> 1.3)
+      progress (~> 3.0, >= 3.0.1)
+    docile (1.3.2)
+    email_reply_trimmer (0.1.13)
+    ember-data-source (3.0.2)
+      ember-source (>= 2, < 3.0)
+    ember-handlebars-template (0.8.0)
+      barber (>= 0.11.0)
+      sprockets (>= 3.3, < 4.1)
+    ember-source (2.18.2)
+    erubi (1.10.0)
+    excon (0.78.0)
+    execjs (2.7.0)
+    exifr (1.3.9)
+    fabrication (2.21.1)
+    fakeweb (1.3.0)
+    faraday (1.1.0)
+      multipart-post (>= 1.2, < 3)
+      ruby2_keywords
+    fast_blank (1.0.0)
+    fast_xs (0.8.0)
+    fastimage (2.2.0)
+    ffi (1.13.1)
+    flamegraph (0.9.5)
+    fspath (3.1.2)
+    gc_tracer (1.5.1)
+    globalid (0.4.2)
+      activesupport (>= 4.2.0)
+    guess_html_encoding (0.0.11)
+    hashdiff (1.0.1)
+    hashie (4.1.0)
+    highline (2.0.3)
+    hkdf (0.3.0)
+    htmlentities (4.3.4)
+    http_accept_language (2.1.1)
+    i18n (1.8.5)
+      concurrent-ruby (~> 1.0)
+    image_size (1.5.0)
+    in_threads (1.5.4)
+    jmespath (1.4.0)
+    jquery-rails (4.4.0)
+      rails-dom-testing (>= 1, < 3)
+      railties (>= 4.2.0)
+      thor (>= 0.14, < 2.0)
+    json (2.3.1)
+    json-schema (2.8.1)
+      addressable (>= 2.4)
+    jwt (2.2.2)
+    kgio (2.11.3)
+    libv8 (8.4.255.0)
+    listen (3.3.1)
+      rb-fsevent (~> 0.10, >= 0.10.3)
+      rb-inotify (~> 0.9, >= 0.9.10)
+    lograge (0.11.2)
+      actionpack (>= 4)
+      activesupport (>= 4)
+      railties (>= 4)
+      request_store (~> 1.0)
+    logstash-event (1.2.02)
+    logstash-logger (0.26.1)
+      logstash-event (~> 1.2)
+    logster (2.9.4)
+    loofah (2.8.0)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.5.9)
+    lru_redux (1.1.0)
+    lz4-ruby (0.3.3)
+    mail (2.7.1)
+      mini_mime (>= 0.1.1)
+    maxminddb (0.1.22)
+    memory_profiler (0.9.14)
+    message_bus (3.3.4)
+      rack (>= 1.1.3)
+    method_source (1.0.0)
+    mini_mime (1.0.2)
+    mini_portile2 (2.4.0)
+    mini_racer (0.3.1)
+      libv8 (~> 8.4.255)
+    mini_scheduler (0.12.3)
+      sidekiq
+    mini_sql (0.3)
+    mini_suffix (0.3.0)
+      ffi (~> 1.9)
+    minitest (5.14.2)
+    mocha (1.11.2)
+    mock_redis (0.26.0)
+    msgpack (1.3.3)
+    multi_json (1.15.0)
+    multi_xml (0.6.0)
+    multipart-post (2.1.1)
+    mustache (1.1.1)
+    nio4r (2.5.4)
+    nokogiri (1.10.10)
+      mini_portile2 (~> 2.4.0)
+    nokogumbo (2.0.2)
+      nokogiri (~> 1.8, >= 1.8.4)
+    oauth (0.5.4)
+    oauth2 (1.4.4)
+      faraday (>= 0.8, < 2.0)
+      jwt (>= 1.0, < 3.0)
+      multi_json (~> 1.3)
+      multi_xml (~> 0.5)
+      rack (>= 1.2, < 3)
+    oj (3.10.16)
+    omniauth (1.9.1)
+      hashie (>= 3.4.6)
+      rack (>= 1.6.2, < 3)
+    omniauth-facebook (8.0.0)
+      omniauth-oauth2 (~> 1.2)
+    omniauth-github (1.4.0)
+      omniauth (~> 1.5)
+      omniauth-oauth2 (>= 1.4.0, < 2.0)
+    omniauth-google-oauth2 (0.8.0)
+      jwt (>= 2.0)
+      omniauth (>= 1.1.1)
+      omniauth-oauth2 (>= 1.6)
+    omniauth-oauth (1.1.0)
+      oauth
+      omniauth (~> 1.0)
+    omniauth-oauth2 (1.7.0)
+      oauth2 (~> 1.4)
+      omniauth (~> 1.9)
+    omniauth-twitter (1.4.0)
+      omniauth-oauth (~> 1.1)
+      rack
+    onebox (2.2.1)
+      addressable (~> 2.7.0)
+      htmlentities (~> 4.3)
+      multi_json (~> 1.11)
+      mustache
+      nokogiri (~> 1.7)
+      sanitize
+    openssl-signature_algorithm (1.0.0)
+    optimist (3.0.1)
+    parallel (1.20.1)
+    parallel_tests (3.4.0)
+      parallel
+    parser (2.7.2.0)
+      ast (~> 2.4.1)
+    pg (1.2.3)
+    progress (3.5.2)
+    pry (0.13.1)
+      coderay (~> 1.1)
+      method_source (~> 1.0)
+    pry-byebug (3.9.0)
+      byebug (~> 11.0)
+      pry (~> 0.13.0)
+    pry-rails (0.3.9)
+      pry (>= 0.10.4)
+    public_suffix (4.0.6)
+    puma (5.0.4)
+      nio4r (~> 2.0)
+    r2 (0.2.7)
+    rack (2.2.3)
+    rack-mini-profiler (2.2.0)
+      rack (>= 1.2.0)
+    rack-protection (2.1.0)
+      rack
+    rack-test (1.1.0)
+      rack (>= 1.0, < 3)
+    rails-dom-testing (2.0.3)
+      activesupport (>= 4.2.0)
+      nokogiri (>= 1.6)
+    rails-html-sanitizer (1.3.0)
+      loofah (~> 2.3)
+    rails_failover (0.6.2)
+      activerecord (~> 6.0)
+      concurrent-ruby
+      railties (~> 6.0)
+    rails_multisite (2.5.0)
+      activerecord (> 5.0, < 7)
+      railties (> 5.0, < 7)
+    railties (6.0.3.3)
+      actionpack (= 6.0.3.3)
+      activesupport (= 6.0.3.3)
+      method_source
+      rake (>= 0.8.7)
+      thor (>= 0.20.3, < 2.0)
+    rainbow (3.0.0)
+    raindrops (0.19.1)
+    rake (13.0.1)
+    rb-fsevent (0.10.4)
+    rb-inotify (0.10.1)
+      ffi (~> 1.0)
+    rbtrace (0.4.14)
+      ffi (>= 1.0.6)
+      msgpack (>= 0.4.3)
+      optimist (>= 3.0.0)
+    rchardet (1.8.0)
+    redis (4.2.5)
+    redis-namespace (1.8.0)
+      redis (>= 3.0.4)
+    regexp_parser (2.0.0)
+    request_store (1.5.0)
+      rack (>= 1.4)
+    rexml (3.2.4)
+    rinku (2.0.6)
+    rotp (6.2.0)
+    rqrcode (1.1.2)
+      chunky_png (~> 1.0)
+      rqrcode_core (~> 0.1)
+    rqrcode_core (0.1.2)
+    rspec (3.10.0)
+      rspec-core (~> 3.10.0)
+      rspec-expectations (~> 3.10.0)
+      rspec-mocks (~> 3.10.0)
+    rspec-core (3.10.0)
+      rspec-support (~> 3.10.0)
+    rspec-expectations (3.10.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.10.0)
+    rspec-html-matchers (0.9.4)
+      nokogiri (~> 1)
+      rspec (>= 3.0.0.a, < 4)
+    rspec-mocks (3.10.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.10.0)
+    rspec-rails (4.0.1)
+      actionpack (>= 4.2)
+      activesupport (>= 4.2)
+      railties (>= 4.2)
+      rspec-core (~> 3.9)
+      rspec-expectations (~> 3.9)
+      rspec-mocks (~> 3.9)
+      rspec-support (~> 3.9)
+    rspec-support (3.10.0)
+    rswag-specs (2.3.1)
+      activesupport (>= 3.1, < 7.0)
+      json-schema (~> 2.2)
+      railties (>= 3.1, < 7.0)
+    rtlit (0.0.5)
+    rubocop (1.4.2)
+      parallel (~> 1.10)
+      parser (>= 2.7.1.5)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8)
+      rexml
+      rubocop-ast (>= 1.1.1)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 1.4.0, < 2.0)
+    rubocop-ast (1.2.0)
+      parser (>= 2.7.1.5)
+    rubocop-discourse (2.4.1)
+      rubocop (>= 1.1.0)
+      rubocop-rspec (>= 2.0.0)
+    rubocop-rspec (2.0.0)
+      rubocop (~> 1.0)
+      rubocop-ast (>= 1.1.0)
+    ruby-prof (1.4.2)
+    ruby-progressbar (1.10.1)
+    ruby-readability (0.7.0)
+      guess_html_encoding (>= 0.0.4)
+      nokogiri (>= 1.6.0)
+    ruby2_keywords (0.0.2)
+    rubyzip (2.3.0)
+    sanitize (5.2.1)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.8.0)
+      nokogumbo (~> 2.0)
+    sassc (2.0.1)
+      ffi (~> 1.9)
+      rake
+    sassc-rails (2.1.2)
+      railties (>= 4.0.0)
+      sassc (>= 2.0)
+      sprockets (> 3.0)
+      sprockets-rails
+      tilt
+    seed-fu (2.3.9)
+      activerecord (>= 3.1)
+      activesupport (>= 3.1)
+    shoulda-matchers (4.4.1)
+      activesupport (>= 4.2.0)
+    sidekiq (6.1.2)
+      connection_pool (>= 2.2.2)
+      rack (~> 2.0)
+      redis (>= 4.2.0)
+    simplecov (0.20.0)
+      docile (~> 1.1)
+      simplecov-html (~> 0.11)
+      simplecov_json_formatter (~> 0.1)
+    simplecov-html (0.12.3)
+    simplecov_json_formatter (0.1.2)
+    sprockets (3.7.2)
+      concurrent-ruby (~> 1.0)
+      rack (> 1, < 3)
+    sprockets-rails (3.2.2)
+      actionpack (>= 4.0)
+      activesupport (>= 4.0)
+      sprockets (>= 3.0.0)
+    sshkey (2.0.0)
+    stackprof (0.2.16)
+    test-prof (0.12.2)
+    thor (1.0.1)
+    thread_safe (0.3.6)
+    tilt (2.0.10)
+    tzinfo (1.2.8)
+      thread_safe (~> 0.1)
+    uglifier (4.2.0)
+      execjs (>= 0.3.0, < 3)
+    unf (0.1.4)
+      unf_ext
+    unf_ext (0.0.7.7)
+    unicode-display_width (1.7.0)
+    unicorn (5.7.0)
+      kgio (~> 2.6)
+      raindrops (~> 0.7)
+    uniform_notifier (1.13.0)
+    webmock (3.10.0)
+      addressable (>= 2.3.6)
+      crack (>= 0.3.2)
+      hashdiff (>= 0.4.0, < 2.0.0)
+    webpush (1.1.0)
+      hkdf (~> 0.2)
+      jwt (~> 2.0)
+    xorcist (1.1.2)
+    yaml-lint (0.0.10)
+    zeitwerk (2.4.1)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  actionmailer (= 6.0.3.3)
+  actionpack (= 6.0.3.3)
+  actionview (= 6.0.3.3)
+  actionview_precompiler
+  active_model_serializers (~> 0.8.3)
+  activemodel (= 6.0.3.3)
+  activerecord (= 6.0.3.3)
+  activesupport (= 6.0.3.3)
+  addressable
+  annotate
+  aws-sdk-s3
+  aws-sdk-sns
+  barber
+  better_errors
+  binding_of_caller
+  bootsnap
+  bullet
+  byebug
+  cbor
+  certified
+  colored2
+  cose
+  cppjieba_rb
+  css_parser
+  diffy
+  discourse-ember-rails (= 0.18.6)
+  discourse-ember-source (~> 3.12.2)
+  discourse-fonts
+  discourse_image_optim
+  email_reply_trimmer
+  ember-handlebars-template (= 0.8.0)
+  excon
+  execjs
+  fabrication
+  fakeweb
+  fast_blank
+  fast_xs
+  fastimage
+  flamegraph
+  gc_tracer
+  highline
+  htmlentities
+  http_accept_language
+  json
+  listen
+  lograge
+  logstash-event
+  logstash-logger
+  logster
+  lru_redux
+  lz4-ruby
+  mail
+  maxminddb
+  memory_profiler
+  message_bus
+  mini_mime
+  mini_racer
+  mini_scheduler
+  mini_sql
+  mini_suffix
+  minitest
+  mocha
+  mock_redis
+  multi_json
+  mustache
+  nokogiri
+  oj
+  omniauth
+  omniauth-facebook
+  omniauth-github
+  omniauth-google-oauth2
+  omniauth-oauth2
+  omniauth-twitter
+  onebox
+  parallel_tests
+  pg
+  pry-byebug
+  pry-rails
+  puma
+  r2
+  rack
+  rack-mini-profiler
+  rack-protection
+  rails_failover
+  rails_multisite
+  railties (= 6.0.3.3)
+  rake
+  rb-fsevent
+  rbtrace
+  rchardet
+  redis
+  redis-namespace
+  rinku
+  rotp
+  rqrcode
+  rspec
+  rspec-html-matchers
+  rspec-rails
+  rswag-specs
+  rtlit
+  rubocop-discourse
+  ruby-prof
+  ruby-readability
+  rubyzip
+  sassc (= 2.0.1)
+  sassc-rails
+  seed-fu
+  shoulda-matchers
+  sidekiq
+  simplecov
+  sprockets (= 3.7.2)
+  sprockets-rails
+  sshkey
+  stackprof
+  test-prof
+  thor
+  uglifier
+  unf
+  unicorn
+  webmock
+  webpush
+  xorcist
+  yaml-lint
+
+BUNDLED WITH
+   2.1.4
diff --git a/pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix b/pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix
new file mode 100644
index 00000000000..b2cf191a444
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/rubyEnv/gemset.nix
@@ -0,0 +1,2272 @@
+{
+  actionmailer = {
+    dependencies = ["actionpack" "actionview" "activejob" "mail" "rails-dom-testing"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1spq0dbfn0qkqg9sq0rsjn360b4j36zly8hawaivkrwr3rsvyz75";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  actionpack = {
+    dependencies = ["actionview" "activesupport" "rack" "rack-test" "rails-dom-testing" "rails-html-sanitizer"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1p873nqwmpsvmkb5n86d70wndx1qhy15pc9mbcd1mc8sj174578b";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  actionview = {
+    dependencies = ["activesupport" "builder" "erubi" "rails-dom-testing" "rails-html-sanitizer"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "08pvmjddlw01q5r9zdfgddwp4csndpf5i2w47677z5r36jznz36q";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  actionview_precompiler = {
+    dependencies = ["actionview"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "07dx8vkwig8han4zccs0chahcf9ibd4abzx9n56qah8zak5cyrhd";
+      type = "gem";
+    };
+    version = "0.2.3";
+  };
+  active_model_serializers = {
+    dependencies = ["activemodel"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0k3mgia2ahh7mbk30hjq9pzqbk0kh281s91kq2z6p555nv9y6l3k";
+      type = "gem";
+    };
+    version = "0.8.4";
+  };
+  activejob = {
+    dependencies = ["activesupport" "globalid"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0w54ckvc229iaax879hkhyc93j7z8p0v7acp6mk3h8xjfvmwy5jp";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  activemodel = {
+    dependencies = ["activesupport"];
+    groups = ["default" "development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "166jlx6kbby01vr37srh081a9fykgsz873yg5i9gl2ar3vw9gs56";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  activerecord = {
+    dependencies = ["activemodel" "activesupport"];
+    groups = ["default" "development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0y2a4ss6ld6yrhpcbcb3kjn5gj6zk9qklp2aq5rl1awl8vbdbdb7";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  activesupport = {
+    dependencies = ["concurrent-ruby" "i18n" "minitest" "tzinfo" "zeitwerk"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1dmkqbvndbz011a1byg6f990936vfadbnwjwjw9vjzr4kd8bxk96";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  addressable = {
+    dependencies = ["public_suffix"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1fvchp2rhp2rmigx7qglf69xvjqvzq7x0g49naliw29r2bz656sy";
+      type = "gem";
+    };
+    version = "2.7.0";
+  };
+  annotate = {
+    dependencies = ["activerecord" "rake"];
+    groups = ["development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1dxrfppwfg13vqmambbs56xjj8qsdgcy58r2yc44vvy3z1g5yflw";
+      type = "gem";
+    };
+    version = "3.1.1";
+  };
+  ast = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1l3468czzjmxl93ap40hp7z94yxp4nbag0bxqs789bm30md90m2a";
+      type = "gem";
+    };
+    version = "2.4.1";
+  };
+  aws-eventstream = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0r0pn66yqrdkrfdin7qdim0yj2x75miyg4wp6mijckhzhrjb7cv5";
+      type = "gem";
+    };
+    version = "1.1.0";
+  };
+  aws-partitions = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "17xranmng1mg6238zdmnfvaig82r2ymp2apra9yh5d8rhvn8hkwm";
+      type = "gem";
+    };
+    version = "1.390.0";
+  };
+  aws-sdk-core = {
+    dependencies = ["aws-eventstream" "aws-partitions" "aws-sigv4" "jmespath"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "05dq7jfv5qf1y00ib96nqsipf08hflw8n8fwkyjw4qav84wjqaq4";
+      type = "gem";
+    };
+    version = "3.109.2";
+  };
+  aws-sdk-kms = {
+    dependencies = ["aws-sdk-core" "aws-sigv4"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0ly1m631qm2ciif7sysbzrgczjvz95ga3g6w6vrzvfdv31jjnl9a";
+      type = "gem";
+    };
+    version = "1.39.0";
+  };
+  aws-sdk-s3 = {
+    dependencies = ["aws-sdk-core" "aws-sdk-kms" "aws-sigv4"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1mld0yh6q6i2nbb143g5xc6gm70sqpvpwxfknlihrd8jmw3xc0bs";
+      type = "gem";
+    };
+    version = "1.83.2";
+  };
+  aws-sdk-sns = {
+    dependencies = ["aws-sdk-core" "aws-sigv4"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1dw80ldqhb1mny5irgi2jh36hykcmyd07xalv21xncxqzmf8aiag";
+      type = "gem";
+    };
+    version = "1.35.0";
+  };
+  aws-sigv4 = {
+    dependencies = ["aws-eventstream"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1ll9382c1x2hp750cilh01h1cycgyhdr4cmmgx23k94hyyb8chv5";
+      type = "gem";
+    };
+    version = "1.2.2";
+  };
+  barber = {
+    dependencies = ["ember-source" "execjs"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "07rnlbh7kgamcbnl1sqlcdrjj8src4qc687klqq4a3vqq2slnscx";
+      type = "gem";
+    };
+    version = "0.12.2";
+  };
+  better_errors = {
+    dependencies = ["coderay" "erubi" "rack"];
+    groups = ["development"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "11220lfzhsyf5fcril3qd689kgg46qlpiiaj00hc9mh4mcbc3vrr";
+      type = "gem";
+    };
+    version = "2.9.1";
+  };
+  binding_of_caller = {
+    dependencies = ["debug_inspector"];
+    groups = ["development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "05syqlks7463zsy1jdfbbdravdhj9hpj5pv2m74blqpv8bq4vv5g";
+      type = "gem";
+    };
+    version = "0.8.0";
+  };
+  bootsnap = {
+    dependencies = ["msgpack"];
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1qx1f729bgh391agsqb4ngzn22wdn4cc6mkp0cipf0d5hsg9cpaq";
+      type = "gem";
+    };
+    version = "1.5.1";
+  };
+  builder = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "045wzckxpwcqzrjr353cxnyaxgf0qg22jh00dcx7z38cys5g1jlr";
+      type = "gem";
+    };
+    version = "3.2.4";
+  };
+  bullet = {
+    dependencies = ["activesupport" "uniform_notifier"];
+    groups = ["development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "18ifwnvn13755qkfigapyj5bflpby3phxzbb7x5336d0kzv5k7d9";
+      type = "gem";
+    };
+    version = "6.1.0";
+  };
+  byebug = {
+    groups = ["development" "test"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0nx3yjf4xzdgb8jkmk2344081gqr22pgjqnmjg2q64mj5d6r9194";
+      type = "gem";
+    };
+    version = "11.1.3";
+  };
+  cbor = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0511idr8xps9625nh3kxr68sdy6l3xy2kcz7r57g47fxb1v18jj3";
+      type = "gem";
+    };
+    version = "0.5.9.6";
+  };
+  certified = {
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1706p6p0a8adyvd943af2a3093xakvislgffw3v9dvp7j07dyk5a";
+      type = "gem";
+    };
+    version = "1.0.0";
+  };
+  chunky_png = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1v52ndgx9r4jybq8yzr8anzfbnjk4y2hvz97nm9924wi4bad3xkf";
+      type = "gem";
+    };
+    version = "1.3.14";
+  };
+  coderay = {
+    groups = ["default" "development"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0jvxqxzply1lwp7ysn94zjhh57vc14mcshw1ygw14ib8lhc00lyw";
+      type = "gem";
+    };
+    version = "1.1.3";
+  };
+  colored2 = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0jlbqa9q4mvrm73aw9mxh23ygzbjiqwisl32d8szfb5fxvbjng5i";
+      type = "gem";
+    };
+    version = "3.1.2";
+  };
+  concurrent-ruby = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1vnxrbhi7cq3p4y2v9iwd10v1c7l15is4var14hwnb2jip4fyjzz";
+      type = "gem";
+    };
+    version = "1.1.7";
+  };
+  connection_pool = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1qikl4av1z8kqnk5ba18136dpqzw8wjawc2w9b4zb5psdd5z8nwf";
+      type = "gem";
+    };
+    version = "2.2.3";
+  };
+  cose = {
+    dependencies = ["cbor" "openssl-signature_algorithm"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1gx239d2fracq9az74wfdwmp5zm7zpzkcgchwnv2ng33d8r33p3m";
+      type = "gem";
+    };
+    version = "1.2.0";
+  };
+  cppjieba_rb = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1sslff7yy8jvp4rcn1b6jn9v0d3iibb68i79shgd94rs2yq8k117";
+      type = "gem";
+    };
+    version = "0.3.3";
+  };
+  crack = {
+    groups = ["default" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1awi8jy4jn0f7vxpdvz3xvn1zzjbjh33n28lfkijh77dla5zb7lc";
+      type = "gem";
+    };
+    version = "0.4.4";
+  };
+  crass = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0pfl5c0pyqaparxaqxi6s4gfl21bdldwiawrc0aknyvflli60lfw";
+      type = "gem";
+    };
+    version = "1.0.6";
+  };
+  css_parser = {
+    dependencies = ["addressable"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "04c4dl8cm5rjr50k9qa6yl9r05fk9zcb1zxh0y0cdahxlsgcydfw";
+      type = "gem";
+    };
+    version = "1.7.1";
+  };
+  debug_inspector = {
+    groups = ["default" "development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0vxr0xa1mfbkfcrn71n7c4f2dj7la5hvphn904vh20j3x4j5lrx0";
+      type = "gem";
+    };
+    version = "0.0.3";
+  };
+  diff-lcs = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0m925b8xc6kbpnif9dldna24q1szg4mk0fvszrki837pfn46afmz";
+      type = "gem";
+    };
+    version = "1.4.4";
+  };
+  diffy = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0nrg7kpgz6cn1gv2saj2fa5sfiykamvd7vn9lw2v625k7pjwf31l";
+      type = "gem";
+    };
+    version = "3.4.0";
+  };
+  discourse-ember-rails = {
+    dependencies = ["active_model_serializers" "ember-data-source" "ember-handlebars-template" "ember-source" "jquery-rails" "railties"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0ax5x2d6q6hkm7r58ai9p0sahlg842aqlm7dpv6svrfpnjlaz7sf";
+      type = "gem";
+    };
+    version = "0.18.6";
+  };
+  discourse-ember-source = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0mqkwiqb5n64lc5jdjnmpgb9apq08ywkz9yk8mj1sx2lqcsw11pc";
+      type = "gem";
+    };
+    version = "3.12.2.2";
+  };
+  discourse-fonts = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0xhwgqclh3jncjr55m0hyq3w3iw8jw2r7ickzq1zn1282pc3n2i7";
+      type = "gem";
+    };
+    version = "0.0.5";
+  };
+  discourse_image_optim = {
+    dependencies = ["exifr" "fspath" "image_size" "in_threads" "progress"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "11nqmga5ygxyhjmsc07gsa0fwwyhdpwi20yyr4fnh263xs1xylvv";
+      type = "gem";
+    };
+    version = "0.26.2";
+  };
+  docile = {
+    groups = ["default" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0qrwiyagxzl8zlx3dafb0ay8l14ib7imb2rsmx70i5cp420v8gif";
+      type = "gem";
+    };
+    version = "1.3.2";
+  };
+  email_reply_trimmer = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1jgcxifm48xq5dz9k47q43pqm5bfnf14l62l3bqhmv8f6z8dw4ki";
+      type = "gem";
+    };
+    version = "0.1.13";
+  };
+  ember-data-source = {
+    dependencies = ["ember-source"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1803nh3knvwl12h63jd48qvbbrp42yy291wcb35960daklip0fd8";
+      type = "gem";
+    };
+    version = "3.0.2";
+  };
+  ember-handlebars-template = {
+    dependencies = ["barber" "sprockets"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1wxj3vi4xs3vjxrdbzi4j4w6vv45r5dkz2rg2ldid3p8dp3irlf4";
+      type = "gem";
+    };
+    version = "0.8.0";
+  };
+  ember-source = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0sixy30ym9j2slhlr0lfq943g958w8arlb0lsizh59iv1w5gmxxy";
+      type = "gem";
+    };
+    version = "2.18.2";
+  };
+  erubi = {
+    groups = ["default" "development" "test"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "09l8lz3j00m898li0yfsnb6ihc63rdvhw3k5xczna5zrjk104f2l";
+      type = "gem";
+    };
+    version = "1.10.0";
+  };
+  excon = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1hi89v53pm2abfv9j8lgqdd7hgkr7fr0gwrczr940iwbb3xv7rrs";
+      type = "gem";
+    };
+    version = "0.78.0";
+  };
+  execjs = {
+    groups = ["assets" "default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1yz55sf2nd3l666ms6xr18sm2aggcvmb8qr3v53lr4rir32y1yp1";
+      type = "gem";
+    };
+    version = "2.7.0";
+  };
+  exifr = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0mylhwmh6n4xihxr9s3zj0lc286f5maxbqd4dgk3paqnd7afz88s";
+      type = "gem";
+    };
+    version = "1.3.9";
+  };
+  fabrication = {
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1pdrl55xf76pbc5kjzp7diawxxvgbk2cm38532in6df823431n6z";
+      type = "gem";
+    };
+    version = "2.21.1";
+  };
+  fakeweb = {
+    groups = ["test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1a09z9nb369bvwpghncgd5y4f95lh28w0q258srh02h22fz9dj8y";
+      type = "gem";
+    };
+    version = "1.3.0";
+  };
+  faraday = {
+    dependencies = ["multipart-post" "ruby2_keywords"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "16dapwi5pivrl25r4lkr1mxjrzkznj4wlcb08fzkmxnj4g5c6y35";
+      type = "gem";
+    };
+    version = "1.1.0";
+  };
+  fast_blank = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "rbx";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "16s1ilyvwzmkcgmklbrn0c2pch5n02vf921njx0bld4crgdr6z56";
+      type = "gem";
+    };
+    version = "1.0.0";
+  };
+  fast_xs = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "rbx";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1iydzaqmvqq7ncxkr182aybkk6xap0cb2w9amr73vbdxi2qf3wjz";
+      type = "gem";
+    };
+    version = "0.8.0";
+  };
+  fastimage = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "11ny2pj0j6pljszrf1w3iqdv2pcl2iwwghjbgcjlizy424zbh0hb";
+      type = "gem";
+    };
+    version = "2.2.0";
+  };
+  ffi = {
+    groups = ["default" "development" "test"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "12lpwaw82bb0rm9f52v1498bpba8aj2l2q359mkwbxsswhpga5af";
+      type = "gem";
+    };
+    version = "1.13.1";
+  };
+  flamegraph = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1p785nmhdzbwj0qpxn5fzrmr4kgimcds83v4f95f387z6w3050x6";
+      type = "gem";
+    };
+    version = "0.9.5";
+  };
+  fspath = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0xcxikkrjv8ws328nn5ax5pyfjs8pn7djg1hks7qyb3yp6prpb5m";
+      type = "gem";
+    };
+    version = "3.1.2";
+  };
+  gc_tracer = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1yv3mp8lx74lfzs04fd5h4g89209iwhzpc407y35p7cmzgx6a4kv";
+      type = "gem";
+    };
+    version = "1.5.1";
+  };
+  globalid = {
+    dependencies = ["activesupport"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1zkxndvck72bfw235bd9nl2ii0lvs5z88q14706cmn702ww2mxv1";
+      type = "gem";
+    };
+    version = "0.4.2";
+  };
+  guess_html_encoding = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "16700fk6kmif3q3kpc1ldhy3nsc9pkxlgl8sqhznff2zjj5lddna";
+      type = "gem";
+    };
+    version = "0.0.11";
+  };
+  hashdiff = {
+    groups = ["default" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1nynpl0xbj0nphqx1qlmyggq58ms1phf5i03hk64wcc0a17x1m1c";
+      type = "gem";
+    };
+    version = "1.0.1";
+  };
+  hashie = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "02bsx12ihl78x0vdm37byp78jjw2ff6035y7rrmbd90qxjwxr43q";
+      type = "gem";
+    };
+    version = "4.1.0";
+  };
+  highline = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0yclf57n2j3cw8144ania99h1zinf8q3f5zrhqa754j6gl95rp9d";
+      type = "gem";
+    };
+    version = "2.0.3";
+  };
+  hkdf = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "04fixg0a51n4vy0j6c1hvisa2yl33m3jrrpxpb5sq6j511vjriil";
+      type = "gem";
+    };
+    version = "0.3.0";
+  };
+  htmlentities = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1nkklqsn8ir8wizzlakncfv42i32wc0w9hxp00hvdlgjr7376nhj";
+      type = "gem";
+    };
+    version = "4.3.4";
+  };
+  http_accept_language = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0d0nlfz9vm4jr1l6q0chx4rp2hrnrfbx3gadc1dz930lbbaz0hq0";
+      type = "gem";
+    };
+    version = "2.1.1";
+  };
+  i18n = {
+    dependencies = ["concurrent-ruby"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "153sx77p16vawrs4qpkv7qlzf9v5fks4g7xqcj1dwk40i6g7rfzk";
+      type = "gem";
+    };
+    version = "1.8.5";
+  };
+  image_size = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0zrn2mqaf1kk548wn1y35i1a6kwh3320q62m929kn9m8sqpy4fk7";
+      type = "gem";
+    };
+    version = "1.5.0";
+  };
+  in_threads = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0m71806p1gm4kxiz4gvkyr8qip16hifn2kdf926jz44jj6kc6bbs";
+      type = "gem";
+    };
+    version = "1.5.4";
+  };
+  jmespath = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1d4wac0dcd1jf6kc57891glih9w57552zgqswgy74d1xhgnk0ngf";
+      type = "gem";
+    };
+    version = "1.4.0";
+  };
+  jquery-rails = {
+    dependencies = ["rails-dom-testing" "railties" "thor"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0dkhm8lan1vnyl3ll0ks2q06576pdils8a1dr354vfc1y5dqw15i";
+      type = "gem";
+    };
+    version = "4.4.0";
+  };
+  json = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "158fawfwmv2sq4whqqaksfykkiad2xxrrj0nmpnc6vnlzi1bp7iz";
+      type = "gem";
+    };
+    version = "2.3.1";
+  };
+  json-schema = {
+    dependencies = ["addressable"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1yv5lfmr2nzd14af498xqd5p89f3g080q8wk0klr3vxgypsikkb5";
+      type = "gem";
+    };
+    version = "2.8.1";
+  };
+  jwt = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "14ynyq1q483spj20ffl4xayfqx1a8qr761mqjfxczf8lwlap392n";
+      type = "gem";
+    };
+    version = "2.2.2";
+  };
+  kgio = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "rbx";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0ai6bzlvxbzpdl466p1qi4dlhx8ri2wcrp6x1l19y3yfs3a29rng";
+      type = "gem";
+    };
+    version = "2.11.3";
+  };
+  libv8 = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0317sr3nrl51sp844bps71smkrwim3fjn47wdfpbycixnbxspivm";
+      type = "gem";
+    };
+    version = "8.4.255.0";
+  };
+  listen = {
+    dependencies = ["rb-fsevent" "rb-inotify"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0028p1fss6pvw4mlpjqdmxfzsm8ww79irsadbibrr7f23qfn8ykr";
+      type = "gem";
+    };
+    version = "3.3.1";
+  };
+  lograge = {
+    dependencies = ["actionpack" "activesupport" "railties" "request_store"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1vrjm4yqn5l6q5gsl72fmk95fl6j9z1a05gzbrwmsm3gp1a1bgac";
+      type = "gem";
+    };
+    version = "0.11.2";
+  };
+  logstash-event = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1bk7fhhryjxp1klr3hq6i6srrc21wl4p980bysjp0w66z9hdr9w9";
+      type = "gem";
+    };
+    version = "1.2.02";
+  };
+  logstash-logger = {
+    dependencies = ["logstash-event"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1nh0jgz4rl46axqb9l0fa866kh34wb7yf11qc3j30xhprdqb8yjp";
+      type = "gem";
+    };
+    version = "0.26.1";
+  };
+  logster = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1ldikj3p0bakxg57didaw05pldjn0i5r20zawhqa34knlsqm66r6";
+      type = "gem";
+    };
+    version = "2.9.4";
+  };
+  loofah = {
+    dependencies = ["crass" "nokogiri"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0ndimir6k3kfrh8qrb7ir1j836l4r3qlwyclwjh88b86clblhszh";
+      type = "gem";
+    };
+    version = "2.8.0";
+  };
+  lru_redux = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1yxghzg7476sivz8yyr9nkak2dlbls0b89vc2kg52k0nmg6d0wgf";
+      type = "gem";
+    };
+    version = "1.1.0";
+  };
+  lz4-ruby = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "rbx";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "12fymsvcb9kw6ycyfzc8b9svriq0afqf1qnl121xrz8c4gpfa6q1";
+      type = "gem";
+    };
+    version = "0.3.3";
+  };
+  mail = {
+    dependencies = ["mini_mime"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "00wwz6ys0502dpk8xprwcqfwyf3hmnx6lgxaiq6vj43mkx43sapc";
+      type = "gem";
+    };
+    version = "2.7.1";
+  };
+  maxminddb = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0zlhqilyggiryywgswfi624bv10qnkm66hggmg79vvgv73j3p4sh";
+      type = "gem";
+    };
+    version = "0.1.22";
+  };
+  memory_profiler = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "04ivhv1bilwqm33jv28gar2vwzsichb5nipaq395d3axabv8qmfy";
+      type = "gem";
+    };
+    version = "0.9.14";
+  };
+  message_bus = {
+    dependencies = ["rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0hckijk9aa628nx66vr7axfsk7zfdkskaxj1mdzikk019q3h54fr";
+      type = "gem";
+    };
+    version = "3.3.4";
+  };
+  method_source = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1pnyh44qycnf9mzi1j6fywd5fkskv3x7nmsqrrws0rjn5dd4ayfp";
+      type = "gem";
+    };
+    version = "1.0.0";
+  };
+  mini_mime = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1axm0rxyx3ss93wbmfkm78a6x03l8y4qy60rhkkiq0aza0vwq3ha";
+      type = "gem";
+    };
+    version = "1.0.2";
+  };
+  mini_portile2 = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "15zplpfw3knqifj9bpf604rb3wc1vhq6363pd6lvhayng8wql5vy";
+      type = "gem";
+    };
+    version = "2.4.0";
+  };
+  mini_racer = {
+    dependencies = ["libv8"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0r7j241mvhyyc017bqgp0pvf3jyrwbcqvz2pzm0r8zn2r85ks1jl";
+      type = "gem";
+    };
+    version = "0.3.1";
+  };
+  mini_scheduler = {
+    dependencies = ["sidekiq"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0vigv7f1q5bkcb55ab2lyhq15yqfkg5mq61p7m7mw9b3jac7qjz1";
+      type = "gem";
+    };
+    version = "0.12.3";
+  };
+  mini_sql = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0qi4bj5jkh3673ybsxvsf7y485znyxb72vxg84gk9x65mf0y0m6h";
+      type = "gem";
+    };
+    version = "0.3";
+  };
+  mini_suffix = {
+    dependencies = ["ffi"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0bxd1fgzb20gvfvhbkrxym9fr7skm5x6fzvqfg4a0jijb34ww50h";
+      type = "gem";
+    };
+    version = "0.3.0";
+  };
+  minitest = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "170y2cvx51gm3cm3nhdf7j36sxnkh6vv8ls36p90ric7w8w16h4v";
+      type = "gem";
+    };
+    version = "5.14.2";
+  };
+  mocha = {
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0hxmkm8qxd04vwj8mqnpyrf2dwy7g1k9zipdfhl4y71cw7ijm9n4";
+      type = "gem";
+    };
+    version = "1.11.2";
+  };
+  mock_redis = {
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "06yj6j9x4zjckah4ixiwhy3hb6xzjp7yk7lmmcvcb8hpd0z0x95q";
+      type = "gem";
+    };
+    version = "0.26.0";
+  };
+  msgpack = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1lva6bkvb4mfa0m3bqn4lm4s4gi81c40jvdcsrxr6vng49q9daih";
+      type = "gem";
+    };
+    version = "1.3.3";
+  };
+  multi_json = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0pb1g1y3dsiahavspyzkdy39j4q377009f6ix0bh1ag4nqw43l0z";
+      type = "gem";
+    };
+    version = "1.15.0";
+  };
+  multi_xml = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0lmd4f401mvravi1i1yq7b2qjjli0yq7dfc4p1nj5nwajp7r6hyj";
+      type = "gem";
+    };
+    version = "0.6.0";
+  };
+  multipart-post = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1zgw9zlwh2a6i1yvhhc4a84ry1hv824d6g2iw2chs3k5aylpmpfj";
+      type = "gem";
+    };
+    version = "2.1.1";
+  };
+  mustache = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1l0p4wx15mi3wnamfv92ipkia4nsx8qi132c6g51jfdma3fiz2ch";
+      type = "gem";
+    };
+    version = "1.1.1";
+  };
+  nio4r = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1cbwp1kbv6b2qfxv8sarv0d0ilb257jihlvdqj8f5pdm0ksq1sgk";
+      type = "gem";
+    };
+    version = "2.5.4";
+  };
+  nokogiri = {
+    dependencies = ["mini_portile2"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0xmf60nj5kg9vaj5bysy308687sgmkasgx06vbbnf94p52ih7si2";
+      type = "gem";
+    };
+    version = "1.10.10";
+  };
+  nokogumbo = {
+    dependencies = ["nokogiri"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0sxjnpjvrn10gdmfw2dimhch861lz00f28hvkkz0b1gc2rb65k9s";
+      type = "gem";
+    };
+    version = "2.0.2";
+  };
+  oauth = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1zszdg8q1b135z7l7crjj234k4j0m347hywp5kj6zsq7q78pw09y";
+      type = "gem";
+    };
+    version = "0.5.4";
+  };
+  oauth2 = {
+    dependencies = ["faraday" "jwt" "multi_json" "multi_xml" "rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1bhakjh30vi8scqwnhd1c9qkac9r8hh2lr0dbs5ynwmrc5djxknm";
+      type = "gem";
+    };
+    version = "1.4.4";
+  };
+  oj = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1xqmzqldi9a0wpilwx87yh61xd7647gg8ffammg4ava0bsx375g2";
+      type = "gem";
+    };
+    version = "3.10.16";
+  };
+  omniauth = {
+    dependencies = ["hashie" "rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "002vi9gwamkmhf0dsj2im1d47xw2n1jfhnzl18shxf3ampkqfmyz";
+      type = "gem";
+    };
+    version = "1.9.1";
+  };
+  omniauth-facebook = {
+    dependencies = ["omniauth-oauth2"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1z0f5sr2ddnvfva0jrfd4926nlv4528rfj7z595288n39304r092";
+      type = "gem";
+    };
+    version = "8.0.0";
+  };
+  omniauth-github = {
+    dependencies = ["omniauth" "omniauth-oauth2"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0xbk0dbxqfpyfb33ghz6vrlz3m6442rp18ryf13gwzlnifcawhlb";
+      type = "gem";
+    };
+    version = "1.4.0";
+  };
+  omniauth-google-oauth2 = {
+    dependencies = ["jwt" "omniauth" "omniauth-oauth2"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "17pgqasl048irs2c6w6g57zvk0ygb5ml1krwir4qi4b6y53zyr55";
+      type = "gem";
+    };
+    version = "0.8.0";
+  };
+  omniauth-oauth = {
+    dependencies = ["oauth" "omniauth"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1n5vk4by7hkyc09d9blrw2argry5awpw4gbw1l4n2s9b3j4qz037";
+      type = "gem";
+    };
+    version = "1.1.0";
+  };
+  omniauth-oauth2 = {
+    dependencies = ["oauth2" "omniauth"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0v6rw7sd223k7qw0l13wikgfcqbvbk81r53a9i2z0k7jl5vd97w5";
+      type = "gem";
+    };
+    version = "1.7.0";
+  };
+  omniauth-twitter = {
+    dependencies = ["omniauth-oauth" "rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0r5j65hkpgzhvvbs90id3nfsjgsad6ymzggbm7zlaxvnrmvnrk65";
+      type = "gem";
+    };
+    version = "1.4.0";
+  };
+  onebox = {
+    dependencies = ["addressable" "htmlentities" "multi_json" "mustache" "nokogiri" "sanitize"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0b2aih0d5cva9bris36gh1mk3ym61wgxlpwvzjd6qphdrjfzqx8v";
+      type = "gem";
+    };
+    version = "2.2.1";
+  };
+  openssl-signature_algorithm = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0h1pfx49j8d9vbdbi8jyj0mr63l7rhflgvgc0nhfygm1v77d7nkn";
+      type = "gem";
+    };
+    version = "1.0.0";
+  };
+  optimist = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1vg2chy1cfmdj6c1gryl8zvjhhmb3plwgyh1jfnpq4fnfqv7asrk";
+      type = "gem";
+    };
+    version = "3.0.1";
+  };
+  parallel = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0055br0mibnqz0j8wvy20zry548dhkakws681bhj3ycb972awkzd";
+      type = "gem";
+    };
+    version = "1.20.1";
+  };
+  parallel_tests = {
+    dependencies = ["parallel"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1mvdk8vgzqjv2pvadxwc8w2vf8dmiw145rjf47c36nn6l5hh02j6";
+      type = "gem";
+    };
+    version = "3.4.0";
+  };
+  parser = {
+    dependencies = ["ast"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1f7gmm60yla325wlnd3qkxs59qm2y0aan8ljpg6k18rwzrrfil6z";
+      type = "gem";
+    };
+    version = "2.7.2.0";
+  };
+  pg = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "13mfrysrdrh8cka1d96zm0lnfs59i5x2g6ps49r2kz5p3q81xrzj";
+      type = "gem";
+    };
+    version = "1.2.3";
+  };
+  progress = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1pm3bv5n8c8j0vfm7wghd7xf6yq4m068cksxjldmna11qi0h0s8s";
+      type = "gem";
+    };
+    version = "3.5.2";
+  };
+  pry = {
+    dependencies = ["coderay" "method_source"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0iyw4q4an2wmk8v5rn2ghfy2jaz9vmw2nk8415nnpx2s866934qk";
+      type = "gem";
+    };
+    version = "0.13.1";
+  };
+  pry-byebug = {
+    dependencies = ["byebug" "pry"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "096y5vmzpyy4x9h4ky4cs4y7d19vdq9vbwwrqafbh5gagzwhifiv";
+      type = "gem";
+    };
+    version = "3.9.0";
+  };
+  pry-rails = {
+    dependencies = ["pry"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1cf4ii53w2hdh7fn8vhqpzkymmchjbwij4l3m7s6fsxvb9bn51j6";
+      type = "gem";
+    };
+    version = "0.3.9";
+  };
+  public_suffix = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1xqcgkl7bwws1qrlnmxgh8g4g9m10vg60bhlw40fplninb3ng6d9";
+      type = "gem";
+    };
+    version = "4.0.6";
+  };
+  puma = {
+    dependencies = ["nio4r"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0mkmfbf4qyiknwi9bb5432cpbbz06r855gknxb8grn24gmgs4d9i";
+      type = "gem";
+    };
+    version = "5.0.4";
+  };
+  r2 = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0wk0p55zp3l96xy5ps28b33dn5z0jwsjl74bwfdn6z81pzjs5sfk";
+      type = "gem";
+    };
+    version = "0.2.7";
+  };
+  rack = {
+    groups = ["default" "development" "test"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0i5vs0dph9i5jn8dfc6aqd6njcafmb20rwqngrf759c9cvmyff16";
+      type = "gem";
+    };
+    version = "2.2.3";
+  };
+  rack-mini-profiler = {
+    dependencies = ["rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "05s7y56ayn56bn7y5ah3krm5d53vsj7apmcxlwc2qp7ik0xlypvq";
+      type = "gem";
+    };
+    version = "2.2.0";
+  };
+  rack-protection = {
+    dependencies = ["rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "159a4j4kragqh0z0z8vrpilpmaisnlz3n7kgiyf16bxkwlb3qlhz";
+      type = "gem";
+    };
+    version = "2.1.0";
+  };
+  rack-test = {
+    dependencies = ["rack"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0rh8h376mx71ci5yklnpqqn118z3bl67nnv5k801qaqn1zs62h8m";
+      type = "gem";
+    };
+    version = "1.1.0";
+  };
+  rails-dom-testing = {
+    dependencies = ["activesupport" "nokogiri"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1lfq2a7kp2x64dzzi5p4cjcbiv62vxh9lyqk2f0rqq3fkzrw8h5i";
+      type = "gem";
+    };
+    version = "2.0.3";
+  };
+  rails-html-sanitizer = {
+    dependencies = ["loofah"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1icpqmxbppl4ynzmn6dx7wdil5hhq6fz707m9ya6d86c7ys8sd4f";
+      type = "gem";
+    };
+    version = "1.3.0";
+  };
+  rails_failover = {
+    dependencies = ["activerecord" "concurrent-ruby" "railties"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0ibxn7lk6rqk7q76cd9ir3xnh19p2pqr9mzam46n3h37f12yyax5";
+      type = "gem";
+    };
+    version = "0.6.2";
+  };
+  rails_multisite = {
+    dependencies = ["activerecord" "railties"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0p7g9gkcmw030zfqlw3k933i40j31wf3jh4bj1niihzk7slha97y";
+      type = "gem";
+    };
+    version = "2.5.0";
+  };
+  railties = {
+    dependencies = ["actionpack" "activesupport" "method_source" "rake" "thor"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "05b79r0ms8jrs91zml1190qfxmnmks90g0sd820ks9msyr8xdp7j";
+      type = "gem";
+    };
+    version = "6.0.3.3";
+  };
+  rainbow = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0bb2fpjspydr6x0s8pn1pqkzmxszvkfapv0p4627mywl7ky4zkhk";
+      type = "gem";
+    };
+    version = "3.0.0";
+  };
+  raindrops = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "rbx";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0zjja00mzgx2lddb7qrn14k7qrnwhf4bpmnlqj78m1pfxh7svync";
+      type = "gem";
+    };
+    version = "0.19.1";
+  };
+  rake = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0w6qza25bq1s825faaglkx1k6d59aiyjjk3yw3ip5sb463mhhai9";
+      type = "gem";
+    };
+    version = "13.0.1";
+  };
+  rb-fsevent = {
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1k9bsj7ni0g2fd7scyyy1sk9dy2pg9akniahab0iznvjmhn54h87";
+      type = "gem";
+    };
+    version = "0.10.4";
+  };
+  rb-inotify = {
+    dependencies = ["ffi"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1jm76h8f8hji38z3ggf4bzi8vps6p7sagxn3ab57qc0xyga64005";
+      type = "gem";
+    };
+    version = "0.10.1";
+  };
+  rbtrace = {
+    dependencies = ["ffi" "msgpack" "optimist"];
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0s8prj0klfgpmpfcpdzbf149qrrsdxgnb6w6kkqc9gyars4vyaqn";
+      type = "gem";
+    };
+    version = "0.4.14";
+  };
+  rchardet = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1isj1b3ywgg2m1vdlnr41lpvpm3dbyarf1lla4dfibfmad9csfk9";
+      type = "gem";
+    };
+    version = "1.8.0";
+  };
+  redis = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "15x2sr6h094rjbvg8pkq6m3lcd5abpyx93aifvfdz3wv6x55xa48";
+      type = "gem";
+    };
+    version = "4.2.5";
+  };
+  redis-namespace = {
+    dependencies = ["redis"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "05i6s898z5w31z385cba1683pgg5nnmj4m686cbravg7j4pgbcgv";
+      type = "gem";
+    };
+    version = "1.8.0";
+  };
+  regexp_parser = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1racz3w9s4w0ls32bvjypfifk4a7qxngm2cv1rh16jyz0c1wjd70";
+      type = "gem";
+    };
+    version = "2.0.0";
+  };
+  request_store = {
+    dependencies = ["rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0cx74kispmnw3ljwb239j65a2j14n8jlsygy372hrsa8mxc71hxi";
+      type = "gem";
+    };
+    version = "1.5.0";
+  };
+  rexml = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1mkvkcw9fhpaizrhca0pdgjcrbns48rlz4g6lavl5gjjq3rk2sq3";
+      type = "gem";
+    };
+    version = "3.2.4";
+  };
+  rinku = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0zcdha17s1wzxyc5814j6319wqg33jbn58pg6wmxpws36476fq4b";
+      type = "gem";
+    };
+    version = "2.0.6";
+  };
+  rotp = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "11q7rkjx40yi6lpylgl2jkpy162mjw7mswrcgcax86vgpbpjx6i3";
+      type = "gem";
+    };
+    version = "6.2.0";
+  };
+  rqrcode = {
+    dependencies = ["chunky_png" "rqrcode_core"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "06lw8b6wfshxd61xw98xyp1a0zsz6av4nls2c9fwb7q59wb05sci";
+      type = "gem";
+    };
+    version = "1.1.2";
+  };
+  rqrcode_core = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "071jqmhk3hf0grsvi0jx5sl449pf82p40ls5b3likbq4q516zc0j";
+      type = "gem";
+    };
+    version = "0.1.2";
+  };
+  rspec = {
+    dependencies = ["rspec-core" "rspec-expectations" "rspec-mocks"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1dwai7jnwmdmd7ajbi2q0k0lx1dh88knv5wl7c34wjmf94yv8w5q";
+      type = "gem";
+    };
+    version = "3.10.0";
+  };
+  rspec-core = {
+    dependencies = ["rspec-support"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0n2rdv8f26yw8c6asymc0mgddyr5d2b5n6mfvpd3n6lnpf1jdyv2";
+      type = "gem";
+    };
+    version = "3.10.0";
+  };
+  rspec-expectations = {
+    dependencies = ["diff-lcs" "rspec-support"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0j37dvnvfbjwj8dqx27yfvz0frl7f2jc1abqg99h0ppriz9za6dc";
+      type = "gem";
+    };
+    version = "3.10.0";
+  };
+  rspec-html-matchers = {
+    dependencies = ["nokogiri" "rspec"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0883rqv77n2wawnk5lp3la48l7pckyz8l013qddngzmksi5p1v3f";
+      type = "gem";
+    };
+    version = "0.9.4";
+  };
+  rspec-mocks = {
+    dependencies = ["diff-lcs" "rspec-support"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1pz89y1522i6f8wzrg72ykmch3318ih87nlpl0y1ghsrs5hqymw3";
+      type = "gem";
+    };
+    version = "3.10.0";
+  };
+  rspec-rails = {
+    dependencies = ["actionpack" "activesupport" "railties" "rspec-core" "rspec-expectations" "rspec-mocks" "rspec-support"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0lzik01ziaskgpdpy8knffpw0fsy9151f5lfigyhb89wq4q45hfs";
+      type = "gem";
+    };
+    version = "4.0.1";
+  };
+  rspec-support = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0j0n28i6zci5j7gg370bdy87dy43hlwx6dw428d9kamf5a0i2klz";
+      type = "gem";
+    };
+    version = "3.10.0";
+  };
+  rswag-specs = {
+    dependencies = ["activesupport" "json-schema" "railties"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0lyp2m76p960bvgy4xcz0dilp4w5lq2cwh8md5z7cwxdg8qsbr83";
+      type = "gem";
+    };
+    version = "2.3.1";
+  };
+  rtlit = {
+    groups = ["assets"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0srfh7cl95srjiwbyc9pmn3w739zlvyj89hyj0bm7g92zrsd27qm";
+      type = "gem";
+    };
+    version = "0.0.5";
+  };
+  rubocop = {
+    dependencies = ["parallel" "parser" "rainbow" "regexp_parser" "rexml" "rubocop-ast" "ruby-progressbar" "unicode-display_width"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1kvzhzhzcdd5bqwjilb0fpp51sqjniww2b0g713n0cvhnlgchn2y";
+      type = "gem";
+    };
+    version = "1.4.2";
+  };
+  rubocop-ast = {
+    dependencies = ["parser"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0q0kdi89ad7dd1xmzrdf5ikk32bllzr68hf4x8fd7azcv5jnch2l";
+      type = "gem";
+    };
+    version = "1.2.0";
+  };
+  rubocop-discourse = {
+    dependencies = ["rubocop" "rubocop-rspec"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1z1h8spsjnsqz6c25n9ib1yimkwr7a76bas8w1k9c404hcqhlahv";
+      type = "gem";
+    };
+    version = "2.4.1";
+  };
+  rubocop-rspec = {
+    dependencies = ["rubocop" "rubocop-ast"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1gl7hdd9lq0si4gb510g33dbysmk3iydas2b0sbl5pwfkhv0k4g1";
+      type = "gem";
+    };
+    version = "2.0.0";
+  };
+  ruby-prof = {
+    groups = ["development"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1lm3wdxc6gjldkb5pdwwipapf84lgrvxck4h5kg8jdfd8arrpyis";
+      type = "gem";
+    };
+    version = "1.4.2";
+  };
+  ruby-progressbar = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1k77i0d4wsn23ggdd2msrcwfy0i376cglfqypkk2q77r2l3408zf";
+      type = "gem";
+    };
+    version = "1.10.1";
+  };
+  ruby-readability = {
+    dependencies = ["guess_html_encoding" "nokogiri"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "15ivhbry7hf82lww1bzcrwfyjymijfb3rb0wdd32g2z0942wdspa";
+      type = "gem";
+    };
+    version = "0.7.0";
+  };
+  ruby2_keywords = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "17pcc0wgvh3ikrkr7bm3nx0qhyiqwidd13ij0fa50k7gsbnr2p0l";
+      type = "gem";
+    };
+    version = "0.0.2";
+  };
+  rubyzip = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0590m2pr9i209pp5z4mx0nb1961ishdiqb28995hw1nln1d1b5ji";
+      type = "gem";
+    };
+    version = "2.3.0";
+  };
+  sanitize = {
+    dependencies = ["crass" "nokogiri" "nokogumbo"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "18m3zcf207gcrmghx288w3n2kpphc22lbmbc1wdx1nzcn8g2yddh";
+      type = "gem";
+    };
+    version = "5.2.1";
+  };
+  sassc = {
+    dependencies = ["ffi" "rake"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1sr4825rlwsrl7xrsm0sgalcpf5zgp4i56dbi3qxfa9lhs8r6zh4";
+      type = "gem";
+    };
+    version = "2.0.1";
+  };
+  sassc-rails = {
+    dependencies = ["railties" "sassc" "sprockets" "sprockets-rails" "tilt"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1d9djmwn36a5m8a83bpycs48g8kh1n2xkyvghn7dr6zwh4wdyksz";
+      type = "gem";
+    };
+    version = "2.1.2";
+  };
+  seed-fu = {
+    dependencies = ["activerecord" "activesupport"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0y7lzcshsq6i20qn1p8zczir4fivr6nbl1km91ns320vvh92v43d";
+      type = "gem";
+    };
+    version = "2.3.9";
+  };
+  shoulda-matchers = {
+    dependencies = ["activesupport"];
+    groups = ["development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1wd1bblxr4dfmrnh3j83kvfds6a7nak4ifq37ab0pg1kdi6iiw7l";
+      type = "gem";
+    };
+    version = "4.4.1";
+  };
+  sidekiq = {
+    dependencies = ["connection_pool" "rack" "redis"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0mjxrxppv08a1hwqi8gpg6n168cxqhp7c2r2jwc4rbz9j5k41vcw";
+      type = "gem";
+    };
+    version = "6.1.2";
+  };
+  simplecov = {
+    dependencies = ["docile" "simplecov-html" "simplecov_json_formatter"];
+    groups = ["test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1mm20dvd64w46l5k11il9z5sjgdpp0bknml76glcngvl2w03k3cb";
+      type = "gem";
+    };
+    version = "0.20.0";
+  };
+  simplecov-html = {
+    groups = ["default" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0yx01bxa8pbf9ip4hagqkp5m0mqfnwnw2xk8kjraiywz4lrss6jb";
+      type = "gem";
+    };
+    version = "0.12.3";
+  };
+  simplecov_json_formatter = {
+    groups = ["default" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0cl3j7p3b5q7sxsx1va63c8imc5x6g99xablz08qrmqhpi0d6g6j";
+      type = "gem";
+    };
+    version = "0.1.2";
+  };
+  sprockets = {
+    dependencies = ["concurrent-ruby" "rack"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "182jw5a0fbqah5w9jancvfmjbk88h8bxdbwnl4d3q809rpxdg8ay";
+      type = "gem";
+    };
+    version = "3.7.2";
+  };
+  sprockets-rails = {
+    dependencies = ["actionpack" "activesupport" "sprockets"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0mwmz36265646xqfyczgr1mhkm1hfxgxxvgdgr4xfcbf2g72p1k2";
+      type = "gem";
+    };
+    version = "3.2.2";
+  };
+  sshkey = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "03bkn55qsng484iqwz2lmm6rkimj01vsvhwk661s3lnmpkl65lbp";
+      type = "gem";
+    };
+    version = "2.0.0";
+  };
+  stackprof = {
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "147rb66p3n062vc433afqhkd99iazvkrqnghxgh871r62yhha93f";
+      type = "gem";
+    };
+    version = "0.2.16";
+  };
+  test-prof = {
+    groups = ["test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1jfq8ylxpxanc3f0i6qb3nchawx9hj6qcqj6ccfyixrnvzswwjvi";
+      type = "gem";
+    };
+    version = "0.12.2";
+  };
+  thor = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1xbhkmyhlxwzshaqa7swy2bx6vd64mm0wrr8g3jywvxy7hg0cwkm";
+      type = "gem";
+    };
+    version = "1.0.1";
+  };
+  thread_safe = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0nmhcgq6cgz44srylra07bmaw99f5271l0dpsvl5f75m44l0gmwy";
+      type = "gem";
+    };
+    version = "0.3.6";
+  };
+  tilt = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0rn8z8hda4h41a64l0zhkiwz2vxw9b1nb70gl37h1dg2k874yrlv";
+      type = "gem";
+    };
+    version = "2.0.10";
+  };
+  tzinfo = {
+    dependencies = ["thread_safe"];
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0skr6ih9cr3pwp8l84f0z7fy3q9kiq8hw0sg3zqw0hpbbyj05743";
+      type = "gem";
+    };
+    version = "1.2.8";
+  };
+  uglifier = {
+    dependencies = ["execjs"];
+    groups = ["assets"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0wgh7bzy68vhv9v68061519dd8samcy8sazzz0w3k8kqpy3g4s5f";
+      type = "gem";
+    };
+    version = "4.2.0";
+  };
+  unf = {
+    dependencies = ["unf_ext"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0bh2cf73i2ffh4fcpdn9ir4mhq8zi50ik0zqa1braahzadx536a9";
+      type = "gem";
+    };
+    version = "0.1.4";
+  };
+  unf_ext = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0wc47r23h063l8ysws8sy24gzh74mks81cak3lkzlrw4qkqb3sg4";
+      type = "gem";
+    };
+    version = "0.0.7.7";
+  };
+  unicode-display_width = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "06i3id27s60141x6fdnjn5rar1cywdwy64ilc59cz937303q3mna";
+      type = "gem";
+    };
+    version = "1.7.0";
+  };
+  unicorn = {
+    dependencies = ["kgio" "raindrops"];
+    groups = ["default"];
+    platforms = [{
+      engine = "maglev";
+    } {
+      engine = "rbx";
+    } {
+      engine = "ruby";
+    }];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1qzdhbmab2w034wpdj5ippnyyvgqm8gpx9wbchb4zgs4i1mswzhv";
+      type = "gem";
+    };
+    version = "5.7.0";
+  };
+  uniform_notifier = {
+    groups = ["default" "development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0vm4aix8jmv42s1x58m3lj3xwkbxyn9qn6lzhhig0d1j8fv6j30c";
+      type = "gem";
+    };
+    version = "1.13.0";
+  };
+  webmock = {
+    dependencies = ["addressable" "crack" "hashdiff"];
+    groups = ["test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "0wbdjagk2qpr76k3zw2gmkfp5aqlrc1a4qrpjv7sq1q39qbn8xax";
+      type = "gem";
+    };
+    version = "3.10.0";
+  };
+  webpush = {
+    dependencies = ["hkdf" "jwt"];
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1z9ma580q80czw46gi1bvsr2iwxr63aiyr7i9gilav6hbhg3sxv3";
+      type = "gem";
+    };
+    version = "1.1.0";
+  };
+  xorcist = {
+    groups = ["default"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1q7hr3qyn1hczv9fglqc2cbaax0fb37gjjr0y24x19mmp817csdn";
+      type = "gem";
+    };
+    version = "1.1.2";
+  };
+  yaml-lint = {
+    groups = ["development"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "1m9n4sg7i0334yac7dcrhnhv5rzvrccgnh687n9x77ba3awk4yx1";
+      type = "gem";
+    };
+    version = "0.0.10";
+  };
+  zeitwerk = {
+    groups = ["default" "development" "test"];
+    platforms = [];
+    source = {
+      remotes = ["https://rubygems.org"];
+      sha256 = "12n0hiawqayzchi0yga5n19hi63b2snd49fv3n23n2i4pp05jzrp";
+      type = "gem";
+    };
+    version = "2.4.1";
+  };
+}
diff --git a/pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch b/pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch
new file mode 100644
index 00000000000..1dbfed67919
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/unicorn_logging_and_timeout.patch
@@ -0,0 +1,25 @@
+diff --git a/config/unicorn.conf.rb b/config/unicorn.conf.rb
+index 373e235b3f..57d4d7a55b 100644
+--- a/config/unicorn.conf.rb
++++ b/config/unicorn.conf.rb
+@@ -27,18 +27,10 @@ pid (ENV["UNICORN_PID_PATH"] || "#{discourse_path}/tmp/pids/unicorn.pid")
+ 
+ if ENV["RAILS_ENV"] == "development" || !ENV["RAILS_ENV"]
+   logger Logger.new($stdout)
+-  # we want a longer timeout in dev cause first request can be really slow
+-  timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60)
+-else
+-  # By default, the Unicorn logger will write to stderr.
+-  # Additionally, some applications/frameworks log to stderr or stdout,
+-  # so prevent them from going to /dev/null when daemonized here:
+-  stderr_path "#{discourse_path}/log/unicorn.stderr.log"
+-  stdout_path "#{discourse_path}/log/unicorn.stdout.log"
+-  # nuke workers after 30 seconds instead of 60 seconds (the default)
+-  timeout 30
+ end
+ 
++timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60)
++
+ # important for Ruby 2.0
+ preload_app true
+ 
diff --git a/pkgs/servers/web-apps/discourse/update.py b/pkgs/servers/web-apps/discourse/update.py
new file mode 100755
index 00000000000..c401ab552bb
--- /dev/null
+++ b/pkgs/servers/web-apps/discourse/update.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env nix-shell
+#! nix-shell -i python3 -p bundix bundler nix-update python3 python3Packages.requests python3Packages.click python3Packages.click-log
+
+import click
+import click_log
+import shutil
+import tempfile
+import re
+import logging
+import subprocess
+import pathlib
+from distutils.version import LooseVersion
+from typing import Iterable
+
+import requests
+
+logger = logging.getLogger(__name__)
+
+
+class DiscourseRepo:
+    version_regex = re.compile(r'^v\d+\.\d+\.\d+$')
+    def __init__(self, owner: str = 'discourse', repo: str = 'discourse'):
+        self.owner = owner
+        self.repo = repo
+
+    @property
+    def tags(self) -> Iterable[str]:
+        r = requests.get(f'https://api.github.com/repos/{self.owner}/{self.repo}/git/refs/tags').json()
+        tags = [x['ref'].replace('refs/tags/', '') for x in r]
+
+        # filter out versions not matching version_regex
+        versions = list(filter(self.version_regex.match, tags))
+
+        # sort, but ignore v for sorting comparisons
+        versions.sort(key=lambda x: LooseVersion(x.replace('v', '')), reverse=True)
+        return versions
+
+    @staticmethod
+    def rev2version(tag: str) -> str:
+        """
+        normalize a tag to a version number.
+        This obviously isn't very smart if we don't pass something that looks like a tag
+        :param tag: the tag to normalize
+        :return: a normalized version number
+        """
+        # strip v prefix
+        return re.sub(r'^v', '', tag)
+
+    def get_file(self, filepath, rev):
+        """returns file contents at a given rev :param filepath: the path to
+        the file, relative to the repo root :param rev: the rev to
+        fetch at :return:
+
+        """
+        return requests.get(f'https://raw.githubusercontent.com/{self.owner}/{self.repo}/{rev}/{filepath}').text
+
+
+def _call_nix_update(pkg, version):
+    """calls nix-update from nixpkgs root dir"""
+    nixpkgs_path = pathlib.Path(__file__).parent / '../../../../'
+    return subprocess.check_output(['nix-update', pkg, '--version', version], cwd=nixpkgs_path)
+
+
+def _get_current_package_version(pkg: str):
+    nixpkgs_path = pathlib.Path(__file__).parent / '../../../../'
+    return subprocess.check_output(['nix', 'eval', '--raw', f'nixpkgs.{pkg}.version'], text=True)
+
+
+def _diff_file(filepath: str, old_version: str, new_version: str):
+    repo = DiscourseRepo()
+
+    current_dir = pathlib.Path(__file__).parent
+
+    old = repo.get_file(filepath, 'v' + old_version)
+    new = repo.get_file(filepath, 'v' + new_version)
+
+    if old == new:
+        click.secho(f'{filepath} is unchanged', fg='green')
+        return
+
+    with tempfile.NamedTemporaryFile(mode='w') as o, tempfile.NamedTemporaryFile(mode='w') as n:
+        o.write(old), n.write(new)
+        width = shutil.get_terminal_size((80, 20)).columns
+        diff_proc = subprocess.run(
+            ['diff', '--color=always', f'--width={width}', '-y', o.name, n.name],
+            stdout=subprocess.PIPE,
+            cwd=current_dir,
+            text=True
+        )
+
+    click.secho(f'Diff for {filepath} ({old_version} -> {new_version}):', fg='bright_blue', bold=True)
+    click.echo(diff_proc.stdout + '\n')
+    return
+
+
+@click_log.simple_verbosity_option(logger)
+
+
+@click.group()
+def cli():
+    pass
+
+
+@cli.command()
+@click.argument('rev', default='latest')
+@click.option('--reverse/--no-reverse', default=False, help='Print diffs from REV to current.')
+def print_diffs(rev, reverse):
+    """Print out diffs for files used as templates for the NixOS module.
+
+    The current package version found in the nixpkgs worktree the
+    script is run from will be used to download the "from" file and
+    REV used to download the "to" file for the diff, unless the
+    '--reverse' flag is specified.
+
+    REV should be the git rev to find changes in ('vX.Y.Z') or
+    'latest'; defaults to 'latest'.
+
+    """
+    if rev == 'latest':
+        repo = DiscourseRepo()
+        rev = repo.tags[0]
+
+    old_version = _get_current_package_version('discourse')
+    new_version = DiscourseRepo.rev2version(rev)
+
+    if reverse:
+        old_version, new_version = new_version, old_version
+
+    for f in ['config/nginx.sample.conf', 'config/discourse_defaults.conf']:
+        _diff_file(f, old_version, new_version)
+
+
+@cli.command()
+@click.argument('rev', default='latest')
+def update(rev):
+    """Update gem files and version.
+
+    REV should be the git rev to update to ('vX.Y.Z') or 'latest';
+    defaults to 'latest'.
+
+    """
+    repo = DiscourseRepo()
+
+    if rev == 'latest':
+        rev = repo.tags[0]
+    logger.debug(f"Using rev {rev}")
+
+    version = repo.rev2version(rev)
+    logger.debug(f"Using version {version}")
+
+    rubyenv_dir = pathlib.Path(__file__).parent / "rubyEnv"
+
+    for fn in ['Gemfile.lock', 'Gemfile']:
+        with open(rubyenv_dir / fn, 'w') as f:
+            f.write(repo.get_file(fn, rev))
+
+    subprocess.check_output(['bundle', 'lock'], cwd=rubyenv_dir)
+    subprocess.check_output(['bundix'], cwd=rubyenv_dir)
+
+    _call_nix_update('discourse', repo.rev2version(rev))
+
+
+if __name__ == '__main__':
+    cli()
diff --git a/pkgs/tools/filesystems/sshfs-fuse/common.nix b/pkgs/tools/filesystems/sshfs-fuse/common.nix
new file mode 100644
index 00000000000..f85faa712dd
--- /dev/null
+++ b/pkgs/tools/filesystems/sshfs-fuse/common.nix
@@ -0,0 +1,61 @@
+{ version, sha256, platforms, patches ? [ ] }:
+
+{ lib, stdenv, fetchFromGitHub
+, meson, pkg-config, ninja, docutils, makeWrapper
+, fuse3, macfuse-stubs, glib
+, which, python3Packages
+, openssh
+}:
+
+let
+  fuse = if stdenv.isDarwin then macfuse-stubs else fuse3;
+in stdenv.mkDerivation rec {
+  pname = "sshfs-fuse";
+  inherit version;
+
+  src = fetchFromGitHub {
+    owner = "libfuse";
+    repo = "sshfs";
+    rev = "sshfs-${version}";
+    inherit sha256;
+  };
+
+  inherit patches;
+
+  nativeBuildInputs = [ meson pkg-config ninja docutils makeWrapper ];
+  buildInputs = [ fuse glib ];
+  checkInputs = [ which python3Packages.pytest ];
+
+  NIX_CFLAGS_COMPILE = lib.optionalString
+    (stdenv.hostPlatform.system == "i686-linux")
+    "-D_FILE_OFFSET_BITS=64";
+
+  postInstall = ''
+    mkdir -p $out/sbin
+    ln -sf $out/bin/sshfs $out/sbin/mount.sshfs
+  '' + lib.optionalString (!stdenv.isDarwin) ''
+    wrapProgram $out/bin/sshfs --prefix PATH : "${openssh}/bin"
+  '';
+
+  # doCheck = true;
+  checkPhase = lib.optionalString (!stdenv.isDarwin) ''
+    # The tests need fusermount:
+    mkdir bin
+    cp ${fuse}/bin/fusermount3 bin/fusermount
+    export PATH=bin:$PATH
+    # Can't access /dev/fuse within the sandbox: "FUSE kernel module does not seem to be loaded"
+    substituteInPlace test/util.py --replace "/dev/fuse" "/dev/null"
+    # TODO: "fusermount executable not setuid, and we are not root"
+    # We should probably use a VM test instead
+    ${python3Packages.python.interpreter} -m pytest test/
+  '';
+
+  meta = with lib; {
+    inherit platforms;
+    description = "FUSE-based filesystem that allows remote filesystems to be mounted over SSH";
+    longDescription = macfuse-stubs.warning;
+    homepage = "https://github.com/libfuse/sshfs";
+    license = licenses.gpl2Plus;
+    maintainers = with maintainers; [ primeos ];
+  };
+}
diff --git a/pkgs/tools/filesystems/sshfs-fuse/default.nix b/pkgs/tools/filesystems/sshfs-fuse/default.nix
index 69826941d00..e575bd3e138 100644
--- a/pkgs/tools/filesystems/sshfs-fuse/default.nix
+++ b/pkgs/tools/filesystems/sshfs-fuse/default.nix
@@ -1,52 +1,28 @@
-{ lib, stdenv, fetchFromGitHub
-, meson, pkg-config, ninja, docutils, makeWrapper
-, fuse3, glib
-, which, python3Packages
-, openssh
-}:
+{ lib, stdenv, callPackage, fetchpatch }:
 
-stdenv.mkDerivation rec {
-  version = "3.7.1";
-  pname = "sshfs-fuse";
+let mkSSHFS = args: callPackage (import ./common.nix args) { };
+in if stdenv.isDarwin then
+  mkSSHFS {
+    version = "2.10"; # macFUSE isn't yet compatible with libfuse 3.x
+    sha256 = "1dmw4kx6vyawcywiv8drrajnam0m29mxfswcp4209qafzx3mjlp1";
+    patches = [
+      # remove reference to fuse_darwin.h which doens't exist on recent macFUSE
+      ./fix-fuse-darwin-h.patch
 
-  src = fetchFromGitHub {
-    owner = "libfuse";
-    repo = "sshfs";
-    rev = "sshfs-${version}";
+      # From https://github.com/libfuse/sshfs/pull/185:
+      # > With this patch, setting I/O size to a reasonable large value, will
+      # > result in much improved performance, e.g.: -o iosize=1048576
+      (fetchpatch {
+        name = "fix-configurable-blksize.patch";
+        url = "https://github.com/libfuse/sshfs/commit/667cf34622e2e873db776791df275c7a582d6295.patch";
+        sha256 = "0d65lawd2g2aisk1rw2vl65dgxywf4vqgv765n9zj9zysyya8a54";
+      })
+    ];
+    platforms = lib.platforms.darwin;
+  }
+else
+  mkSSHFS {
+    version = "3.7.1";
     sha256 = "088mgcsqv9f2vly4xn6lvvkmqkgr9jjmjs9qp8938hl7j6rrgd17";
-  };
-
-  nativeBuildInputs = [ meson pkg-config ninja docutils makeWrapper ];
-  buildInputs = [ fuse3 glib ];
-  checkInputs = [ which python3Packages.pytest ];
-
-  NIX_CFLAGS_COMPILE = lib.optionalString
-    (stdenv.hostPlatform.system == "i686-linux")
-    "-D_FILE_OFFSET_BITS=64";
-
-  postInstall = ''
-    mkdir -p $out/sbin
-    ln -sf $out/bin/sshfs $out/sbin/mount.sshfs
-    wrapProgram $out/bin/sshfs --prefix PATH : "${openssh}/bin"
-  '';
-
-  #doCheck = true;
-  checkPhase = ''
-    # The tests need fusermount:
-    mkdir bin && cp ${fuse3}/bin/fusermount3 bin/fusermount
-    export PATH=bin:$PATH
-    # Can't access /dev/fuse within the sandbox: "FUSE kernel module does not seem to be loaded"
-    substituteInPlace test/util.py --replace "/dev/fuse" "/dev/null"
-    # TODO: "fusermount executable not setuid, and we are not root"
-    # We should probably use a VM test instead
-    python3 -m pytest test/
-  '';
-
-  meta = with lib; {
-    inherit (src.meta) homepage;
-    description = "FUSE-based filesystem that allows remote filesystems to be mounted over SSH";
-    platforms = platforms.linux;
-    license = licenses.gpl2;
-    maintainers = with maintainers; [ primeos ];
-  };
-}
+    platforms = lib.platforms.linux;
+  }
diff --git a/pkgs/tools/filesystems/sshfs-fuse/fix-fuse-darwin-h.patch b/pkgs/tools/filesystems/sshfs-fuse/fix-fuse-darwin-h.patch
new file mode 100644
index 00000000000..045cc0e61f3
--- /dev/null
+++ b/pkgs/tools/filesystems/sshfs-fuse/fix-fuse-darwin-h.patch
@@ -0,0 +1,14 @@
+diff --git a/sshfs.c b/sshfs.c
+index 97eaf06..d442577 100644
+--- a/sshfs.c
++++ b/sshfs.c
+@@ -14,9 +14,6 @@
+ #if !defined(__CYGWIN__)
+ #include <fuse_lowlevel.h>
+ #endif
+-#ifdef __APPLE__
+-#  include <fuse_darwin.h>
+-#endif
+ #include <assert.h>
+ #include <stdio.h>
+ #include <stdlib.h>
diff --git a/pkgs/tools/networking/openssh/copyid.nix b/pkgs/tools/networking/openssh/copyid.nix
new file mode 100644
index 00000000000..71baa6a23f1
--- /dev/null
+++ b/pkgs/tools/networking/openssh/copyid.nix
@@ -0,0 +1,11 @@
+{ runCommandNoCC, openssh }:
+
+runCommandNoCC "ssh-copy-id-${openssh.version}" {
+  meta = openssh.meta // {
+    description = "A tool to copy SSH public keys to a remote machine";
+    priority = (openssh.meta.priority or 0) - 1;
+  };
+} ''
+  install -Dm 755 {${openssh},$out}/bin/ssh-copy-id
+  install -Dm 644 {${openssh},$out}/share/man/man1/ssh-copy-id.1.gz
+''
diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix
index 5989f155500..720d2dcdd9e 100644
--- a/pkgs/top-level/aliases.nix
+++ b/pkgs/top-level/aliases.nix
@@ -620,7 +620,7 @@ mapAliases ({
   qt-3 = throw "qt-3 has been removed from nixpkgs, as it's unmaintained and insecure"; # added 2021-02-15
   rfkill = throw "rfkill has been removed, as it's included in util-linux"; # added 2020-08-23
   riak-cs = throw "riak-cs is not maintained anymore"; # added 2020-10-14
-  radare2-cutter = cutter;
+  radare2-cutter = cutter; # added 2021-03-30
   rkt = throw "rkt was archived by upstream"; # added 2020-05-16
   ruby_2_0_0 = throw "ruby_2_0_0 was deprecated on 2018-02-13: use a newer version of ruby";
   ruby_2_1_0 = throw "ruby_2_1_0 was deprecated on 2018-02-13: use a newer version of ruby";
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 45ca305c7d4..111346e6938 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -2252,6 +2252,14 @@ in
 
   discount = callPackage ../tools/text/discount { };
 
+  discourse = callPackage ../servers/web-apps/discourse {
+    ruby = ruby_2_7;
+  };
+
+  discourse-mail-receiver = callPackage ../servers/web-apps/discourse/mail_receiver {
+    ruby = ruby_2_7;
+  };
+
   discocss = callPackage ../tools/misc/discocss { };
 
   disfetch = callPackage ../tools/misc/disfetch { };
@@ -7109,6 +7117,8 @@ in
     etcDir = "/etc/ssh";
   };
 
+  ssh-copy-id = callPackage ../tools/networking/openssh/copyid.nix { };
+
   opensp = callPackage ../tools/text/sgml/opensp { };
 
   opentracker = callPackage ../applications/networking/p2p/opentracker { };
@@ -17631,12 +17641,8 @@ in
     stdenv = gcc6Stdenv;
   });
 
-  v8_6_x = v8;
   v8 = callPackage ../development/libraries/v8 {
     inherit (python2Packages) python;
-  } // lib.optionalAttrs stdenv.isLinux {
-    # doesn't build with gcc7
-    stdenv = gcc6Stdenv;
   };
 
   vaapiIntel = callPackage ../development/libraries/vaapi-intel { };
@@ -21997,6 +22003,10 @@ in
 
   blogc = callPackage ../applications/misc/blogc { };
 
+  blucontrol = callPackage ../applications/misc/blucontrol/wrapper.nix {
+    inherit (haskellPackages) ghcWithPackages;
+  };
+
   bluefish = callPackage ../applications/editors/bluefish {
     gtk = gtk3;
   };
@@ -22049,7 +22059,9 @@ in
 
   caerbannog = callPackage ../applications/misc/caerbannog { };
 
-  cage = callPackage ../applications/window-managers/cage { };
+  cage = callPackage ../applications/window-managers/cage {
+    wlroots = wlroots_0_12;
+  };
 
   calf = callPackage ../applications/audio/calf {
       inherit (gnome2) libglade;
@@ -23488,7 +23500,11 @@ in
 
   super-productivity = callPackage ../applications/networking/super-productivity { };
 
-  wlroots = callPackage ../development/libraries/wlroots { };
+  wlroots = callPackage ../development/libraries/wlroots {
+    inherit (xorg) xcbutilrenderutil;
+  };
+
+  wlroots_0_12 = callPackage ../development/libraries/wlroots/0.12.nix {};
 
   sway-unwrapped = callPackage ../applications/window-managers/sway { };
   sway = callPackage ../applications/window-managers/sway/wrapper.nix { };
@@ -23510,7 +23526,9 @@ in
 
   wbg = callPackage ../applications/misc/wbg { };
 
-  hikari = callPackage ../applications/window-managers/hikari { };
+  hikari = callPackage ../applications/window-managers/hikari {
+    wlroots = wlroots_0_12;
+  };
 
   i3 = callPackage ../applications/window-managers/i3 {
     xcb-util-cursor = if stdenv.isDarwin then xcb-util-cursor-HEAD else xcb-util-cursor;
@@ -23572,7 +23590,9 @@ in
 
   i3-wk-switch = callPackage ../applications/window-managers/i3/wk-switch.nix { };
 
-  waybox = callPackage ../applications/window-managers/waybox { };
+  waybox = callPackage ../applications/window-managers/waybox {
+    wlroots = wlroots_0_12;
+  };
 
   windowchef = callPackage ../applications/window-managers/windowchef/default.nix { };
 
@@ -26443,10 +26463,15 @@ in
 
   wayfireApplications = wayfireApplications-unwrapped.withPlugins (plugins: [ plugins.wf-shell ]);
   inherit (wayfireApplications) wayfire wcm;
-  wayfireApplications-unwrapped = recurseIntoAttrs (callPackage ../applications/window-managers/wayfire/applications.nix { });
-  wayfirePlugins = recurseIntoAttrs (callPackage ../applications/window-managers/wayfire/plugins.nix {
-    inherit (wayfireApplications-unwrapped) wayfire;
-  });
+  wayfireApplications-unwrapped = recurseIntoAttrs (
+    (callPackage ../applications/window-managers/wayfire/applications.nix { }).
+    extend (_: _: { wlroots = wlroots_0_12; })
+  );
+  wayfirePlugins = recurseIntoAttrs (
+    callPackage ../applications/window-managers/wayfire/plugins.nix {
+      inherit (wayfireApplications-unwrapped) wayfire;
+    }
+  );
   wf-config = callPackage ../applications/window-managers/wayfire/wf-config.nix { };
 
   waypipe = callPackage ../applications/networking/remote/waypipe { };
@@ -26488,7 +26513,9 @@ in
 
   weston = callPackage ../applications/window-managers/weston { pipewire = pipewire_0_2; };
 
-  wio = callPackage ../applications/window-managers/wio { };
+  wio = callPackage ../applications/window-managers/wio {
+    wlroots = wlroots_0_12;
+  };
 
   whitebox-tools = callPackage ../applications/gis/whitebox-tools {
     inherit (darwin.apple_sdk.frameworks) Security;
@@ -30817,7 +30844,9 @@ in
 
   bottom = callPackage ../tools/system/bottom {};
 
-  cagebreak = callPackage ../applications/window-managers/cagebreak/default.nix {};
+  cagebreak = callPackage ../applications/window-managers/cagebreak/default.nix {
+    wlroots = wlroots_0_12;
+  };
 
   psftools = callPackage ../os-specific/linux/psftools {};
 
diff --git a/pkgs/top-level/php-packages.nix b/pkgs/top-level/php-packages.nix
index 7db0eb4d6a4..3bbcb453a3c 100644
--- a/pkgs/top-level/php-packages.nix
+++ b/pkgs/top-level/php-packages.nix
@@ -146,8 +146,8 @@ lib.makeScope pkgs.newScope (self: with self; {
 
       sha256 = "103nys7zkpi1hifqp9miyl0m1mn07xqshw3sapyz365nb35g5q71";
 
-      buildInputs = [ pkgs.v8_6_x ];
-      configureFlags = [ "--with-v8=${pkgs.v8_6_x}" ];
+      buildInputs = [ pkgs.v8 ];
+      configureFlags = [ "--with-v8=${pkgs.v8}" ];
 
       meta.maintainers = lib.teams.php.members;
       meta.broken = true;
@@ -159,8 +159,8 @@ lib.makeScope pkgs.newScope (self: with self; {
 
       sha256 = "0g63dyhhicngbgqg34wl91nm3556vzdgkq19gy52gvmqj47rj6rg";
 
-      buildInputs = [ pkgs.v8_6_x ];
-      configureFlags = [ "--with-v8js=${pkgs.v8_6_x}" ];
+      buildInputs = [ pkgs.v8 ];
+      configureFlags = [ "--with-v8js=${pkgs.v8}" ];
 
       meta.maintainers = lib.teams.php.members;
       meta.broken = true;
diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix
index 96dcb55a9c1..d165dd2e1a3 100644
--- a/pkgs/top-level/python-packages.nix
+++ b/pkgs/top-level/python-packages.nix
@@ -549,6 +549,8 @@ in {
 
   asyncio-mqtt = callPackage ../development/python-modules/asyncio_mqtt { };
 
+  asyncio-nats-client = callPackage ../development/python-modules/asyncio-nats-client { };
+
   asyncio-throttle = callPackage ../development/python-modules/asyncio-throttle { };
 
   asyncpg = callPackage ../development/python-modules/asyncpg { };
@@ -4530,6 +4532,8 @@ in {
 
   nassl = callPackage ../development/python-modules/nassl { };
 
+  nats-python = callPackage ../development/python-modules/nats-python { };
+
   natsort = callPackage ../development/python-modules/natsort { };
 
   naturalsort = callPackage ../development/python-modules/naturalsort { };
@@ -7197,6 +7201,10 @@ in {
 
   recaptcha_client = callPackage ../development/python-modules/recaptcha_client { };
 
+  recoll = disabledIf (!isPy3k) (toPythonModule (pkgs.recoll.override {
+    python3Packages = self;
+  }));
+
   recommonmark = callPackage ../development/python-modules/recommonmark { };
 
   redbaron = callPackage ../development/python-modules/redbaron { };