diff --git a/maintainers/team-list.nix b/maintainers/team-list.nix index b872142c313c..9885c9cfcf99 100644 --- a/maintainers/team-list.nix +++ b/maintainers/team-list.nix @@ -429,6 +429,7 @@ with lib.maintainers; { cleeyv ryantm lassulus + yayayayaka ]; scope = "Maintain Jitsi."; shortName = "Jitsi"; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 674707ec69e7..820ce54ddeb6 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -971,6 +971,7 @@ ./services/networking/iwd.nix ./services/networking/jibri/default.nix ./services/networking/jicofo.nix + ./services/networking/jigasi.nix ./services/networking/jitsi-videobridge.nix ./services/networking/jool.nix ./services/networking/kea.nix diff --git a/nixos/modules/services/networking/jigasi.nix b/nixos/modules/services/networking/jigasi.nix new file mode 100644 index 000000000000..8d2d25c6edfc --- /dev/null +++ b/nixos/modules/services/networking/jigasi.nix @@ -0,0 +1,237 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.jigasi; + homeDirName = "jigasi-home"; + stateDir = "/tmp"; + sipCommunicatorPropertiesFile = "${stateDir}/${homeDirName}/sip-communicator.properties"; + sipCommunicatorPropertiesFileUnsubstituted = "${pkgs.jigasi}/etc/jitsi/jigasi/sip-communicator.properties"; +in +{ + options.services.jigasi = with types; { + enable = mkEnableOption "Jitsi Gateway to SIP - component of Jitsi Meet"; + + xmppHost = mkOption { + type = str; + example = "localhost"; + description = '' + Hostname of the XMPP server to connect to. + ''; + }; + + xmppDomain = mkOption { + type = nullOr str; + example = "meet.example.org"; + description = '' + Domain name of the XMMP server to which to connect as a component. + + If null, is used. + ''; + }; + + componentPasswordFile = mkOption { + type = str; + example = "/run/keys/jigasi-component"; + description = '' + Path to file containing component secret. + ''; + }; + + userName = mkOption { + type = str; + default = "callcontrol"; + description = '' + User part of the JID for XMPP user connection. + ''; + }; + + userDomain = mkOption { + type = str; + example = "internal.meet.example.org"; + description = '' + Domain part of the JID for XMPP user connection. + ''; + }; + + userPasswordFile = mkOption { + type = str; + example = "/run/keys/jigasi-user"; + description = '' + Path to file containing password for XMPP user connection. + ''; + }; + + bridgeMuc = mkOption { + type = str; + example = "jigasibrewery@internal.meet.example.org"; + description = '' + JID of the internal MUC used to communicate with Videobridges. + ''; + }; + + defaultJvbRoomName = mkOption { + type = str; + default = ""; + example = "siptest"; + description = '' + Name of the default JVB room that will be joined if no special header is included in SIP invite. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing environment variables to be passed to the jigasi service, + in which secret tokens can be specified securely by defining values for + JIGASI_SIPUSER, + JIGASI_SIPPWD, + JIGASI_SIPSERVER and + JIGASI_SIPPORT. + ''; + }; + + config = mkOption { + type = attrsOf str; + default = { }; + example = literalExample '' + { + "org.jitsi.jigasi.auth.URL" = "XMPP:jitsi-meet.example.com"; + } + ''; + description = '' + Contents of the sip-communicator.properties configuration file for jigasi. + ''; + }; + }; + + config = mkIf cfg.enable { + services.jicofo.config = { + "org.jitsi.jicofo.jigasi.BREWERY" = "${cfg.bridgeMuc}"; + }; + + services.jigasi.config = mapAttrs (_: v: mkDefault v) { + "org.jitsi.jigasi.BRIDGE_MUC" = cfg.bridgeMuc; + }; + + users.groups.jitsi-meet = {}; + + systemd.services.jigasi = let + jigasiProps = { + "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "${stateDir}"; + "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "${homeDirName}"; + "-Djava.util.logging.config.file" = "${pkgs.jigasi}/etc/jitsi/jigasi/logging.properties"; + }; + in + { + description = "Jitsi Gateway to SIP"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + preStart = '' + [ -f "${sipCommunicatorPropertiesFile}" ] && rm -f "${sipCommunicatorPropertiesFile}" + mkdir -p "$(dirname ${sipCommunicatorPropertiesFile})" + temp="${sipCommunicatorPropertiesFile}.unsubstituted" + + export DOMAIN_BASE="${cfg.xmppDomain}" + export JIGASI_XMPP_PASSWORD=$(cat "${cfg.userPasswordFile}") + export JIGASI_DEFAULT_JVB_ROOM_NAME="${cfg.defaultJvbRoomName}" + + # encode the credentials to base64 + export JIGASI_SIPPWD=$(echo -n "$JIGASI_SIPPWD" | base64 -w 0) + export JIGASI_XMPP_PASSWORD_BASE64=$(cat "${cfg.userPasswordFile}" | base64 -w 0) + + cp "${sipCommunicatorPropertiesFileUnsubstituted}" "$temp" + chmod 644 "$temp" + cat <>"$temp" + net.java.sip.communicator.impl.protocol.sip.acc1403273890647.SERVER_PORT=$JIGASI_SIPPORT + net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PREFERRED_TRANSPORT=udp + EOF + chmod 444 "$temp" + + # Replace <<$VAR_NAME>> from example config to $VAR_NAME for environment substitution + sed -i -E \ + 's/<<([^>]+)>>/\$\1/g' \ + "$temp" + + sed -i \ + 's|\(net\.java\.sip\.communicator\.impl\.protocol\.jabber\.acc-xmpp-1\.PASSWORD=\).*|\1\$JIGASI_XMPP_PASSWORD_BASE64|g' \ + "$temp" + + sed -i \ + 's|\(#\)\(org.jitsi.jigasi.DEFAULT_JVB_ROOM_NAME=\).*|\2\$JIGASI_DEFAULT_JVB_ROOM_NAME|g' \ + "$temp" + + ${pkgs.envsubst}/bin/envsubst \ + -o "${sipCommunicatorPropertiesFile}" \ + -i "$temp" + + # Set the brewery room name + sed -i \ + 's|\(net\.java\.sip\.communicator\.impl\.protocol\.jabber\.acc-xmpp-1\.BREWERY=\).*|\1${cfg.bridgeMuc}|g' \ + "${sipCommunicatorPropertiesFile}" + sed -i \ + 's|\(org\.jitsi\.jigasi\.ALLOWED_JID=\).*|\1${cfg.bridgeMuc}|g' \ + "${sipCommunicatorPropertiesFile}" + + + # Disable certificate verification for self-signed certificates + sed -i \ + 's|\(# \)\(net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED=true\)|\2|g' \ + "${sipCommunicatorPropertiesFile}" + ''; + + restartTriggers = [ + config.environment.etc."jitsi/jigasi/sip-communicator.properties".source + ]; + environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jigasiProps); + + script = '' + ${pkgs.jigasi}/bin/jigasi \ + --host="${cfg.xmppHost}" \ + --domain="${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain}" \ + --secret="$(cat ${cfg.componentPasswordFile})" \ + --user_name="${cfg.userName}" \ + --user_domain="${cfg.userDomain}" \ + --user_password="$(cat ${cfg.userPasswordFile})" \ + --configdir="${stateDir}" \ + --configdirname="${homeDirName}" + ''; + + serviceConfig = { + Type = "exec"; + + DynamicUser = true; + User = "jigasi"; + Group = "jitsi-meet"; + + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + RestrictNamespaces = true; + LockPersonality = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + StateDirectory = baseNameOf stateDir; + EnvironmentFile = cfg.environmentFile; + }; + }; + + environment.etc."jitsi/jigasi/sip-communicator.properties".source = + mkDefault "${sipCommunicatorPropertiesFile}"; + environment.etc."jitsi/jigasi/logging.properties".source = + mkDefault "${stateDir}/logging.properties-journal"; + }; + + meta.maintainers = lib.teams.jitsi.members; +} diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 0c0eb66e65b7..c4505534d635 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -35,6 +35,7 @@ let domain = cfg.hostName; muc = "conference.${cfg.hostName}"; focus = "focus.${cfg.hostName}"; + jigasi = "jigasi.${cfg.hostName}"; }; bosh = "//${cfg.hostName}/http-bind"; websocket = "wss://${cfg.hostName}/xmpp-websocket"; @@ -145,6 +146,16 @@ in ''; }; + jigasi.enable = mkOption { + type = bool; + default = false; + description = '' + Whether to enable jigasi instance and configure it to connect to Prosody. + + Additional configuration is possible with . + ''; + }; + nginx.enable = mkOption { type = bool; default = true; @@ -224,7 +235,7 @@ in roomDefaultPublicJids = true; extraConfig = '' storage = "memory" - admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" } + admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}", "jigasi@auth.${cfg.hostName}" } ''; #-- muc_room_cache_size = 1000 } @@ -263,6 +274,9 @@ in Component "focus.${cfg.hostName}" "client_proxy" target_address = "focus@auth.${cfg.hostName}" + Component "jigasi.${cfg.hostName}" "client_proxy" + target_address = "jigasi@auth.${cfg.hostName}" + Component "speakerstats.${cfg.hostName}" "speakerstats_component" muc_component = "conference.${cfg.hostName}" @@ -356,7 +370,10 @@ in ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} ${config.services.prosody.package}/bin/prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)" ${config.services.prosody.package}/bin/prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)" + '' + optionalString cfg.jigasi.enable '' + ${config.services.prosody.package}/bin/prosodyctl register jigasi auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jigasi-user-secret)" ''; + serviceConfig = { EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; SupplementaryGroups = [ "jitsi-meet" ]; @@ -371,13 +388,13 @@ in systemd.services.jitsi-meet-init-secrets = { wantedBy = [ "multi-user.target" ]; - before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service"); + before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service") ++ (optional cfg.jigasi.enable "jigasi.service"); serviceConfig = { Type = "oneshot"; }; script = let - secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); + secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optionals cfg.jigasi.enable [ "jigasi-user-secret" "jigasi-component-secret" ]) ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); in '' cd /var/lib/jitsi-meet @@ -391,6 +408,7 @@ in # for easy access in prosody echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env + echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env chown root:jitsi-meet secrets-env chmod 640 secrets-env '' @@ -592,6 +610,20 @@ in stripFromRoomDomain = "conference."; }; }; + + services.jigasi = mkIf cfg.jigasi.enable { + enable = true; + xmppHost = "localhost"; + xmppDomain = cfg.hostName; + userDomain = "auth.${cfg.hostName}"; + userName = "jigasi"; + userPasswordFile = "/var/lib/jitsi-meet/jigasi-user-secret"; + componentPasswordFile = "/var/lib/jitsi-meet/jigasi-component-secret"; + bridgeMuc = "jigasibrewery@internal.${cfg.hostName}"; + config = { + "org.jitsi.jigasi.ALWAYS_TRUST_MODE_ENABLED" = "true"; + }; + }; }; meta.doc = ./jitsi-meet.md; diff --git a/pkgs/servers/jigasi/default.nix b/pkgs/servers/jigasi/default.nix new file mode 100644 index 000000000000..d9f8ed60c09b --- /dev/null +++ b/pkgs/servers/jigasi/default.nix @@ -0,0 +1,46 @@ +{ lib, stdenv, fetchurl, dpkg, jdk11, nixosTests }: + +let + pname = "jigasi"; + version = "1.1-311-g3de47d0"; + src = fetchurl { + url = "https://download.jitsi.org/stable/${pname}_${version}-1_all.deb"; + hash = "sha256-pwUgkId7AHFjbqYo02fBgm0gsiMqEz+wvwkdy6sgTD0="; + }; +in +stdenv.mkDerivation { + inherit pname version src; + + nativeBuildInputs = [ dpkg ]; + + dontBuild = true; + + unpackCmd = "dpkg-deb -x $src debcontents"; + + installPhase = '' + runHook preInstall + substituteInPlace usr/share/${pname}/${pname}.sh \ + --replace "exec java" "exec ${jdk11}/bin/java" + + mkdir -p $out/{share,bin} + mv usr/share/${pname} $out/share/ + mv etc $out/ + ln -s $out/share/${pname}/${pname}.sh $out/bin/${pname} + runHook postInstall + ''; + + passthru.tests = { + single-node-smoke-test = nixosTests.jitsi-meet; + }; + + meta = with lib; { + description = "A server-side application that allows regular SIP clients to join Jitsi Meet conferences"; + longDescription = '' + Jitsi Gateway to SIP: a server-side application that allows regular SIP clients to join Jitsi Meet conferences hosted by Jitsi Videobridge. + ''; + homepage = "https://github.com/jitsi/jigasi"; + license = licenses.asl20; + maintainers = teams.jitsi.members; + platforms = platforms.linux; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index e4a45f63f44c..c72fc1749793 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -26587,6 +26587,8 @@ with pkgs; jitsi-excalidraw = callPackage ../servers/jitsi-excalidraw { }; + jigasi = callPackage ../servers/jigasi { }; + jitsi-meet = callPackage ../servers/web-apps/jitsi-meet { }; jitsi-meet-prosody = callPackage ../misc/jitsi-meet-prosody { };