diff options
Diffstat (limited to 'nixos/modules/services/networking/jibri/default.nix')
-rw-r--r-- | nixos/modules/services/networking/jibri/default.nix | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/nixos/modules/services/networking/jibri/default.nix b/nixos/modules/services/networking/jibri/default.nix new file mode 100644 index 00000000000..113a7aa4384 --- /dev/null +++ b/nixos/modules/services/networking/jibri/default.nix @@ -0,0 +1,417 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.jibri; + + # Copied from the jitsi-videobridge.nix file. + toHOCON = x: + if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}") + else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}" + else if isList x then "[${ concatMapStringsSep "," toHOCON x }]" + else builtins.toJSON x; + + # We're passing passwords in environment variables that have names generated + # from an attribute name, which may not be a valid bash identifier. + toVarName = s: "XMPP_PASSWORD_" + stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s; + + defaultJibriConfig = { + id = ""; + single-use-mode = false; + + api = { + http.external-api-port = 2222; + http.internal-api-port = 3333; + + xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: { + inherit name; + + xmpp-server-hosts = env.xmppServerHosts; + xmpp-domain = env.xmppDomain; + control-muc = { + domain = env.control.muc.domain; + room-name = env.control.muc.roomName; + nickname = env.control.muc.nickname; + }; + + control-login = { + domain = env.control.login.domain; + username = env.control.login.username; + password.__hocon_envvar = toVarName "${name}_control"; + }; + + call-login = { + domain = env.call.login.domain; + username = env.call.login.username; + password.__hocon_envvar = toVarName "${name}_call"; + }; + + strip-from-room-domain = env.stripFromRoomDomain; + usage-timeout = env.usageTimeout; + trust-all-xmpp-certs = env.disableCertificateVerification; + }); + }; + + recording = { + recordings-directory = "/tmp/recordings"; + finalize-script = "${cfg.finalizeScript}"; + }; + + streaming.rtmp-allow-list = [ ".*" ]; + + chrome.flags = [ + "--use-fake-ui-for-media-stream" + "--start-maximized" + "--kiosk" + "--enabled" + "--disable-infobars" + "--autoplay-policy=no-user-gesture-required" + ] + ++ lists.optional cfg.ignoreCert + "--ignore-certificate-errors"; + + + stats.enable-stats-d = true; + webhook.subscribers = [ ]; + + jwt-info = { }; + + call-status-checks = { + no-media-timout = "30 seconds"; + all-muted-timeout = "10 minutes"; + default-call-empty-timout = "30 seconds"; + }; + }; + # Allow overriding leaves of the default config despite types.attrs not doing any merging. + jibriConfig = recursiveUpdate defaultJibriConfig cfg.config; + configFile = pkgs.writeText "jibri.conf" (toHOCON { jibri = jibriConfig; }); +in +{ + options.services.jibri = with types; { + enable = mkEnableOption "Jitsi BRoadcasting Infrastructure. Currently Jibri must be run on a host that is also running <option>services.jitsi-meet.enable</option>, so for most use cases it will be simpler to run <option>services.jitsi-meet.jibri.enable</option>"; + config = mkOption { + type = attrs; + default = { }; + description = '' + Jibri configuration. + See <link xlink:href="https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf" /> + for default configuration with comments. + ''; + }; + + finalizeScript = mkOption { + type = types.path; + default = pkgs.writeScript "finalize_recording.sh" '' + #!/bin/sh + + RECORDINGS_DIR=$1 + + echo "This is a dummy finalize script" > /tmp/finalize.out + echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out + echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out + echo "or storage provider, etc.) in this script" >> /tmp/finalize.out + + exit 0 + ''; + defaultText = literalExpression '' + pkgs.writeScript "finalize_recording.sh" '''''' + #!/bin/sh + + RECORDINGS_DIR=$1 + + echo "This is a dummy finalize script" > /tmp/finalize.out + echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out + echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out + echo "or storage provider, etc.) in this script" >> /tmp/finalize.out + + exit 0 + ''''''; + ''; + example = literalExpression '' + pkgs.writeScript "finalize_recording.sh" '''''' + #!/bin/sh + RECORDINGS_DIR=$1 + ''${pkgs.rclone}/bin/rclone copy $RECORDINGS_DIR RCLONE_REMOTE:jibri-recordings/ -v --log-file=/var/log/jitsi/jibri/recording-upload.txt + exit 0 + ''''''; + ''; + description = '' + This script runs when jibri finishes recording a video of a conference. + ''; + }; + + ignoreCert = mkOption { + type = bool; + default = false; + example = true; + description = '' + Whether to enable the flag "--ignore-certificate-errors" for the Chromium browser opened by Jibri. + Intended for use in automated tests or anywhere else where using a verified cert for Jitsi-Meet is not possible. + ''; + }; + + xmppEnvironments = mkOption { + description = '' + XMPP servers to connect to. + ''; + example = literalExpression '' + "jitsi-meet" = { + xmppServerHosts = [ "localhost" ]; + xmppDomain = config.services.jitsi-meet.hostName; + + control.muc = { + domain = "internal.''${config.services.jitsi-meet.hostName}"; + roomName = "JibriBrewery"; + nickname = "jibri"; + }; + + control.login = { + domain = "auth.''${config.services.jitsi-meet.hostName}"; + username = "jibri"; + passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret"; + }; + + call.login = { + domain = "recorder.''${config.services.jitsi-meet.hostName}"; + username = "recorder"; + passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret"; + }; + + usageTimeout = "0"; + disableCertificateVerification = true; + stripFromRoomDomain = "conference."; + }; + ''; + default = { }; + type = attrsOf (submodule ({ name, ... }: { + options = { + xmppServerHosts = mkOption { + type = listOf str; + example = [ "xmpp.example.org" ]; + description = '' + Hostnames of the XMPP servers to connect to. + ''; + }; + xmppDomain = mkOption { + type = str; + example = "xmpp.example.org"; + description = '' + The base XMPP domain. + ''; + }; + control.muc.domain = mkOption { + type = str; + description = '' + The domain part of the MUC to connect to for control. + ''; + }; + control.muc.roomName = mkOption { + type = str; + default = "JibriBrewery"; + description = '' + The room name of the MUC to connect to for control. + ''; + }; + control.muc.nickname = mkOption { + type = str; + default = "jibri"; + description = '' + The nickname for this Jibri instance in the MUC. + ''; + }; + control.login.domain = mkOption { + type = str; + description = '' + The domain part of the JID for this Jibri instance. + ''; + }; + control.login.username = mkOption { + type = str; + default = "jvb"; + description = '' + User part of the JID. + ''; + }; + control.login.passwordFile = mkOption { + type = str; + example = "/run/keys/jibri-xmpp1"; + description = '' + File containing the password for the user. + ''; + }; + + call.login.domain = mkOption { + type = str; + example = "recorder.xmpp.example.org"; + description = '' + The domain part of the JID for the recorder. + ''; + }; + call.login.username = mkOption { + type = str; + default = "recorder"; + description = '' + User part of the JID for the recorder. + ''; + }; + call.login.passwordFile = mkOption { + type = str; + example = "/run/keys/jibri-recorder-xmpp1"; + description = '' + File containing the password for the user. + ''; + }; + disableCertificateVerification = mkOption { + type = bool; + default = false; + description = '' + Whether to skip validation of the server's certificate. + ''; + }; + + stripFromRoomDomain = mkOption { + type = str; + default = "0"; + example = "conference."; + description = '' + The prefix to strip from the room's JID domain to derive the call URL. + ''; + }; + usageTimeout = mkOption { + type = str; + default = "0"; + example = "1 hour"; + description = '' + The duration that the Jibri session can be. + A value of zero means indefinitely. + ''; + }; + }; + + config = + let + nick = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] ( + config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}" + )); + in + { + call.login.username = nick; + control.muc.nickname = nick; + }; + })); + }; + }; + + config = mkIf cfg.enable { + users.groups.jibri = { }; + users.groups.plugdev = { }; + users.users.jibri = { + isSystemUser = true; + group = "jibri"; + home = "/var/lib/jibri"; + extraGroups = [ "jitsi-meet" "adm" "audio" "video" "plugdev" ]; + }; + + systemd.services.jibri-xorg = { + description = "Jitsi Xorg Process"; + + after = [ "network.target" ]; + wantedBy = [ "jibri.service" "jibri-icewm.service" ]; + + preStart = '' + cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri + mv /var/lib/jibri/{,.}asoundrc + ''; + + environment.DISPLAY = ":0"; + serviceConfig = { + Type = "simple"; + + User = "jibri"; + Group = "jibri"; + KillMode = "process"; + Restart = "on-failure"; + RestartPreventExitStatus = 255; + + StateDirectory = "jibri"; + + 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"; + }; + }; + + systemd.services.jibri-icewm = { + description = "Jitsi Window Manager"; + + requires = [ "jibri-xorg.service" ]; + after = [ "jibri-xorg.service" ]; + wantedBy = [ "jibri.service" ]; + + environment.DISPLAY = ":0"; + serviceConfig = { + Type = "simple"; + + User = "jibri"; + Group = "jibri"; + Restart = "on-failure"; + RestartPreventExitStatus = 255; + + StateDirectory = "jibri"; + + ExecStart = "${pkgs.icewm}/bin/icewm-session"; + }; + }; + + systemd.services.jibri = { + description = "Jibri Process"; + + requires = [ "jibri-icewm.service" "jibri-xorg.service" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + path = with pkgs; [ chromedriver chromium ffmpeg-full ]; + + script = (concatStrings (mapAttrsToList + (name: env: '' + export ${toVarName "${name}_control"}=$(cat ${env.control.login.passwordFile}) + export ${toVarName "${name}_call"}=$(cat ${env.call.login.passwordFile}) + '') + cfg.xmppEnvironments)) + + '' + ${pkgs.jre8_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 + ''; + + environment.HOME = "/var/lib/jibri"; + + serviceConfig = { + Type = "simple"; + + User = "jibri"; + Group = "jibri"; + Restart = "always"; + RestartPreventExitStatus = 255; + + StateDirectory = "jibri"; + }; + }; + + systemd.tmpfiles.rules = [ + "d /var/log/jitsi/jibri 755 jibri jibri" + ]; + + + + # Configure Chromium to not show the "Chrome is being controlled by automatic test software" message. + environment.etc."chromium/policies/managed/managed_policies.json".text = builtins.toJSON { CommandLineFlagSecurityWarningsEnabled = false; }; + 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." ]; + + boot = { + extraModprobeConfig = '' + options snd-aloop enable=1,1,1,1,1,1,1,1 + ''; + kernelModules = [ "snd-aloop" ]; + }; + }; + + meta.maintainers = lib.teams.jitsi.members; +} |