diff options
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/from_md/release-notes/rl-2111.section.xml | 8 | ||||
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2111.section.md | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/networking/jibri/default.nix | 417 | ||||
-rw-r--r-- | nixos/modules/services/networking/jibri/logging.properties-journal | 32 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/jitsi-meet.nix | 73 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/jibri.nix | 69 |
8 files changed, 597 insertions, 6 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index aa64080f470..908d2cf7fbe 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -138,6 +138,14 @@ </listitem> <listitem> <para> + <link xlink:href="https://github.com/jitsi/jibri">Jibri</link>, + a service for recording or streaming a Jitsi Meet conference. + Available as + <link xlink:href="options.html#opt-services.jibri.enable">services.jibri</link>. + </para> + </listitem> + <listitem> + <para> <link xlink:href="https://www.isc.org/kea/">Kea</link>, ISCs 2nd generation DHCP and DDNS server suite. Available at <link xlink:href="options.html#opt-services.kea">services.kea</link>. diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 7caa3c9083c..645bcf6d477 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -45,6 +45,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [geoipupdate](https://github.com/maxmind/geoipupdate), a GeoIP database updater from MaxMind. Available as [services.geoipupdate](options.html#opt-services.geoipupdate.enable). +- [Jibri](https://github.com/jitsi/jibri), a service for recording or streaming a Jitsi Meet conference. Available as [services.jibri](options.html#opt-services.jibri.enable). + - [Kea](https://www.isc.org/kea/), ISCs 2nd generation DHCP and DDNS server suite. Available at [services.kea](options.html#opt-services.kea). - [owncast](https://owncast.online/), self-hosted video live streaming solution. Available at [services.owncast](options.html#opt-services.owncast). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index d4aaa7ebd22..41a7db17c32 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -756,6 +756,7 @@ ./services/networking/iscsi/root-initiator.nix ./services/networking/iscsi/target.nix ./services/networking/iwd.nix + ./services/networking/jibri/default.nix ./services/networking/jicofo.nix ./services/networking/jitsi-videobridge.nix ./services/networking/kea.nix diff --git a/nixos/modules/services/networking/jibri/default.nix b/nixos/modules/services/networking/jibri/default.nix new file mode 100644 index 00000000000..96832b0eb55 --- /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; +} diff --git a/nixos/modules/services/networking/jibri/logging.properties-journal b/nixos/modules/services/networking/jibri/logging.properties-journal new file mode 100644 index 00000000000..61eadbfddcb --- /dev/null +++ b/nixos/modules/services/networking/jibri/logging.properties-journal @@ -0,0 +1,32 @@ +handlers = java.util.logging.FileHandler + +java.util.logging.FileHandler.level = FINE +java.util.logging.FileHandler.pattern = /var/log/jitsi/jibri/log.%g.txt +java.util.logging.FileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter +java.util.logging.FileHandler.count = 10 +java.util.logging.FileHandler.limit = 10000000 + +org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.level = FINE +org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.pattern = /var/log/jitsi/jibri/ffmpeg.%g.txt +org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter +org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.count = 10 +org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.limit = 10000000 + +org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.level = FINE +org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.pattern = /var/log/jitsi/jibri/pjsua.%g.txt +org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter +org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.count = 10 +org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.limit = 10000000 + +org.jitsi.jibri.selenium.util.BrowserFileHandler.level = FINE +org.jitsi.jibri.selenium.util.BrowserFileHandler.pattern = /var/log/jitsi/jibri/browser.%g.txt +org.jitsi.jibri.selenium.util.BrowserFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter +org.jitsi.jibri.selenium.util.BrowserFileHandler.count = 10 +org.jitsi.jibri.selenium.util.BrowserFileHandler.limit = 10000000 + +org.jitsi.level = FINE +org.jitsi.jibri.config.level = INFO + +org.glassfish.level = INFO +org.osgi.level = INFO +org.jitsi.xmpp.level = INFO diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 2eacd87ae6f..2f1c4acec1e 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -38,6 +38,10 @@ let }; bosh = "//${cfg.hostName}/http-bind"; websocket = "wss://${cfg.hostName}/xmpp-websocket"; + + fileRecordingsEnabled = true; + liveStreamingEnabled = true; + hiddenDomain = "recorder.${cfg.hostName}"; }; in { @@ -48,7 +52,7 @@ in type = str; example = "meet.example.org"; description = '' - Hostname of the Jitsi Meet instance. + FQDN of the Jitsi Meet instance. ''; }; @@ -130,6 +134,17 @@ in ''; }; + jibri.enable = mkOption { + type = bool; + default = false; + description = '' + Whether to enable a Jibri instance and configure it to connect to Prosody. + + Additional configuration is possible with <option>services.jibri</option>, and + <option>services.jibri.finalizeScript</option> is especially useful. + ''; + }; + nginx.enable = mkOption { type = bool; default = true; @@ -229,6 +244,14 @@ in key = "/var/lib/jitsi-meet/jitsi-meet.key"; }; }; + virtualHosts."recorder.${cfg.hostName}" = { + enabled = true; + domain = "recorder.${cfg.hostName}"; + extraConfig = '' + authentication = "internal_plain" + c2s_require_encryption = false + ''; + }; }; systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable { EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; @@ -243,12 +266,13 @@ in systemd.services.jitsi-meet-init-secrets = { wantedBy = [ "multi-user.target" ]; before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service"); + path = [ config.services.prosody.package ]; serviceConfig = { Type = "oneshot"; }; script = let - secrets = [ "jicofo-component-secret" "jicofo-user-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); + secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret"; in '' @@ -267,9 +291,11 @@ in chmod 640 secrets-env '' + optionalString cfg.prosody.enable '' - ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" - ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" - ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} + prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" + prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" + prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} + prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)" + prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)" # generate self-signed certificates if [ ! -f /var/lib/jitsi-meet.crt ]; then @@ -380,8 +406,43 @@ in userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret"; componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret"; bridgeMuc = "jvbbrewery@internal.${cfg.hostName}"; - config = { + config = mkMerge [{ "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true"; + #} (lib.mkIf cfg.jibri.enable { + } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) { + "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}"; + "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90"; + })]; + }; + + services.jibri = mkIf cfg.jibri.enable { + enable = true; + + xmppEnvironments."jitsi-meet" = { + xmppServerHosts = [ "localhost" ]; + xmppDomain = cfg.hostName; + + control.muc = { + domain = "internal.${cfg.hostName}"; + roomName = "JibriBrewery"; + nickname = "jibri"; + }; + + control.login = { + domain = "auth.${cfg.hostName}"; + username = "jibri"; + passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret"; + }; + + call.login = { + domain = "recorder.${cfg.hostName}"; + username = "recorder"; + passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret"; + }; + + usageTimeout = "0"; + disableCertificateVerification = true; + stripFromRoomDomain = "conference."; }; }; }; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 2b276851644..b0623896a2d 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -207,6 +207,7 @@ in jackett = handleTest ./jackett.nix {}; jellyfin = handleTest ./jellyfin.nix {}; jenkins = handleTest ./jenkins.nix {}; + jibri = handleTest ./jibri.nix {}; jirafeau = handleTest ./jirafeau.nix {}; jitsi-meet = handleTest ./jitsi-meet.nix {}; k3s = handleTest ./k3s.nix {}; diff --git a/nixos/tests/jibri.nix b/nixos/tests/jibri.nix new file mode 100644 index 00000000000..3dd28e6aac1 --- /dev/null +++ b/nixos/tests/jibri.nix @@ -0,0 +1,69 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "jibri"; + meta = with pkgs.lib; { + maintainers = teams.jitsi.members; + }; + + machine = { config, pkgs, ... }: { + virtualisation.memorySize = 5120; + + services.jitsi-meet = { + enable = true; + hostName = "machine"; + jibri.enable = true; + }; + services.jibri.ignoreCert = true; + services.jitsi-videobridge.openFirewall = true; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + services.nginx.virtualHosts.machine = { + enableACME = true; + forceSSL = true; + }; + + security.acme.email = "me@example.org"; + security.acme.acceptTerms = true; + security.acme.server = "https://example.com"; # self-signed only + }; + + testScript = '' + machine.wait_for_unit("jitsi-videobridge2.service") + machine.wait_for_unit("jicofo.service") + machine.wait_for_unit("nginx.service") + machine.wait_for_unit("prosody.service") + machine.wait_for_unit("jibri.service") + + machine.wait_until_succeeds( + "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'", timeout=30 + ) + machine.wait_until_succeeds( + "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.machine'", timeout=31 + ) + machine.wait_until_succeeds( + "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.machine'", timeout=32 + ) + machine.wait_until_succeeds( + "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jibri@auth.machine'", timeout=33 + ) + machine.wait_until_succeeds( + "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Joined MUC: jibribrewery@internal.machine'", timeout=34 + ) + + assert '"busyStatus":"IDLE","health":{"healthStatus":"HEALTHY"' in machine.succeed( + "curl -X GET http://machine:2222/jibri/api/v1.0/health" + ) + machine.succeed( + """curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/startService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'""" + ) + machine.wait_until_succeeds( + "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'File recording service transitioning from state Starting up to Running'", timeout=35 + ) + machine.succeed( + """sleep 15 && curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/stopService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'""" + ) + machine.wait_until_succeeds( + "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Recording finalize script finished with exit value 0'", timeout=36 + ) + ''; +}) |