mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-07 18:18:01 +09:00
nixos/draupnir: init, nixosTests.draupnir: init (#400194)
This commit is contained in:
commit
c80ea7a732
8 changed files with 487 additions and 1 deletions
|
@ -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"
|
||||
],
|
||||
|
|
|
@ -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).
|
||||
|
||||
[dwl](https://codeberg.org/dwl/dwl), a compact, hackable compositor for Wayland based on wlroots. Available as [programs.dwl](#opt-programs.dwl.enable).
|
||||
|
|
|
@ -758,6 +758,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
|
||||
|
|
62
nixos/modules/services/matrix/draupnir.md
Normal file
62
nixos/modules/services/matrix/draupnir.md
Normal file
|
@ -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.
|
257
nixos/modules/services/matrix/draupnir.nix
Normal file
257
nixos/modules/services/matrix/draupnir.nix
Normal file
|
@ -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 <https://the-draupnir-project.github.io/draupnir-documentation/bot/encryption> 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:
|
||||
<https://the-draupnir-project.github.io/draupnir-documentation/bot/encryption> and
|
||||
<https://the-draupnir-project.github.io/draupnir-documentation/shared/dogfood#e2ee-support>.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
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 <https://the-draupnir-project.github.io/draupnir-documentation/bot/synapse-http-antispam> 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
|
||||
];
|
||||
};
|
||||
}
|
|
@ -409,6 +409,7 @@ in
|
|||
domination = runTest ./domination.nix;
|
||||
dovecot = handleTest ./dovecot.nix { };
|
||||
drawterm = discoverTests (import ./drawterm.nix);
|
||||
draupnir = runTest ./matrix/draupnir.nix;
|
||||
drbd = runTest ./drbd.nix;
|
||||
druid = handleTestOn [ "x86_64-linux" ] ./druid { };
|
||||
drupal = runTest ./drupal.nix;
|
||||
|
|
150
nixos/tests/matrix/draupnir.nix
Normal file
150
nixos/tests/matrix/draupnir.nix
Normal file
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
name = "draupnir";
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
RorySys
|
||||
emilylange
|
||||
];
|
||||
|
||||
nodes = {
|
||||
homeserver =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.matrix-synapse = {
|
||||
enable = true;
|
||||
log.root.level = "WARNING";
|
||||
settings = {
|
||||
database.name = "sqlite3";
|
||||
registration_shared_secret = "supersecret-registration";
|
||||
|
||||
listeners = [
|
||||
{
|
||||
bind_addresses = [
|
||||
"::"
|
||||
];
|
||||
port = 8008;
|
||||
resources = [
|
||||
{
|
||||
compress = true;
|
||||
names = [ "client" ];
|
||||
}
|
||||
{
|
||||
compress = false;
|
||||
names = [ "federation" ];
|
||||
}
|
||||
];
|
||||
tls = false;
|
||||
type = "http";
|
||||
x_forwarded = false;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
specialisation.draupnir = {
|
||||
inheritParentConfig = true;
|
||||
|
||||
configuration.services.draupnir = {
|
||||
enable = true;
|
||||
settings = {
|
||||
homeserverUrl = "http://localhost:8008";
|
||||
managementRoom = "#moderators:homeserver";
|
||||
};
|
||||
secrets = {
|
||||
accessToken = "/tmp/draupnir-access-token";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
curl
|
||||
jq
|
||||
(writers.writePython3Bin "test_draupnir_in_matrix"
|
||||
{
|
||||
libraries = [ python3Packages.matrix-nio ];
|
||||
flakeIgnore = [ "E501" ];
|
||||
}
|
||||
''
|
||||
import asyncio
|
||||
from nio import AsyncClient, MatrixRoom, RoomMemberEvent, RoomMessageNotice
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = AsyncClient("http://localhost:8008", "moderator")
|
||||
|
||||
async def member_callback(room: MatrixRoom, event: RoomMemberEvent) -> None:
|
||||
if event.membership == "join" and event.sender == "@draupnir:homeserver":
|
||||
await client.room_send(
|
||||
room_id=room.room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.text",
|
||||
"body": "!draupnir status"
|
||||
}
|
||||
)
|
||||
|
||||
async def message_callback(room: MatrixRoom, event: RoomMessageNotice) -> None:
|
||||
print(f"{event.sender}: {event.body}")
|
||||
if event.sender == "@draupnir:homeserver":
|
||||
await client.close()
|
||||
exit(0)
|
||||
|
||||
client.add_event_callback(member_callback, RoomMemberEvent)
|
||||
client.add_event_callback(message_callback, RoomMessageNotice)
|
||||
|
||||
print(await client.login("password"))
|
||||
|
||||
room = await client.room_create(
|
||||
name="Moderators",
|
||||
alias="moderators",
|
||||
invite=["@draupnir:homeserver"],
|
||||
power_level_override={
|
||||
"users": {
|
||||
"@draupnir:homeserver": 100,
|
||||
"@moderator:homeserver": 100,
|
||||
}
|
||||
}
|
||||
)
|
||||
print(room)
|
||||
|
||||
print(await client.join(room.room_id))
|
||||
|
||||
await client.sync_forever(timeout=30000)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
''
|
||||
)
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import json
|
||||
|
||||
homeserver.wait_for_unit("matrix-synapse.service")
|
||||
homeserver.wait_until_succeeds("curl --fail -L http://localhost:8008/")
|
||||
|
||||
homeserver.succeed("matrix-synapse-register_new_matrix_user -u draupnir -p password --no-admin")
|
||||
homeserver.succeed("matrix-synapse-register_new_matrix_user -u moderator -p password --no-admin")
|
||||
|
||||
# get draupnir access token
|
||||
payload = json.dumps({ "type": "m.login.password", "user": "draupnir", "password": "password" })
|
||||
homeserver.succeed(
|
||||
f"curl --fail --json '{payload}' http://localhost:8008/_matrix/client/v3/login"
|
||||
+ " | jq -r .access_token"
|
||||
+ " | tee /tmp/draupnir-access-token"
|
||||
)
|
||||
|
||||
homeserver.succeed("${nodes.homeserver.system.build.toplevel}/specialisation/draupnir/bin/switch-to-configuration test")
|
||||
homeserver.wait_for_unit("draupnir.service")
|
||||
|
||||
print(homeserver.succeed("test_draupnir_in_matrix >&2", timeout=60))
|
||||
'';
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
fetchYarnDeps,
|
||||
stdenv,
|
||||
cctools,
|
||||
nixosTests,
|
||||
}:
|
||||
|
||||
# docs: https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/javascript.section.md#yarn2nix-javascript-yarn2nix
|
||||
|
@ -95,7 +96,10 @@ mkYarnPackage rec {
|
|||
|
||||
distPhase = "true";
|
||||
|
||||
passthru.updateScript = ./update.sh;
|
||||
passthru = {
|
||||
tests = { inherit (nixosTests) draupnir; };
|
||||
updateScript = ./update.sh;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
description = "Moderation tool for Matrix";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue