1 { config, lib, pkgs, ... }:
3 cfg = config.services.jitsi-videobridge;
4 attrsToArgs = a: lib.concatStringsSep " " (lib.mapAttrsToList (k: v: "${k}=${toString v}") a);
6 format = pkgs.formats.hocon { };
8 # We're passing passwords in environment variables that have names generated
9 # from an attribute name, which may not be a valid bash identifier.
10 toVarName = s: "XMPP_PASSWORD_" + lib.stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s;
23 transports = [ { type = "muc"; } ];
25 apis.xmpp-client.configs = lib.flip lib.mapAttrs cfg.xmppConfigs (name: xmppConfig: {
26 hostname = xmppConfig.hostName;
27 domain = xmppConfig.domain;
28 username = xmppConfig.userName;
29 password = format.lib.mkSubstitution (toVarName name);
30 muc_jids = xmppConfig.mucJids;
31 muc_nickname = xmppConfig.mucNickname;
32 disable_certificate_verification = xmppConfig.disableCertificateVerification;
34 apis.rest.enabled = cfg.colibriRestApi;
38 # Allow overriding leaves of the default config despite types.attrs not doing any merging.
39 jvbConfig = lib.recursiveUpdate defaultJvbConfig cfg.config;
43 (lib.mkRemovedOptionModule [ "services" "jitsi-videobridge" "apis" ]
44 "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."
47 options.services.jitsi-videobridge = with lib.types; {
48 enable = lib.mkEnableOption "Jitsi Videobridge, a WebRTC compatible video router";
50 config = lib.mkOption {
53 example = lib.literalExpression ''
65 Videobridge configuration.
67 See <https://github.com/jitsi/jitsi-videobridge/blob/master/jvb/src/main/resources/reference.conf>
68 for default configuration with comments.
72 xmppConfigs = lib.mkOption {
74 XMPP servers to connect to.
76 See <https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md> for more information.
79 example = lib.literalExpression ''
82 hostName = "localhost";
84 domain = "auth.xmpp.example.org";
85 passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
86 mucJids = "jvbbrewery@internal.xmpp.example.org";
90 type = attrsOf (submodule ({ name, ... }: {
92 hostName = lib.mkOption {
94 example = "xmpp.example.org";
96 Hostname of the XMPP server to connect to. Name of the attribute set is used by default.
99 domain = lib.mkOption {
102 example = "auth.xmpp.example.org";
104 Domain part of JID of the XMPP user, if it is different from hostName.
107 userName = lib.mkOption {
111 User part of the JID.
114 passwordFile = lib.mkOption {
116 example = "/run/keys/jitsi-videobridge-xmpp1";
118 File containing the password for the user.
121 mucJids = lib.mkOption {
123 example = "jvbbrewery@internal.xmpp.example.org";
125 JID of the MUC to join. JiCoFo needs to be configured to join the same MUC.
128 mucNickname = lib.mkOption {
129 # Upstream DEBs use UUID, let's use hostname instead.
132 Videobridges use the same XMPP account and need to be distinguished by the
133 nickname (aka resource part of the JID). By default, system hostname is used.
136 disableCertificateVerification = lib.mkOption {
140 Whether to skip validation of the server's certificate.
145 hostName = lib.mkDefault name;
146 mucNickname = lib.mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
147 config.networking.fqdnOrHostName
154 localAddress = lib.mkOption {
157 example = "192.168.1.42";
159 Local address to assume when running behind NAT.
163 publicAddress = lib.mkOption {
168 Public address to assume when running behind NAT.
172 harvesterAddresses = lib.mkOption {
175 "stunserver.stunprotocol.org:3478"
176 "stun.framasoft.org:3478"
177 "meet-jit-si-turnrelay.jitsi.net:443"
181 Addresses of public STUN services to use to automatically find
182 the public and local addresses of this Jitsi-Videobridge instance
183 without the need for manual configuration.
185 This option is ignored if {option}`services.jitsi-videobridge.nat.localAddress`
186 and {option}`services.jitsi-videobridge.nat.publicAddress` are set.
191 extraProperties = lib.mkOption {
195 Additional Java properties passed to jitsi-videobridge.
199 openFirewall = lib.mkOption {
203 Whether to open ports in the firewall for the videobridge.
207 colibriRestApi = lib.mkOption {
210 Whether to enable the private rest API for the COLIBRI control interface.
211 Needed for monitoring jitsi, enabling scraping of the /colibri/stats endpoint.
217 config = lib.mkIf cfg.enable {
218 users.groups.jitsi-meet = {};
220 services.jitsi-videobridge.extraProperties =
221 if (cfg.nat.localAddress != null) then {
222 "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress;
223 "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress;
225 "org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES" = lib.concatStringsSep "," cfg.nat.harvesterAddresses;
228 systemd.services.jitsi-videobridge2 = let
230 "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
231 "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "videobridge";
232 "-Djava.util.logging.config.file" = "/etc/jitsi/videobridge/logging.properties";
233 "-Dconfig.file" = format.generate "jvb.conf" jvbConfig;
234 # Mitigate CVE-2021-44228
235 "-Dlog4j2.formatMsgNoLookups" = true;
236 } // (lib.mapAttrs' (k: v: lib.nameValuePair "-D${k}" v) cfg.extraProperties);
239 aliases = [ "jitsi-videobridge.service" ];
240 description = "Jitsi Videobridge";
241 after = [ "network.target" ];
242 wantedBy = [ "multi-user.target" ];
244 environment.JAVA_SYS_PROPS = attrsToArgs jvbProps;
246 script = (lib.concatStrings (lib.mapAttrsToList (name: xmppConfig:
247 "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
250 ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge
257 User = "jitsi-videobridge";
258 Group = "jitsi-meet";
260 CapabilityBoundingSet = "";
261 NoNewPrivileges = true;
262 ProtectSystem = "strict";
265 PrivateDevices = true;
266 ProtectHostname = true;
267 ProtectKernelTunables = true;
268 ProtectKernelModules = true;
269 ProtectControlGroups = true;
270 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
271 RestrictNamespaces = true;
272 LockPersonality = true;
273 RestrictRealtime = true;
274 RestrictSUIDSGID = true;
282 environment.etc."jitsi/videobridge/logging.properties".source =
283 lib.mkDefault "${pkgs.jitsi-videobridge}/etc/jitsi/videobridge/logging.properties-journal";
285 # (from videobridge2 .deb)
286 # this sets the max, so that we can bump the JVB UDP single port buffer size.
287 boot.kernel.sysctl."net.core.rmem_max" = lib.mkDefault 10485760;
288 boot.kernel.sysctl."net.core.netdev_max_backlog" = lib.mkDefault 100000;
290 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall
291 [ jvbConfig.videobridge.ice.tcp.port ];
292 networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall
293 [ jvbConfig.videobridge.ice.udp.port ];
296 message = "publicAddress must be set if and only if localAddress is set";
297 assertion = (cfg.nat.publicAddress == null) == (cfg.nat.localAddress == null);
301 meta.maintainers = lib.teams.jitsi.members;