vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / jibri / default.nix
blobb8cddafb74f9785b69f76eb61604b40f925bccb6
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.jibri;
8   format = pkgs.formats.hocon { };
10   # We're passing passwords in environment variables that have names generated
11   # from an attribute name, which may not be a valid bash identifier.
12   toVarName = s: "XMPP_PASSWORD_" + stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s;
14   defaultJibriConfig = {
15     id = "";
16     single-use-mode = false;
18     api = {
19       http.external-api-port = 2222;
20       http.internal-api-port = 3333;
22       xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: {
23         inherit name;
25         xmpp-server-hosts = env.xmppServerHosts;
26         xmpp-domain = env.xmppDomain;
27         control-muc = {
28           domain = env.control.muc.domain;
29           room-name = env.control.muc.roomName;
30           nickname = env.control.muc.nickname;
31         };
33         control-login = {
34           domain = env.control.login.domain;
35           username = env.control.login.username;
36           password = format.lib.mkSubstitution (toVarName "${name}_control");
37         };
39         call-login = {
40           domain = env.call.login.domain;
41           username = env.call.login.username;
42           password = format.lib.mkSubstitution (toVarName "${name}_call");
43         };
45         strip-from-room-domain = env.stripFromRoomDomain;
46         usage-timeout = env.usageTimeout;
47         trust-all-xmpp-certs = env.disableCertificateVerification;
48       });
49     };
51     recording = {
52       recordings-directory = "/tmp/recordings";
53       finalize-script = "${cfg.finalizeScript}";
54     };
56     streaming.rtmp-allow-list = [ ".*" ];
58     chrome.flags = [
59       "--use-fake-ui-for-media-stream"
60       "--start-maximized"
61       "--kiosk"
62       "--enabled"
63       "--disable-infobars"
64       "--autoplay-policy=no-user-gesture-required"
65     ]
66     ++ lists.optional cfg.ignoreCert
67       "--ignore-certificate-errors";
70     stats.enable-stats-d = true;
71     webhook.subscribers = [ ];
73     jwt-info = { };
75     call-status-checks = {
76       no-media-timout = "30 seconds";
77       all-muted-timeout = "10 minutes";
78       default-call-empty-timout = "30 seconds";
79     };
80   };
81   # Allow overriding leaves of the default config despite types.attrs not doing any merging.
82   jibriConfig = recursiveUpdate defaultJibriConfig cfg.config;
83   configFile = format.generate "jibri.conf" { jibri = jibriConfig; };
86   options.services.jibri = with types; {
87     enable = mkEnableOption "Jitsi BRoadcasting Infrastructure. Currently Jibri must be run on a host that is also running {option}`services.jitsi-meet.enable`, so for most use cases it will be simpler to run {option}`services.jitsi-meet.jibri.enable`";
88     config = mkOption {
89       type = format.type;
90       default = { };
91       description = ''
92         Jibri configuration.
93         See <https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf>
94         for default configuration with comments.
95       '';
96     };
98     finalizeScript = mkOption {
99       type = types.path;
100       default = pkgs.writeScript "finalize_recording.sh" ''
101         #!/bin/sh
103         RECORDINGS_DIR=$1
105         echo "This is a dummy finalize script" > /tmp/finalize.out
106         echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out
107         echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out
108         echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
110         exit 0
111       '';
112       defaultText = literalExpression ''
113         pkgs.writeScript "finalize_recording.sh" ''''''
114         #!/bin/sh
116         RECORDINGS_DIR=$1
118         echo "This is a dummy finalize script" > /tmp/finalize.out
119         echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out
120         echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out
121         echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
123         exit 0
124         '''''';
125       '';
126       example = literalExpression ''
127         pkgs.writeScript "finalize_recording.sh" ''''''
128         #!/bin/sh
129         RECORDINGS_DIR=$1
130         ''${pkgs.rclone}/bin/rclone copy $RECORDINGS_DIR RCLONE_REMOTE:jibri-recordings/ -v --log-file=/var/log/jitsi/jibri/recording-upload.txt
131         exit 0
132         '''''';
133       '';
134       description = ''
135         This script runs when jibri finishes recording a video of a conference.
136       '';
137     };
139     ignoreCert = mkOption {
140       type = bool;
141       default = false;
142       example = true;
143       description = ''
144         Whether to enable the flag "--ignore-certificate-errors" for the Chromium browser opened by Jibri.
145         Intended for use in automated tests or anywhere else where using a verified cert for Jitsi-Meet is not possible.
146       '';
147     };
149     xmppEnvironments = mkOption {
150       description = ''
151         XMPP servers to connect to.
152       '';
153       example = literalExpression ''
154         "jitsi-meet" = {
155           xmppServerHosts = [ "localhost" ];
156           xmppDomain = config.services.jitsi-meet.hostName;
158           control.muc = {
159             domain = "internal.''${config.services.jitsi-meet.hostName}";
160             roomName = "JibriBrewery";
161             nickname = "jibri";
162           };
164           control.login = {
165             domain = "auth.''${config.services.jitsi-meet.hostName}";
166             username = "jibri";
167             passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
168           };
170           call.login = {
171             domain = "recorder.''${config.services.jitsi-meet.hostName}";
172             username = "recorder";
173             passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
174           };
176           usageTimeout = "0";
177           disableCertificateVerification = true;
178           stripFromRoomDomain = "conference.";
179         };
180       '';
181       default = { };
182       type = attrsOf (submodule ({ name, ... }: {
183         options = {
184           xmppServerHosts = mkOption {
185             type = listOf str;
186             example = [ "xmpp.example.org" ];
187             description = ''
188               Hostnames of the XMPP servers to connect to.
189             '';
190           };
191           xmppDomain = mkOption {
192             type = str;
193             example = "xmpp.example.org";
194             description = ''
195               The base XMPP domain.
196             '';
197           };
198           control.muc.domain = mkOption {
199             type = str;
200             description = ''
201               The domain part of the MUC to connect to for control.
202             '';
203           };
204           control.muc.roomName = mkOption {
205             type = str;
206             default = "JibriBrewery";
207             description = ''
208               The room name of the MUC to connect to for control.
209             '';
210           };
211           control.muc.nickname = mkOption {
212             type = str;
213             default = "jibri";
214             description = ''
215               The nickname for this Jibri instance in the MUC.
216             '';
217           };
218           control.login.domain = mkOption {
219             type = str;
220             description = ''
221               The domain part of the JID for this Jibri instance.
222             '';
223           };
224           control.login.username = mkOption {
225             type = str;
226             default = "jvb";
227             description = ''
228               User part of the JID.
229             '';
230           };
231           control.login.passwordFile = mkOption {
232             type = str;
233             example = "/run/keys/jibri-xmpp1";
234             description = ''
235               File containing the password for the user.
236             '';
237           };
239           call.login.domain = mkOption {
240             type = str;
241             example = "recorder.xmpp.example.org";
242             description = ''
243               The domain part of the JID for the recorder.
244             '';
245           };
246           call.login.username = mkOption {
247             type = str;
248             default = "recorder";
249             description = ''
250               User part of the JID for the recorder.
251             '';
252           };
253           call.login.passwordFile = mkOption {
254             type = str;
255             example = "/run/keys/jibri-recorder-xmpp1";
256             description = ''
257               File containing the password for the user.
258             '';
259           };
260           disableCertificateVerification = mkOption {
261             type = bool;
262             default = false;
263             description = ''
264               Whether to skip validation of the server's certificate.
265             '';
266           };
268           stripFromRoomDomain = mkOption {
269             type = str;
270             default = "0";
271             example = "conference.";
272             description = ''
273               The prefix to strip from the room's JID domain to derive the call URL.
274             '';
275           };
276           usageTimeout = mkOption {
277             type = str;
278             default = "0";
279             example = "1 hour";
280             description = ''
281               The duration that the Jibri session can be.
282               A value of zero means indefinitely.
283             '';
284           };
285         };
287         config =
288           let
289             nick = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
290               config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
291             ));
292           in
293           {
294             call.login.username = nick;
295             control.muc.nickname = nick;
296           };
297       }));
298     };
299   };
301   config = mkIf cfg.enable {
302     users.groups.jibri = { };
303     users.groups.plugdev = { };
304     users.users.jibri = {
305       isSystemUser = true;
306       group = "jibri";
307       home = "/var/lib/jibri";
308       extraGroups = [ "jitsi-meet" "adm" "audio" "video" "plugdev" ];
309     };
311     systemd.services.jibri-xorg = {
312       description = "Jitsi Xorg Process";
314       after = [ "network.target" ];
315       wantedBy = [ "jibri.service" "jibri-icewm.service" ];
317       preStart = ''
318         cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri
319         mv /var/lib/jibri/{,.}asoundrc
320       '';
322       environment.DISPLAY = ":0";
323       serviceConfig = {
324         Type = "simple";
326         User = "jibri";
327         Group = "jibri";
328         KillMode = "process";
329         Restart = "on-failure";
330         RestartPreventExitStatus = 255;
332         StateDirectory = "jibri";
334         ExecStart = "${pkgs.xorg.xorgserver}/bin/Xorg -nocursor -noreset +extension RANDR +extension RENDER -config ${pkgs.jibri}/etc/jitsi/jibri/xorg-video-dummy.conf -logfile /dev/null :0";
335       };
336     };
338     systemd.services.jibri-icewm = {
339       description = "Jitsi Window Manager";
341       requires = [ "jibri-xorg.service" ];
342       after = [ "jibri-xorg.service" ];
343       wantedBy = [ "jibri.service" ];
345       environment.DISPLAY = ":0";
346       serviceConfig = {
347         Type = "simple";
349         User = "jibri";
350         Group = "jibri";
351         Restart = "on-failure";
352         RestartPreventExitStatus = 255;
354         StateDirectory = "jibri";
356         ExecStart = "${pkgs.icewm}/bin/icewm-session";
357       };
358     };
360     systemd.services.jibri = {
361       description = "Jibri Process";
363       requires = [ "jibri-icewm.service" "jibri-xorg.service" ];
364       after = [ "network.target" ];
365       wantedBy = [ "multi-user.target" ];
367       path = with pkgs; [ chromedriver chromium ffmpeg-full ];
369       script = (concatStrings (mapAttrsToList
370         (name: env: ''
371           export ${toVarName "${name}_control"}=$(cat ${env.control.login.passwordFile})
372           export ${toVarName "${name}_call"}=$(cat ${env.call.login.passwordFile})
373         '')
374         cfg.xmppEnvironments))
375       + ''
376         ${pkgs.jdk11_headless}/bin/java -Djava.util.logging.config.file=${./logging.properties-journal} -Dconfig.file=${configFile} -jar ${pkgs.jibri}/opt/jitsi/jibri/jibri.jar --config /var/lib/jibri/jibri.json
377       '';
379       environment.HOME = "/var/lib/jibri";
381       serviceConfig = {
382         Type = "simple";
384         User = "jibri";
385         Group = "jibri";
386         Restart = "always";
387         RestartPreventExitStatus = 255;
389         StateDirectory = "jibri";
390       };
391     };
393     systemd.tmpfiles.settings."10-jibri"."/var/log/jitsi/jibri".d = {
394       user = "jibri";
395       group = "jibri";
396       mode = "755";
397     };
399     # Configure Chromium to not show the "Chrome is being controlled by automatic test software" message.
400     environment.etc."chromium/policies/managed/managed_policies.json".text = builtins.toJSON { CommandLineFlagSecurityWarningsEnabled = false; };
401     warnings = [ "All security warnings for Chromium have been disabled. This is necessary for Jibri, but it also impacts all other uses of Chromium on this system." ];
403     boot = {
404       extraModprobeConfig = ''
405         options snd-aloop enable=1,1,1,1,1,1,1,1
406       '';
407       kernelModules = [ "snd-aloop" ];
408     };
409   };
411   meta.maintainers = lib.teams.jitsi.members;