From 355e9d6b251020faf8b3c8829e52f4382346dd16 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 18 Mar 2020 01:50:40 +0100 Subject: [PATCH 01/11] jitsi-videobridge: init at 2.1-202-g5f9377b9 --- pkgs/servers/jitsi-videobridge/default.nix | 47 +++++++++++++++++++ .../logging.properties-journal | 7 +++ pkgs/top-level/all-packages.nix | 2 + 3 files changed, 56 insertions(+) create mode 100644 pkgs/servers/jitsi-videobridge/default.nix create mode 100644 pkgs/servers/jitsi-videobridge/logging.properties-journal diff --git a/pkgs/servers/jitsi-videobridge/default.nix b/pkgs/servers/jitsi-videobridge/default.nix new file mode 100644 index 000000000000..242092056803 --- /dev/null +++ b/pkgs/servers/jitsi-videobridge/default.nix @@ -0,0 +1,47 @@ +{ stdenv, fetchurl, dpkg, jre_headless, nixosTests }: + +let + pname = "jitsi-videobridge2"; + version = "2.1-202-g5f9377b9"; + src = fetchurl { + url = "https://download.jitsi.org/stable/${pname}_${version}-1_all.deb"; + sha256 = "16xj4m6kz4di6y3vxrjkwajd7sfm92zzhrc6q9ljmrwiqnly5z0a"; + }; +in +stdenv.mkDerivation { + inherit pname version src; + + dontBuild = true; + + unpackCmd = "${dpkg}/bin/dpkg-deb -x $src debcontents"; + + installPhase = '' + substituteInPlace usr/share/jitsi-videobridge/jvb.sh \ + --replace "exec java" "exec ${jre_headless}/bin/java" + + mkdir -p $out/{bin,share/jitsi-videobridge,etc/jitsi/videobridge} + mv etc/jitsi/videobridge/logging.properties $out/etc/jitsi/videobridge/ + cp ${./logging.properties-journal} $out/etc/jitsi/videobridge/logging.properties-journal + mv usr/share/jitsi-videobridge/* $out/share/jitsi-videobridge/ + ln -s $out/share/jitsi-videobridge/jvb.sh $out/bin/jitsi-videobridge + ''; + + passthru.tests = { + single-host-smoke-test = nixosTests.jitsi-meet; + }; + + meta = with stdenv.lib; { + description = "A WebRTC compatible video router"; + longDescription = '' + Jitsi Videobridge is an XMPP server component that allows for multiuser video communication. + Unlike the expensive dedicated hardware videobridges, Jitsi Videobridge does not mix the video + channels into a composite video stream, but only relays the received video channels to all call + participants. Therefore, while it does need to run on a server with good network bandwidth, + CPU horsepower is not that critical for performance. + ''; + homepage = "https://github.com/jitsi/jitsi-videobridge"; + license = licenses.asl20; + maintainers = with maintainers; [ ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/servers/jitsi-videobridge/logging.properties-journal b/pkgs/servers/jitsi-videobridge/logging.properties-journal new file mode 100644 index 000000000000..915e32929122 --- /dev/null +++ b/pkgs/servers/jitsi-videobridge/logging.properties-journal @@ -0,0 +1,7 @@ +handlers = java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = org.jitsi.utils.logging2.JitsiLogFormatter +.level = INFO +org.jitsi.videobridge.xmpp.ComponentImpl.level = FINE +org.jitsi.impl.neomedia.MediaStreamImpl.level = WARNING +org.jitsi.utils.logging2.JitsiLogFormatter.disableTimestamp = true diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index fd47b359681f..1b38740ede1f 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -15949,6 +15949,8 @@ in jetty = callPackage ../servers/http/jetty { }; + jitsi-videobridge = callPackage ../servers/jitsi-videobridge { }; + kapow = callPackage ../servers/kapow { }; keycloak = callPackage ../servers/keycloak { }; From 26410b2b0bb2921c0b44dc64350a8609be4583d8 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 18 Mar 2020 01:51:03 +0100 Subject: [PATCH 02/11] jicofo: init at 1.0-589 --- pkgs/servers/jicofo/default.nix | 43 +++++++++++++++++++ .../servers/jicofo/logging.properties-journal | 10 +++++ pkgs/top-level/all-packages.nix | 2 + 3 files changed, 55 insertions(+) create mode 100644 pkgs/servers/jicofo/default.nix create mode 100644 pkgs/servers/jicofo/logging.properties-journal diff --git a/pkgs/servers/jicofo/default.nix b/pkgs/servers/jicofo/default.nix new file mode 100644 index 000000000000..e33a18603074 --- /dev/null +++ b/pkgs/servers/jicofo/default.nix @@ -0,0 +1,43 @@ +{ pkgs, stdenv, fetchurl, dpkg, jre_headless, nixosTests }: + +let + pname = "jicofo"; + version = "1.0-589"; + src = fetchurl { + url = "https://download.jitsi.org/stable/${pname}_${version}-1_all.deb"; + sha256 = "0bsagnmw2rxf9s9kjl4y7gfqx408iv0qlwgy3mz0339g5503p5r9"; + }; +in +stdenv.mkDerivation { + inherit pname version src; + + dontBuild = true; + + unpackCmd = "${dpkg}/bin/dpkg-deb -x $src debcontents"; + + installPhase = '' + substituteInPlace usr/share/jicofo/jicofo.sh \ + --replace "exec java" "exec ${jre_headless}/bin/java" + + mkdir -p $out/{share,bin} + mv usr/share/jicofo $out/share/ + mv etc $out/ + cp ${./logging.properties-journal} $out/etc/jitsi/jicofo/logging.properties-journal + ln -s $out/share/jicofo/jicofo.sh $out/bin/jicofo + ''; + + passthru.tests = { + single-node-smoke-test = nixosTests.jitsi-meet; + }; + + meta = with stdenv.lib; { + description = "A server side focus component used in Jitsi Meet conferences"; + longDescription = '' + JItsi COnference FOcus is a server side focus component used in Jitsi Meet conferences. + ''; + homepage = "https://github.com/jitsi/jicofo"; + license = licenses.asl20; + maintainers = with maintainers; [ ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/servers/jicofo/logging.properties-journal b/pkgs/servers/jicofo/logging.properties-journal new file mode 100644 index 000000000000..2d68dec1b0ba --- /dev/null +++ b/pkgs/servers/jicofo/logging.properties-journal @@ -0,0 +1,10 @@ +handlers = java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = net.java.sip.communicator.util.ScLogFormatter +.level = INFO +net.sf.level = SEVERE +net.java.sip.communicator.plugin.reconnectplugin.level = FINE +org.ice4j.level = SEVERE +org.jitsi.impl.neomedia.level = SEVERE +net.java.sip.communicator.service.resources.AbstractResourcesService.level = SEVERE +net.java.sip.communicator.util.ScLogFormatter.disableTimestamp = true diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 1b38740ede1f..559018930a3d 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -15949,6 +15949,8 @@ in jetty = callPackage ../servers/http/jetty { }; + jicofo = callPackage ../servers/jicofo { }; + jitsi-videobridge = callPackage ../servers/jitsi-videobridge { }; kapow = callPackage ../servers/kapow { }; From b128516ef1c7e788cff3afbeb8f34354a3af688e Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 18 Mar 2020 01:53:08 +0100 Subject: [PATCH 03/11] jitsi-meet: init at 1.0.4627 --- pkgs/servers/web-apps/jitsi-meet/default.nix | 34 ++++++++++++++++++++ pkgs/top-level/all-packages.nix | 2 ++ 2 files changed, 36 insertions(+) create mode 100644 pkgs/servers/web-apps/jitsi-meet/default.nix diff --git a/pkgs/servers/web-apps/jitsi-meet/default.nix b/pkgs/servers/web-apps/jitsi-meet/default.nix new file mode 100644 index 000000000000..a187add0e06b --- /dev/null +++ b/pkgs/servers/web-apps/jitsi-meet/default.nix @@ -0,0 +1,34 @@ +{ pkgs, stdenv, fetchurl, nixosTests }: + +stdenv.mkDerivation rec { + pname = "jitsi-meet"; + version = "1.0.4127"; + + src = fetchurl { + url = "https://download.jitsi.org/jitsi-meet/src/jitsi-meet-${version}.tar.bz2"; + sha256 = "1jrrsvgysihd73pjqfv605ax01pg2gn76znr64v7nhli55ddgzqx"; + }; + + dontBuild = true; + + installPhase = '' + mkdir $out + mv * $out/ + ''; + + passthru.tests = { + single-host-smoke-test = nixosTests.jitsi-meet; + }; + + meta = with stdenv.lib; { + description = "Secure, Simple and Scalable Video Conferences"; + longDescription = '' + Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses Jitsi Videobridge + to provide high quality, secure and scalable video conferences. + ''; + homepage = "https://github.com/jitsi/jitsi-meet"; + license = licenses.asl20; + maintainers = with maintainers; [ ]; + platforms = platforms.all; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 559018930a3d..b2b6c17be3a6 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -15951,6 +15951,8 @@ in jicofo = callPackage ../servers/jicofo { }; + jitsi-meet = callPackage ../servers/web-apps/jitsi-meet { }; + jitsi-videobridge = callPackage ../servers/jitsi-videobridge { }; kapow = callPackage ../servers/kapow { }; From c695d57895b9ad8b0ca4d6127ec8a83efbbaf86c Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Thu, 30 Apr 2020 00:40:51 +0200 Subject: [PATCH 04/11] nixos/jitsi-videobridge: init --- nixos/modules/module-list.nix | 1 + .../services/networking/jitsi-videobridge.nix | 276 ++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 nixos/modules/services/networking/jitsi-videobridge.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index f361163ca631..3b374a34ac91 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -642,6 +642,7 @@ ./services/networking/iperf3.nix ./services/networking/ircd-hybrid/default.nix ./services/networking/iwd.nix + ./services/networking/jitsi-videobridge.nix ./services/networking/keepalived/default.nix ./services/networking/keybase.nix ./services/networking/kippo.nix diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix new file mode 100644 index 000000000000..b368ee14903d --- /dev/null +++ b/nixos/modules/services/networking/jitsi-videobridge.nix @@ -0,0 +1,276 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.jitsi-videobridge; + attrsToArgs = a: concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") a); + + # HOCON is a JSON superset that videobridge2 uses for configuration. + # It can substitute environment variables which we use for passwords here. + # https://github.com/lightbend/config/blob/master/README.md + # + # Substitution for environment variable FOO is represented as attribute set + # { __hocon_envvar = "FOO"; } + 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; + + defaultJvbConfig = { + videobridge = { + ice = { + tcp = { + enabled = true; + port = 4443; + }; + udp.port = 10000; + }; + stats = { + enabled = true; + transports = [ { type = "muc"; } ]; + }; + apis.xmpp-client.configs = flip mapAttrs cfg.xmppConfigs (name: xmppConfig: { + hostname = xmppConfig.hostName; + domain = xmppConfig.domain; + username = xmppConfig.userName; + password = { __hocon_envvar = toVarName name; }; + muc_jids = xmppConfig.mucJids; + muc_nickname = xmppConfig.mucNickname; + disable_certificate_verification = xmppConfig.disableCertificateVerification; + }); + }; + }; + + # Allow overriding leaves of the default config despite types.attrs not doing any merging. + jvbConfig = recursiveUpdate defaultJvbConfig cfg.config; +in +{ + options.services.jitsi-videobridge = with types; { + enable = mkEnableOption "Jitsi Videobridge, a WebRTC compatible video router"; + + config = mkOption { + type = attrs; + default = { }; + example = literalExample '' + { + videobridge = { + ice.udp.port = 5000; + websockets = { + enabled = true; + server-id = "jvb1"; + }; + }; + } + ''; + description = '' + Videobridge configuration. + + See + for default configuration with comments. + ''; + }; + + xmppConfigs = mkOption { + description = '' + XMPP servers to connect to. + + See for more information. + ''; + default = { }; + example = literalExample '' + { + "localhost" = { + hostName = "localhost"; + userName = "jvb"; + domain = "auth.xmpp.example.org"; + passwordFile = "/var/lib/jitsi-meet/videobridge-secret"; + mucJids = "jvbbrewery@internal.xmpp.example.org"; + }; + } + ''; + type = attrsOf (submodule ({ name, ... }: { + options = { + hostName = mkOption { + type = str; + example = "xmpp.example.org"; + description = '' + Hostname of the XMPP server to connect to. Name of the attribute set is used by default. + ''; + }; + domain = mkOption { + type = nullOr str; + default = null; + example = "auth.xmpp.example.org"; + description = '' + Domain part of JID of the XMPP user, if it is different from hostName. + ''; + }; + userName = mkOption { + type = str; + default = "jvb"; + description = '' + User part of the JID. + ''; + }; + passwordFile = mkOption { + type = str; + example = "/run/keys/jitsi-videobridge-xmpp1"; + description = '' + File containing the password for the user. + ''; + }; + mucJids = mkOption { + type = str; + example = "jvbbrewery@internal.xmpp.example.org"; + description = '' + JID of the MUC to join. JiCoFo needs to be configured to join the same MUC. + ''; + }; + mucNickname = mkOption { + # Upstream DEBs use UUID, let's use hostname instead. + type = str; + description = '' + Videobridges use the same XMPP account and need to be distinguished by the + nickname (aka resource part of the JID). By default, system hostname is used. + ''; + }; + disableCertificateVerification = mkOption { + type = bool; + default = false; + description = '' + Whether to skip validation of the server's certificate. + ''; + }; + }; + config = { + hostName = mkDefault name; + mucNickname = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] ( + config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}" + )); + }; + })); + }; + + nat = { + localAddress = mkOption { + type = nullOr str; + default = null; + example = "192.168.1.42"; + description = '' + Local address when running behind NAT. + ''; + }; + + publicAddress = mkOption { + type = nullOr str; + default = null; + example = "1.2.3.4"; + description = '' + Public address when running behind NAT. + ''; + }; + }; + + extraProperties = mkOption { + type = attrsOf str; + default = { }; + description = '' + Additional Java properties passed to jitsi-videobridge. + ''; + }; + + openFirewall = mkOption { + type = bool; + default = false; + description = '' + Whether to open ports in the firewall for the videobridge. + ''; + }; + }; + + config = mkIf cfg.enable { + users.groups.jitsi-meet = {}; + + services.jitsi-videobridge.extraProperties = optionalAttrs (cfg.nat.localAddress != null) { + "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress; + "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress; + }; + + systemd.services.jitsi-videobridge2 = let + jvbProps = { + "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi"; + "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "videobridge"; + "-Djava.util.logging.config.file" = "/etc/jitsi/videobridge/logging.properties"; + "-Dconfig.file" = pkgs.writeText "jvb.conf" (toHOCON jvbConfig); + } // (mapAttrs' (k: v: nameValuePair "-D${k}" v) cfg.extraProperties); + in + { + aliases = [ "jitsi-videobridge.service" ]; + description = "Jitsi Videobridge"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment.JAVA_SYS_PROPS = attrsToArgs jvbProps; + + script = (concatStrings (mapAttrsToList (name: xmppConfig: + "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n" + ) cfg.xmppConfigs)) + + '' + ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=none + ''; + + serviceConfig = { + Type = "exec"; + + DynamicUser = true; + User = "jitsi-videobridge"; + 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; + + TasksMax = 65000; + LimitNPROC = 65000; + LimitNOFILE = 65000; + }; + }; + + environment.etc."jitsi/videobridge/logging.properties".source = + mkDefault "${pkgs.jitsi-videobridge}/etc/jitsi/videobridge/logging.properties-journal"; + + # (from videobridge2 .deb) + # this sets the max, so that we can bump the JVB UDP single port buffer size. + boot.kernel.sysctl."net.core.rmem_max" = mkDefault 10485760; + boot.kernel.sysctl."net.core.netdev_max_backlog" = mkDefault 100000; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall + [ jvbConfig.videobridge.ice.tcp.port ]; + networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall + [ jvbConfig.videobridge.ice.udp.port ]; + + assertions = [{ + message = "publicAddress must be set if and only if localAddress is set"; + assertion = (cfg.nat.publicAddress == null) == (cfg.nat.localAddress == null); + }]; + }; + + meta.maintainers = with lib.maintainers; [ ]; +} From 47c38f00b2143c1958e6e3e0141d6d6ae4579c4a Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Mon, 4 May 2020 17:00:58 +0200 Subject: [PATCH 05/11] nixos/jicofo: init --- nixos/modules/module-list.nix | 1 + nixos/modules/services/networking/jicofo.nix | 152 +++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 nixos/modules/services/networking/jicofo.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 3b374a34ac91..07405e4a416c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -642,6 +642,7 @@ ./services/networking/iperf3.nix ./services/networking/ircd-hybrid/default.nix ./services/networking/iwd.nix + ./services/networking/jicofo.nix ./services/networking/jitsi-videobridge.nix ./services/networking/keepalived/default.nix ./services/networking/keybase.nix diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix new file mode 100644 index 000000000000..64e57eef97fb --- /dev/null +++ b/nixos/modules/services/networking/jicofo.nix @@ -0,0 +1,152 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.jicofo; +in +{ + options.services.jicofo = with types; { + enable = mkEnableOption "Jitsi Conference Focus - 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/jicofo-component"; + description = '' + Path to file containing component secret. + ''; + }; + + userName = mkOption { + type = str; + default = "focus"; + description = '' + User part of the JID for XMPP user connection. + ''; + }; + + userDomain = mkOption { + type = str; + example = "auth.meet.example.org"; + description = '' + Domain part of the JID for XMPP user connection. + ''; + }; + + userPasswordFile = mkOption { + type = str; + example = "/run/keys/jicofo-user"; + description = '' + Path to file containing password for XMPP user connection. + ''; + }; + + bridgeMuc = mkOption { + type = str; + example = "jvbbrewery@internal.meet.example.org"; + description = '' + JID of the internal MUC used to communicate with Videobridges. + ''; + }; + + config = mkOption { + type = attrsOf str; + default = { }; + example = literalExample '' + { + "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com"; + } + ''; + description = '' + Contents of the sip-communicator.properties configuration file for jicofo. + ''; + }; + }; + + config = mkIf cfg.enable { + services.jicofo.config = mapAttrs (_: v: mkDefault v) { + "org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc; + }; + + users.groups.jitsi-meet = {}; + + systemd.services.jicofo = let + jicofoProps = { + "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi"; + "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo"; + "-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties"; + }; + in + { + description = "JItsi COnference FOcus"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + restartTriggers = [ + config.environment.etc."jitsi/jicofo/sip-communicator.properties".source + ]; + environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps); + + script = '' + ${pkgs.jicofo}/bin/jicofo \ + --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}) + ''; + + serviceConfig = { + Type = "exec"; + + DynamicUser = true; + User = "jicofo"; + 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; + }; + }; + + environment.etc."jitsi/jicofo/sip-communicator.properties".source = + pkgs.writeText "sip-communicator.properties" ( + concatStringsSep "\n" (mapAttrsToList (k: v: "${k}=${v}") cfg.config) + ); + environment.etc."jitsi/jicofo/logging.properties".source = + mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal"; + }; + + meta.maintainers = with lib.maintainers; [ ]; +} From 3f68a83c88987f22128ae6974ffd789ae1c82a21 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 18 Mar 2020 04:23:15 +0100 Subject: [PATCH 06/11] nixos/jitsi-meet: init --- nixos/modules/module-list.nix | 1 + .../modules/services/web-apps/jitsi-meet.nix | 339 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/jitsi-meet.nix | 55 +++ 4 files changed, 396 insertions(+) create mode 100644 nixos/modules/services/web-apps/jitsi-meet.nix create mode 100644 nixos/tests/jitsi-meet.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 07405e4a416c..60ef0fa850c9 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -849,6 +849,7 @@ ./services/web-apps/icingaweb2/module-monitoring.nix ./services/web-apps/ihatemoney ./services/web-apps/jirafeau.nix + ./services/web-apps/jitsi-meet.nix ./services/web-apps/limesurvey.nix ./services/web-apps/mattermost.nix ./services/web-apps/mediawiki.nix diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix new file mode 100644 index 000000000000..569901a869b9 --- /dev/null +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -0,0 +1,339 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.jitsi-meet; + + # The configuration files are JS of format "var <> = <>;". In order to + # override only some settings, we need to extract the JSON, use jq to merge it with + # the config provided by user, and then reconstruct the file. + overrideJs = + source: varName: userCfg: appendExtra: + let + extractor = pkgs.writeText "extractor.js" '' + var fs = require("fs"); + eval(fs.readFileSync(process.argv[2], 'utf8')); + process.stdout.write(JSON.stringify(eval(process.argv[3]))); + ''; + userJson = pkgs.writeText "user.json" (builtins.toJSON userCfg); + in (pkgs.runCommand "${varName}.js" { } '' + ${pkgs.nodejs}/bin/node ${extractor} ${source} ${varName} > default.json + ( + echo "var ${varName} = " + ${pkgs.jq}/bin/jq -s '.[0] * .[1]' default.json ${userJson} + echo ";" + echo ${escapeShellArg appendExtra} + ) > $out + ''); + + # Essential config - it's probably not good to have these as option default because + # types.attrs doesn't do merging. Let's merge explicitly, can still be overriden if + # user desires. + defaultCfg = { + hosts = { + domain = cfg.hostName; + muc = "conference.${cfg.hostName}"; + focus = "focus.${cfg.hostName}"; + }; + bosh = "//${cfg.hostName}/http-bind"; + }; +in +{ + options.services.jitsi-meet = with types; { + enable = mkEnableOption "Jitsi Meet - Secure, Simple and Scalable Video Conferences"; + + hostName = mkOption { + type = str; + example = "meet.example.org"; + description = '' + Hostname of the Jitsi Meet instance. + ''; + }; + + config = mkOption { + type = attrs; + default = { }; + example = literalExample '' + { + enableWelcomePage = false; + defaultLang = "fi"; + } + ''; + description = '' + Client-side web application settings that override the defaults in config.js. + + See for default + configuration with comments. + ''; + }; + + extraConfig = mkOption { + type = lines; + default = ""; + description = '' + Text to append to config.js web application config file. + + Can be used to insert JavaScript logic to determine user's region in cascading bridges setup. + ''; + }; + + interfaceConfig = mkOption { + type = attrs; + default = { }; + example = literalExample '' + { + SHOW_JITSI_WATERMARK = false; + SHOW_WATERMARK_FOR_GUESTS = false; + } + ''; + description = '' + Client-side web-app interface settings that override the defaults in interface_config.js. + + See for + default configuration with comments. + ''; + }; + + videobridge = { + enable = mkOption { + type = bool; + default = true; + description = '' + Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody. + + Additional configuration is possible with . + ''; + }; + + passwordFile = mkOption { + type = nullOr str; + default = null; + example = "/run/keys/videobridge"; + description = '' + File containing password to the Prosody account for videobridge. + + If null, a file with password will be generated automatically. Setting + this option is useful if you plan to connect additional videobridges to the XMPP server. + ''; + }; + }; + + jicofo.enable = mkOption { + type = bool; + default = true; + description = '' + Whether to enable JiCoFo instance and configure it to connect to Prosody. + + Additional configuration is possible with . + ''; + }; + + nginx.enable = mkOption { + type = bool; + default = true; + description = '' + Whether to enable nginx virtual host that will serve the javascript application and act as + a proxy for the XMPP server. Further nginx configuration can be done by adapting + . It is highly recommended to + enable the and options: + + + services.nginx.virtualHosts.''${config.services.jitsi-meet.hostName} = { + enableACME = true; + forceSSL = true; + }; + + ''; + }; + + prosody.enable = mkOption { + type = bool; + default = true; + description = '' + Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this + off if you want to configure it manually. + ''; + }; + }; + + config = mkIf cfg.enable { + services.prosody = mkIf cfg.prosody.enable { + enable = mkDefault true; + xmppComplianceSuite = mkDefault false; + modules = { + admin_adhoc = mkDefault false; + bosh = mkDefault true; + ping = mkDefault true; + roster = mkDefault true; + saslauth = mkDefault true; + tls = mkDefault true; + }; + muc = [ + { + domain = "conference.${cfg.hostName}"; + name = "Jitsi Meet MUC"; + roomLocking = false; + roomDefaultPublicJids = true; + extraConfig = '' + storage = "memory" + ''; + } + { + domain = "internal.${cfg.hostName}"; + name = "Jitsi Meet Videobridge MUC"; + extraConfig = '' + storage = "memory" + admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" } + ''; + #-- muc_room_cache_size = 1000 + } + ]; + extraModules = [ "pubsub" ]; + extraConfig = mkAfter '' + Component "focus.${cfg.hostName}" + component_secret = os.getenv("JICOFO_COMPONENT_SECRET") + ''; + virtualHosts.${cfg.hostName} = { + enabled = true; + domain = cfg.hostName; + extraConfig = '' + authentication = "anonymous" + c2s_require_encryption = false + admins = { "focus@auth.${cfg.hostName}" } + ''; + ssl = { + cert = "/var/lib/jitsi-meet/jitsi-meet.crt"; + key = "/var/lib/jitsi-meet/jitsi-meet.key"; + }; + }; + virtualHosts."auth.${cfg.hostName}" = { + enabled = true; + domain = "auth.${cfg.hostName}"; + extraConfig = '' + authentication = "internal_plain" + ''; + ssl = { + cert = "/var/lib/jitsi-meet/jitsi-meet.crt"; + key = "/var/lib/jitsi-meet/jitsi-meet.key"; + }; + }; + }; + systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable { + EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; + SupplementaryGroups = [ "jitsi-meet" ]; + }; + + users.groups.jitsi-meet = {}; + systemd.tmpfiles.rules = [ + "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -" + ]; + + systemd.services.jitsi-meet-init-secrets = { + wantedBy = [ "multi-user.target" ]; + before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service"); + serviceConfig = { + Type = "oneshot"; + }; + + script = let + secrets = [ "jicofo-component-secret" "jicofo-user-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 + '' + cd /var/lib/jitsi-meet + ${concatMapStringsSep "\n" (s: '' + if [ ! -f ${s} ]; then + tr -dc a-zA-Z0-9 ${s} + chown root:jitsi-meet ${s} + chmod 640 ${s} + fi + '') secrets} + + # for easy access in prosody + echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env + chown root:jitsi-meet secrets-env + 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})" + + # generate self-signed certificates + if [ ! -f /var/lib/jitsi-meet.crt ]; then + ${getBin pkgs.openssl}/bin/openssl req \ + -x509 \ + -newkey rsa:4096 \ + -keyout /var/lib/jitsi-meet/jitsi-meet.key \ + -out /var/lib/jitsi-meet/jitsi-meet.crt \ + -days 36500 \ + -nodes \ + -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}' + chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key} + chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key} + fi + ''; + }; + + services.nginx = mkIf cfg.nginx.enable { + enable = mkDefault true; + virtualHosts.${cfg.hostName} = { + root = pkgs.jitsi-meet; + locations."~ ^/([a-zA-Z0-9=\\?]+)$" = { + extraConfig = '' + rewrite ^/(.*)$ / break; + ''; + }; + locations."/" = { + index = "index.html"; + extraConfig = '' + ssi on; + ''; + }; + locations."/http-bind" = { + proxyPass = "http://localhost:5280/http-bind"; + extraConfig = '' + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + ''; + }; + locations."=/external_api.js" = mkDefault { + alias = "${pkgs.jitsi-meet}/libs/external_api.min.js"; + }; + locations."=/config.js" = mkDefault { + alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig; + }; + locations."=/interface_config.js" = mkDefault { + alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""; + }; + }; + }; + + services.jitsi-videobridge = mkIf cfg.videobridge.enable { + enable = true; + xmppConfigs."localhost" = { + userName = "jvb"; + domain = "auth.${cfg.hostName}"; + passwordFile = "/var/lib/jitsi-meet/videobridge-secret"; + mucJids = "jvbbrewery@internal.${cfg.hostName}"; + disableCertificateVerification = true; + }; + }; + + services.jicofo = mkIf cfg.jicofo.enable { + enable = true; + xmppHost = "localhost"; + xmppDomain = cfg.hostName; + userDomain = "auth.${cfg.hostName}"; + userName = "focus"; + userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret"; + componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret"; + bridgeMuc = "jvbbrewery@internal.${cfg.hostName}"; + config = { + "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 7f3bb9bcc819..0abdf795803e 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -161,6 +161,7 @@ in jellyfin = handleTest ./jellyfin.nix {}; jenkins = handleTest ./jenkins.nix {}; jirafeau = handleTest ./jirafeau.nix {}; + jitsi-meet = handleTest ./jitsi-meet.nix {}; k3s = handleTest ./k3s.nix {}; kafka = handleTest ./kafka.nix {}; keepalived = handleTest ./keepalived.nix {}; diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix new file mode 100644 index 000000000000..d615a137febe --- /dev/null +++ b/nixos/tests/jitsi-meet.nix @@ -0,0 +1,55 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "jitsi-meet"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ mmilata ]; + }; + + nodes = { + client = { nodes, pkgs, ... }: { + }; + server = { config, pkgs, ... }: { + services.jitsi-meet = { + enable = true; + hostName = "server"; + }; + services.jitsi-videobridge.openFirewall = true; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + services.nginx.virtualHosts.server = { + enableACME = true; + forceSSL = true; + }; + + security.acme.email = "me@example.org"; + security.acme.acceptTerms = true; + security.acme.server = "https://example.com"; # self-signed only + }; + }; + + testScript = '' + server.wait_for_unit("jitsi-videobridge2.service") + server.wait_for_unit("jicofo.service") + server.wait_for_unit("nginx.service") + server.wait_for_unit("prosody.service") + + server.wait_until_succeeds( + "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'" + ) + server.wait_until_succeeds( + "journalctl -b -u jicofo -o cat | grep -q 'connected .JID: focus@auth.server'" + ) + server.wait_until_succeeds( + "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'" + ) + server.wait_until_succeeds( + "journalctl -b -u prosody -o cat | grep -q 'focus.server:component: External component successfully authenticated'" + ) + server.wait_until_succeeds( + "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.server'" + ) + + client.wait_for_unit("network.target") + assert "Jitsi Meet" in client.succeed("curl -sSfkL http://server/") + ''; +}) From 1a071343f27e8ceacfbe4337e6a8e7be903285a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20P=C3=A4ssler?= Date: Wed, 8 Jul 2020 23:47:37 +0200 Subject: [PATCH 07/11] nioxs/jicofo: use existing generator --- nixos/modules/services/networking/jicofo.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix index 64e57eef97fb..8c4926009443 100644 --- a/nixos/modules/services/networking/jicofo.nix +++ b/nixos/modules/services/networking/jicofo.nix @@ -142,7 +142,7 @@ in environment.etc."jitsi/jicofo/sip-communicator.properties".source = pkgs.writeText "sip-communicator.properties" ( - concatStringsSep "\n" (mapAttrsToList (k: v: "${k}=${v}") cfg.config) + generators.toKeyValue {} cfg.config ); environment.etc."jitsi/jicofo/logging.properties".source = mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal"; From dcc12e4df64827133fba2996febc03a6ddd0fcda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20P=C3=A4ssler?= Date: Wed, 8 Jul 2020 23:47:56 +0200 Subject: [PATCH 08/11] nixos/jitsi-meet: enableACME by default --- nixos/modules/services/web-apps/jitsi-meet.nix | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 569901a869b9..d2cdbdc8ba1b 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -135,15 +135,11 @@ in description = '' Whether to enable nginx virtual host that will serve the javascript application and act as a proxy for the XMPP server. Further nginx configuration can be done by adapting - . It is highly recommended to - enable the and options: - - - services.nginx.virtualHosts.''${config.services.jitsi-meet.hostName} = { - enableACME = true; - forceSSL = true; - }; - + . + When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable + this, set the to + false and if appropriate do the same for + . ''; }; @@ -278,6 +274,8 @@ in services.nginx = mkIf cfg.nginx.enable { enable = mkDefault true; virtualHosts.${cfg.hostName} = { + enableACME = mkDefault true; + forceSSL = mkDefault true; root = pkgs.jitsi-meet; locations."~ ^/([a-zA-Z0-9=\\?]+)$" = { extraConfig = '' From 5ff05249cfbcea7a31dc008b159371bcef0f613b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20P=C3=A4ssler?= Date: Wed, 8 Jul 2020 23:55:25 +0200 Subject: [PATCH 09/11] nixos/jitsi-meet: allow more room names --- .../modules/services/web-apps/jitsi-meet.nix | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index d2cdbdc8ba1b..8b601910ba75 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -277,18 +277,14 @@ in enableACME = mkDefault true; forceSSL = mkDefault true; root = pkgs.jitsi-meet; - locations."~ ^/([a-zA-Z0-9=\\?]+)$" = { - extraConfig = '' - rewrite ^/(.*)$ / break; - ''; - }; - locations."/" = { - index = "index.html"; - extraConfig = '' - ssi on; - ''; - }; - locations."/http-bind" = { + extraConfig = '' + ssi on; + ''; + locations."@root_path".extraConfig = '' + rewrite ^/(.*)$ / break; + ''; + locations."~ ^/([^/\\?&:'\"]+)$".tryFiles = "$uri @root_path"; + locations."=/http-bind" = { proxyPass = "http://localhost:5280/http-bind"; extraConfig = '' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From d3a26a5ecd81e3aa1618679e05759083c9b5a9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20P=C3=A4ssler?= Date: Thu, 9 Jul 2020 00:24:53 +0200 Subject: [PATCH 10/11] nixos/jicofo: use ExecStart instead of script --- nixos/modules/services/networking/jicofo.nix | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix index 8c4926009443..2142d8f75917 100644 --- a/nixos/modules/services/networking/jicofo.nix +++ b/nixos/modules/services/networking/jicofo.nix @@ -105,17 +105,16 @@ in ]; environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps); - script = '' - ${pkgs.jicofo}/bin/jicofo \ - --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}) - ''; - serviceConfig = { + ExecStart = '' + ${pkgs.jicofo}/bin/jicofo \ + --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}) + ''; Type = "exec"; DynamicUser = true; From 1a5f3d133db20fd76369e0cdf7a11f6525145daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20P=C3=A4ssler?= Date: Wed, 15 Jul 2020 21:41:29 +0200 Subject: [PATCH 11/11] Revert "nixos/jicofo: use ExecStart instead of script" This reverts commit d3a26a5ecd81e3aa1618679e05759083c9b5a9af. Using ServiceConfig.ExecStart instead of script lead to the content not being executed in a shell anymore, which broke the secrets being read from a file and passed as a command line parameter. --- nixos/modules/services/networking/jicofo.nix | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix index 2142d8f75917..8c4926009443 100644 --- a/nixos/modules/services/networking/jicofo.nix +++ b/nixos/modules/services/networking/jicofo.nix @@ -105,16 +105,17 @@ in ]; environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps); + script = '' + ${pkgs.jicofo}/bin/jicofo \ + --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}) + ''; + serviceConfig = { - ExecStart = '' - ${pkgs.jicofo}/bin/jicofo \ - --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}) - ''; Type = "exec"; DynamicUser = true;