From 2aeea08ccb44ba8733c8c00c02ad31857e0287ed Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 2 Sep 2021 11:55:51 +0200 Subject: [PATCH] nixos/conduit: init --- .../from_md/release-notes/rl-2111.section.xml | 8 + .../manual/release-notes/rl-2111.section.md | 2 + nixos/modules/module-list.nix | 1 + .../modules/services/misc/matrix-conduit.nix | 194 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/matrix-conduit.nix | 92 +++++++++ 6 files changed, 298 insertions(+) create mode 100644 nixos/modules/services/misc/matrix-conduit.nix create mode 100644 nixos/tests/matrix-conduit.nix diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index 4bbd4642852..8731683feb9 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -357,6 +357,14 @@ services.multipath. + + + matrix-conduit, + a simple, fast and reliable chat server powered by matrix. + Available as + services.matrix-conduit. + +
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 36d03fd0b59..d101445026e 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -110,6 +110,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [multipath](https://github.com/opensvc/multipath-tools), the device mapper multipath (DM-MP) daemon. Available as [services.multipath](#opt-services.multipath.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). + ## Backward Incompatibilities {#sec-release-21.11-incompatibilities} - The `services.wakeonlan` option was removed, and replaced with `networking.interfaces..wakeOnLan`. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index df35a57d047..1f855059c81 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -543,6 +543,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 00000000000..cabe84ca1ca --- /dev/null +++ b/nixos/modules/services/misc/matrix-conduit.nix @@ -0,0 +1,194 @@ +{ 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"; }; + }; + + nginx.enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to enable a nginx vhost that will listen on all interfaces on tcp/443 + for https connections and proxy them to conduit. Further nginx configuration + can be done by adapting . + When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable + this, set the to + false and if appropriate do the same for + . + ''; + }; + 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 = false; + description = "Whether new encrypted rooms can be created. Note: existing rooms will continue to work."; + }; + global.allow_federation = mkOption { + type = types.bool; + default = false; + 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 { + assertions = [ + { + assertion = !(hasInfix ":" cfg.settings.global.server_name && cfg.nginx.enable); + message = '' + It appears you are trying to specify a port in the server_name variable. + This is not supported by the conduit nginx module. Please define your own vhost. + ''; + } + ]; + + 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; + }; + }; + services.nginx = mkIf cfg.nginx.enable { + enable = mkDefault true; + virtualHosts.${cfg.settings.global.server_name} = { + enableACME = mkDefault true; + forceSSL = mkDefault true; + locations."= /.well-known/matrix/server".extraConfig = + let + server = { "m.server" = "${cfg.settings.global.server_name}:443"; }; + in if cfg.settings.global.allow_federation then '' + add_header Content-Type application/json; + return 200 '${builtins.toJSON server}'; + '' + else + '' + deny all; + ''; + + locations."/".extraConfig = "deny all;"; + locations."/_matrix" = let + rawUpstream = cfg.settings.global.address; + isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2; + upstream = if (isIPv6 rawUpstream) then "[${rawUpstream}]" else rawUpstream; + in { + proxyPass = "http://${upstream}:${(toString cfg.settings.global.port)}"; + extraConfig = "client_max_body_size ${(toString cfg.settings.global.max_request_size)};"; + }; + }; + }; + }; + } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 12b67008291..b28147cffee 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -248,6 +248,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 {}; mediawiki = handleTest ./mediawiki.nix {}; meilisearch = handleTest ./meilisearch.nix {}; diff --git a/nixos/tests/matrix-conduit.nix b/nixos/tests/matrix-conduit.nix new file mode 100644 index 00000000000..b028fc63c35 --- /dev/null +++ b/nixos/tests/matrix-conduit.nix @@ -0,0 +1,92 @@ +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; + nginx.enable = true; + extraEnvironment.RUST_BACKTRACE = "yes"; + }; + services.nginx.virtualHosts."${name}" = { + enableACME = false; + forceSSL = false; + enableSSL = false; + }; + 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") + ''; +})