base16-schemes: unstable-2024-06-21 -> unstable-2024-11-12
[NixPkgs.git] / nixos / modules / services / matrix / mjolnir.nix
blob085f38e522e6e0616996d38e545484bfeca626a9
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.mjolnir;
5   yamlConfig = {
6     inherit (cfg) dataPath managementRoom protectedRooms;
8     accessToken = "@ACCESS_TOKEN@"; # will be replaced in "generateConfig"
9     homeserverUrl =
10       if cfg.pantalaimon.enable then
11         "http://${cfg.pantalaimon.options.listenAddress}:${toString cfg.pantalaimon.options.listenPort}"
12       else
13         cfg.homeserverUrl;
15     rawHomeserverUrl = cfg.homeserverUrl;
17     pantalaimon = {
18       inherit (cfg.pantalaimon) username;
20       use = cfg.pantalaimon.enable;
21       password = "@PANTALAIMON_PASSWORD@"; # will be replaced in "generateConfig"
22     };
23   };
25   moduleConfigFile = pkgs.writeText "module-config.yaml" (
26     lib.generators.toYAML { } (lib.filterAttrs (_: v: v != null)
27       (lib.fold lib.recursiveUpdate { } [ yamlConfig cfg.settings ])));
29   # these config files will be merged one after the other to build the final config
30   configFiles = [
31     "${pkgs.mjolnir}/libexec/mjolnir/deps/mjolnir/config/default.yaml"
32     moduleConfigFile
33   ];
35   # this will generate the default.yaml file with all configFiles as inputs and
36   # replace all secret strings using replace-secret
37   generateConfig = pkgs.writeShellScript "mjolnir-generate-config" (
38     let
39       yqEvalStr = lib.concatImapStringsSep " * " (pos: _: "select(fileIndex == ${toString (pos - 1)})") configFiles;
40       yqEvalArgs = lib.concatStringsSep " " configFiles;
41     in
42     ''
43       set -euo pipefail
45       umask 077
47       # mjolnir will try to load a config from "./config/default.yaml" in the working directory
48       # -> let's place the generated config there
49       mkdir -p ${cfg.dataPath}/config
51       # merge all config files into one, overriding settings of the previous one with the next config
52       # e.g. "eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' filea.yaml fileb.yaml" will merge filea.yaml with fileb.yaml
53       ${pkgs.yq-go}/bin/yq eval-all -P '${yqEvalStr}' ${yqEvalArgs} > ${cfg.dataPath}/config/default.yaml
55       ${lib.optionalString (cfg.accessTokenFile != null) ''
56         ${pkgs.replace-secret}/bin/replace-secret '@ACCESS_TOKEN@' '${cfg.accessTokenFile}' ${cfg.dataPath}/config/default.yaml
57       ''}
58       ${lib.optionalString (cfg.pantalaimon.passwordFile != null) ''
59         ${pkgs.replace-secret}/bin/replace-secret '@PANTALAIMON_PASSWORD@' '${cfg.pantalaimon.passwordFile}' ${cfg.dataPath}/config/default.yaml
60       ''}
61     ''
62   );
65   options.services.mjolnir = {
66     enable = lib.mkEnableOption "Mjolnir, a moderation tool for Matrix";
68     homeserverUrl = lib.mkOption {
69       type = lib.types.str;
70       default = "https://matrix.org";
71       description = ''
72         Where the homeserver is located (client-server URL).
74         If `pantalaimon.enable` is `true`, this option will become the homeserver to which `pantalaimon` connects.
75         The listen address of `pantalaimon` will then become the `homeserverUrl` of `mjolnir`.
76       '';
77     };
79     accessTokenFile = lib.mkOption {
80       type = with lib.types; nullOr path;
81       default = null;
82       description = ''
83         File containing the matrix access token for the `mjolnir` user.
84       '';
85     };
87     pantalaimon = lib.mkOption {
88       description = ''
89         `pantalaimon` options (enables E2E Encryption support).
91         This will create a `pantalaimon` instance with the name "mjolnir".
92       '';
93       default = { };
94       type = lib.types.submodule {
95         options = {
96           enable = lib.mkEnableOption ''
97             ignoring the accessToken. If true, accessToken is ignored and the username/password below will be
98             used instead. The access token of the bot will be stored in the dataPath
99           '';
101           username = lib.mkOption {
102             type = lib.types.str;
103             description = "The username to login with.";
104           };
106           passwordFile = lib.mkOption {
107             type = with lib.types; nullOr path;
108             default = null;
109             description = ''
110               File containing the matrix password for the `mjolnir` user.
111             '';
112           };
114           options = lib.mkOption {
115             type = lib.types.submodule (import ./pantalaimon-options.nix);
116             default = { };
117             description = ''
118               passthrough additional options to the `pantalaimon` service.
119             '';
120           };
121         };
122       };
123     };
125     dataPath = lib.mkOption {
126       type = lib.types.path;
127       default = "/var/lib/mjolnir";
128       description = ''
129         The directory the bot should store various bits of information in.
130       '';
131     };
133     managementRoom = lib.mkOption {
134       type = lib.types.str;
135       default = "#moderators:example.org";
136       description = ''
137         The room ID where people can use the bot. The bot has no access controls, so
138         anyone in this room can use the bot - secure your room!
139         This should be a room alias or room ID - not a matrix.to URL.
140         Note: `mjolnir` is fairly verbose - expect a lot of messages from it.
141       '';
142     };
144     protectedRooms = lib.mkOption {
145       type = lib.types.listOf lib.types.str;
146       default = [ ];
147       example = lib.literalExpression ''
148         [
149           "https://matrix.to/#/#yourroom:example.org"
150           "https://matrix.to/#/#anotherroom:example.org"
151         ]
152       '';
153       description = ''
154         A list of rooms to protect (matrix.to URLs).
155       '';
156     };
158     settings = lib.mkOption {
159       default = { };
160       type = (pkgs.formats.yaml { }).type;
161       example = lib.literalExpression ''
162         {
163           autojoinOnlyIfManager = true;
164           automaticallyRedactForReasons = [ "spam" "advertising" ];
165         }
166       '';
167       description = ''
168         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.
169       '';
170     };
171   };
173   config = lib.mkIf config.services.mjolnir.enable {
174     assertions = [
175       {
176         assertion = !(cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile == null);
177         message = "Specify pantalaimon.passwordFile";
178       }
179       {
180         assertion = !(cfg.pantalaimon.enable && cfg.accessTokenFile != null);
181         message = "Do not specify accessTokenFile when using pantalaimon";
182       }
183       {
184         assertion = !(!cfg.pantalaimon.enable && cfg.accessTokenFile == null);
185         message = "Specify accessTokenFile when not using pantalaimon";
186       }
187     ];
189     # This defaults to true in the application,
190     # which breaks older configs using pantalaimon or access tokens
191     services.mjolnir.settings.encryption.use = lib.mkDefault false;
193     services.pantalaimon-headless.instances."mjolnir" = lib.mkIf cfg.pantalaimon.enable
194       {
195         homeserver = cfg.homeserverUrl;
196       } // cfg.pantalaimon.options;
198     systemd.services.mjolnir = {
199       description = "mjolnir - a moderation tool for Matrix";
200       wants = [ "network-online.target" ] ++ lib.optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
201       after = [ "network-online.target" ] ++ lib.optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
202       wantedBy = [ "multi-user.target" ];
204       serviceConfig = {
205         ExecStart = ''${pkgs.mjolnir}/bin/mjolnir --mjolnir-config ./config/default.yaml'';
206         ExecStartPre = [ generateConfig ];
207         WorkingDirectory = cfg.dataPath;
208         StateDirectory = "mjolnir";
209         StateDirectoryMode = "0700";
210         ProtectSystem = "strict";
211         ProtectHome = true;
212         PrivateTmp = true;
213         NoNewPrivileges = true;
214         PrivateDevices = true;
215         User = "mjolnir";
216         Restart = "on-failure";
218         /* TODO: wait for #102397 to be resolved. Then load secrets from $CREDENTIALS_DIRECTORY+"/NAME"
219         DynamicUser = true;
220         LoadCredential = [] ++
221           lib.optionals (cfg.accessTokenFile != null) [
222             "access_token:${cfg.accessTokenFile}"
223           ] ++
224           lib.optionals (cfg.pantalaimon.passwordFile != null) [
225             "pantalaimon_password:${cfg.pantalaimon.passwordFile}"
226           ];
227         */
228       };
229     };
231     users = {
232       users.mjolnir = {
233         group = "mjolnir";
234         isSystemUser = true;
235       };
236       groups.mjolnir = { };
237     };
238   };
240   meta = {
241     doc = ./mjolnir.md;
242     maintainers = with lib.maintainers; [ jojosch ];
243   };