Skip to content

Commit

Permalink
nixos/jitsi-meet: init
Browse files Browse the repository at this point in the history
  • Loading branch information
mmilata committed May 4, 2020
1 parent b4e3eb9 commit 7e4960a
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 0 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Expand Up @@ -836,6 +836,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
Expand Down
337 changes: 337 additions & 0 deletions nixos/modules/services/web-apps/jitsi-meet.nix
@@ -0,0 +1,337 @@
{ config, lib, pkgs, ... }:

with lib;

let
cfg = config.services.jitsi-meet;

# The configuration files are JS of format "var <<string>> = <<JSON>>;". 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 <filename>config.js</filename>.
See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/config.js" /> for default
configuration with comments.
'';
};

extraConfig = mkOption {
type = lines;
default = "";
description = ''
Text to append to <filename>config.js</filename> 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 <filename>interface_config.js</filename>.
See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js" /> 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 <option>services.jitsi-videobridge</option>.
'';
};

passwordFile = mkOption {
type = nullOr str;
default = null;
example = "/run/keys/videobridge";
description = ''
File containing password to the Prosody account for videobridge.
If <literal>null</literal>, 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 <option>services.jicofo</option>.
'';
};

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
<option>services.nginx.virtualHosts.&lt;hostName&gt;</option>. It is highly recommended to
enable the <option>enableACME</option> and <option>forceSSL</option> options:
<programlisting>
services.nginx.virtualHosts.''${config.services.jitsi-meet.hostName} = {
enableACME = true;
forceSSL = true;
};
</programlisting>
'';
};

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 </dev/urandom | head -c 64 > ${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
${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}
'';
};

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; [ mmilata ];
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Expand Up @@ -148,6 +148,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 {};
Expand Down

0 comments on commit 7e4960a

Please sign in to comment.