at 24.11-pre 13 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.jibri; 7 8 format = pkgs.formats.hocon { }; 9 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; 13 14 defaultJibriConfig = { 15 id = ""; 16 single-use-mode = false; 17 18 api = { 19 http.external-api-port = 2222; 20 http.internal-api-port = 3333; 21 22 xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: { 23 inherit name; 24 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 }; 32 33 control-login = { 34 domain = env.control.login.domain; 35 username = env.control.login.username; 36 password = format.lib.mkSubstitution (toVarName "${name}_control"); 37 }; 38 39 call-login = { 40 domain = env.call.login.domain; 41 username = env.call.login.username; 42 password = format.lib.mkSubstitution (toVarName "${name}_call"); 43 }; 44 45 strip-from-room-domain = env.stripFromRoomDomain; 46 usage-timeout = env.usageTimeout; 47 trust-all-xmpp-certs = env.disableCertificateVerification; 48 }); 49 }; 50 51 recording = { 52 recordings-directory = "/tmp/recordings"; 53 finalize-script = "${cfg.finalizeScript}"; 54 }; 55 56 streaming.rtmp-allow-list = [ ".*" ]; 57 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"; 68 69 70 stats.enable-stats-d = true; 71 webhook.subscribers = [ ]; 72 73 jwt-info = { }; 74 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; }; 84in 85{ 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 }; 97 98 finalizeScript = mkOption { 99 type = types.path; 100 default = pkgs.writeScript "finalize_recording.sh" '' 101 #!/bin/sh 102 103 RECORDINGS_DIR=$1 104 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 109 110 exit 0 111 ''; 112 defaultText = literalExpression '' 113 pkgs.writeScript "finalize_recording.sh" '''''' 114 #!/bin/sh 115 116 RECORDINGS_DIR=$1 117 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 122 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 }; 138 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 }; 148 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; 157 158 control.muc = { 159 domain = "internal.''${config.services.jitsi-meet.hostName}"; 160 roomName = "JibriBrewery"; 161 nickname = "jibri"; 162 }; 163 164 control.login = { 165 domain = "auth.''${config.services.jitsi-meet.hostName}"; 166 username = "jibri"; 167 passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret"; 168 }; 169 170 call.login = { 171 domain = "recorder.''${config.services.jitsi-meet.hostName}"; 172 username = "recorder"; 173 passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret"; 174 }; 175 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 }; 238 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 }; 267 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 }; 286 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 }; 300 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 }; 310 311 systemd.services.jibri-xorg = { 312 description = "Jitsi Xorg Process"; 313 314 after = [ "network.target" ]; 315 wantedBy = [ "jibri.service" "jibri-icewm.service" ]; 316 317 preStart = '' 318 cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri 319 mv /var/lib/jibri/{,.}asoundrc 320 ''; 321 322 environment.DISPLAY = ":0"; 323 serviceConfig = { 324 Type = "simple"; 325 326 User = "jibri"; 327 Group = "jibri"; 328 KillMode = "process"; 329 Restart = "on-failure"; 330 RestartPreventExitStatus = 255; 331 332 StateDirectory = "jibri"; 333 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 }; 337 338 systemd.services.jibri-icewm = { 339 description = "Jitsi Window Manager"; 340 341 requires = [ "jibri-xorg.service" ]; 342 after = [ "jibri-xorg.service" ]; 343 wantedBy = [ "jibri.service" ]; 344 345 environment.DISPLAY = ":0"; 346 serviceConfig = { 347 Type = "simple"; 348 349 User = "jibri"; 350 Group = "jibri"; 351 Restart = "on-failure"; 352 RestartPreventExitStatus = 255; 353 354 StateDirectory = "jibri"; 355 356 ExecStart = "${pkgs.icewm}/bin/icewm-session"; 357 }; 358 }; 359 360 systemd.services.jibri = { 361 description = "Jibri Process"; 362 363 requires = [ "jibri-icewm.service" "jibri-xorg.service" ]; 364 after = [ "network.target" ]; 365 wantedBy = [ "multi-user.target" ]; 366 367 path = with pkgs; [ chromedriver chromium ffmpeg-full ]; 368 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 ''; 378 379 environment.HOME = "/var/lib/jibri"; 380 381 serviceConfig = { 382 Type = "simple"; 383 384 User = "jibri"; 385 Group = "jibri"; 386 Restart = "always"; 387 RestartPreventExitStatus = 255; 388 389 StateDirectory = "jibri"; 390 }; 391 }; 392 393 systemd.tmpfiles.settings."10-jibri"."/var/log/jitsi/jibri".d = { 394 user = "jibri"; 395 group = "jibri"; 396 mode = "755"; 397 }; 398 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." ]; 402 403 boot = { 404 extraModprobeConfig = '' 405 options snd-aloop enable=1,1,1,1,1,1,1,1 406 ''; 407 kernelModules = [ "snd-aloop" ]; 408 }; 409 }; 410 411 meta.maintainers = lib.teams.jitsi.members; 412}