Merge #361424: refactor lib.packagesFromDirectoryRecursive (v2)
[NixPkgs.git] / nixos / modules / services / networking / jitsi-videobridge.nix
blob3303aeb7f78cd4c869b70d5e09234c7b0e81b512
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   cfg = config.services.jitsi-videobridge;
9   attrsToArgs = a: lib.concatStringsSep " " (lib.mapAttrsToList (k: v: "${k}=${toString v}") a);
11   format = pkgs.formats.hocon { };
13   # We're passing passwords in environment variables that have names generated
14   # from an attribute name, which may not be a valid bash identifier.
15   toVarName =
16     s:
17     "XMPP_PASSWORD_"
18     + lib.stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s;
20   defaultJvbConfig = {
21     videobridge = {
22       ice = {
23         tcp = {
24           enabled = true;
25           port = 4443;
26         };
27         udp.port = 10000;
28       };
29       stats = {
30         enabled = true;
31         transports = [ { type = "muc"; } ];
32       };
33       apis.xmpp-client.configs = lib.flip lib.mapAttrs cfg.xmppConfigs (
34         name: xmppConfig: {
35           hostname = xmppConfig.hostName;
36           domain = xmppConfig.domain;
37           username = xmppConfig.userName;
38           password = format.lib.mkSubstitution (toVarName name);
39           muc_jids = xmppConfig.mucJids;
40           muc_nickname = xmppConfig.mucNickname;
41           disable_certificate_verification = xmppConfig.disableCertificateVerification;
42         }
43       );
44       apis.rest.enabled = cfg.colibriRestApi;
45     };
46   };
48   # Allow overriding leaves of the default config despite types.attrs not doing any merging.
49   jvbConfig = lib.recursiveUpdate defaultJvbConfig cfg.config;
52   imports = [
53     (lib.mkRemovedOptionModule [ "services" "jitsi-videobridge" "apis" ]
54       "services.jitsi-videobridge.apis was broken and has been migrated into the boolean option services.jitsi-videobridge.colibriRestApi. It is set to false by default, setting it to true will correctly enable the private /colibri rest API."
55     )
56   ];
57   options.services.jitsi-videobridge = with lib.types; {
58     enable = lib.mkEnableOption "Jitsi Videobridge, a WebRTC compatible video router";
60     config = lib.mkOption {
61       type = attrs;
62       default = { };
63       example = lib.literalExpression ''
64         {
65           videobridge = {
66             ice.udp.port = 5000;
67             websockets = {
68               enabled = true;
69               server-id = "jvb1";
70             };
71           };
72         }
73       '';
74       description = ''
75         Videobridge configuration.
77         See <https://github.com/jitsi/jitsi-videobridge/blob/master/jvb/src/main/resources/reference.conf>
78         for default configuration with comments.
79       '';
80     };
82     xmppConfigs = lib.mkOption {
83       description = ''
84         XMPP servers to connect to.
86         See <https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md> for more information.
87       '';
88       default = { };
89       example = lib.literalExpression ''
90         {
91           "localhost" = {
92             hostName = "localhost";
93             userName = "jvb";
94             domain = "auth.xmpp.example.org";
95             passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
96             mucJids = "jvbbrewery@internal.xmpp.example.org";
97           };
98         }
99       '';
100       type = attrsOf (
101         submodule (
102           { name, ... }:
103           {
104             options = {
105               hostName = lib.mkOption {
106                 type = str;
107                 example = "xmpp.example.org";
108                 description = ''
109                   Hostname of the XMPP server to connect to. Name of the attribute set is used by default.
110                 '';
111               };
112               domain = lib.mkOption {
113                 type = nullOr str;
114                 default = null;
115                 example = "auth.xmpp.example.org";
116                 description = ''
117                   Domain part of JID of the XMPP user, if it is different from hostName.
118                 '';
119               };
120               userName = lib.mkOption {
121                 type = str;
122                 default = "jvb";
123                 description = ''
124                   User part of the JID.
125                 '';
126               };
127               passwordFile = lib.mkOption {
128                 type = str;
129                 example = "/run/keys/jitsi-videobridge-xmpp1";
130                 description = ''
131                   File containing the password for the user.
132                 '';
133               };
134               mucJids = lib.mkOption {
135                 type = str;
136                 example = "jvbbrewery@internal.xmpp.example.org";
137                 description = ''
138                   JID of the MUC to join. JiCoFo needs to be configured to join the same MUC.
139                 '';
140               };
141               mucNickname = lib.mkOption {
142                 # Upstream DEBs use UUID, let's use hostname instead.
143                 type = str;
144                 description = ''
145                   Videobridges use the same XMPP account and need to be distinguished by the
146                   nickname (aka resource part of the JID). By default, system hostname is used.
147                 '';
148               };
149               disableCertificateVerification = lib.mkOption {
150                 type = bool;
151                 default = false;
152                 description = ''
153                   Whether to skip validation of the server's certificate.
154                 '';
155               };
156             };
157             config = {
158               hostName = lib.mkDefault name;
159               mucNickname = lib.mkDefault (
160                 builtins.replaceStrings [ "." ] [ "-" ] (config.networking.fqdnOrHostName)
161               );
162             };
163           }
164         )
165       );
166     };
168     nat = {
169       localAddress = lib.mkOption {
170         type = nullOr str;
171         default = null;
172         example = "192.168.1.42";
173         description = ''
174           Local address to assume when running behind NAT.
175         '';
176       };
178       publicAddress = lib.mkOption {
179         type = nullOr str;
180         default = null;
181         example = "1.2.3.4";
182         description = ''
183           Public address to assume when running behind NAT.
184         '';
185       };
187       harvesterAddresses = lib.mkOption {
188         type = listOf str;
189         default = [
190           "stunserver.stunprotocol.org:3478"
191           "stun.framasoft.org:3478"
192           "meet-jit-si-turnrelay.jitsi.net:443"
193         ];
194         example = [ ];
195         description = ''
196           Addresses of public STUN services to use to automatically find
197           the public and local addresses of this Jitsi-Videobridge instance
198           without the need for manual configuration.
200           This option is ignored if {option}`services.jitsi-videobridge.nat.localAddress`
201           and {option}`services.jitsi-videobridge.nat.publicAddress` are set.
202         '';
203       };
204     };
206     extraProperties = lib.mkOption {
207       type = attrsOf str;
208       default = { };
209       description = ''
210         Additional Java properties passed to jitsi-videobridge.
211       '';
212     };
214     openFirewall = lib.mkOption {
215       type = bool;
216       default = false;
217       description = ''
218         Whether to open ports in the firewall for the videobridge.
219       '';
220     };
222     colibriRestApi = lib.mkOption {
223       type = bool;
224       description = ''
225         Whether to enable the private rest API for the COLIBRI control interface.
226         Needed for monitoring jitsi, enabling scraping of the /colibri/stats endpoint.
227       '';
228       default = false;
229     };
230   };
232   config = lib.mkIf cfg.enable {
233     users.groups.jitsi-meet = { };
235     services.jitsi-videobridge.extraProperties =
236       if (cfg.nat.localAddress != null) then
237         {
238           "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress;
239           "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress;
240         }
241       else
242         {
243           "org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES" =
244             lib.concatStringsSep "," cfg.nat.harvesterAddresses;
245         };
247     systemd.services.jitsi-videobridge2 =
248       let
249         jvbProps = {
250           "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
251           "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "videobridge";
252           "-Djava.util.logging.config.file" = "/etc/jitsi/videobridge/logging.properties";
253           "-Dconfig.file" = format.generate "jvb.conf" jvbConfig;
254           # Mitigate CVE-2021-44228
255           "-Dlog4j2.formatMsgNoLookups" = true;
256         } // (lib.mapAttrs' (k: v: lib.nameValuePair "-D${k}" v) cfg.extraProperties);
257       in
258       {
259         aliases = [ "jitsi-videobridge.service" ];
260         description = "Jitsi Videobridge";
261         after = [ "network.target" ];
262         wantedBy = [ "multi-user.target" ];
264         environment.JAVA_SYS_PROPS = attrsToArgs jvbProps;
266         script =
267           (lib.concatStrings (
268             lib.mapAttrsToList (
269               name: xmppConfig: "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
270             ) cfg.xmppConfigs
271           ))
272           + ''
273             ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge
274           '';
276         serviceConfig = {
277           Type = "exec";
279           DynamicUser = true;
280           User = "jitsi-videobridge";
281           Group = "jitsi-meet";
283           CapabilityBoundingSet = "";
284           NoNewPrivileges = true;
285           ProtectSystem = "strict";
286           ProtectHome = true;
287           PrivateTmp = true;
288           PrivateDevices = true;
289           ProtectHostname = true;
290           ProtectKernelTunables = true;
291           ProtectKernelModules = true;
292           ProtectControlGroups = true;
293           RestrictAddressFamilies = [
294             "AF_INET"
295             "AF_INET6"
296             "AF_UNIX"
297           ];
298           RestrictNamespaces = true;
299           LockPersonality = true;
300           RestrictRealtime = true;
301           RestrictSUIDSGID = true;
303           TasksMax = 65000;
304           LimitNPROC = 65000;
305           LimitNOFILE = 65000;
306         };
307       };
309     environment.etc."jitsi/videobridge/logging.properties".source =
310       lib.mkDefault "${pkgs.jitsi-videobridge}/etc/jitsi/videobridge/logging.properties-journal";
312     # (from videobridge2 .deb)
313     # this sets the max, so that we can bump the JVB UDP single port buffer size.
314     boot.kernel.sysctl."net.core.rmem_max" = lib.mkDefault 10485760;
315     boot.kernel.sysctl."net.core.netdev_max_backlog" = lib.mkDefault 100000;
317     networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
318       jvbConfig.videobridge.ice.tcp.port
319     ];
320     networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
321       jvbConfig.videobridge.ice.udp.port
322     ];
324     assertions = [
325       {
326         message = "publicAddress must be set if and only if localAddress is set";
327         assertion = (cfg.nat.publicAddress == null) == (cfg.nat.localAddress == null);
328       }
329     ];
330   };
332   meta.maintainers = lib.teams.jitsi.members;