nixos/conduit: init

conduit-nginx
Philipp 2021-09-02 11:55:51 +02:00
parent ebce1ba5ac
commit 2aeea08ccb
No known key found for this signature in database
GPG Key ID: 2258EE3B85DE1748
6 changed files with 298 additions and 0 deletions

View File

@ -357,6 +357,14 @@
<link linkend="opt-services.multipath.enable">services.multipath</link>.
</para>
</listitem>
<listitem>
<para>
<link xlink:href="https://conduit.rs/">matrix-conduit</link>,
a simple, fast and reliable chat server powered by matrix.
Available as
<link xlink:href="option.html#opt-services.matrix-conduit.enable">services.matrix-conduit</link>.
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="sec-release-21.11-incompatibilities">

View File

@ -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.<name>.wakeOnLan`.

View File

@ -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

View File

@ -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 <option>services.nginx.virtualHosts.&lt;settings.global.server_name&gt;</option>.
When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable
this, set the <option>services.nginx.virtualHosts.&lt;settings.global.server_name&gt;.enableACME</option> to
<literal>false</literal> and if appropriate do the same for
<option>services.nginx.virtualHosts.&lt;settings.global.server_name&gt;.forceSSL</option>.
'';
};
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
<link xlink:href="https://gitlab.com/famedly/conduit/-/blob/master/conduit-example.toml"/>
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)};";
};
};
};
};
}

View File

@ -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 {};

View File

@ -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")
'';
})