lol

nixos/jitsi-meet: add jibri.enable

This option enables a jibri service on the same host that is running
jitsi-meet. It was written, along with the jibri module, by @puckipedia
for nixcon-video-infra 2020.
Co-authored-by: Puck Meerburg <puck@puck.moe>

authored by

Cleeyv
Puck Meerburg
and committed by
tomberek
ff8ed900 f220223a

+419 -5
+353
nixos/modules/services/networking/jibri/default.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + with lib; 4 + 5 + let 6 + cfg = config.services.jibri; 7 + 8 + # Copied from the jitsi-videobridge.nix file. 9 + toHOCON = x: 10 + if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}") 11 + else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}" 12 + else if isList x then "[${ concatMapStringsSep "," toHOCON x }]" 13 + else builtins.toJSON x; 14 + 15 + # We're passing passwords in environment variables that have names generated 16 + # from an attribute name, which may not be a valid bash identifier. 17 + toVarName = s: "XMPP_PASSWORD_" + stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s; 18 + 19 + defaultJibriConfig = { 20 + id = ""; 21 + single-use-mode = false; 22 + 23 + api = { 24 + http.external-api-port = 2222; 25 + http.internal-api-port = 3333; 26 + 27 + xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: { 28 + inherit name; 29 + 30 + xmpp-server-hosts = env.xmppServerHosts; 31 + xmpp-domain = env.xmppDomain; 32 + control-muc = { 33 + domain = env.control.muc.domain; 34 + room-name = env.control.muc.roomName; 35 + nickname = env.control.muc.nickname; 36 + }; 37 + 38 + control-login = { 39 + domain = env.control.login.domain; 40 + username = env.control.login.username; 41 + password.__hocon_envvar = toVarName "${name}_control"; 42 + }; 43 + 44 + call-login = { 45 + domain = env.call.login.domain; 46 + username = env.call.login.username; 47 + password.__hocon_envvar = toVarName "${name}_call"; 48 + }; 49 + 50 + strip-from-room-domain = env.stripFromRoomDomain; 51 + usage-timeout = env.usageTimeout; 52 + trust-all-xmpp-certs = env.disableCertificateVerification; 53 + }); 54 + }; 55 + 56 + recording = { 57 + recordings-directory = "/tmp/recordings"; 58 + finalize-script = "${cfg.finalizeScript}"; 59 + }; 60 + 61 + streaming.rtmp-allow-list = [ ".*" ]; 62 + 63 + chrome.flags = [ 64 + "--use-fake-ui-for-media-stream" 65 + "--start-maximized" 66 + "--kiosk" 67 + "--enabled" 68 + "--disable-infobars" 69 + "--autoplay-policy=no-user-gesture-required" 70 + ]; 71 + 72 + stats.enable-stats-d = true; 73 + webhook.subscribers = [ ]; 74 + 75 + jwt-info = { }; 76 + 77 + call-status-checks = { 78 + no-media-timout = "30 seconds"; 79 + all-muted-timeout = "10 minutes"; 80 + default-call-empty-timout = "30 seconds"; 81 + }; 82 + }; 83 + # Allow overriding leaves of the default config despite types.attrs not doing any merging. 84 + jibriConfig = recursiveUpdate defaultJibriConfig cfg.config; 85 + configFile = pkgs.writeText "jibri.conf" (toHOCON { jibri = jibriConfig; }); 86 + in 87 + { 88 + options.services.jibri = with types; { 89 + enable = mkEnableOption "Jitsi BRoadcasting Infrastructure. Currently the only supported way of enabling a jibri instance is with <option>services.jitsi-meet.jibri.enable</option>"; 90 + config = mkOption { 91 + type = attrs; 92 + default = { }; 93 + description = '' 94 + Jibri configuration. 95 + See <link xlink:href="https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf" /> 96 + for default configuration with comments. 97 + ''; 98 + }; 99 + 100 + finalizeScript = mkOption { 101 + type = types.path; 102 + default = pkgs.writeScript "finalize_recording.sh" '' 103 + #!/bin/sh 104 + 105 + RECORDINGS_DIR=$1 106 + 107 + echo "This is a dummy finalize script" > /tmp/finalize.out 108 + echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out 109 + echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out 110 + echo "or storage provider, etc.) in this script" >> /tmp/finalize.out 111 + 112 + exit 0 113 + ''; 114 + description = '' 115 + This script runs when jibri finishes recording a video of a conference. 116 + ''; 117 + }; 118 + 119 + xmppEnvironments = mkOption { 120 + description = '' 121 + XMPP servers to connect to. 122 + ''; 123 + default = { }; 124 + type = attrsOf (submodule ({ name, ... }: { 125 + options = { 126 + xmppServerHosts = mkOption { 127 + type = listOf str; 128 + example = [ "xmpp.example.org" ]; 129 + description = '' 130 + Hostnames of the XMPP servers to connect to. 131 + ''; 132 + }; 133 + xmppDomain = mkOption { 134 + type = str; 135 + example = "xmpp.example.org"; 136 + description = '' 137 + The base XMPP domain. 138 + ''; 139 + }; 140 + control.muc.domain = mkOption { 141 + type = str; 142 + description = '' 143 + The domain part of the MUC to connect to for control. 144 + ''; 145 + }; 146 + control.muc.roomName = mkOption { 147 + type = str; 148 + default = "JibriBrewery"; 149 + description = '' 150 + The room name of the MUC to connect to for control. 151 + ''; 152 + }; 153 + control.muc.nickname = mkOption { 154 + type = str; 155 + default = "jibri"; 156 + description = '' 157 + The nickname for this Jibri instance in the MUC. 158 + ''; 159 + }; 160 + control.login.domain = mkOption { 161 + type = str; 162 + description = '' 163 + The domain part of the JID for this Jibri instance. 164 + ''; 165 + }; 166 + control.login.username = mkOption { 167 + type = str; 168 + default = "jvb"; 169 + description = '' 170 + User part of the JID. 171 + ''; 172 + }; 173 + control.login.passwordFile = mkOption { 174 + type = str; 175 + example = "/run/keys/jibri-xmpp1"; 176 + description = '' 177 + File containing the password for the user. 178 + ''; 179 + }; 180 + 181 + call.login.domain = mkOption { 182 + type = str; 183 + example = "recorder.xmpp.example.org"; 184 + description = '' 185 + The domain part of the JID for the recorder. 186 + ''; 187 + }; 188 + call.login.username = mkOption { 189 + type = str; 190 + default = "recorder"; 191 + description = '' 192 + User part of the JID for the recorder. 193 + ''; 194 + }; 195 + call.login.passwordFile = mkOption { 196 + type = str; 197 + example = "/run/keys/jibri-recorder-xmpp1"; 198 + description = '' 199 + File containing the password for the user. 200 + ''; 201 + }; 202 + disableCertificateVerification = mkOption { 203 + type = bool; 204 + default = false; 205 + description = '' 206 + Whether to skip validation of the server's certificate. 207 + ''; 208 + }; 209 + 210 + stripFromRoomDomain = mkOption { 211 + type = str; 212 + default = "0"; 213 + example = "conference."; 214 + description = '' 215 + The prefix to strip from the room's JID domain to derive the call URL. 216 + ''; 217 + }; 218 + usageTimeout = mkOption { 219 + type = str; 220 + default = "0"; 221 + example = "1 hour"; 222 + description = '' 223 + The duration that the Jibri session can be. 224 + A value of zero means indefinitely. 225 + ''; 226 + }; 227 + }; 228 + 229 + config = 230 + let 231 + nick = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] ( 232 + config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}" 233 + )); 234 + in 235 + { 236 + call.login.username = nick; 237 + control.muc.nickname = nick; 238 + }; 239 + })); 240 + }; 241 + }; 242 + 243 + config = mkIf cfg.enable { 244 + users.groups.jibri = { }; 245 + users.users.jibri = { 246 + isSystemUser = true; 247 + group = "jibri"; 248 + home = "/var/lib/jibri"; 249 + extraGroups = [ "jitsi-meet" "adm" "audio" "video" "plugdev" ]; 250 + }; 251 + 252 + systemd.services.jibri-xorg = { 253 + description = "Jitsi Xorg Process"; 254 + 255 + after = [ "network.target" ]; 256 + wantedBy = [ "jibri.service" "jibri-icewm.service" ]; 257 + 258 + preStart = '' 259 + cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri 260 + mv /var/lib/jibri/{,.}asoundrc 261 + ''; 262 + 263 + environment.DISPLAY = ":0"; 264 + serviceConfig = { 265 + Type = "simple"; 266 + 267 + User = "jibri"; 268 + Group = "jibri"; 269 + KillMode = "process"; 270 + Restart = "on-failure"; 271 + RestartPreventExitStatus = 255; 272 + 273 + StateDirectory = "jibri"; 274 + 275 + 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"; 276 + }; 277 + }; 278 + 279 + systemd.services.jibri-icewm = { 280 + description = "Jitsi Window Manager"; 281 + 282 + requires = [ "jibri-xorg.service" ]; 283 + after = [ "jibri-xorg.service" ]; 284 + wantedBy = [ "jibri.service" ]; 285 + 286 + environment.DISPLAY = ":0"; 287 + serviceConfig = { 288 + Type = "simple"; 289 + 290 + User = "jibri"; 291 + Group = "jibri"; 292 + Restart = "on-failure"; 293 + RestartPreventExitStatus = 255; 294 + 295 + StateDirectory = "jibri"; 296 + 297 + ExecStart = "${pkgs.icewm}/bin/icewm-session"; 298 + }; 299 + }; 300 + 301 + systemd.services.jibri = { 302 + description = "Jibri Process"; 303 + 304 + requires = [ "jibri-icewm.service" "jibri-xorg.service" ]; 305 + after = [ "network.target" ]; 306 + wantedBy = [ "multi-user.target" ]; 307 + 308 + path = [ pkgs.chromedriver pkgs.chromium pkgs.ffmpeg-full ]; 309 + 310 + script = (concatStrings (mapAttrsToList 311 + (name: env: '' 312 + export ${toVarName "${name}_control"}=$(cat ${env.control.login.passwordFile}) 313 + export ${toVarName "${name}_call"}=$(cat ${env.call.login.passwordFile}) 314 + '') 315 + cfg.xmppEnvironments)) 316 + + '' 317 + ${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 318 + ''; 319 + 320 + environment.HOME = "/var/lib/jibri"; 321 + 322 + serviceConfig = { 323 + Type = "simple"; 324 + 325 + User = "jibri"; 326 + Group = "jibri"; 327 + Restart = "always"; 328 + RestartPreventExitStatus = 255; 329 + 330 + StateDirectory = "jibri"; 331 + }; 332 + }; 333 + 334 + systemd.tmpfiles.rules = 335 + [ 336 + "d /var/log/jitsi/jibri 755 jibri" 337 + ]; 338 + 339 + 340 + 341 + # Configure Chromium to not show the "Chrome is being controlled by automatic test software" message. 342 + environment.etc."chromium/policies/managed/managed_policies.json".text = builtins.toJSON { CommandLineFlagSecurityWarningsEnabled = false; }; 343 + 344 + boot = { 345 + extraModprobeConfig = '' 346 + options snd-aloop enable=1,1,1,1,1,1,1,1 347 + ''; 348 + kernelModules = [ "snd-aloop" ]; 349 + }; 350 + }; 351 + 352 + meta.maintainers = with lib.maintainers; [ ]; 353 + }
+66 -5
nixos/modules/services/web-apps/jitsi-meet.nix
··· 38 38 }; 39 39 bosh = "//${cfg.hostName}/http-bind"; 40 40 websocket = "wss://${cfg.hostName}/xmpp-websocket"; 41 + 42 + fileRecordingsEnabled = true; 43 + liveStreamingEnabled = true; 44 + hiddenDomain = "recorder.${cfg.hostName}"; 41 45 }; 42 46 in 43 47 { ··· 130 134 ''; 131 135 }; 132 136 137 + jibri.enable = mkOption { 138 + type = bool; 139 + default = false; 140 + description = '' 141 + Whether to enable a Jibri instance and configure it to connect to Prosody. 142 + 143 + Although additional configuration is possible with <option>services.jibri</option>, this is 144 + currently not very supported and most users will only want to edit the finalize recordings 145 + script at <option>services.jibri.finalizeScript</option>. 146 + ''; 147 + }; 148 + 133 149 nginx.enable = mkOption { 134 150 type = bool; 135 151 default = true; ··· 229 245 key = "/var/lib/jitsi-meet/jitsi-meet.key"; 230 246 }; 231 247 }; 248 + virtualHosts."recorder.${cfg.hostName}" = { 249 + enabled = true; 250 + domain = "recorder.${cfg.hostName}"; 251 + extraConfig = '' 252 + authentication = "internal_plain" 253 + c2s_require_encryption = false 254 + ''; 255 + }; 232 256 }; 233 257 systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable { 234 258 EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; ··· 243 267 systemd.services.jitsi-meet-init-secrets = { 244 268 wantedBy = [ "multi-user.target" ]; 245 269 before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service"); 270 + path = [ config.services.prosody.package ]; 246 271 serviceConfig = { 247 272 Type = "oneshot"; 248 273 }; 249 274 250 275 script = let 251 - secrets = [ "jicofo-component-secret" "jicofo-user-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); 276 + secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); 252 277 videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret"; 253 278 in 254 279 '' ··· 267 292 chmod 640 secrets-env 268 293 '' 269 294 + optionalString cfg.prosody.enable '' 270 - ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" 271 - ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" 272 - ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} 295 + prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" 296 + prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" 297 + prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} 298 + prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)" 299 + prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)" 273 300 274 301 # generate self-signed certificates 275 302 if [ ! -f /var/lib/jitsi-meet.crt ]; then ··· 380 407 userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret"; 381 408 componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret"; 382 409 bridgeMuc = "jvbbrewery@internal.${cfg.hostName}"; 383 - config = { 410 + config = mkMerge [{ 384 411 "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true"; 412 + } (lib.mkIf cfg.jibri.enable { 413 + "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}"; 414 + "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90"; 415 + })]; 416 + }; 417 + 418 + services.jibri = mkIf cfg.jibri.enable { 419 + enable = true; 420 + 421 + xmppEnvironments."jitsi-meet" = { 422 + xmppServerHosts = [ "localhost" ]; 423 + xmppDomain = cfg.hostName; 424 + 425 + control.muc = { 426 + domain = "internal.${cfg.hostName}"; 427 + roomName = "JibriBrewery"; 428 + nickname = "jibri"; 429 + }; 430 + 431 + control.login = { 432 + domain = "auth.${cfg.hostName}"; 433 + username = "jibri"; 434 + passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret"; 435 + }; 436 + 437 + call.login = { 438 + domain = "recorder.${cfg.hostName}"; 439 + username = "recorder"; 440 + passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret"; 441 + }; 442 + 443 + usageTimeout = "0"; 444 + disableCertificateVerification = true; 445 + stripFromRoomDomain = "conference."; 385 446 }; 386 447 }; 387 448 };