diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 4a2e86a60b92..bfabda4cb4da 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -59,6 +59,14 @@
virtualisation.docker.rootless.enable.
+
+
+ matrix-conduit,
+ a simple, fast and reliable chat server powered by matrix.
+ Available as
+ services.matrix-conduit.
+
+
filebeat,
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index 2a062d0573b5..05f1a26a0b6d 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -21,6 +21,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable).
- [rootless Docker](https://docs.docker.com/engine/security/rootless/), a `systemd --user` Docker service which runs without root permissions. Available as [virtualisation.docker.rootless.enable](options.html#opt-virtualisation.docker.rootless.enable).
+- [matrix-conduit](https://conduit.rs/), a simple, fast and reliable chat server powered by matrix. Available as [services.matrix-conduit](option.html#opt-services.matrix-conduit.enable).
+
- [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html), a lightweight shipper for forwarding and centralizing log data. Available as [services.filebeat](#opt-services.filebeat.enable).
- [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](options.html#opt-services.powerdns-admin.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 5dd98644ac85..73a61c4ca0e7 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -555,6 +555,7 @@
./services/misc/mame.nix
./services/misc/matrix-appservice-discord.nix
./services/misc/matrix-appservice-irc.nix
+ ./services/misc/matrix-conduit.nix
./services/misc/matrix-synapse.nix
./services/misc/mautrix-facebook.nix
./services/misc/mautrix-telegram.nix
diff --git a/nixos/modules/services/misc/matrix-conduit.nix b/nixos/modules/services/misc/matrix-conduit.nix
new file mode 100644
index 000000000000..d6cd575ee9a3
--- /dev/null
+++ b/nixos/modules/services/misc/matrix-conduit.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.matrix-conduit;
+
+ format = pkgs.formats.toml {};
+ configFile = format.generate "conduit.toml" cfg.settings;
+in
+ {
+ meta.maintainers = with maintainers; [ pstn piegames ];
+ options.services.matrix-conduit = {
+ enable = mkEnableOption "matrix-conduit";
+
+ extraEnvironment = mkOption {
+ type = types.attrsOf types.str;
+ description = "Extra Environment variables to pass to the conduit server.";
+ default = {};
+ example = { RUST_BACKTRACE="yes"; };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.matrix-conduit;
+ defaultText = "pkgs.matrix-conduit";
+ example = "pkgs.matrix-conduit";
+ description = ''
+ Package of the conduit matrix server to use.
+ '';
+ };
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = format.type;
+ options = {
+ global.server_name = mkOption {
+ type = types.str;
+ example = "example.com";
+ description = "The server_name is the name of this server. It is used as a suffix for user # and room ids.";
+ };
+ global.port = mkOption {
+ type = types.port;
+ default = 6167;
+ description = "The port Conduit will be running on. You need to set up a reverse proxy in your web server (e.g. apache or nginx), so all requests to /_matrix on port 443 and 8448 will be forwarded to the Conduit instance running on this port";
+ };
+ global.max_request_size = mkOption {
+ type = types.ints.positive;
+ default = 20000000;
+ description = "Max request size in bytes. Don't forget to also change it in the proxy.";
+ };
+ global.allow_registration = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether new users can register on this server.";
+ };
+ global.allow_encryption = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether new encrypted rooms can be created. Note: existing rooms will continue to work.";
+ };
+ global.allow_federation = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether this server federates with other servers.
+ '';
+ };
+ global.trusted_servers = mkOption {
+ type = types.listOf types.str;
+ default = [ "matrix.org" ];
+ description = "Servers trusted with signing server keys.";
+ };
+ global.address = mkOption {
+ type = types.str;
+ default = "::1";
+ description = "Address to listen on for connections by the reverse proxy/tls terminator.";
+ };
+ global.database_path = mkOption {
+ type = types.str;
+ default = "/var/lib/matrix-conduit/";
+ readOnly = true;
+ description = ''
+ Path to the conduit database, the directory where conduit will save its data.
+ Note that due to using the DynamicUser feature of systemd, this value should not be changed
+ and is set to be read only.
+ '';
+ };
+ };
+ };
+ default = {};
+ description = ''
+ Generates the conduit.toml configuration file. Refer to
+
+ for details on supported values.
+ Note that database_path can not be edited because the service's reliance on systemd StateDir.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.conduit = {
+ description = "Conduit Matrix Server";
+ documentation = [ "https://gitlab.com/famedly/conduit/" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = lib.mkMerge ([
+ { CONDUIT_CONFIG = configFile; }
+ cfg.extraEnvironment
+ ]);
+ serviceConfig = {
+ DynamicUser = true;
+ User = "conduit";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ PrivateDevices = true;
+ PrivateMounts = true;
+ PrivateUsers = true;
+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ SystemCallArchitectures = "native";
+ SystemCallFilter = [
+ "@system-service"
+ "~@privileged"
+ ];
+ StateDirectory = "matrix-conduit";
+ ExecStart = "${cfg.package}/bin/conduit";
+ Restart = "on-failure";
+ RestartSec = 10;
+ StartLimitBurst = 5;
+ };
+ };
+ };
+ }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 9f3e97ceb13d..4f62980e8e91 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -260,6 +260,7 @@ in
mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {};
matomo = handleTest ./matomo.nix {};
matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {};
+ matrix-conduit = handleTest ./matrix-conduit.nix {};
matrix-synapse = handleTest ./matrix-synapse.nix {};
mattermost = handleTest ./mattermost.nix {};
mediawiki = handleTest ./mediawiki.nix {};
diff --git a/nixos/tests/matrix-conduit.nix b/nixos/tests/matrix-conduit.nix
new file mode 100644
index 000000000000..d159fbaa4800
--- /dev/null
+++ b/nixos/tests/matrix-conduit.nix
@@ -0,0 +1,95 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+ let
+ name = "conduit";
+ in
+ {
+ nodes = {
+ conduit = args: {
+ services.matrix-conduit = {
+ enable = true;
+ settings.global.server_name = name;
+ settings.global.allow_registration = true;
+ extraEnvironment.RUST_BACKTRACE = "yes";
+ };
+ services.nginx = {
+ enable = true;
+ virtualHosts.${name} = {
+ enableACME = false;
+ forceSSL = false;
+ enableSSL = false;
+
+ locations."/_matrix" = {
+ proxyPass = "http://[::1]:6167";
+ };
+ };
+ };
+ networking.firewall.allowedTCPPorts = [ 80 ];
+ };
+ client = { pkgs, ... }: {
+ environment.systemPackages = [
+ (
+ pkgs.writers.writePython3Bin "do_test"
+ { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
+ import asyncio
+
+ from nio import AsyncClient
+
+
+ async def main() -> None:
+ # Connect to conduit
+ client = AsyncClient("http://conduit:80", "alice")
+
+ # Register as user alice
+ response = await client.register("alice", "my-secret-password")
+
+ # Log in as user alice
+ response = await client.login("my-secret-password")
+
+ # Create a new room
+ response = await client.room_create(federate=False)
+ room_id = response.room_id
+
+ # Join the room
+ response = await client.join(room_id)
+
+ # Send a message to the room
+ response = await client.room_send(
+ room_id=room_id,
+ message_type="m.room.message",
+ content={
+ "msgtype": "m.text",
+ "body": "Hello conduit!"
+ }
+ )
+
+ # Sync responses
+ response = await client.sync(timeout=30000)
+
+ # Check the message was received by conduit
+ last_message = response.rooms.join[room_id].timeline.events[-1].body
+ assert last_message == "Hello conduit!"
+
+ # Leave the room
+ response = await client.room_leave(room_id)
+
+ # Close the client
+ await client.close()
+
+ asyncio.get_event_loop().run_until_complete(main())
+ ''
+ )
+ ];
+ };
+ };
+
+ testScript = ''
+ start_all()
+
+ with subtest("start conduit"):
+ conduit.wait_for_unit("conduit.service")
+ conduit.wait_for_open_port(80)
+
+ with subtest("ensure messages can be exchanged"):
+ client.succeed("do_test")
+ '';
+ })