From d6ec16257895796f7f2ac0c209ece95c35a803fd Mon Sep 17 00:00:00 2001 From: Niklas Korz Date: Mon, 4 Nov 2024 15:17:35 +0100 Subject: [PATCH] nixos/conduwuit: init --- nixos/modules/module-list.nix | 1 + nixos/modules/services/matrix/conduwuit.nix | 155 ++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/matrix/conduwuit.nix | 97 ++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 nixos/modules/services/matrix/conduwuit.nix create mode 100644 nixos/tests/matrix/conduwuit.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 7b753937e8082..479a574f805b1 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -702,6 +702,7 @@ ./services/matrix/appservice-discord.nix ./services/matrix/appservice-irc.nix ./services/matrix/conduit.nix + ./services/matrix/conduwuit.nix ./services/matrix/dendrite.nix ./services/matrix/hebbot.nix ./services/matrix/hookshot.nix diff --git a/nixos/modules/services/matrix/conduwuit.nix b/nixos/modules/services/matrix/conduwuit.nix new file mode 100644 index 0000000000000..e72a5a32299e3 --- /dev/null +++ b/nixos/modules/services/matrix/conduwuit.nix @@ -0,0 +1,155 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.conduwuit; + + format = pkgs.formats.toml {}; + configFile = format.generate "conduwuit.toml" cfg.settings; +in + { + meta.maintainers = with lib.maintainers; [ pstn ]; + options.services.conduwuit = { + enable = lib.mkEnableOption "conduwuit"; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + description = "Extra Environment variables to pass to the conduwuit server."; + default = {}; + example = { RUST_BACKTRACE="yes"; }; + }; + + package = lib.mkPackageOption pkgs "conduwuit" { }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = format.type; + options = { + global.server_name = lib.mkOption { + type = lib.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 = lib.mkOption { + type = lib.types.port; + default = 6167; + description = "The port conduwuit 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 conduwuit instance running on this port"; + }; + global.max_request_size = lib.mkOption { + type = lib.types.ints.positive; + default = 20000000; + description = "Max request size in bytes. Don't forget to also change it in the proxy."; + }; + global.allow_registration = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether new users can register on this server."; + }; + global.allow_encryption = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether new encrypted rooms can be created. Note: existing rooms will continue to work."; + }; + global.allow_federation = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether this server federates with other servers. + ''; + }; + global.trusted_servers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "matrix.org" ]; + description = "Servers trusted with signing server keys."; + }; + global.address = lib.mkOption { + type = lib.types.str; + default = "::1"; + description = "Address to listen on for connections by the reverse proxy/tls terminator."; + }; + global.database_path = lib.mkOption { + type = lib.types.str; + default = "/var/lib/conduwuit/"; + readOnly = true; + description = '' + Path to the conduwuit database, the directory where conduwuit 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. + ''; + }; + global.database_backend = lib.mkOption { + type = lib.types.enum [ "rocksdb" ]; + default = "rocksdb"; + example = "rocksdb"; + description = '' + The database backend for the service. Switching it on an existing + instance will require manual migration of data. + + Only rocksdb is supported. + ''; + }; + global.allow_check_for_updates = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + If enabled, conduwuit will send a simple GET request periodically to + for any new announcements made. + Despite the name, this is not an update check endpoint, it is simply an announcement check endpoint. + + Disabled by default. + ''; + }; + }; + }; + default = {}; + description = '' + Generates the conduwuit.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 = lib.mkIf cfg.enable { + systemd.services.conduwuit = { + description = "Conduwuit Matrix Server"; + documentation = [ "https://conduwuit.puppyirl.gay/" ]; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + environment = lib.mkMerge ([ + { CONDUWUIT_CONFIG = configFile; } + cfg.extraEnvironment + ]); + serviceConfig = { + DynamicUser = true; + User = "conduwuit"; + 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 = "conduwuit"; + StateDirectoryMode = "0700"; + ExecStart = lib.getExe cfg.package; + Restart = "on-failure"; + RestartSec = 10; + StartLimitBurst = 5; + UMask = "077"; + }; + }; + }; + } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 478a9964f46a0..f6c55cccb67b2 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -214,6 +214,7 @@ in { coder = handleTest ./coder.nix {}; collectd = handleTest ./collectd.nix {}; commafeed = handleTest ./commafeed.nix {}; + conduwuit = handleTest ./matrix/conduwuit.nix {}; connman = handleTest ./connman.nix {}; consul = handleTest ./consul.nix {}; consul-template = handleTest ./consul-template.nix {}; diff --git a/nixos/tests/matrix/conduwuit.nix b/nixos/tests/matrix/conduwuit.nix new file mode 100644 index 0000000000000..a4b28caa9bc45 --- /dev/null +++ b/nixos/tests/matrix/conduwuit.nix @@ -0,0 +1,97 @@ +import ../make-test-python.nix ({ pkgs, ... }: + let + name = "conduwuit"; + in + { + name = "conduwuit"; + + nodes = { + conduwuit = args: { + services.conduwuit = { + 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 conduwuit + client = AsyncClient("http://conduwuit: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 conduwuit!" + } + ) + + # Sync responses + response = await client.sync(timeout=30000) + + # Check the message was received by conduwuit + last_message = response.rooms.join[room_id].timeline.events[-1].body + assert last_message == "Hello conduwuit!" + + # 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 conduwuit"): + conduwuit.wait_for_unit("conduwuit.service") + conduwuit.wait_for_open_port(80) + + with subtest("ensure messages can be exchanged"): + client.succeed("do_test") + ''; + })