From 4b153aad5d363b9d88ca4b47b4628f8032d05863 Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 25 Apr 2024 12:54:37 +0000 Subject: [PATCH] nixos/draupnir: init Co-authored-by: emilylange Co-authored-by: Martin Weinelt Co-authored-by: teutat3s <10206665+teutat3s@users.noreply.github.com> --- nixos/doc/manual/redirects.json | 9 + .../manual/release-notes/rl-2511.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/matrix/draupnir.md | 62 +++++ nixos/modules/services/matrix/draupnir.nix | 257 ++++++++++++++++++ 5 files changed, 331 insertions(+) create mode 100644 nixos/modules/services/matrix/draupnir.md create mode 100644 nixos/modules/services/matrix/draupnir.nix diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index bde64c2c14e8..625dd1516cd5 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -749,6 +749,15 @@ "module-services-davis-basic-usage": [ "index.html#module-services-davis-basic-usage" ], + "module-services-draupnir": [ + "index.html#module-services-draupnir" + ], + "module-services-draupnir-setup": [ + "index.html#module-services-draupnir-setup" + ], + "module-services-draupnir-setup-ems": [ + "index.html#module-services-draupnir-setup-ems" + ], "module-services-castopod": [ "index.html#module-services-castopod" ], diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index 4c44cc4e5b08..c7857a9f28a8 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -22,6 +22,8 @@ - [Broadcast Box](https://github.com/Glimesh/broadcast-box), a WebRTC broadcast server. Available as [services.broadcast-box](options.html#opt-services.broadcast-box.enable). +- [Draupnir](https://github.com/the-draupnir-project/draupnir), a Matrix moderation bot. Available as [services.draupnir](#opt-services.draupnir.enable). + - [SuiteNumérique Docs](https://github.com/suitenumerique/docs), a collaborative note taking, wiki and documentation web platform and alternative to Notion or Outline. Available as [services.lasuite-docs](#opt-services.lasuite-docs.enable). ## Backward Incompatibilities {#sec-release-25.11-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index f093b10dc188..be543280a59d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -757,6 +757,7 @@ ./services/matrix/conduit.nix ./services/matrix/continuwuity.nix ./services/matrix/dendrite.nix + ./services/matrix/draupnir.nix ./services/matrix/hebbot.nix ./services/matrix/hookshot.nix ./services/matrix/lk-jwt-service.nix diff --git a/nixos/modules/services/matrix/draupnir.md b/nixos/modules/services/matrix/draupnir.md new file mode 100644 index 000000000000..1d6bce2c4e63 --- /dev/null +++ b/nixos/modules/services/matrix/draupnir.md @@ -0,0 +1,62 @@ +# Draupnir (Matrix Moderation Bot) {#module-services-draupnir} + +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 unencrypted, private room which will be used as the management room for Draupnir. +This is the room in which moderators will interact with Draupnir and where it will log possible errors and debugging information. +You'll need to set this room ID or alias in [services.draupnir.settings.managementRoom](#opt-services.draupnir.settings.managementRoom). + +Next, create a new user for Draupnir on your homeserver, if one does not already exist. + +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. +Draupnir will automatically try to join this room on startup. + +```nix +{ + services.draupnir = { + enable = true; + + settings = { + homeserverUrl = "https://matrix.org"; + managementRoom = "!yyy:example.org"; + }; + + secrets = { + accessToken = "/path/to/secret/containing/access-token"; + }; + }; +} +``` + +### 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. diff --git a/nixos/modules/services/matrix/draupnir.nix b/nixos/modules/services/matrix/draupnir.nix new file mode 100644 index 000000000000..e4dfa5d2917b --- /dev/null +++ b/nixos/modules/services/matrix/draupnir.nix @@ -0,0 +1,257 @@ +{ + config, + options, + lib, + pkgs, + ... +}: + +let + cfg = config.services.draupnir; + opt = options.services.draupnir; + + format = pkgs.formats.yaml { }; + configFile = format.generate "draupnir.yaml" cfg.settings; + + inherit (lib) + literalExpression + mkEnableOption + mkOption + mkPackageOption + mkRemovedOptionModule + mkRenamedOptionModule + types + ; +in +{ + imports = [ + # Removed options for those migrating from the Mjolnir module + (mkRenamedOptionModule + [ "services" "draupnir" "dataPath" ] + [ "services" "draupnir" "settings" "dataPath" ] + ) + (mkRenamedOptionModule + [ "services" "draupnir" "homeserverUrl" ] + [ "services" "draupnir" "settings" "homeserverUrl" ] + ) + (mkRenamedOptionModule + [ "services" "draupnir" "managementRoom" ] + [ "services" "draupnir" "settings" "managementRoom" ] + ) + (mkRenamedOptionModule + [ "services" "draupnir" "accessTokenFile" ] + [ "services" "draupnir" "secrets" "accessToken" ] + ) + (mkRemovedOptionModule [ "services" "draupnir" "pantalaimon" ] '' + `services.draupnir.pantalaimon.*` has been removed because it depends on the deprecated and vulnerable + libolm library for end-to-end encryption and upstream support for Pantalaimon in Draupnir is limited. + See for details. + If you nontheless require E2EE via Pantalaimon, you can configure `services.pantalaimon-headless.instances` + yourself and use that with `services.draupnir.settings.pantalaimon` and `services.draupnir.secrets.pantalaimon.password`. + '') + ]; + + options.services.draupnir = { + enable = mkEnableOption "Draupnir, a moderations bot for Matrix"; + + package = mkPackageOption pkgs "draupnir" { }; + + settings = mkOption { + example = literalExpression '' + { + homeserverUrl = "https://matrix.org"; + managementRoom = "#moderators:example.org"; + + autojoinOnlyIfManager = true; + automaticallyRedactForReasons = [ "spam" "advertising" ]; + } + ''; + description = '' + Free-form settings written to Draupnir's configuration file. + See [Draupnir's default configuration](https://github.com/the-draupnir-project/Draupnir/blob/main/config/default.yaml) for available settings. + ''; + default = { }; + type = types.submodule { + freeformType = format.type; + options = { + homeserverUrl = mkOption { + type = types.str; + example = "https://matrix.org"; + description = '' + Base URL of the Matrix homeserver that provides the Client-Server API. + + ::: {.note} + When using Pantalaimon, set this to the Pantalaimon URL and + {option}`${opt.settings}.rawHomeserverUrl` to the public URL. + ::: + ''; + }; + + rawHomeserverUrl = mkOption { + type = types.str; + example = "https://matrix.org"; + default = cfg.settings.homeserverUrl; + defaultText = literalExpression "config.${opt.settings}.homeserverUrl"; + description = '' + Public base URL of the Matrix homeserver that provides the Client-Server API when using the Draupnir's + [Report forwarding feature](https://the-draupnir-project.github.io/draupnir-documentation/bot/homeserver-administration#report-forwarding). + + ::: {.warning} + When using Pantalaimon, do not set this to the Pantalaimon URL! + ::: + ''; + }; + + managementRoom = mkOption { + type = 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! + Do not enable end-to-end encryption for this room, unless set up with Pantalaimon. + + ::: {.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. + ::: + ''; + }; + + dataPath = mkOption { + type = types.path; + readOnly = true; + default = "/var/lib/draupnir"; + description = '' + The path Draupnir will store its state/data in. + + ::: {.warning} + This option is read-only. + ::: + + ::: {.note} + If you want to customize where this data is stored, use a bind mount. + ::: + ''; + }; + }; + }; + }; + + secrets = { + accessToken = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing the access token for Draupnir's Matrix account + to be used in place of {option}`${opt.settings}.accessToken`. + ''; + }; + + pantalaimon.password = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing the password for Draupnir's Matrix account when used in + conjunction with Pantalaimon to be used in place of + {option}`${opt.settings}.pantalaimon.password`. + + ::: {.warning} + Take note that upstream has limited Pantalaimon and E2EE support: + and + . + ::: + ''; + }; + + web.synapseHTTPAntispam.authorization = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing the secret token when using the Synapse HTTP Antispam module + to be used in place of + {option}`${opt.settings}.web.synapseHTTPAntispam.authorization`. + + See for details. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + # Removed option for those migrating from the Mjolnir module - mkRemovedOption module does *not* work with submodules. + assertion = !(cfg.settings ? protectedRooms); + message = "Unset ${opt.settings}.protectedRooms, as it is unsupported on Draupnir. Add these rooms via `!draupnir rooms add` instead."; + } + ]; + + systemd.services.draupnir = { + description = "Draupnir - a moderation bot for Matrix"; + 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" ]; + + startLimitIntervalSec = 0; + serviceConfig = { + ExecStart = toString ( + [ + (lib.getExe cfg.package) + "--draupnir-config" + configFile + ] + ++ lib.optionals (cfg.secrets.accessToken != null) [ + "--access-token-path" + "%d/access_token" + ] + ++ lib.optionals (cfg.secrets.pantalaimon.password != null) [ + "--pantalaimon-password-path" + "%d/pantalaimon_password" + ] + ++ lib.optionals (cfg.secrets.web.synapseHTTPAntispam.authorization != null) [ + "--http-antispam-authorization-path" + "%d/http_antispam_authorization" + ] + ); + + WorkingDirectory = "/var/lib/draupnir"; + StateDirectory = "draupnir"; + StateDirectoryMode = "0700"; + ProtectHome = true; + PrivateDevices = true; + Restart = "on-failure"; + RestartSec = "5s"; + DynamicUser = true; + LoadCredential = + lib.optionals (cfg.secrets.accessToken != null) [ + "access_token:${cfg.secrets.accessToken}" + ] + ++ lib.optionals (cfg.secrets.pantalaimon.password != null) [ + "pantalaimon_password:${cfg.secrets.pantalaimon.password}" + ] + ++ lib.optionals (cfg.secrets.web.synapseHTTPAntispam.authorization != null) [ + "http_antispam_authorization:${cfg.secrets.web.synapseHTTPAntispam.authorization}" + ]; + }; + }; + }; + + meta = { + doc = ./draupnir.md; + maintainers = with lib.maintainers; [ + RorySys + emilylange + ]; + }; +}