aboutsummaryrefslogtreecommitdiff
path: root/overlays
diff options
context:
space:
mode:
authorsefidel <contact@sefidel.net>2023-03-29 20:54:19 +0900
committersefidel <contact@sefidel.net>2023-04-03 18:32:29 +0900
commitce06f43476863da90dc60dcee606d2b6c5a89a8e (patch)
tree5d14946330cb09ff0ebd97bee59407fccee4d860 /overlays
downloadinfra-ce06f43476863da90dc60dcee606d2b6c5a89a8e.zip
project: initial commit
Diffstat (limited to 'overlays')
-rw-r--r--overlays/README.md4
-rw-r--r--overlays/default.nix4
-rw-r--r--overlays/git-daemon-module.nix137
-rw-r--r--overlays/mautrix-signal-module.nix196
-rw-r--r--overlays/mautrix-whatsapp-module.nix192
-rw-r--r--overlays/mjolnir-module/default.nix242
-rw-r--r--overlays/mjolnir-module/mjolnir.md110
-rw-r--r--overlays/mjolnir-module/pantalaimon-options.nix70
-rw-r--r--overlays/mjolnir-package/default.nix80
-rw-r--r--overlays/mjolnir-package/package.json69
-rw-r--r--overlays/mjolnir-package/pin.json5
-rwxr-xr-xoverlays/mjolnir-package/update.sh36
-rw-r--r--overlays/sliding-sync-module.nix87
-rw-r--r--overlays/sliding-sync.nix22
-rw-r--r--overlays/soju-module.nix132
15 files changed, 1386 insertions, 0 deletions
diff --git a/overlays/README.md b/overlays/README.md
new file mode 100644
index 0000000..4d257f8
--- /dev/null
+++ b/overlays/README.md
@@ -0,0 +1,4 @@
+infra->overlays
+===============
+
+Nixpkgs overlays or module replacements.
diff --git a/overlays/default.nix b/overlays/default.nix
new file mode 100644
index 0000000..353d5bd
--- /dev/null
+++ b/overlays/default.nix
@@ -0,0 +1,4 @@
+self: super: {
+ mjolnir = super.callPackage ./mjolnir-package { };
+ sliding-sync = super.callPackage ./sliding-sync.nix { };
+}
diff --git a/overlays/git-daemon-module.nix b/overlays/git-daemon-module.nix
new file mode 100644
index 0000000..76b395e
--- /dev/null
+++ b/overlays/git-daemon-module.nix
@@ -0,0 +1,137 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+
+ cfg = config.services.gitDaemon;
+
+in
+{
+
+ ###### interface
+
+ options = {
+ services.gitDaemon = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Enable Git daemon, which allows public hosting of git repositories
+ without any access controls. This is mostly intended for read-only access.
+
+ You can allow write access by setting daemon.receivepack configuration
+ item of the repository to true. This is solely meant for a closed LAN setting
+ where everybody is friendly.
+
+ If you need any access controls, use something else.
+ '';
+ };
+
+ basePath = mkOption {
+ type = types.str;
+ default = "";
+ example = "/srv/git/";
+ description = lib.mdDoc ''
+ Remap all the path requests as relative to the given path. For example,
+ if you set base-path to /srv/git, then if you later try to pull
+ git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git.
+ '';
+ };
+
+ exportAll = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Publish all directories that look like Git repositories (have the objects
+ and refs subdirectories), even if they do not have the git-daemon-export-ok file.
+
+ If disabled, you need to touch .git/git-daemon-export-ok in each repository
+ you want the daemon to publish.
+
+ Warning: enabling this without a repository whitelist or basePath
+ publishes every git repository you have.
+ '';
+ };
+
+ repositories = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "/srv/git" "/home/user/git/repo2" ];
+ description = lib.mdDoc ''
+ A whitelist of paths of git repositories, or directories containing repositories
+ all of which would be published. Paths must not end in "/".
+
+ Warning: leaving this empty and enabling exportAll publishes all
+ repositories in your filesystem or basePath if specified.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "";
+ example = "example.com";
+ description = lib.mdDoc "Listen on a specific IP address or hostname.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 9418;
+ description = lib.mdDoc "Port to listen on.";
+ };
+
+ options = mkOption {
+ type = types.str;
+ default = "";
+ description = lib.mdDoc "Extra configuration options to be passed to Git daemon.";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "git";
+ description = lib.mdDoc "User under which Git daemon would be running.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "git";
+ description = lib.mdDoc "Group under which Git daemon would be running.";
+ };
+
+ createUserAndGroup = mkOption {
+ type = types.bool;
+ default = true;
+ description = lib.mdDoc ''
+ Create the specified group and user.
+ Disable this option if you want to use the existing user
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ users.users.${cfg.user} = optionalAttrs (cfg.createUserAndGroup == true) {
+ uid = config.ids.uids.git;
+ group = cfg.group;
+ description = "Git daemon user";
+ };
+
+ users.groups.${cfg.group} = optionalAttrs (cfg.createUserAndGroup == true) {
+ gid = config.ids.gids.git;
+ };
+
+ systemd.services.git-daemon = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ script = "${pkgs.git}/bin/git daemon --reuseaddr "
+ + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ")
+ + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ")
+ + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} "
+ + "--verbose " + (optionalString cfg.exportAll "--export-all ") + concatStringsSep " " cfg.repositories;
+ };
+
+ };
+
+}
diff --git a/overlays/mautrix-signal-module.nix b/overlays/mautrix-signal-module.nix
new file mode 100644
index 0000000..22abbb5
--- /dev/null
+++ b/overlays/mautrix-signal-module.nix
@@ -0,0 +1,196 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/mautrix-signal";
+ registrationFile = "${dataDir}/signal-registration.yaml";
+ cfg = config.services.mautrix-signal;
+ settingsFormat = pkgs.formats.json { };
+ settingsFile =
+ settingsFormat.generate "mautrix-signal-config.json" cfg.settings;
+
+in
+{
+ options = {
+ services.mautrix-signal = {
+ enable = mkEnableOption (lib.mdDoc "Mautrix-Signal, a Matrix-Signal puppeting bridge.");
+
+ settings = mkOption rec {
+ apply = recursiveUpdate default;
+ inherit (settingsFormat) type;
+ default = {
+ homeserver = {
+ software = "standard";
+ };
+
+ appservice = rec {
+ database = "sqlite:///${dataDir}/mautrix-signal.db";
+ database_opts = { };
+ hostname = "0.0.0.0";
+ port = 8080;
+ address = "http://localhost:${toString port}";
+ };
+
+ signal.socket_path = config.services.signald.socketPath;
+
+ bridge = {
+ permissions."*" = "relay";
+ relay.whitelist = [ ];
+ double_puppet_server_map = { };
+ login_shared_secret_map = { };
+ };
+
+ logging = {
+ version = 1;
+
+ formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
+
+ handlers.console = {
+ class = "logging.StreamHandler";
+ formatter = "precise";
+ };
+
+ loggers = {
+ mau.level = "INFO";
+
+ # prevent tokens from leaking in the logs:
+ # https://github.com/tulir/mautrix-telegram/issues/351
+ aiohttp.level = "WARNING";
+ };
+
+ # log to console/systemd instead of file
+ root = {
+ level = "INFO";
+ handlers = [ "console" ];
+ };
+ };
+ };
+ example = literalExpression ''
+ {
+ homeserver = {
+ address = "http://localhost:8008";
+ domain = "public-domain.tld";
+ };
+
+ appservice.public = {
+ prefix = "/public";
+ external = "https://public-appservice-address/public";
+ };
+
+ bridge.permissions = {
+ "example.com" = "full";
+ "@admin:example.com" = "admin";
+ };
+ }
+ '';
+ description = lib.mdDoc ''
+ {file}`config.yaml` configuration as a Nix attribute set.
+ Configuration options should match those described in
+ [example-config.yaml](https://github.com/mautrix/signal/blob/master/signal/example-config.yaml).
+
+ Secret tokens should be specified using {option}`environmentFile`
+ instead of this world-readable attribute set.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = lib.mdDoc ''
+ File containing environment variables to be passed to the mautrix-signal service,
+ in which secret tokens can be specified securely by defining values for e.g.
+ `MAUTRIX_SIGNAL_APPSERVICE_AS_TOKEN`,
+ `MAUTRIX_SIGNAL_APPSERVICE_HS_TOKEN`,
+
+ These environment variables can also be used to set other options by
+ replacing hierarchy levels by `.`, converting the name to uppercase
+ and prepending `MAUTRIX_SIGNAL_`.
+ For example, the first value above maps to
+ {option}`settings.appservice.as_token`.
+
+ The environment variable values can be prefixed with `json::` to have
+ them be parsed as JSON. For example, `login_shared_secret_map` can be
+ set as follows:
+ `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`.
+ '';
+ };
+
+ serviceDependencies = mkOption {
+ type = with types; listOf str;
+ default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+ defaultText = literalExpression ''
+ optional config.services.matrix-synapse.enable "matrix-synapse.service"
+ '';
+ description = lib.mdDoc ''
+ List of Systemd services to require and wait for when starting the application service.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.signald.enable = true;
+
+ systemd.services.mautrix-signal = {
+ description = "Mautrix-Signal, a Matrix-Signal puppeting bridge.";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+ after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+ path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];
+
+ # mautrix-signal tries to generate a dotfile in the home directory of
+ # the running user if using a postgresql database:
+ #
+ # File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre>
+ # return (pathlib.Path.home() / '.postgresql' / filename).resolve()
+ # File "python3.10/pathlib.py", line 1000, in home
+ # return cls("~").expanduser()
+ # File "python3.10/pathlib.py", line 1440, in expanduser
+ # raise RuntimeError("Could not determine home directory.")
+ # RuntimeError: Could not determine home directory.
+ environment.HOME = dataDir;
+
+ preStart = ''
+ # generate the appservice's registration file if absent
+ if [ ! -f '${registrationFile}' ]; then
+ ${pkgs.mautrix-signal}/bin/mautrix-signal \
+ --generate-registration \
+ --base-config='${pkgs.mautrix-signal}/${pkgs.mautrix-signal.pythonModule.sitePackages}/mautrix_signal/example-config.yaml' \
+ --config='${settingsFile}' \
+ --registration='${registrationFile}'
+ fi
+ '' + lib.optionalString (pkgs.mautrix-signal ? alembic) ''
+ # run automatic database init and migration scripts
+ ${pkgs.mautrix-signal.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+
+ DynamicUser = true;
+ SupplementaryGroups = [ "signald" ];
+ PrivateTmp = true;
+ WorkingDirectory = pkgs.mautrix-signal; # necessary for the database migration scripts to be found
+ StateDirectory = baseNameOf dataDir;
+ UMask = "0027";
+ EnvironmentFile = cfg.environmentFile;
+
+ ExecStart = ''
+ ${pkgs.mautrix-signal}/bin/mautrix-signal \
+ --config='${settingsFile}'
+ '';
+ };
+ };
+ };
+
+ # meta.maintainers = with maintainers; [ boppyt ];
+}
diff --git a/overlays/mautrix-whatsapp-module.nix b/overlays/mautrix-whatsapp-module.nix
new file mode 100644
index 0000000..4cebcb6
--- /dev/null
+++ b/overlays/mautrix-whatsapp-module.nix
@@ -0,0 +1,192 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/mautrix-whatsapp";
+ registrationFile = "${dataDir}/whatsapp-registration.yaml";
+ cfg = config.services.mautrix-whatsapp;
+ settingsFormat = pkgs.formats.json { };
+ settingsFile =
+ settingsFormat.generate "mautrix-whatsapp-config.json" cfg.settings;
+in
+{
+ options = {
+ services.mautrix-whatsapp = {
+ enable = mkEnableOption (lib.mdDoc "Mautrix-Whatsapp, a Matrix-Whatsapp puppeting bridge.");
+
+ settings = mkOption rec {
+ apply = recursiveUpdate default;
+ inherit (settingsFormat) type;
+ default = {
+ homeserver = {
+ software = "standard";
+ };
+
+ appservice = rec {
+ database = {
+ type = "sqlite";
+ uri = "sqlite:///${dataDir}/mautrix-whatsapp.db";
+ };
+ hostname = "0.0.0.0";
+ port = 8080;
+ address = "http://localhost:${toString port}";
+ as_token = "$MAUTRIX_WHATSAPP_APPSERVICE_AS_TOKEN";
+ hs_token = "$MAUTRIX_WHATSAPP_APPSERVICE_HS_TOKEN";
+ };
+
+ bridge = {
+ permissions."*" = "relay";
+ relay.whitelist = [ ];
+ double_puppet_server_map = { };
+ login_shared_secret_map = { };
+ };
+
+ logging = {
+ version = 1;
+
+ formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
+
+ handlers.console = {
+ class = "logging.StreamHandler";
+ formatter = "precise";
+ };
+
+ # log to console/systemd instead of file
+ file_name_format = null;
+
+ loggers = {
+ mau.level = "INFO";
+ telethon.level = "INFO";
+
+ # prevent tokens from leaking in the logs:
+ # https://github.com/tulir/mautrix-telegram/issues/351
+ aiohttp.level = "WARNING";
+ };
+ };
+ };
+ example = literalExpression ''
+ {
+ homeserver = {
+ address = "http://localhost:8008";
+ domain = "public-domain.tld";
+ };
+
+ appservice.public = {
+ prefix = "/public";
+ external = "https://public-appservice-address/public";
+ };
+
+ bridge.permissions = {
+ "example.com" = "full";
+ "@admin:example.com" = "admin";
+ };
+ }
+ '';
+ description = lib.mdDoc ''
+ {file}`config.yaml` configuration as a Nix attribute set.
+ Configuration options should match those described in
+ [example-config.yaml](https://github.com/mautrix/whatsapp/blob/master/mautrix_whatsapp/example-config.yaml).
+
+ Secret tokens should be specified using {option}`environmentFile`
+ instead of this world-readable attribute set.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = lib.mdDoc ''
+ File containing environment variables to be passed to the mautrix-whatsapp service,
+ in which secret tokens can be specified securely by defining values for e.g.
+ `MAUTRIX_WHATSAPP_APPSERVICE_AS_TOKEN`,
+ `MAUTRIX_WHATSAPP_APPSERVICE_HS_TOKEN`,
+
+ For Mautrix-Whatsapp, only AS_TOKEN and HS_TOKEN is available.
+ '';
+ };
+
+ serviceDependencies = mkOption {
+ type = with types; listOf str;
+ default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+ defaultText = literalExpression ''
+ optional config.services.matrix-synapse.enable "matrix-synapse.service"
+ '';
+ description = lib.mdDoc ''
+ List of Systemd services to require and wait for when starting the application service.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.mautrix-whatsapp = {
+ description = "Mautrix-Whatsapp, a Matrix-Whatsapp puppeting bridge.";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+ after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+ path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];
+
+ # mautrix-whatsapp tries to generate a dotfile in the home directory of
+ # the running user if using a postgresql database:
+ #
+ # File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre>
+ # return (pathlib.Path.home() / '.postgresql' / filename).resolve()
+ # File "python3.10/pathlib.py", line 1000, in home
+ # return cls("~").expanduser()
+ # File "python3.10/pathlib.py", line 1440, in expanduser
+ # raise RuntimeError("Could not determine home directory.")
+ # RuntimeError: Could not determine home directory.
+ environment.HOME = dataDir;
+
+ preStart = ''
+ # generate the appservice's registration file if absent
+ if [ ! -f '${registrationFile}' ]; then
+ ${pkgs.mautrix-whatsapp}/bin/mautrix-whatsapp \
+ --generate-registration \
+ --config='${settingsFile}' \
+ --registration='${registrationFile}'
+ fi
+
+ ${pkgs.envsubst}/bin/envsubst \
+ -i ${settingsFile} \
+ -o /run/mautrix-whatsapp/config.json
+
+ # wait until dendrite grabs the config
+ sleep 5
+ '' + lib.optionalString (pkgs.mautrix-whatsapp ? alembic) ''
+ # run automatic database init and migration scripts
+ ${pkgs.mautrix-whatsapp.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+
+ DynamicUser = true;
+ Group = "mautrix-whatsapp";
+ PrivateTmp = true;
+ WorkingDirectory = pkgs.mautrix-whatsapp; # necessary for the database migration scripts to be found
+ StateDirectory = baseNameOf dataDir;
+ RuntimeDirectory = "mautrix-whatsapp";
+ RuntimeDirectoryMode = "0700";
+ UMask = "0027";
+ EnvironmentFile = cfg.environmentFile;
+
+ ExecStart = ''
+ ${pkgs.mautrix-whatsapp}/bin/mautrix-whatsapp \
+ --config='/run/mautrix-whatsapp/config.json'
+ '';
+ };
+ };
+ };
+
+ # meta.maintainers = with maintainers; [ boppyt ];
+}
diff --git a/overlays/mjolnir-module/default.nix b/overlays/mjolnir-module/default.nix
new file mode 100644
index 0000000..87ed761
--- /dev/null
+++ b/overlays/mjolnir-module/default.nix
@@ -0,0 +1,242 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.mjolnir;
+
+ yamlConfig = {
+ inherit (cfg) dataPath managementRoom protectedRooms;
+
+ accessToken = "@ACCESS_TOKEN@"; # will be replaced in "generateConfig"
+ homeserverUrl =
+ if cfg.pantalaimon.enable then
+ "http://${cfg.pantalaimon.options.listenAddress}:${toString cfg.pantalaimon.options.listenPort}"
+ else
+ cfg.homeserverUrl;
+
+ rawHomeserverUrl = cfg.homeserverUrl;
+
+ pantalaimon = {
+ inherit (cfg.pantalaimon) username;
+
+ use = cfg.pantalaimon.enable;
+ password = "@PANTALAIMON_PASSWORD@"; # will be replaced in "generateConfig"
+ };
+ };
+
+ moduleConfigFile = pkgs.writeText "module-config.yaml" (
+ generators.toYAML { } (filterAttrs (_: v: v != null)
+ (fold recursiveUpdate { } [ yamlConfig cfg.settings ])));
+
+ # these config files will be merged one after the other to build the final config
+ configFiles = [
+ "${pkgs.mjolnir}/libexec/mjolnir/deps/mjolnir/config/default.yaml"
+ moduleConfigFile
+ ];
+
+ # this will generate the default.yaml file with all configFiles as inputs and
+ # replace all secret strings using replace-secret
+ generateConfig = pkgs.writeShellScript "mjolnir-generate-config" (
+ let
+ yqEvalStr = concatImapStringsSep " * " (pos: _: "select(fileIndex == ${toString (pos - 1)})") configFiles;
+ yqEvalArgs = concatStringsSep " " configFiles;
+ in
+ ''
+ set -euo pipefail
+
+ umask 077
+
+ # mjolnir will try to load a config from "./config/default.yaml" in the working directory
+ # -> let's place the generated config there
+ mkdir -p ${cfg.dataPath}/config
+
+ # merge all config files into one, overriding settings of the previous one with the next config
+ # e.g. "eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' filea.yaml fileb.yaml" will merge filea.yaml with fileb.yaml
+ ${pkgs.yq-go}/bin/yq eval-all -P '${yqEvalStr}' ${yqEvalArgs} > ${cfg.dataPath}/config/default.yaml
+
+ ${optionalString (cfg.accessTokenFile != null) ''
+ ${pkgs.replace-secret}/bin/replace-secret '@ACCESS_TOKEN@' '${cfg.accessTokenFile}' ${cfg.dataPath}/config/default.yaml
+ ''}
+ ${optionalString (cfg.pantalaimon.passwordFile != null) ''
+ ${pkgs.replace-secret}/bin/replace-secret '@PANTALAIMON_PASSWORD@' '${cfg.pantalaimon.passwordFile}' ${cfg.dataPath}/config/default.yaml
+ ''}
+ ''
+ );
+in
+{
+ options.services.mjolnir = {
+ enable = mkEnableOption (lib.mdDoc "Mjolnir, a moderation tool for Matrix");
+
+ homeserverUrl = mkOption {
+ type = types.str;
+ default = "https://matrix.org";
+ description = lib.mdDoc ''
+ Where the homeserver is located (client-server URL).
+
+ If `pantalaimon.enable` is `true`, this option will become the homeserver to which `pantalaimon` connects.
+ The listen address of `pantalaimon` will then become the `homeserverUrl` of `mjolnir`.
+ '';
+ };
+
+ accessTokenFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = lib.mdDoc ''
+ File containing the matrix access token for the `mjolnir` user.
+ '';
+ };
+
+ pantalaimon = mkOption {
+ description = lib.mdDoc ''
+ `pantalaimon` options (enables E2E Encryption support).
+
+ This will create a `pantalaimon` instance with the name "mjolnir".
+ '';
+ default = { };
+ type = types.submodule {
+ options = {
+ enable = mkEnableOption (lib.mdDoc ''
+ If true, accessToken is ignored and the username/password below will be
+ used instead. The access token of the bot will be stored in the dataPath.
+ '');
+
+ username = mkOption {
+ type = types.str;
+ description = lib.mdDoc "The username to login with.";
+ };
+
+ passwordFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = lib.mdDoc ''
+ File containing the matrix password for the `mjolnir` user.
+ '';
+ };
+
+ options = mkOption {
+ type = types.submodule (import ./pantalaimon-options.nix);
+ default = { };
+ description = lib.mdDoc ''
+ passthrough additional options to the `pantalaimon` service.
+ '';
+ };
+ };
+ };
+ };
+
+ dataPath = mkOption {
+ type = types.path;
+ default = "/var/lib/mjolnir";
+ description = lib.mdDoc ''
+ The directory the bot should store various bits of information in.
+ '';
+ };
+
+ managementRoom = mkOption {
+ type = types.str;
+ default = "#moderators:example.org";
+ description = lib.mdDoc ''
+ The room ID where people can use the bot. The bot has no access controls, so
+ anyone in this room can use the bot - secure your room!
+ This should be a room alias or room ID - not a matrix.to URL.
+ Note: `mjolnir` is fairly verbose - expect a lot of messages from it.
+ '';
+ };
+
+ protectedRooms = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = literalExpression ''
+ [
+ "https://matrix.to/#/#yourroom:example.org"
+ "https://matrix.to/#/#anotherroom:example.org"
+ ]
+ '';
+ description = lib.mdDoc ''
+ A list of rooms to protect (matrix.to URLs).
+ '';
+ };
+
+ settings = mkOption {
+ default = { };
+ type = (pkgs.formats.yaml { }).type;
+ example = literalExpression ''
+ {
+ autojoinOnlyIfManager = true;
+ automaticallyRedactForReasons = [ "spam" "advertising" ];
+ }
+ '';
+ description = lib.mdDoc ''
+ Additional settings (see [mjolnir default config](https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml) for available settings). These settings will override settings made by the module config.
+ '';
+ };
+ };
+
+ config = mkIf config.services.mjolnir.enable {
+ assertions = [
+ {
+ assertion = !(cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile == null);
+ message = "Specify pantalaimon.passwordFile";
+ }
+ {
+ assertion = !(cfg.pantalaimon.enable && cfg.accessTokenFile != null);
+ message = "Do not specify accessTokenFile when using pantalaimon";
+ }
+ {
+ assertion = !(!cfg.pantalaimon.enable && cfg.accessTokenFile == null);
+ message = "Specify accessTokenFile when not using pantalaimon";
+ }
+ ];
+
+ services.pantalaimon-headless.instances."mjolnir" = mkIf cfg.pantalaimon.enable
+ {
+ homeserver = cfg.homeserverUrl;
+ } // cfg.pantalaimon.options;
+
+ systemd.services.mjolnir = {
+ description = "mjolnir - a moderation tool for Matrix";
+ wants = [ "network-online.target" ] ++ optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
+ after = [ "network-online.target" ] ++ optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = ''${pkgs.mjolnir}/bin/mjolnir'';
+ ExecStartPre = [ generateConfig ];
+ WorkingDirectory = cfg.dataPath;
+ StateDirectory = "mjolnir";
+ StateDirectoryMode = "0700";
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ User = "mjolnir";
+ Restart = "on-failure";
+
+ /* TODO: wait for #102397 to be resolved. Then load secrets from $CREDENTIALS_DIRECTORY+"/NAME"
+ DynamicUser = true;
+ LoadCredential = [] ++
+ optionals (cfg.accessTokenFile != null) [
+ "access_token:${cfg.accessTokenFile}"
+ ] ++
+ optionals (cfg.pantalaimon.passwordFile != null) [
+ "pantalaimon_password:${cfg.pantalaimon.passwordFile}"
+ ];
+ */
+ };
+ };
+
+ users = {
+ users.mjolnir = {
+ group = "mjolnir";
+ isSystemUser = true;
+ };
+ groups.mjolnir = { };
+ };
+ };
+
+ meta = {
+ doc = ./mjolnir.md;
+ maintainers = with maintainers; [ jojosch ];
+ };
+}
diff --git a/overlays/mjolnir-module/mjolnir.md b/overlays/mjolnir-module/mjolnir.md
new file mode 100644
index 0000000..f6994ee
--- /dev/null
+++ b/overlays/mjolnir-module/mjolnir.md
@@ -0,0 +1,110 @@
+# Mjolnir (Matrix Moderation Tool) {#module-services-mjolnir}
+
+This chapter will show you how to set up your own, self-hosted
+[Mjolnir](https://github.com/matrix-org/mjolnir) 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, Mjolnir 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.
+
+See the [README](https://github.com/matrix-org/mjolnir#readme)
+page and the [Moderator's guide](https://github.com/matrix-org/mjolnir/blob/main/docs/moderators.md)
+for additional instructions on how to setup and use Mjolnir.
+
+For [additional settings](#opt-services.mjolnir.settings)
+see [the default configuration](https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml).
+
+## Mjolnir Setup {#module-services-mjolnir-setup}
+
+First create a new Room which will be used as a management room for Mjolnir. In
+this room, Mjolnir will log possible errors and debugging information. You'll
+need to set this Room-ID in [services.mjolnir.managementRoom](#opt-services.mjolnir.managementRoom).
+
+Next, create a new user for Mjolnir on your homeserver, if not present already.
+
+The Mjolnir 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 Mjolnir to be able to deactivate users, move room aliases, shutdown rooms, etc.
+you'll need to make the Mjolnir user a Matrix server admin.
+
+Now invite the Mjolnir user to the management room.
+
+It is recommended to use [Pantalaimon](https://github.com/matrix-org/pantalaimon),
+so your management room can be encrypted. This also applies if you are looking to moderate an encrypted room.
+
+To enable the Pantalaimon E2E Proxy for mjolnir, enable
+[services.mjolnir.pantalaimon](#opt-services.mjolnir.pantalaimon.enable). This will
+autoconfigure a new Pantalaimon instance, which will connect to the homeserver
+set in [services.mjolnir.homeserverUrl](#opt-services.mjolnir.homeserverUrl) and Mjolnir itself
+will be configured to connect to the new Pantalaimon instance.
+
+```
+{
+ services.mjolnir = {
+ enable = true;
+ homeserverUrl = "https://matrix.domain.tld";
+ pantalaimon = {
+ enable = true;
+ username = "mjolnir";
+ passwordFile = "/run/secrets/mjolnir-password";
+ };
+ protectedRooms = [
+ "https://matrix.to/#/!xxx:domain.tld"
+ ];
+ managementRoom = "!yyy:domain.tld";
+ };
+}
+```
+
+### Element Matrix Services (EMS) {#module-services-mjolnir-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-mjolnir-matrix-synapse-antispam}
+
+A Synapse module is also available to apply the same rulesets the bot
+uses across an entire homeserver.
+
+To use the Antispam Module, add `matrix-synapse-plugins.matrix-synapse-mjolnir-antispam`
+to the Synapse plugin list and enable the `mjolnir.Module` module.
+
+```
+{
+ services.matrix-synapse = {
+ plugins = with pkgs; [
+ matrix-synapse-plugins.matrix-synapse-mjolnir-antispam
+ ];
+ extraConfig = ''
+ modules:
+ - module: mjolnir.Module
+ config:
+ # Prevent servers/users in the ban lists from inviting users on this
+ # server to rooms. Default true.
+ block_invites: true
+ # Flag messages sent by servers/users in the ban lists as spam. Currently
+ # this means that spammy messages will appear as empty to users. Default
+ # false.
+ block_messages: false
+ # Remove users from the user directory search by filtering matrix IDs and
+ # display names by the entries in the user ban list. Default false.
+ block_usernames: false
+ # The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
+ # this list cannot be room aliases or permalinks. This server is expected
+ # to already be joined to the room - Mjolnir will not automatically