at 22.05-pre 16 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.jitsi-meet; 7 8 # The configuration files are JS of format "var <<string>> = <<JSON>>;". In order to 9 # override only some settings, we need to extract the JSON, use jq to merge it with 10 # the config provided by user, and then reconstruct the file. 11 overrideJs = 12 source: varName: userCfg: appendExtra: 13 let 14 extractor = pkgs.writeText "extractor.js" '' 15 var fs = require("fs"); 16 eval(fs.readFileSync(process.argv[2], 'utf8')); 17 process.stdout.write(JSON.stringify(eval(process.argv[3]))); 18 ''; 19 userJson = pkgs.writeText "user.json" (builtins.toJSON userCfg); 20 in (pkgs.runCommand "${varName}.js" { } '' 21 ${pkgs.nodejs}/bin/node ${extractor} ${source} ${varName} > default.json 22 ( 23 echo "var ${varName} = " 24 ${pkgs.jq}/bin/jq -s '.[0] * .[1]' default.json ${userJson} 25 echo ";" 26 echo ${escapeShellArg appendExtra} 27 ) > $out 28 ''); 29 30 # Essential config - it's probably not good to have these as option default because 31 # types.attrs doesn't do merging. Let's merge explicitly, can still be overriden if 32 # user desires. 33 defaultCfg = { 34 hosts = { 35 domain = cfg.hostName; 36 muc = "conference.${cfg.hostName}"; 37 focus = "focus.${cfg.hostName}"; 38 }; 39 bosh = "//${cfg.hostName}/http-bind"; 40 websocket = "wss://${cfg.hostName}/xmpp-websocket"; 41 42 fileRecordingsEnabled = true; 43 liveStreamingEnabled = true; 44 hiddenDomain = "recorder.${cfg.hostName}"; 45 }; 46in 47{ 48 options.services.jitsi-meet = with types; { 49 enable = mkEnableOption "Jitsi Meet - Secure, Simple and Scalable Video Conferences"; 50 51 hostName = mkOption { 52 type = str; 53 example = "meet.example.org"; 54 description = '' 55 FQDN of the Jitsi Meet instance. 56 ''; 57 }; 58 59 config = mkOption { 60 type = attrs; 61 default = { }; 62 example = literalExpression '' 63 { 64 enableWelcomePage = false; 65 defaultLang = "fi"; 66 } 67 ''; 68 description = '' 69 Client-side web application settings that override the defaults in <filename>config.js</filename>. 70 71 See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/config.js" /> for default 72 configuration with comments. 73 ''; 74 }; 75 76 extraConfig = mkOption { 77 type = lines; 78 default = ""; 79 description = '' 80 Text to append to <filename>config.js</filename> web application config file. 81 82 Can be used to insert JavaScript logic to determine user's region in cascading bridges setup. 83 ''; 84 }; 85 86 interfaceConfig = mkOption { 87 type = attrs; 88 default = { }; 89 example = literalExpression '' 90 { 91 SHOW_JITSI_WATERMARK = false; 92 SHOW_WATERMARK_FOR_GUESTS = false; 93 } 94 ''; 95 description = '' 96 Client-side web-app interface settings that override the defaults in <filename>interface_config.js</filename>. 97 98 See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js" /> for 99 default configuration with comments. 100 ''; 101 }; 102 103 videobridge = { 104 enable = mkOption { 105 type = bool; 106 default = true; 107 description = '' 108 Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody. 109 110 Additional configuration is possible with <option>services.jitsi-videobridge</option>. 111 ''; 112 }; 113 114 passwordFile = mkOption { 115 type = nullOr str; 116 default = null; 117 example = "/run/keys/videobridge"; 118 description = '' 119 File containing password to the Prosody account for videobridge. 120 121 If <literal>null</literal>, a file with password will be generated automatically. Setting 122 this option is useful if you plan to connect additional videobridges to the XMPP server. 123 ''; 124 }; 125 }; 126 127 jicofo.enable = mkOption { 128 type = bool; 129 default = true; 130 description = '' 131 Whether to enable JiCoFo instance and configure it to connect to Prosody. 132 133 Additional configuration is possible with <option>services.jicofo</option>. 134 ''; 135 }; 136 137 jibri.enable = mkOption { 138 type = bool; 139 default = false; 140 description = '' 141 Whether to enable a Jibri instance and configure it to connect to Prosody. 142 143 Additional configuration is possible with <option>services.jibri</option>, and 144 <option>services.jibri.finalizeScript</option> is especially useful. 145 ''; 146 }; 147 148 nginx.enable = mkOption { 149 type = bool; 150 default = true; 151 description = '' 152 Whether to enable nginx virtual host that will serve the javascript application and act as 153 a proxy for the XMPP server. Further nginx configuration can be done by adapting 154 <option>services.nginx.virtualHosts.&lt;hostName&gt;</option>. 155 When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable 156 this, set the <option>services.nginx.virtualHosts.&lt;hostName&gt;.enableACME</option> to 157 <literal>false</literal> and if appropriate do the same for 158 <option>services.nginx.virtualHosts.&lt;hostName&gt;.forceSSL</option>. 159 ''; 160 }; 161 162 caddy.enable = mkEnableOption "Whether to enablle caddy reverse proxy to expose jitsi-meet"; 163 164 prosody.enable = mkOption { 165 type = bool; 166 default = true; 167 description = '' 168 Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this 169 off if you want to configure it manually. 170 ''; 171 }; 172 }; 173 174 config = mkIf cfg.enable { 175 services.prosody = mkIf cfg.prosody.enable { 176 enable = mkDefault true; 177 xmppComplianceSuite = mkDefault false; 178 modules = { 179 admin_adhoc = mkDefault false; 180 bosh = mkDefault true; 181 ping = mkDefault true; 182 roster = mkDefault true; 183 saslauth = mkDefault true; 184 smacks = mkDefault true; 185 tls = mkDefault true; 186 websocket = mkDefault true; 187 }; 188 muc = [ 189 { 190 domain = "conference.${cfg.hostName}"; 191 name = "Jitsi Meet MUC"; 192 roomLocking = false; 193 roomDefaultPublicJids = true; 194 extraConfig = '' 195 storage = "memory" 196 ''; 197 } 198 { 199 domain = "internal.${cfg.hostName}"; 200 name = "Jitsi Meet Videobridge MUC"; 201 extraConfig = '' 202 storage = "memory" 203 admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" } 204 ''; 205 #-- muc_room_cache_size = 1000 206 } 207 ]; 208 extraModules = [ "pubsub" "smacks" ]; 209 extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ]; 210 extraConfig = lib.mkMerge [ (mkAfter '' 211 Component "focus.${cfg.hostName}" "client_proxy" 212 target_address = "focus@auth.${cfg.hostName}" 213 '') 214 (mkBefore '' 215 cross_domain_websocket = true; 216 consider_websocket_secure = true; 217 '') 218 ]; 219 virtualHosts.${cfg.hostName} = { 220 enabled = true; 221 domain = cfg.hostName; 222 extraConfig = '' 223 authentication = "anonymous" 224 c2s_require_encryption = false 225 admins = { "focus@auth.${cfg.hostName}" } 226 smacks_max_unacked_stanzas = 5 227 smacks_hibernation_time = 60 228 smacks_max_hibernated_sessions = 1 229 smacks_max_old_sessions = 1 230 ''; 231 ssl = { 232 cert = "/var/lib/jitsi-meet/jitsi-meet.crt"; 233 key = "/var/lib/jitsi-meet/jitsi-meet.key"; 234 }; 235 }; 236 virtualHosts."auth.${cfg.hostName}" = { 237 enabled = true; 238 domain = "auth.${cfg.hostName}"; 239 extraConfig = '' 240 authentication = "internal_plain" 241 ''; 242 ssl = { 243 cert = "/var/lib/jitsi-meet/jitsi-meet.crt"; 244 key = "/var/lib/jitsi-meet/jitsi-meet.key"; 245 }; 246 }; 247 virtualHosts."recorder.${cfg.hostName}" = { 248 enabled = true; 249 domain = "recorder.${cfg.hostName}"; 250 extraConfig = '' 251 authentication = "internal_plain" 252 c2s_require_encryption = false 253 ''; 254 }; 255 }; 256 systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable { 257 EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; 258 SupplementaryGroups = [ "jitsi-meet" ]; 259 }; 260 261 users.groups.jitsi-meet = {}; 262 systemd.tmpfiles.rules = [ 263 "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -" 264 ]; 265 266 systemd.services.jitsi-meet-init-secrets = { 267 wantedBy = [ "multi-user.target" ]; 268 before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service"); 269 path = [ config.services.prosody.package ]; 270 serviceConfig = { 271 Type = "oneshot"; 272 }; 273 274 script = let 275 secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); 276 videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret"; 277 in 278 '' 279 cd /var/lib/jitsi-meet 280 ${concatMapStringsSep "\n" (s: '' 281 if [ ! -f ${s} ]; then 282 tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s} 283 chown root:jitsi-meet ${s} 284 chmod 640 ${s} 285 fi 286 '') secrets} 287 288 # for easy access in prosody 289 echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env 290 chown root:jitsi-meet secrets-env 291 chmod 640 secrets-env 292 '' 293 + optionalString cfg.prosody.enable '' 294 prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" 295 prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" 296 prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} 297 prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)" 298 prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)" 299 300 # generate self-signed certificates 301 if [ ! -f /var/lib/jitsi-meet.crt ]; then 302 ${getBin pkgs.openssl}/bin/openssl req \ 303 -x509 \ 304 -newkey rsa:4096 \ 305 -keyout /var/lib/jitsi-meet/jitsi-meet.key \ 306 -out /var/lib/jitsi-meet/jitsi-meet.crt \ 307 -days 36500 \ 308 -nodes \ 309 -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}' 310 chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key} 311 chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key} 312 fi 313 ''; 314 }; 315 316 services.nginx = mkIf cfg.nginx.enable { 317 enable = mkDefault true; 318 virtualHosts.${cfg.hostName} = { 319 enableACME = mkDefault true; 320 forceSSL = mkDefault true; 321 root = pkgs.jitsi-meet; 322 extraConfig = '' 323 ssi on; 324 ''; 325 locations."@root_path".extraConfig = '' 326 rewrite ^/(.*)$ / break; 327 ''; 328 locations."~ ^/([^/\\?&:'\"]+)$".tryFiles = "$uri @root_path"; 329 locations."^~ /xmpp-websocket" = { 330 priority = 100; 331 proxyPass = "http://localhost:5280/xmpp-websocket"; 332 proxyWebsockets = true; 333 }; 334 locations."=/http-bind" = { 335 proxyPass = "http://localhost:5280/http-bind"; 336 extraConfig = '' 337 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 338 proxy_set_header Host $host; 339 ''; 340 }; 341 locations."=/external_api.js" = mkDefault { 342 alias = "${pkgs.jitsi-meet}/libs/external_api.min.js"; 343 }; 344 locations."=/config.js" = mkDefault { 345 alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig; 346 }; 347 locations."=/interface_config.js" = mkDefault { 348 alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""; 349 }; 350 }; 351 }; 352 353 services.caddy = mkIf cfg.caddy.enable { 354 enable = mkDefault true; 355 virtualHosts.${cfg.hostName} = { 356 extraConfig = 357 let 358 templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" {} '' 359 cp -R ${pkgs.jitsi-meet}/* . 360 for file in *.html **/*.html ; do 361 ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file 362 done 363 rm config.js 364 rm interface_config.js 365 cp -R . $out 366 cp ${overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig} $out/config.js 367 cp ${overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""} $out/interface_config.js 368 cp ./libs/external_api.min.js $out/external_api.js 369 ''; 370 in '' 371 handle /http-bind { 372 header Host ${cfg.hostName} 373 reverse_proxy 127.0.0.1:5280 374 } 375 handle /xmpp-websocket { 376 reverse_proxy 127.0.0.1:5280 377 } 378 handle { 379 templates 380 root * ${templatedJitsiMeet} 381 try_files {path} {path} 382 try_files {path} /index.html 383 file_server 384 } 385 ''; 386 }; 387 }; 388 389 services.jitsi-videobridge = mkIf cfg.videobridge.enable { 390 enable = true; 391 xmppConfigs."localhost" = { 392 userName = "jvb"; 393 domain = "auth.${cfg.hostName}"; 394 passwordFile = "/var/lib/jitsi-meet/videobridge-secret"; 395 mucJids = "jvbbrewery@internal.${cfg.hostName}"; 396 disableCertificateVerification = true; 397 }; 398 }; 399 400 services.jicofo = mkIf cfg.jicofo.enable { 401 enable = true; 402 xmppHost = "localhost"; 403 xmppDomain = cfg.hostName; 404 userDomain = "auth.${cfg.hostName}"; 405 userName = "focus"; 406 userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret"; 407 componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret"; 408 bridgeMuc = "jvbbrewery@internal.${cfg.hostName}"; 409 config = mkMerge [{ 410 "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true"; 411 #} (lib.mkIf cfg.jibri.enable { 412 } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) { 413 "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}"; 414 "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90"; 415 })]; 416 }; 417 418 services.jibri = mkIf cfg.jibri.enable { 419 enable = true; 420 421 xmppEnvironments."jitsi-meet" = { 422 xmppServerHosts = [ "localhost" ]; 423 xmppDomain = cfg.hostName; 424 425 control.muc = { 426 domain = "internal.${cfg.hostName}"; 427 roomName = "JibriBrewery"; 428 nickname = "jibri"; 429 }; 430 431 control.login = { 432 domain = "auth.${cfg.hostName}"; 433 username = "jibri"; 434 passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret"; 435 }; 436 437 call.login = { 438 domain = "recorder.${cfg.hostName}"; 439 username = "recorder"; 440 passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret"; 441 }; 442 443 usageTimeout = "0"; 444 disableCertificateVerification = true; 445 stripFromRoomDomain = "conference."; 446 }; 447 }; 448 }; 449 450 meta.doc = ./jitsi-meet.xml; 451 meta.maintainers = lib.teams.jitsi.members; 452}