Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draupnir: init at 2.0.0 #274052

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions maintainers/maintainer-list.nix
TheArcaneBrony marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19782,6 +19782,13 @@
githubId = 76747196;
name = "Robert Rose";
};
RorySys = {
email = "[email protected]";
github = "TheArcaneBrony";
githubId = 13570458;
matrix = "@emma:rory.gay"; # preferred
name = "Rory&";
};
rosehobgoblin = {
name = "J. L. Bowden";
github = "rosehobgoblin";
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@
./services/matrix/dendrite.nix
./services/matrix/hebbot.nix
./services/matrix/hookshot.nix
./services/matrix/draupnir.nix
./services/matrix/maubot.nix
./services/matrix/mautrix-facebook.nix
./services/matrix/mautrix-meta.nix
Expand Down
86 changes: 86 additions & 0 deletions nixos/modules/services/matrix/draupnir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Draupnir (Matrix Moderation Bot) {#module-services-draupnir}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add an entry in redirects.json for each section with ID

  • module-services-draupnir
  • module-services-draupnir-setup
  • module-services-draupnir-matrix-synapse-antispam
  • module-services-draupnir-setup-ems


This chapter will show you how to set up your own, self-hosted
[Draupnir](https://github.com/the-draupnir-project/Draupnir) instance.

As an all-in-one moderation tool, it can protect your server from
malicious invites, spam messages, and whatever else you don't want.
In addition to server-level protection, Draupnir is great for communities
wanting to protect their rooms without having to use their personal
accounts for moderation.

The bot by default includes support for bans, redactions, anti-spam,
server ACLs, room directory changes, room alias transfers, account
deactivation, room shutdown, and more. (This depends on homeserver configuration and implementation.)

See the [README](https://github.com/the-draupnir-project/draupnir#readme)
page and the [Moderator's guide](https://the-draupnir-project.github.io/draupnir-documentation/moderator/setting-up-and-configuring)
for additional instructions on how to setup and use Draupnir.

For [additional settings](#opt-services.draupnir.settings)
see [the default configuration](https://github.com/the-draupnir-project/Draupnir/blob/main/config/default.yaml).

## Draupnir Setup {#module-services-draupnir-setup}

First create a new Room which will be used as a management room for Draupnir. In
this room, Draupnir will log possible errors and debugging information. You'll
need to set this Room-ID in [services.draupnir.settings.managementRoom](#opt-services.draupnir.settings.managementRoom).

Next, create a new user for Draupnir on your homeserver, if not present already.

The Draupnir Matrix user expects to be free of any rate limiting.
See [Synapse #6286](https://github.com/matrix-org/synapse/issues/6286)
for an example on how to achieve this.

If you want Draupnir to be able to deactivate users, move room aliases, shut down rooms, etc.
you'll need to make the Draupnir user a Matrix server admin.

Now invite the Draupnir user to the management room.

It is not recommended to use End to End Encryption when not needed,
as it is known to break parts of Draupnir.

To enable the Pantalaimon E2EE Proxy for Draupnir, enable
[services.draupnir.pantalaimon](#opt-services.draupnir.pantalaimon.enable). This will
autoconfigure a new Pantalaimon instance, which will connect to the homeserver
set in [services.draupnir.homeserverUrl](#opt-services.draupnir.homeserverUrl) and Draupnir itself
will be configured to connect to the new Pantalaimon instance.

```
{
services.draupnir = {
enable = true;

# Point this to your reverse proxy, if eg. Synapse's workers are in use!
homeserverUrl = "http://localhost:8008";

settings = {
managementRoom = "!yyy:domain.tld";
};
};
}
```

Additional config for Pantalaimon:
```
pantalaimon = {
enable = true;
username = "draupnir";
passwordFile = "/run/secrets/draupnir-password";
options = {
ssl = false;
};
};
```

### Element Matrix Services (EMS) {#module-services-draupnir-setup-ems}

If you are using a managed ["Element Matrix Services (EMS)"](https://ems.element.io/)
server, you will need to consent to the terms and conditions. Upon startup, an error
log entry with a URL to the consent page will be generated.

## Synapse Antispam Module {#module-services-draupnir-matrix-synapse-antispam}

Use the Mjolnir Antispam module, Draupnir made no changes here and as such was not packaged.
It may be possible that the Mjolir Antispam module does *not* work with Draupnir in the future,
nor is the one in the Draupnir repository maintained or tested.
272 changes: 272 additions & 0 deletions nixos/modules/services/matrix/draupnir.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
{
config,
lib,
pkgs,
utils,
...
}:

let
cfg = config.services.draupnir;

format = pkgs.formats.yaml { };
configFile = format.generate "draupnir.yaml" (
lib.filterAttrsRecursive (_: value: value != null) cfg.settings
);
in
{
#region Options
options.services.draupnir = {
enable = lib.mkEnableOption "Draupnir, a moderation bot for Matrix";

package = lib.mkPackageOption pkgs "draupnir" { };

accessTokenFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
File containing the access token for Draupnir's Matrix account.
Make sure this does not contain newlines if writing manually: `:set noeol nofixeol` for vim or -L for nano.
'';
};

homeserverUrl = lib.mkOption {
type = lib.types.str;
description = ''
Base URL of the Matrix homeserver, that provides the Client-Server API.
Will be used by either Draupnir directly, or by Pantalaimon, if enabled.
'';
};

#region Pantalaimon options
pantalaimon = lib.mkOption {
description = ''
`pantalaimon` options (enables E2E Encryption support).

This will create a `pantalaimon` instance with the name "draupnir".
'';
default = { };
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption (''
pantalaimon, in order to enable E2EE support.
If `true`, accessToken is ignored and the username/password below will be
used instead. The access token of the bot will be stored in /var/lib/draupnir.
'');
Comment on lines +51 to +55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
enable = lib.mkEnableOption (''
pantalaimon, in order to enable E2EE support.
If `true`, accessToken is ignored and the username/password below will be
used instead. The access token of the bot will be stored in /var/lib/draupnir.
'');
enable = lib.mkEnableOption '''
pantalaimon, in order to enable E2EE support.
If `true`, accessToken is ignored and the username/password below will be
used instead. The access token of the bot will be stored in /var/lib/draupnir.
'';


username = lib.mkOption {
type = lib.types.str;
description = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest adding a default here:

default = "draupnir";

Account name on the Matrix homeserver.
'';
};

passwordFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
File containing the password for the Matrix account.
Make sure this does not contain newlines if writing manually: `:set noeol nofixeol` for vim or -L for nano.
'';
};

options = lib.mkOption {
type = lib.types.submodule (
import ./pantalaimon-options.nix {
inherit lib;
inherit config;
name = "draupnir";
}
);
default = { };
description = ''
Pass through additional options to the `pantalaimon` service.
'';
};
};
};
};
#endregion

#region Draupnir settings
settings = lib.mkOption {
example = lib.literalExpression ''
{
autojoinOnlyIfManager = true;
automaticallyRedactForReasons = [ "spam" "advertising" ];
}
'';
description = ''
Draupnir settings (see [Draupnir's default configuration](https://github.com/the-draupnir-project/Draupnir/blob/main/config/default.yaml) for available settings).
'';
default = { };
type = lib.types.submodule {
freeformType = format.type;
options = {
#region Readonly settings - these settings are not configurable
dataPath = lib.mkOption {
type = lib.types.str;
default = "/var/lib/draupnir";
readOnly = true;
description = ''
The path where Draupnir stores its data.
mweinelt marked this conversation as resolved.
Show resolved Hide resolved

::: {.note}
If you want to customize where this data is stored, use a bind mount.
:::
'';
};
#endregion

#region Base settings
managementRoom = lib.mkOption {
type = lib.types.str;
example = "#moderators:example.org";
description = ''
The room ID or alias where moderators can use the bot's functionality.

The bot has no access controls, so anyone in this room can use the bot - secure this room!

Warning: When using a room alias, make sure the alias used is on the local homeserver!
This prevents an issue where the control room becomes undefined when the alias can't be resolved.
'';
};
#endregion
};
};
};
#endregion
};
#endregion

#region Service configuration
config = lib.mkIf cfg.enable {
assertions = [
# pantalaimon enabled - use passwordFile instead of accessTokenFile
{
assertion = cfg.pantalaimon.enable -> cfg.pantalaimon.passwordFile != null;
message = "Set services.draupnir.pantailaimon.passwordFile, as it is required in order to use Pantalaimon.";
}
{
assertion = cfg.pantalaimon.enable -> cfg.accessTokenFile == null;
message = "Unset services.draupnir.accessTokenFile, as it has no effect when Pantalaimon is enabled.";
}

# pantalaimon disabled - use accessTokenFile instead of passwordFile
{
assertion = !cfg.pantalaimon.enable -> cfg.accessTokenFile != null;
message = "Set services.draupnir.accessTokenFile, as it is required in order to use Draupnir without Pantalaimon.";
}
{
assertion = !cfg.pantalaimon.enable -> cfg.pantalaimon.passwordFile == null;
message = "Unset services.draupnir.pantalaimon.passwordFile, as it has no effect when Pantalaimon is disabled.";
}
# Removed options for those migrating from the Mjolnir module - mkRemovedOption module does *not* work with submodules.

# Noop in v2, but should ideally not be used in mjolnir or 1.x either.
{
assertion = (cfg.settings ? protectedRooms) == false;
message = "Unset services.draupnir.settings.protectedRooms, as it is unsupported on Draupnir. Add these rooms via `!draupnir rooms add` instead.";
}
];

warnings =
[ ]
# Unsupported but available options
# - Crypto
++ lib.optionals (cfg.pantalaimon.enable) [
''
Using Draupnir with Pantalaimon is known to break some features, and is thus unsupported.
Encryption support should only be enabled if you require an encrypted management room or use Draupnir in encrypted rooms.''
]
++ lib.optionals (cfg.settings ? experimentalRustCrypto && cfg.settings.experimentalRustCrypto) [
''
Using Draupnir with experimental Rust Crypto support is untested and unsupported.
Encryption support should only be enabled if you require an encrypted management room or use Draupnir in encrypted rooms.''
]

# - Deprecated options
++ lib.optionals (cfg.settings ? verboseLogging && cfg.settings.verboseLogging) [
"Verbose logging in Draupnir is deprecated and may be removed in a future version."
];

services.pantalaimon-headless.instances.draupnir = lib.mkIf cfg.pantalaimon.enable (
cfg.pantalaimon.options // { homeserver = cfg.homeserverUrl; }
);
services.draupnir.settings.homeserverUrl =
if cfg.pantalaimon.enable then
(
with config.services.pantalaimon-headless.instances.draupnir;
"http://${listenAddress}:${toString listenPort}/"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does pantalaimon only listen on HTTP?

)
else
cfg.homeserverUrl;
services.draupnir.settings.pantalaimon = lib.mkIf cfg.pantalaimon.enable ({
use = true;
username = cfg.pantalaimon.username;
});

systemd.services.draupnir = {
description = "Draupnir - a moderation bot for Matrix";
requires = lib.optionals (cfg.pantalaimon.enable) [
"pantalaimon-draupnir.service"
];
wants = [
"network-online.target"
"matrix-synapse.service"
"conduit.service"
"dendrite.service"
];
after = [
"network-online.target"
"matrix-synapse.service"
"conduit.service"
"dendrite.service"
];
wantedBy = [ "multi-user.target" ];

serviceConfig = {
ExecStart = utils.escapeSystemdExecArgs (
[
(lib.getExe cfg.package)
"--draupnir-config"
configFile
]
++ lib.optionals (cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile != null) [
"--pantalaimon-password-path"
"/run/credentials/draupnir.service/pantalaimon_password"
]
++ lib.optionals (!cfg.pantalaimon.enable && cfg.accessTokenFile != null) [
"--access-token-path"
"/run/credentials/draupnir.service/access_token"
]
);

WorkingDirectory = "/var/lib/draupnir";
StateDirectory = "draupnir";
StateDirectoryMode = "0700";
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
NoNewPrivileges = true;
PrivateDevices = true;
Restart = "on-failure";

DynamicUser = true;
LoadCredential =
lib.optionals (cfg.accessTokenFile != null) [
"access_token:${cfg.accessTokenFile}"
]
++ lib.optionals (cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile != null) [
"pantalaimon_password:${cfg.pantalaimon.passwordFile}"
];
};
};
};
#endregion

meta = {
doc = ./draupnir.md;
maintainers = with lib.maintainers; [ RorySys ];
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ in {
domination = handleTest ./domination.nix {};
dovecot = handleTest ./dovecot.nix {};
drawterm = discoverTests (import ./drawterm.nix);
draupnir = handleTest ./matrix/draupnir.nix {};
drbd = handleTest ./drbd.nix {};
druid = handleTestOn [ "x86_64-linux" ] ./druid {};
drbd-driver = handleTest ./drbd-driver.nix {};
Expand Down
Loading
Loading