diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 08a5f32c4c9..2c89bed9c9c 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -646,6 +646,8 @@
./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
./services/networking/kippo.nix
@@ -852,6 +854,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/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix
new file mode 100644
index 00000000000..8c492600944
--- /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" (
+ generators.toKeyValue {} cfg.config
+ );
+ environment.etc."jitsi/jicofo/logging.properties".source =
+ mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal";
+ };
+
+ meta.maintainers = with lib.maintainers; [ ];
+}
diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix
new file mode 100644
index 00000000000..b368ee14903
--- /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; [ ];
+}
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 00000000000..8b601910ba7
--- /dev/null
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -0,0 +1,333 @@
+{ 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
+ .
+ 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
+ .
+ '';
+ };
+
+ 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} = {
+ enableACME = mkDefault true;
+ forceSSL = mkDefault true;
+ root = pkgs.jitsi-meet;
+ 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;
+ 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 639a12fa316..31dad3be814 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -162,6 +162,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 00000000000..d615a137feb
--- /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/")
+ '';
+})
diff --git a/pkgs/servers/jicofo/default.nix b/pkgs/servers/jicofo/default.nix
new file mode 100644
index 00000000000..e33a1860307
--- /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 00000000000..2d68dec1b0b
--- /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/servers/jitsi-videobridge/default.nix b/pkgs/servers/jitsi-videobridge/default.nix
new file mode 100644
index 00000000000..24209205680
--- /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 00000000000..915e3292912
--- /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/servers/web-apps/jitsi-meet/default.nix b/pkgs/servers/web-apps/jitsi-meet/default.nix
new file mode 100644
index 00000000000..a187add0e06
--- /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 c6d406907ac..16cc4dcfbf2 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -16076,6 +16076,12 @@ in
jetty = callPackage ../servers/http/jetty { };
+ jicofo = callPackage ../servers/jicofo { };
+
+ jitsi-meet = callPackage ../servers/web-apps/jitsi-meet { };
+
+ jitsi-videobridge = callPackage ../servers/jitsi-videobridge { };
+
kapow = callPackage ../servers/kapow { };
keycloak = callPackage ../servers/keycloak { };