1 { config, lib, pkgs, ... }:
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 = {
16 single-use-mode = false;
19 http.external-api-port = 2222;
20 http.internal-api-port = 3333;
22 xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: {
25 xmpp-server-hosts = env.xmppServerHosts;
26 xmpp-domain = env.xmppDomain;
28 domain = env.control.muc.domain;
29 room-name = env.control.muc.roomName;
30 nickname = env.control.muc.nickname;
34 domain = env.control.login.domain;
35 username = env.control.login.username;
36 password = format.lib.mkSubstitution (toVarName "${name}_control");
40 domain = env.call.login.domain;
41 username = env.call.login.username;
42 password = format.lib.mkSubstitution (toVarName "${name}_call");
45 strip-from-room-domain = env.stripFromRoomDomain;
46 usage-timeout = env.usageTimeout;
47 trust-all-xmpp-certs = env.disableCertificateVerification;
52 recordings-directory = "/tmp/recordings";
53 finalize-script = "${cfg.finalizeScript}";
56 streaming.rtmp-allow-list = [ ".*" ];
59 "--use-fake-ui-for-media-stream"
64 "--autoplay-policy=no-user-gesture-required"
66 ++ lists.optional cfg.ignoreCert
67 "--ignore-certificate-errors";
70 stats.enable-stats-d = true;
71 webhook.subscribers = [ ];
75 call-status-checks = {
76 no-media-timout = "30 seconds";
77 all-muted-timeout = "10 minutes";
78 default-call-empty-timout = "30 seconds";
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`";
93 See <https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf>
94 for default configuration with comments.
98 finalizeScript = mkOption {
100 default = pkgs.writeScript "finalize_recording.sh" ''
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
112 defaultText = literalExpression ''
113 pkgs.writeScript "finalize_recording.sh" ''''''
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
126 example = literalExpression ''
127 pkgs.writeScript "finalize_recording.sh" ''''''
130 ''${pkgs.rclone}/bin/rclone copy $RECORDINGS_DIR RCLONE_REMOTE:jibri-recordings/ -v --log-file=/var/log/jitsi/jibri/recording-upload.txt
135 This script runs when jibri finishes recording a video of a conference.
139 ignoreCert = mkOption {
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.
149 xmppEnvironments = mkOption {
151 XMPP servers to connect to.
153 example = literalExpression ''
155 xmppServerHosts = [ "localhost" ];
156 xmppDomain = config.services.jitsi-meet.hostName;
159 domain = "internal.''${config.services.jitsi-meet.hostName}";
160 roomName = "JibriBrewery";
165 domain = "auth.''${config.services.jitsi-meet.hostName}";
167 passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
171 domain = "recorder.''${config.services.jitsi-meet.hostName}";
172 username = "recorder";
173 passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
177 disableCertificateVerification = true;
178 stripFromRoomDomain = "conference.";
182 type = attrsOf (submodule ({ name, ... }: {
184 xmppServerHosts = mkOption {
186 example = [ "xmpp.example.org" ];
188 Hostnames of the XMPP servers to connect to.
191 xmppDomain = mkOption {
193 example = "xmpp.example.org";
195 The base XMPP domain.
198 control.muc.domain = mkOption {
201 The domain part of the MUC to connect to for control.
204 control.muc.roomName = mkOption {
206 default = "JibriBrewery";
208 The room name of the MUC to connect to for control.
211 control.muc.nickname = mkOption {
215 The nickname for this Jibri instance in the MUC.
218 control.login.domain = mkOption {
221 The domain part of the JID for this Jibri instance.
224 control.login.username = mkOption {
228 User part of the JID.
231 control.login.passwordFile = mkOption {
233 example = "/run/keys/jibri-xmpp1";
235 File containing the password for the user.
239 call.login.domain = mkOption {
241 example = "recorder.xmpp.example.org";
243 The domain part of the JID for the recorder.
246 call.login.username = mkOption {
248 default = "recorder";
250 User part of the JID for the recorder.
253 call.login.passwordFile = mkOption {
255 example = "/run/keys/jibri-recorder-xmpp1";
257 File containing the password for the user.
260 disableCertificateVerification = mkOption {
264 Whether to skip validation of the server's certificate.
268 stripFromRoomDomain = mkOption {
271 example = "conference.";
273 The prefix to strip from the room's JID domain to derive the call URL.
276 usageTimeout = mkOption {
281 The duration that the Jibri session can be.
282 A value of zero means indefinitely.
289 nick = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
290 config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
294 call.login.username = nick;
295 control.muc.nickname = nick;
301 config = mkIf cfg.enable {
302 users.groups.jibri = { };
303 users.groups.plugdev = { };
304 users.users.jibri = {
307 home = "/var/lib/jibri";
308 extraGroups = [ "jitsi-meet" "adm" "audio" "video" "plugdev" ];
311 systemd.services.jibri-xorg = {
312 description = "Jitsi Xorg Process";
314 after = [ "network.target" ];
315 wantedBy = [ "jibri.service" "jibri-icewm.service" ];
318 cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri
319 mv /var/lib/jibri/{,.}asoundrc
322 environment.DISPLAY = ":0";
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";
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";
351 Restart = "on-failure";
352 RestartPreventExitStatus = 255;
354 StateDirectory = "jibri";
356 ExecStart = "${pkgs.icewm}/bin/icewm-session";
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
371 export ${toVarName "${name}_control"}=$(cat ${env.control.login.passwordFile})
372 export ${toVarName "${name}_call"}=$(cat ${env.call.login.passwordFile})
374 cfg.xmppEnvironments))
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
379 environment.HOME = "/var/lib/jibri";
387 RestartPreventExitStatus = 255;
389 StateDirectory = "jibri";
393 systemd.tmpfiles.settings."10-jibri"."/var/log/jitsi/jibri".d = {
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." ];
404 extraModprobeConfig = ''
405 options snd-aloop enable=1,1,1,1,1,1,1,1
407 kernelModules = [ "snd-aloop" ];
411 meta.maintainers = lib.teams.jitsi.members;