1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11
12 cfg = config.services.jitsi-meet;
13
14 # The configuration files are JS of format "var <<string>> = <<JSON>>;". In order to
15 # override only some settings, we need to extract the JSON, use jq to merge it with
16 # the config provided by user, and then reconstruct the file.
17 overrideJs =
18 source: varName: userCfg: appendExtra:
19 let
20 extractor = pkgs.writeText "extractor.js" ''
21 var fs = require("fs");
22 eval(fs.readFileSync(process.argv[2], 'utf8'));
23 process.stdout.write(JSON.stringify(eval(process.argv[3])));
24 '';
25 userJson = pkgs.writeText "user.json" (builtins.toJSON userCfg);
26 in
27 (pkgs.runCommand "${varName}.js" { } ''
28 ${pkgs.nodejs}/bin/node ${extractor} ${source} ${varName} > default.json
29 (
30 echo "var ${varName} = "
31 ${pkgs.jq}/bin/jq -s '.[0] * .[1]' default.json ${userJson}
32 echo ";"
33 echo ${escapeShellArg appendExtra}
34 ) > $out
35 '');
36
37 # Essential config - it's probably not good to have these as option default because
38 # types.attrs doesn't do merging. Let's merge explicitly, can still be overridden if
39 # user desires.
40 defaultCfg = {
41 hosts = {
42 domain = cfg.hostName;
43 muc = "conference.${cfg.hostName}";
44 focus = "focus.${cfg.hostName}";
45 jigasi = "jigasi.${cfg.hostName}";
46 };
47 bosh = "//${cfg.hostName}/http-bind";
48 websocket = "wss://${cfg.hostName}/xmpp-websocket";
49
50 fileRecordingsEnabled = true;
51 liveStreamingEnabled = true;
52 hiddenDomain = "recorder.${cfg.hostName}";
53 };
54in
55{
56 options.services.jitsi-meet = with types; {
57 enable = mkEnableOption "Jitsi Meet - Secure, Simple and Scalable Video Conferences";
58
59 hostName = mkOption {
60 type = str;
61 example = "meet.example.org";
62 description = ''
63 FQDN of the Jitsi Meet instance.
64 '';
65 };
66
67 config = mkOption {
68 type = attrs;
69 default = { };
70 example = literalExpression ''
71 {
72 enableWelcomePage = false;
73 defaultLang = "fi";
74 }
75 '';
76 description = ''
77 Client-side web application settings that override the defaults in {file}`config.js`.
78
79 See <https://github.com/jitsi/jitsi-meet/blob/master/config.js> for default
80 configuration with comments.
81 '';
82 };
83
84 extraConfig = mkOption {
85 type = lines;
86 default = "";
87 description = ''
88 Text to append to {file}`config.js` web application config file.
89
90 Can be used to insert JavaScript logic to determine user's region in cascading bridges setup.
91 '';
92 };
93
94 interfaceConfig = mkOption {
95 type = attrs;
96 default = { };
97 example = literalExpression ''
98 {
99 SHOW_JITSI_WATERMARK = false;
100 SHOW_WATERMARK_FOR_GUESTS = false;
101 }
102 '';
103 description = ''
104 Client-side web-app interface settings that override the defaults in {file}`interface_config.js`.
105
106 See <https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js> for
107 default configuration with comments.
108 '';
109 };
110
111 videobridge = {
112 enable = mkOption {
113 type = bool;
114 default = true;
115 description = ''
116 Jitsi Videobridge instance and configure it to connect to Prosody.
117
118 Additional configuration is possible with {option}`services.jitsi-videobridge`
119 '';
120 };
121
122 passwordFile = mkOption {
123 type = nullOr str;
124 default = null;
125 example = "/run/keys/videobridge";
126 description = ''
127 File containing password to the Prosody account for videobridge.
128
129 If `null`, a file with password will be generated automatically. Setting
130 this option is useful if you plan to connect additional videobridges to the XMPP server.
131 '';
132 };
133 };
134
135 jicofo.enable = mkOption {
136 type = bool;
137 default = true;
138 description = ''
139 Whether to enable JiCoFo instance and configure it to connect to Prosody.
140
141 Additional configuration is possible with {option}`services.jicofo`.
142 '';
143 };
144
145 jibri.enable = mkOption {
146 type = bool;
147 default = false;
148 description = ''
149 Whether to enable a Jibri instance and configure it to connect to Prosody.
150
151 Additional configuration is possible with {option}`services.jibri`, and
152 {option}`services.jibri.finalizeScript` is especially useful.
153 '';
154 };
155
156 jigasi.enable = mkOption {
157 type = bool;
158 default = false;
159 description = ''
160 Whether to enable jigasi instance and configure it to connect to Prosody.
161
162 Additional configuration is possible with <option>services.jigasi</option>.
163 '';
164 };
165
166 nginx.enable = mkOption {
167 type = bool;
168 default = true;
169 description = ''
170 Whether to enable nginx virtual host that will serve the javascript application and act as
171 a proxy for the XMPP server. Further nginx configuration can be done by adapting
172 {option}`services.nginx.virtualHosts.<hostName>`.
173 When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable
174 this, set the {option}`services.nginx.virtualHosts.<hostName>.enableACME` to
175 `false` and if appropriate do the same for
176 {option}`services.nginx.virtualHosts.<hostName>.forceSSL`.
177 '';
178 };
179
180 caddy.enable = mkEnableOption "caddy reverse proxy to expose jitsi-meet";
181
182 prosody.enable = mkOption {
183 type = bool;
184 default = true;
185 example = false;
186 description = ''
187 Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this
188 off if you want to configure it manually.
189 '';
190 };
191
192 prosody.allowners_muc = mkOption {
193 type = bool;
194 default = false;
195 description = ''
196 Add module allowners, any user in chat is able to
197 kick other. Usefull in jitsi-meet to kick ghosts.
198 '';
199 };
200
201 prosody.lockdown = mkOption {
202 type = bool;
203 default = false;
204 example = true;
205 description = ''
206 Whether to disable Prosody features not needed by Jitsi Meet.
207
208 The default Prosody configuration assumes that it will be used as a
209 general-purpose XMPP server rather than as a companion service for
210 Jitsi Meet. This option reconfigures Prosody to only listen on
211 localhost without support for TLS termination, XMPP federation or
212 the file transfer proxy.
213 '';
214 };
215
216 excalidraw.enable = mkEnableOption "Excalidraw collaboration backend for Jitsi";
217 excalidraw.port = mkOption {
218 type = types.port;
219 default = 3002;
220 description = ''The port which the Excalidraw backend for Jitsi should listen to.'';
221 };
222
223 secureDomain = {
224 enable = mkEnableOption "Authenticated room creation";
225 authentication = mkOption {
226 type = types.str;
227 default = "internal_hashed";
228 description = ''The authentication type to be used by jitsi'';
229 };
230 };
231 };
232
233 config = mkIf cfg.enable {
234 services.prosody = mkIf cfg.prosody.enable {
235
236 # required for muc_breakout_rooms
237 package = lib.mkDefault (
238 pkgs.prosody.override {
239 withExtraLuaPackages = p: with p; [ cjson ];
240 }
241 );
242
243 enable = mkDefault true;
244 xmppComplianceSuite = mkDefault false;
245 modules = {
246 admin_adhoc = mkDefault false;
247 bosh = mkDefault true;
248 ping = mkDefault true;
249 roster = mkDefault true;
250 saslauth = mkDefault true;
251 smacks = mkDefault true;
252 tls = mkDefault true;
253 websocket = mkDefault true;
254 proxy65 = mkIf cfg.prosody.lockdown (mkDefault false);
255 };
256 httpInterfaces = mkIf cfg.prosody.lockdown (mkDefault [ "127.0.0.1" ]);
257 httpsPorts = mkIf cfg.prosody.lockdown (mkDefault [ ]);
258 muc = [
259 {
260 domain = "conference.${cfg.hostName}";
261 name = "Jitsi Meet MUC";
262 allowners_muc = cfg.prosody.allowners_muc;
263 roomLocking = false;
264 roomDefaultPublicJids = true;
265 extraConfig = ''
266 restrict_room_creation = true
267 storage = "memory"
268 admins = { "focus@auth.${cfg.hostName}" }
269 '';
270 }
271 {
272 domain = "breakout.${cfg.hostName}";
273 name = "Jitsi Meet Breakout MUC";
274 roomLocking = false;
275 roomDefaultPublicJids = true;
276 extraConfig = ''
277 restrict_room_creation = true
278 storage = "memory"
279 admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" }
280 '';
281 }
282 {
283 domain = "internal.auth.${cfg.hostName}";
284 name = "Jitsi Meet Videobridge MUC";
285 roomLocking = false;
286 roomDefaultPublicJids = true;
287 extraConfig = ''
288 storage = "memory"
289 admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}", "jigasi@auth.${cfg.hostName}" }
290 '';
291 #-- muc_room_cache_size = 1000
292 }
293 {
294 domain = "lobby.${cfg.hostName}";
295 name = "Jitsi Meet Lobby MUC";
296 roomLocking = false;
297 roomDefaultPublicJids = true;
298 extraConfig = ''
299 restrict_room_creation = true
300 storage = "memory"
301 '';
302 }
303 ];
304 extraModules = [
305 "pubsub"
306 "smacks"
307 "speakerstats"
308 "external_services"
309 "conference_duration"
310 "muc_lobby_rooms"
311 "muc_breakout_rooms"
312 "av_moderation"
313 "muc_hide_all"
314 "muc_meeting_id"
315 "muc_domain_mapper"
316 "muc_rate_limit"
317 "limits_exception"
318 "persistent_lobby"
319 "room_metadata"
320 ];
321 extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ];
322 extraConfig = lib.mkMerge [
323 (mkAfter ''
324 Component "focus.${cfg.hostName}" "client_proxy"
325 target_address = "focus@auth.${cfg.hostName}"
326
327 Component "jigasi.${cfg.hostName}" "client_proxy"
328 target_address = "jigasi@auth.${cfg.hostName}"
329
330 Component "speakerstats.${cfg.hostName}" "speakerstats_component"
331 muc_component = "conference.${cfg.hostName}"
332
333 Component "conferenceduration.${cfg.hostName}" "conference_duration_component"
334 muc_component = "conference.${cfg.hostName}"
335
336 Component "endconference.${cfg.hostName}" "end_conference"
337 muc_component = "conference.${cfg.hostName}"
338
339 Component "avmoderation.${cfg.hostName}" "av_moderation_component"
340 muc_component = "conference.${cfg.hostName}"
341
342 Component "metadata.${cfg.hostName}" "room_metadata_component"
343 muc_component = "conference.${cfg.hostName}"
344 breakout_rooms_component = "breakout.${cfg.hostName}"
345 '')
346 (mkBefore (
347 ''
348 muc_mapper_domain_base = "${cfg.hostName}"
349
350 http_cors_override = {
351 websocket = { enabled = true }
352 }
353 consider_websocket_secure = true;
354
355 unlimited_jids = {
356 "focus@auth.${cfg.hostName}",
357 "jvb@auth.${cfg.hostName}"
358 }
359 ''
360 + optionalString cfg.prosody.lockdown ''
361 c2s_interfaces = { "127.0.0.1" };
362 modules_disabled = { "s2s" };
363 ''
364 ))
365 ];
366 virtualHosts.${cfg.hostName} = {
367 enabled = true;
368 domain = cfg.hostName;
369 extraConfig = ''
370 authentication = ${
371 if cfg.secureDomain.enable then "\"${cfg.secureDomain.authentication}\"" else "\"jitsi-anonymous\""
372 }
373 c2s_require_encryption = false
374 admins = { "focus@auth.${cfg.hostName}" }
375 smacks_max_unacked_stanzas = 5
376 smacks_hibernation_time = 60
377 smacks_max_hibernated_sessions = 1
378 smacks_max_old_sessions = 1
379
380 av_moderation_component = "avmoderation.${cfg.hostName}"
381 speakerstats_component = "speakerstats.${cfg.hostName}"
382 conference_duration_component = "conferenceduration.${cfg.hostName}"
383 end_conference_component = "endconference.${cfg.hostName}"
384
385 lobby_muc = "lobby.${cfg.hostName}"
386 breakout_rooms_muc = "breakout.${cfg.hostName}"
387 room_metadata_component = "metadata.${cfg.hostName}"
388 main_muc = "conference.${cfg.hostName}"
389 '';
390 ssl = {
391 cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
392 key = "/var/lib/jitsi-meet/jitsi-meet.key";
393 };
394 };
395 virtualHosts."auth.${cfg.hostName}" = {
396 enabled = true;
397 domain = "auth.${cfg.hostName}";
398 extraConfig = ''
399 authentication = "internal_hashed"
400 '';
401 ssl = {
402 cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
403 key = "/var/lib/jitsi-meet/jitsi-meet.key";
404 };
405 };
406 virtualHosts."recorder.${cfg.hostName}" = {
407 enabled = true;
408 domain = "recorder.${cfg.hostName}";
409 extraConfig = ''
410 authentication = "internal_plain"
411 c2s_require_encryption = false
412 '';
413 };
414 virtualHosts."guest.${cfg.hostName}" = {
415 enabled = true;
416 domain = "guest.${cfg.hostName}";
417 extraConfig = ''
418 authentication = "anonymous"
419 c2s_require_encryption = false
420 '';
421 };
422 };
423 systemd.services.prosody = mkIf cfg.prosody.enable {
424 preStart =
425 let
426 videobridgeSecret =
427 if cfg.videobridge.passwordFile != null then
428 cfg.videobridge.passwordFile
429 else
430 "/var/lib/jitsi-meet/videobridge-secret";
431
432 in
433 ''
434 ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
435 ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
436 ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
437 ${config.services.prosody.package}/bin/prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
438 ${config.services.prosody.package}/bin/prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
439 ''
440 + optionalString cfg.jigasi.enable ''
441 ${config.services.prosody.package}/bin/prosodyctl register jigasi auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jigasi-user-secret)"
442 '';
443
444 serviceConfig = {
445 EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
446 SupplementaryGroups = [ "jitsi-meet" ];
447 };
448 reloadIfChanged = true;
449 };
450
451 users.groups.jitsi-meet = { };
452 systemd.tmpfiles.rules = [
453 "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -"
454 ];
455
456 systemd.services.jitsi-meet-init-secrets = {
457 wantedBy = [ "multi-user.target" ];
458 before = [
459 "jicofo.service"
460 "jitsi-videobridge2.service"
461 ]
462 ++ (optional cfg.prosody.enable "prosody.service")
463 ++ (optional cfg.jigasi.enable "jigasi.service");
464 serviceConfig = {
465 Type = "oneshot";
466 UMask = "027";
467 User = "root";
468 Group = "jitsi-meet";
469 WorkingDirectory = "/var/lib/jitsi-meet";
470 };
471
472 script =
473 let
474 secrets = [
475 "jicofo-component-secret"
476 "jicofo-user-secret"
477 "jibri-auth-secret"
478 "jibri-recorder-secret"
479 ]
480 ++ (optionals cfg.jigasi.enable [
481 "jigasi-user-secret"
482 "jigasi-component-secret"
483 ])
484 ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
485 in
486 ''
487 ${concatMapStringsSep "\n" (s: ''
488 if [ ! -f ${s} ]; then
489 tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s}
490 fi
491 '') secrets}
492
493 # for easy access in prosody
494 echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env
495 echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env
496 ''
497 + optionalString cfg.prosody.enable ''
498 # generate self-signed certificates
499 if [ ! -f /var/lib/jitsi-meet/jitsi-meet.crt ]; then
500 ${getBin pkgs.openssl}/bin/openssl req \
501 -x509 \
502 -newkey rsa:4096 \
503 -keyout /var/lib/jitsi-meet/jitsi-meet.key \
504 -out /var/lib/jitsi-meet/jitsi-meet.crt \
505 -days 36500 \
506 -nodes \
507 -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}'
508 chmod 640 /var/lib/jitsi-meet/jitsi-meet.key
509 fi
510 '';
511 };
512
513 systemd.services.jitsi-excalidraw = mkIf cfg.excalidraw.enable {
514 description = "Excalidraw collaboration backend for Jitsi";
515 after = [ "network.target" ];
516 wantedBy = [ "multi-user.target" ];
517 environment.PORT = toString cfg.excalidraw.port;
518
519 serviceConfig = {
520 Type = "simple";
521 ExecStart = "${pkgs.jitsi-excalidraw}/bin/jitsi-excalidraw-backend";
522 Restart = "on-failure";
523
524 DynamicUser = true;
525 Group = "jitsi-meet";
526 CapabilityBoundingSet = "";
527 NoNewPrivileges = true;
528 ProtectSystem = "strict";
529 ProtectClock = true;
530 ProtectHome = true;
531 ProtectProc = "noaccess";
532 ProtectKernelLogs = true;
533 PrivateTmp = true;
534 PrivateDevices = true;
535 PrivateUsers = true;
536 ProtectHostname = true;
537 ProtectKernelTunables = true;
538 ProtectKernelModules = true;
539 ProtectControlGroups = true;
540 RestrictAddressFamilies = [
541 "AF_INET"
542 "AF_INET6"
543 ];
544 RestrictNamespaces = true;
545 LockPersonality = true;
546 RestrictRealtime = true;
547 RestrictSUIDSGID = true;
548 SystemCallFilter = [
549 "@system-service @pkey"
550 "~@privileged"
551 ];
552 };
553 };
554
555 services.nginx = mkIf cfg.nginx.enable {
556 enable = mkDefault true;
557 virtualHosts.${cfg.hostName} = {
558 enableACME = mkDefault true;
559 forceSSL = mkDefault true;
560 root = pkgs.jitsi-meet;
561 extraConfig = ''
562 ssi on;
563 '';
564 locations."@root_path".extraConfig = ''
565 rewrite ^/(.*)$ / break;
566 '';
567 locations."~ ^/([^/\\?&:'\"]+)$".tryFiles = "$uri @root_path";
568 locations."^~ /xmpp-websocket" = {
569 priority = 100;
570 proxyPass = "http://localhost:5280/xmpp-websocket";
571 proxyWebsockets = true;
572 };
573 locations."=/http-bind" = {
574 proxyPass = "http://localhost:5280/http-bind";
575 extraConfig = ''
576 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
577 proxy_set_header Host $host;
578 '';
579 };
580 locations."=/external_api.js" = mkDefault {
581 alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
582 };
583 locations."=/_api/room-info" = {
584 proxyPass = "http://localhost:5280/room-info";
585 extraConfig = ''
586 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
587 proxy_set_header Host $host;
588 '';
589 };
590 locations."=/config.js" = mkDefault {
591 alias =
592 overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config)
593 cfg.extraConfig;
594 };
595 locations."=/interface_config.js" = mkDefault {
596 alias =
597 overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig
598 "";
599 };
600 locations."/socket.io/" = mkIf cfg.excalidraw.enable {
601 proxyPass = "http://127.0.0.1:${toString cfg.excalidraw.port}";
602 proxyWebsockets = true;
603 };
604 };
605 };
606
607 services.caddy = mkIf cfg.caddy.enable {
608 enable = mkDefault true;
609 virtualHosts.${cfg.hostName} = {
610 extraConfig =
611 let
612 templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" { } ''
613 cp -R --no-preserve=all ${pkgs.jitsi-meet}/* .
614 for file in *.html **/*.html ; do
615 ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file
616 done
617 rm config.js
618 rm interface_config.js
619 cp -R . $out
620 cp ${
621 overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config)
622 cfg.extraConfig
623 } $out/config.js
624 cp ${
625 overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""
626 } $out/interface_config.js
627 cp ./libs/external_api.min.js $out/external_api.js
628 '';
629 in
630 (optionalString cfg.excalidraw.enable ''
631 handle /socket.io/ {
632 reverse_proxy 127.0.0.1:${toString cfg.excalidraw.port}
633 }
634 '')
635 + ''
636 handle /http-bind {
637 header Host ${cfg.hostName}
638 reverse_proxy 127.0.0.1:5280
639 }
640 handle /xmpp-websocket {
641 reverse_proxy 127.0.0.1:5280
642 }
643 handle {
644 templates
645 root * ${templatedJitsiMeet}
646 try_files {path} {path}
647 try_files {path} /index.html
648 file_server
649 }
650 '';
651 };
652 };
653
654 services.jitsi-meet.config =
655 recursiveUpdate
656 (mkIf cfg.excalidraw.enable {
657 whiteboard = {
658 enabled = true;
659 collabServerBaseUrl = "https://${cfg.hostName}";
660 };
661 })
662 (
663 mkIf cfg.secureDomain.enable {
664 hosts.anonymousdomain = "guest.${cfg.hostName}";
665 }
666 );
667
668 services.jitsi-videobridge = mkIf cfg.videobridge.enable {
669 enable = true;
670 xmppConfigs."localhost" = {
671 userName = "jvb";
672 domain = "auth.${cfg.hostName}";
673 passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
674 mucJids = "jvbbrewery@internal.auth.${cfg.hostName}";
675 disableCertificateVerification = true;
676 };
677 };
678
679 services.jicofo = mkIf cfg.jicofo.enable {
680 enable = true;
681 xmppHost = "localhost";
682 xmppDomain = cfg.hostName;
683 userDomain = "auth.${cfg.hostName}";
684 userName = "focus";
685 userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
686 componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
687 bridgeMuc = "jvbbrewery@internal.auth.${cfg.hostName}";
688 config = mkMerge [
689 {
690 jicofo.xmpp.service.disable-certificate-verification = true;
691 jicofo.xmpp.client.disable-certificate-verification = true;
692 }
693 (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
694 jicofo.jibri = {
695 brewery-jid = "JibriBrewery@internal.auth.${cfg.hostName}";
696 pending-timeout = "90";
697 };
698 })
699 (lib.mkIf cfg.secureDomain.enable {
700 jicofo = {
701 authentication = {
702 enabled = "true";
703 type = "XMPP";
704 login-url = cfg.hostName;
705 };
706 xmpp.client.client-proxy = "focus.${cfg.hostName}";
707 };
708 })
709 ];
710 };
711
712 services.jibri = mkIf cfg.jibri.enable {
713 enable = true;
714
715 xmppEnvironments."jitsi-meet" = {
716 xmppServerHosts = [ "localhost" ];
717 xmppDomain = cfg.hostName;
718
719 control.muc = {
720 domain = "internal.auth.${cfg.hostName}";
721 roomName = "JibriBrewery";
722 nickname = "jibri";
723 };
724
725 control.login = {
726 domain = "auth.${cfg.hostName}";
727 username = "jibri";
728 passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
729 };
730
731 call.login = {
732 domain = "recorder.${cfg.hostName}";
733 username = "recorder";
734 passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
735 };
736
737 usageTimeout = "0";
738 disableCertificateVerification = true;
739 stripFromRoomDomain = "conference.";
740 };
741 };
742
743 services.jigasi = mkIf cfg.jigasi.enable {
744 enable = true;
745 xmppHost = "localhost";
746 xmppDomain = cfg.hostName;
747 userDomain = "auth.${cfg.hostName}";
748 userName = "jigasi";
749 userPasswordFile = "/var/lib/jitsi-meet/jigasi-user-secret";
750 componentPasswordFile = "/var/lib/jitsi-meet/jigasi-component-secret";
751 bridgeMuc = "jigasibrewery@internal.${cfg.hostName}";
752 config = {
753 "org.jitsi.jigasi.ALWAYS_TRUST_MODE_ENABLED" = "true";
754 };
755 };
756 };
757
758 meta.doc = ./jitsi-meet.md;
759 meta.maintainers = lib.teams.jitsi.members;
760}