about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests/matrix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests/matrix')
-rw-r--r--nixpkgs/nixos/tests/matrix/appservice-irc.nix225
-rw-r--r--nixpkgs/nixos/tests/matrix/conduit.nix97
-rw-r--r--nixpkgs/nixos/tests/matrix/dendrite.nix101
-rw-r--r--nixpkgs/nixos/tests/matrix/mjolnir.nix176
-rw-r--r--nixpkgs/nixos/tests/matrix/pantalaimon.nix88
-rw-r--r--nixpkgs/nixos/tests/matrix/synapse-workers.nix50
-rw-r--r--nixpkgs/nixos/tests/matrix/synapse.nix218
7 files changed, 955 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/matrix/appservice-irc.nix b/nixpkgs/nixos/tests/matrix/appservice-irc.nix
new file mode 100644
index 000000000000..78c53024ca6c
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/appservice-irc.nix
@@ -0,0 +1,225 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  let
+    homeserverUrl = "http://homeserver:8008";
+  in
+  {
+    name = "matrix-appservice-irc";
+    meta = {
+      maintainers = pkgs.matrix-appservice-irc.meta.maintainers;
+    };
+
+    nodes = {
+      homeserver = { pkgs, ... }: {
+        # We'll switch to this once the config is copied into place
+        specialisation.running.configuration = {
+          services.matrix-synapse = {
+            enable = true;
+            settings = {
+              database.name = "sqlite3";
+              app_service_config_files = [ "/registration.yml" ];
+
+              enable_registration = true;
+
+              # don't use this in production, always use some form of verification
+              enable_registration_without_verification = true;
+
+              listeners = [ {
+                # The default but tls=false
+                bind_addresses = [
+                  "0.0.0.0"
+                ];
+                port = 8008;
+                resources = [ {
+                  "compress" = true;
+                  "names" = [ "client" ];
+                } {
+                  "compress" = false;
+                  "names" = [ "federation" ];
+                } ];
+                tls = false;
+                type = "http";
+              } ];
+            };
+          };
+
+          networking.firewall.allowedTCPPorts = [ 8008 ];
+        };
+      };
+
+      ircd = { pkgs, ... }: {
+        services.ngircd = {
+          enable = true;
+          config = ''
+            [Global]
+              Name = ircd.ircd
+              Info = Server Info Text
+              AdminInfo1 = _
+
+            [Channel]
+              Name = #test
+              Topic = a cool place
+
+            [Options]
+              PAM = no
+          '';
+        };
+        networking.firewall.allowedTCPPorts = [ 6667 ];
+      };
+
+      appservice = { pkgs, ... }: {
+        services.matrix-appservice-irc = {
+          enable = true;
+          registrationUrl = "http://appservice:8009";
+
+          settings = {
+            homeserver.url = homeserverUrl;
+            homeserver.domain = "homeserver";
+
+            ircService.servers."ircd" = {
+              name = "IRCd";
+              port = 6667;
+              dynamicChannels = {
+                enabled = true;
+                aliasTemplate = "#irc_$CHANNEL";
+              };
+            };
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 8009 ];
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (pkgs.writers.writePython3Bin "do_test"
+          {
+            libraries = [ pkgs.python3Packages.matrix-nio ];
+            flakeIgnore = [
+              # We don't live in the dark ages anymore.
+              # Languages like Python that are whitespace heavy will overrun
+              # 79 characters..
+              "E501"
+            ];
+          } ''
+              import sys
+              import socket
+              import functools
+              from time import sleep
+              import asyncio
+
+              from nio import AsyncClient, RoomMessageText, JoinResponse
+
+
+              async def matrix_room_message_text_callback(matrix: AsyncClient, msg: str, _r, e):
+                  print("Received matrix text message: ", e)
+                  if msg in e.body:
+                      print("Received hi from IRC")
+                      await matrix.close()
+                      exit(0)  # Actual exit point
+
+
+              class IRC:
+                  def __init__(self):
+                      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                      sock.connect(("ircd", 6667))
+                      sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+                      sock.send(b"USER bob bob bob :bob\n")
+                      sock.send(b"NICK bob\n")
+                      self.sock = sock
+
+                  def join(self, room: str):
+                      self.sock.send(f"JOIN {room}\n".encode())
+
+                  def privmsg(self, room: str, msg: str):
+                      self.sock.send(f"PRIVMSG {room} :{msg}\n".encode())
+
+                  def expect_msg(self, body: str):
+                      buffer = ""
+                      while True:
+                          buf = self.sock.recv(1024).decode()
+                          buffer += buf
+                          if body in buffer:
+                              return
+
+
+              async def run(homeserver: str):
+                  irc = IRC()
+
+                  matrix = AsyncClient(homeserver)
+                  response = await matrix.register("alice", "foobar")
+                  print("Matrix register response: ", response)
+
+                  response = await matrix.join("#irc_#test:homeserver")
+                  print("Matrix join room response:", response)
+                  assert isinstance(response, JoinResponse)
+                  room_id = response.room_id
+
+                  irc.join("#test")
+                  # FIXME: what are we waiting on here? Matrix? IRC? Both?
+                  # 10s seem bad for busy hydra machines.
+                  sleep(10)
+
+                  # Exchange messages
+                  print("Sending text message to matrix room")
+                  response = await matrix.room_send(
+                      room_id=room_id,
+                      message_type="m.room.message",
+                      content={"msgtype": "m.text", "body": "hi from matrix"},
+                  )
+                  print("Matrix room send response: ", response)
+                  irc.privmsg("#test", "hi from irc")
+
+                  print("Waiting for the matrix message to appear on the IRC side...")
+                  irc.expect_msg("hi from matrix")
+
+                  callback = functools.partial(
+                      matrix_room_message_text_callback, matrix, "hi from irc"
+                  )
+                  matrix.add_event_callback(callback, RoomMessageText)
+
+                  print("Waiting for matrix message...")
+                  await matrix.sync_forever()
+
+                  exit(1)  # Unreachable
+
+
+              if __name__ == "__main__":
+                  asyncio.run(run(sys.argv[1]))
+            ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      import pathlib
+      import os
+
+      start_all()
+
+      ircd.wait_for_unit("ngircd.service")
+      ircd.wait_for_open_port(6667)
+
+      with subtest("start the appservice"):
+          appservice.wait_for_unit("matrix-appservice-irc.service")
+          appservice.wait_for_open_port(8009)
+
+      with subtest("copy the registration file"):
+          appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml")
+          homeserver.copy_from_host(
+              str(pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml"), "/"
+          )
+          homeserver.succeed("chmod 444 /registration.yml")
+
+      with subtest("start the homeserver"):
+          homeserver.succeed(
+              "/run/current-system/specialisation/running/bin/switch-to-configuration test >&2"
+          )
+
+          homeserver.wait_for_unit("matrix-synapse.service")
+          homeserver.wait_for_open_port(8008)
+
+      with subtest("ensure messages can be exchanged"):
+          client.succeed("do_test ${homeserverUrl} >&2")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/matrix/conduit.nix b/nixpkgs/nixos/tests/matrix/conduit.nix
new file mode 100644
index 000000000000..2b81c23598eb
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/conduit.nix
@@ -0,0 +1,97 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  let
+    name = "conduit";
+  in
+  {
+    name = "matrix-conduit";
+
+    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")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/matrix/dendrite.nix b/nixpkgs/nixos/tests/matrix/dendrite.nix
new file mode 100644
index 000000000000..82e71d912130
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/dendrite.nix
@@ -0,0 +1,101 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+    let
+      homeserverUrl = "http://homeserver:8008";
+
+      private_key = pkgs.runCommand "matrix_key.pem" {
+        buildInputs = [ pkgs.dendrite ];
+      } "generate-keys --private-key $out";
+    in
+      {
+        name = "dendrite";
+        meta = with pkgs.lib; {
+          maintainers = teams.matrix.members;
+        };
+
+        nodes = {
+          homeserver = { pkgs, ... }: {
+            services.dendrite = {
+              enable = true;
+              loadCredential = [ "test_private_key:${private_key}" ];
+              openRegistration = true;
+              settings = {
+                global.server_name = "test-dendrite-server.com";
+                global.private_key = "$CREDENTIALS_DIRECTORY/test_private_key";
+                client_api.registration_disabled = false;
+              };
+            };
+
+            networking.firewall.allowedTCPPorts = [ 8008 ];
+          };
+
+          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 dendrite
+                      client = AsyncClient("http://homeserver:8008", "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 world!"
+                          }
+                      )
+
+                      # Sync responses
+                      response = await client.sync(timeout=30000)
+
+                      # Check the message was received by dendrite
+                      last_message = response.rooms.join[room_id].timeline.events[-1].body
+                      assert last_message == "Hello world!"
+
+                      # 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 the homeserver"):
+              homeserver.wait_for_unit("dendrite.service")
+              homeserver.wait_for_open_port(8008)
+
+          with subtest("ensure messages can be exchanged"):
+              client.succeed("do_test")
+        '';
+
+      }
+)
diff --git a/nixpkgs/nixos/tests/matrix/mjolnir.nix b/nixpkgs/nixos/tests/matrix/mjolnir.nix
new file mode 100644
index 000000000000..8a888b17a3d7
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/mjolnir.nix
@@ -0,0 +1,176 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+  let
+    # Set up SSL certs for Synapse to be happy.
+    runWithOpenSSL = file: cmd: pkgs.runCommand file
+      {
+        buildInputs = [ pkgs.openssl ];
+      }
+      cmd;
+
+    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+    ca_pem = runWithOpenSSL "ca.pem" ''
+      openssl req \
+        -x509 -new -nodes -key ${ca_key} \
+        -days 10000 -out $out -subj "/CN=snakeoil-ca"
+    '';
+    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+    csr = runWithOpenSSL "matrix.csr" ''
+      openssl req \
+         -new -key ${key} \
+         -out $out -subj "/CN=localhost" \
+    '';
+    cert = runWithOpenSSL "matrix_cert.pem" ''
+      openssl x509 \
+        -req -in ${csr} \
+        -CA ${ca_pem} -CAkey ${ca_key} \
+        -CAcreateserial -out $out \
+        -days 365
+    '';
+  in
+  {
+    name = "mjolnir";
+    meta = with pkgs.lib; {
+      maintainers = teams.matrix.members;
+    };
+
+    nodes = {
+      homeserver = { pkgs, ... }: {
+        services.matrix-synapse = {
+          enable = true;
+          settings = {
+            database.name = "sqlite3";
+            tls_certificate_path = "${cert}";
+            tls_private_key_path = "${key}";
+            enable_registration = true;
+            enable_registration_without_verification = true;
+            registration_shared_secret = "supersecret-registration";
+
+            listeners = [ {
+              # The default but tls=false
+              bind_addresses = [
+                "0.0.0.0"
+              ];
+              port = 8448;
+              resources = [ {
+                compress = true;
+                names = [ "client" ];
+              } {
+                compress = false;
+                names = [ "federation" ];
+              } ];
+              tls = false;
+              type = "http";
+              x_forwarded = false;
+            } ];
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 8448 ];
+
+        environment.systemPackages = [
+          (pkgs.writeShellScriptBin "register_mjolnir_user" ''
+            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
+              -u mjolnir \
+              -p mjolnir-password \
+              --admin \
+              --shared-secret supersecret-registration \
+              http://localhost:8448
+          ''
+          )
+          (pkgs.writeShellScriptBin "register_moderator_user" ''
+            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
+              -u moderator \
+              -p moderator-password \
+              --no-admin \
+              --shared-secret supersecret-registration \
+              http://localhost:8448
+          ''
+          )
+        ];
+      };
+
+      mjolnir = { pkgs, ... }: {
+        services.mjolnir = {
+          enable = true;
+          homeserverUrl = "http://homeserver:8448";
+          pantalaimon = {
+            enable = true;
+            username = "mjolnir";
+            passwordFile = pkgs.writeText "password.txt" "mjolnir-password";
+            # otherwise mjolnir tries to connect to ::1, which is not listened by pantalaimon
+            options.listenAddress = "127.0.0.1";
+          };
+          managementRoom = "#moderators:homeserver";
+        };
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (pkgs.writers.writePython3Bin "create_management_room_and_invite_mjolnir"
+            { libraries = with pkgs.python3Packages; [
+                matrix-nio
+              ] ++ matrix-nio.optional-dependencies.e2e;
+            } ''
+            import asyncio
+
+            from nio import (
+                AsyncClient,
+                EnableEncryptionBuilder
+            )
+
+
+            async def main() -> None:
+                client = AsyncClient("http://homeserver:8448", "moderator")
+
+                await client.login("moderator-password")
+
+                room = await client.room_create(
+                    name="Moderators",
+                    alias="moderators",
+                    initial_state=[EnableEncryptionBuilder().as_dict()],
+                )
+
+                await client.join(room.room_id)
+                await client.room_invite(room.room_id, "@mjolnir:homeserver")
+
+            asyncio.run(main())
+          ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      with subtest("start homeserver"):
+        homeserver.start()
+
+        homeserver.wait_for_unit("matrix-synapse.service")
+        homeserver.wait_until_succeeds("curl --fail -L http://localhost:8448/")
+
+      with subtest("register users"):
+        # register mjolnir user
+        homeserver.succeed("register_mjolnir_user")
+        # register moderator user
+        homeserver.succeed("register_moderator_user")
+
+      with subtest("start mjolnir"):
+        mjolnir.start()
+
+        # wait for pantalaimon to be ready
+        mjolnir.wait_for_unit("pantalaimon-mjolnir.service")
+        mjolnir.wait_for_unit("mjolnir.service")
+
+        mjolnir.wait_until_succeeds("curl --fail -L http://localhost:8009/")
+
+      with subtest("ensure mjolnir can be invited to the management room"):
+        client.start()
+
+        client.wait_until_succeeds("curl --fail -L http://homeserver:8448/")
+
+        client.succeed("create_management_room_and_invite_mjolnir")
+
+        mjolnir.wait_for_console_text("Startup complete. Now monitoring rooms")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/matrix/pantalaimon.nix b/nixpkgs/nixos/tests/matrix/pantalaimon.nix
new file mode 100644
index 000000000000..b5d649e6517a
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/pantalaimon.nix
@@ -0,0 +1,88 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+  let
+    pantalaimonInstanceName = "testing";
+
+    # Set up SSL certs for Synapse to be happy.
+    runWithOpenSSL = file: cmd: pkgs.runCommand file
+      {
+        buildInputs = [ pkgs.openssl ];
+      }
+      cmd;
+
+    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+    ca_pem = runWithOpenSSL "ca.pem" ''
+      openssl req \
+        -x509 -new -nodes -key ${ca_key} \
+        -days 10000 -out $out -subj "/CN=snakeoil-ca"
+    '';
+    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+    csr = runWithOpenSSL "matrix.csr" ''
+      openssl req \
+         -new -key ${key} \
+         -out $out -subj "/CN=localhost" \
+    '';
+    cert = runWithOpenSSL "matrix_cert.pem" ''
+      openssl x509 \
+        -req -in ${csr} \
+        -CA ${ca_pem} -CAkey ${ca_key} \
+        -CAcreateserial -out $out \
+        -days 365
+    '';
+  in
+  {
+    name = "pantalaimon";
+    meta = with pkgs.lib; {
+      maintainers = teams.matrix.members;
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      services.pantalaimon-headless.instances.${pantalaimonInstanceName} = {
+        homeserver = "https://localhost:8448";
+        listenAddress = "0.0.0.0";
+        listenPort = 8888;
+        logLevel = "debug";
+        ssl = false;
+      };
+
+      services.matrix-synapse = {
+        enable = true;
+        settings = {
+          listeners = [ {
+            port = 8448;
+            bind_addresses = [
+              "127.0.0.1"
+              "::1"
+            ];
+            type = "http";
+            tls = true;
+            x_forwarded = false;
+            resources = [ {
+              names = [
+                "client"
+              ];
+              compress = true;
+            } {
+              names = [
+                "federation"
+              ];
+              compress = false;
+            } ];
+          } ];
+          database.name = "sqlite3";
+          tls_certificate_path = "${cert}";
+          tls_private_key_path = "${key}";
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("pantalaimon-${pantalaimonInstanceName}.service")
+      machine.wait_for_unit("matrix-synapse.service")
+      machine.wait_until_succeeds(
+          "curl --fail -L http://localhost:8888/"
+      )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/matrix/synapse-workers.nix b/nixpkgs/nixos/tests/matrix/synapse-workers.nix
new file mode 100644
index 000000000000..e90301aeae9e
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/synapse-workers.nix
@@ -0,0 +1,50 @@
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "matrix-synapse-workers";
+  meta = with pkgs.lib; {
+    maintainers = teams.matrix.members;
+  };
+
+  nodes = {
+    homeserver =
+      { pkgs
+      , nodes
+      , ...
+      }: {
+        services.postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "synapse-init.sql" ''
+            CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+            CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+            TEMPLATE template0
+            LC_COLLATE = "C"
+            LC_CTYPE = "C";
+          '';
+        };
+
+        services.matrix-synapse = {
+          enable = true;
+          settings = {
+            database = {
+              name = "psycopg2";
+              args.password = "synapse";
+            };
+            enable_registration = true;
+            enable_registration_without_verification = true;
+
+            federation_sender_instances = [ "federation_sender" ];
+          };
+          configureRedisLocally = true;
+          workers = {
+            "federation_sender" = { };
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    homeserver.wait_for_unit("matrix-synapse.service");
+    homeserver.wait_for_unit("matrix-synapse-worker-federation_sender.service");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/matrix/synapse.nix b/nixpkgs/nixos/tests/matrix/synapse.nix
new file mode 100644
index 000000000000..8c10a575ffbd
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/synapse.nix
@@ -0,0 +1,218 @@
+import ../make-test-python.nix ({ pkgs, ... } : let
+
+  ca_key = mailerCerts.ca.key;
+  ca_pem = mailerCerts.ca.cert;
+
+  bundle = pkgs.runCommand "bundle" {
+    nativeBuildInputs = [ pkgs.minica ];
+  } ''
+    minica -ca-cert ${ca_pem} -ca-key ${ca_key} \
+      -domains localhost
+    install -Dm444 -t $out localhost/{key,cert}.pem
+  '';
+
+  mailerCerts = import ../common/acme/server/snakeoil-certs.nix;
+  mailerDomain = mailerCerts.domain;
+  registrationSharedSecret = "unsecure123";
+  testUser = "alice";
+  testPassword = "alicealice";
+  testEmail = "alice@example.com";
+
+  listeners = [ {
+    port = 8448;
+    bind_addresses = [
+      "127.0.0.1"
+      "::1"
+    ];
+    type = "http";
+    tls = true;
+    x_forwarded = false;
+    resources = [ {
+      names = [
+        "client"
+      ];
+      compress = true;
+    } {
+      names = [
+        "federation"
+      ];
+      compress = false;
+    } ];
+  } ];
+
+in {
+
+  name = "matrix-synapse";
+  meta = with pkgs.lib; {
+    maintainers = teams.matrix.members;
+  };
+
+  nodes = {
+    # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
+    serverpostgres = { pkgs, nodes, config, ... }: let
+      mailserverIP = nodes.mailserver.config.networking.primaryIPAddress;
+    in
+    {
+      services.matrix-synapse = {
+        enable = true;
+        settings = {
+          inherit listeners;
+          database = {
+            name = "psycopg2";
+            args.password = "synapse";
+          };
+          redis = {
+            enabled = true;
+            host = "localhost";
+            port = config.services.redis.servers.matrix-synapse.port;
+          };
+          tls_certificate_path = "${bundle}/cert.pem";
+          tls_private_key_path = "${bundle}/key.pem";
+          registration_shared_secret = registrationSharedSecret;
+          public_baseurl = "https://example.com";
+          email = {
+            smtp_host = mailerDomain;
+            smtp_port = 25;
+            require_transport_security = true;
+            notif_from = "matrix <matrix@${mailerDomain}>";
+            app_name = "Matrix";
+          };
+        };
+      };
+      services.postgresql = {
+        enable = true;
+
+        # The database name and user are configured by the following options:
+        #   - services.matrix-synapse.database_name
+        #   - services.matrix-synapse.database_user
+        #
+        # The values used here represent the default values of the module.
+        initialScript = pkgs.writeText "synapse-init.sql" ''
+          CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+          CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+            TEMPLATE template0
+            LC_COLLATE = "C"
+            LC_CTYPE = "C";
+        '';
+      };
+
+      services.redis.servers.matrix-synapse = {
+        enable = true;
+        port = 6380;
+      };
+
+      networking.extraHosts = ''
+        ${mailserverIP} ${mailerDomain}
+      '';
+
+      security.pki.certificateFiles = [
+        mailerCerts.ca.cert ca_pem
+      ];
+
+      environment.systemPackages = let
+        sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
+          #!${pkgs.python3.interpreter}
+          import smtplib
+          import ssl
+
+          ctx = ssl.create_default_context()
+
+          with smtplib.SMTP('${mailerDomain}') as smtp:
+            smtp.ehlo()
+            smtp.starttls(context=ctx)
+            smtp.ehlo()
+            smtp.sendmail('matrix@${mailerDomain}', '${testEmail}', 'Subject: Test STARTTLS\n\nTest data.')
+            smtp.quit()
+         '';
+
+        obtainTokenAndRegisterEmail = let
+          # adding the email through the API is quite complicated as it involves more than one step and some
+          # client-side calculation
+          insertEmailForAlice = pkgs.writeText "alice-email.sql" ''
+            INSERT INTO user_threepids (user_id, medium, address, validated_at, added_at) VALUES ('${testUser}@serverpostgres', 'email', '${testEmail}', '1629149927271', '1629149927270');
+          '';
+        in
+        pkgs.writeScriptBin "obtain-token-and-register-email" ''
+          #!${pkgs.runtimeShell}
+          set -o errexit
+          set -o pipefail
+          set -o nounset
+          su postgres -c "psql -d matrix-synapse -f ${insertEmailForAlice}"
+          curl --fail -XPOST 'https://localhost:8448/_matrix/client/r0/account/password/email/requestToken' -d '{"email":"${testEmail}","client_secret":"foobar","send_attempt":1}' -v
+        '';
+        in [ sendTestMailStarttls pkgs.matrix-synapse obtainTokenAndRegisterEmail ];
+    };
+
+    # test mail delivery
+    mailserver = args: let
+    in
+    {
+      security.pki.certificateFiles = [
+        mailerCerts.ca.cert
+      ];
+
+      networking.firewall.enable = false;
+
+      services.postfix = {
+        enable = true;
+        hostname = "${mailerDomain}";
+        # open relay for subnet
+        networksStyle = "subnet";
+        enableSubmission = true;
+        tlsTrustedAuthorities = "${mailerCerts.ca.cert}";
+        sslCert = "${mailerCerts.${mailerDomain}.cert}";
+        sslKey = "${mailerCerts.${mailerDomain}.key}";
+
+        # blackhole transport
+        transport = "example.com discard:silently";
+
+        config = {
+          debug_peer_level = "10";
+          smtpd_relay_restrictions = [
+            "permit_mynetworks" "reject_unauth_destination"
+          ];
+
+          # disable obsolete protocols, something old versions of twisted are still using
+          smtpd_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtp_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtpd_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtp_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+        };
+      };
+    };
+
+    serversqlite = args: {
+      services.matrix-synapse = {
+        enable = true;
+        settings = {
+          inherit listeners;
+          database.name = "sqlite3";
+          tls_certificate_path = "${bundle}/cert.pem";
+          tls_private_key_path = "${bundle}/key.pem";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    mailserver.wait_for_unit("postfix.service")
+    serverpostgres.succeed("send-testmail-starttls")
+    serverpostgres.wait_for_unit("matrix-synapse.service")
+    serverpostgres.wait_until_succeeds(
+        "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
+    )
+    serverpostgres.wait_until_succeeds(
+        "journalctl -u matrix-synapse.service | grep -q 'Connected to redis'"
+    )
+    serverpostgres.require_unit_state("postgresql.service")
+    serverpostgres.succeed("REQUESTS_CA_BUNDLE=${ca_pem} register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} https://localhost:8448/")
+    serverpostgres.succeed("obtain-token-and-register-email")
+    serversqlite.wait_for_unit("matrix-synapse.service")
+    serversqlite.wait_until_succeeds(
+        "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
+    )
+    serversqlite.succeed("[ -e /var/lib/matrix-synapse/homeserver.db ]")
+  '';
+
+})