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