From 9c7829780dbe72295ce0bef319f00f13146046bb Mon Sep 17 00:00:00 2001 From: Niklas Korz Date: Sun, 17 Nov 2024 19:53:48 +0100 Subject: [PATCH] nixos/conduwuit: init --- nixos/modules/module-list.nix | 1 + nixos/modules/services/matrix/conduwuit.nix | 236 ++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/matrix/conduwuit.nix | 102 +++++++++ pkgs/by-name/co/conduwuit/package.nix | 10 +- 5 files changed, 347 insertions(+), 3 deletions(-) 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 67545614bdaa13..5bbc5da03b56ba 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -704,6 +704,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 00000000000000..a376883665c331 --- /dev/null +++ b/nixos/modules/services/matrix/conduwuit.nix @@ -0,0 +1,236 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.conduwuit; + + format = pkgs.formats.toml { }; + # TOML does not allow null values, so we just omit those fields + filteredSettings = pkgs.lib.converge (pkgs.lib.filterAttrsRecursive (_: v: v != null)) cfg.settings; + configFile = format.generate "conduwuit.toml" filteredSettings; +in +{ + meta.maintainers = with lib.maintainers; [ niklaskorz ]; + 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.address = lib.mkOption { + type = lib.types.nullOr ( + lib.types.oneOf [ + lib.types.str + (lib.types.listOf lib.types.str) + ] + ); + default = null; + description = '' + Address (IPv4 or IPv6) to listen on for connections by the reverse proxy/tls terminator. + If set to `null`, conduwuit will listen on IPv4 and IPv6 localhost. + Must be `null` if `unix_socket_path` is set. + + To listen on multiple addresses, specify a list. For example: + `[ "127.0.0.1" "::1" ]` + ''; + }; + global.port = lib.mkOption { + type = lib.types.oneOf [ + lib.types.port + (lib.types.listOf lib.types.port) + ]; + default = 6167; + description = "The port(s) 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.unix_socket_path = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Listen on a UNIX socket at the specified path. If listening on a UNIX socket, + listening on an address will be disabled. The `address` option must be set to + `null` (the default value). You must add your reverse proxy to the `conduwuit` + group, unless RW permissions are specified with `unix_socket_perms` (666 minimum). + ''; + }; + global.unix_socket_perms = lib.mkOption { + type = lib.types.ints.positive; + default = 660; + description = "The default permissions (in octal) to create the UNIX socket with."; + }; + 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 listed here will be used to gather public keys of other servers + (notary trusted key servers). + + Currently, conduwuit doesn't support inbound batched key requests, so + this list should only contain other Synapse servers. + + Example: `[ "matrix.org" "constellatory.net" "tchncs.de" ]` + ''; + }; + 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.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 { + assertions = [ + { + assertion = cfg.settings.global.unix_socket_path == null || cfg.settings.global.address == null; + message = '' + In `services.conduwuit.settings.global`, `unix_socket_path` and `address` cannot be set at the + same time. + You can either listen on an IP address or a UNIX socket, but not both. + Leave one of the two options unset or explicitly set them to `null`. + ''; + } + ]; + + 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 + ]); + startLimitBurst = 5; + startLimitIntervalSec = 60; + serviceConfig = { + DynamicUser = true; + User = "conduwuit"; + Group = "conduwuit"; + + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + PrivateIPC = true; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "@resources" + "~@clock" + "@debug" + "@module" + "@mount" + "@reboot" + "@swap" + "@cpu-emulation" + "@obsolete" + "@timer" + "@chown" + "@setuid" + "@privileged" + "@keyring" + "@ipc" + ]; + SystemCallErrorNumber = "EPERM"; + + StateDirectory = "conduwuit"; + StateDirectoryMode = "0700"; + RuntimeDirectory = "conduwuit"; + RuntimeDirectoryMode = "0750"; + + ExecStart = lib.getExe cfg.package; + Restart = "on-failure"; + RestartSec = 10; + TimeoutStopSec = "4m"; + TimeoutStartSec = "4m"; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 042010fe6972bb..038bcd52096236 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 00000000000000..4d4f16ece53a83 --- /dev/null +++ b/nixos/tests/matrix/conduwuit.nix @@ -0,0 +1,102 @@ +import ../make-test-python.nix ( + { pkgs, lib, ... }: + 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") + ''; + + meta.maintainers = with lib.maintainers; [ + niklaskorz + ]; + } +) diff --git a/pkgs/by-name/co/conduwuit/package.nix b/pkgs/by-name/co/conduwuit/package.nix index f0ff83fe73b613..1b398072d821f8 100644 --- a/pkgs/by-name/co/conduwuit/package.nix +++ b/pkgs/by-name/co/conduwuit/package.nix @@ -17,6 +17,7 @@ rust-jemalloc-sys, enableLiburing ? stdenv.hostPlatform.isLinux, liburing, + nixosTests, }: let rust-jemalloc-sys' = rust-jemalloc-sys.override { @@ -92,9 +93,12 @@ rustPlatform.buildRustPackage rec { passthru = { updateScript = nix-update-script { }; - tests.version = testers.testVersion { - package = conduwuit; - version = "${version}"; + tests = { + inherit (nixosTests) conduwuit; + version = testers.testVersion { + package = conduwuit; + version = "${version}"; + }; }; };