1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9let
10 cfg = config.services.prosody;
11
12 sslOpts =
13 { ... }:
14 {
15
16 options = {
17
18 key = mkOption {
19 type = types.path;
20 description = "Path to the key file.";
21 };
22
23 # TODO: rename to certificate to match the prosody config
24 cert = mkOption {
25 type = types.path;
26 description = "Path to the certificate file.";
27 };
28
29 extraOptions = mkOption {
30 type = types.attrs;
31 default = { };
32 description = "Extra SSL configuration options.";
33 };
34
35 };
36 };
37
38 discoOpts = {
39 options = {
40 url = mkOption {
41 type = types.str;
42 description = "URL of the endpoint you want to make discoverable";
43 };
44 description = mkOption {
45 type = types.str;
46 description = "A short description of the endpoint you want to advertise";
47 };
48 };
49 };
50
51 moduleOpts = {
52 # Required for compliance with https://compliance.conversations.im/about/
53 roster = mkOption {
54 type = types.bool;
55 default = true;
56 description = "Allow users to have a roster";
57 };
58
59 saslauth = mkOption {
60 type = types.bool;
61 default = true;
62 description = "Authentication for clients and servers. Recommended if you want to log in.";
63 };
64
65 tls = mkOption {
66 type = types.bool;
67 default = true;
68 description = "Add support for secure TLS on c2s/s2s connections";
69 };
70
71 dialback = mkOption {
72 type = types.bool;
73 default = true;
74 description = "s2s dialback support";
75 };
76
77 disco = mkOption {
78 type = types.bool;
79 default = true;
80 description = "Service discovery";
81 };
82
83 # Not essential, but recommended
84 carbons = mkOption {
85 type = types.bool;
86 default = true;
87 description = "Keep multiple clients in sync";
88 };
89
90 csi = mkOption {
91 type = types.bool;
92 default = true;
93 description = "Implements the CSI protocol that allows clients to report their active/inactive state to the server";
94 };
95
96 cloud_notify = mkOption {
97 type = types.bool;
98 default = true;
99 description = "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online";
100 };
101
102 pep = mkOption {
103 type = types.bool;
104 default = true;
105 description = "Enables users to publish their mood, activity, playing music and more";
106 };
107
108 private = mkOption {
109 type = types.bool;
110 default = true;
111 description = "Private XML storage (for room bookmarks, etc.)";
112 };
113
114 blocklist = mkOption {
115 type = types.bool;
116 default = true;
117 description = "Allow users to block communications with other users";
118 };
119
120 vcard = mkOption {
121 type = types.bool;
122 default = false;
123 description = "Allow users to set vCards";
124 };
125
126 vcard_legacy = mkOption {
127 type = types.bool;
128 default = true;
129 description = "Converts users profiles and Avatars between old and new formats";
130 };
131
132 bookmarks = mkOption {
133 type = types.bool;
134 default = true;
135 description = "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP";
136 };
137
138 # Nice to have
139 version = mkOption {
140 type = types.bool;
141 default = true;
142 description = "Replies to server version requests";
143 };
144
145 uptime = mkOption {
146 type = types.bool;
147 default = true;
148 description = "Report how long server has been running";
149 };
150
151 time = mkOption {
152 type = types.bool;
153 default = true;
154 description = "Let others know the time here on this server";
155 };
156
157 ping = mkOption {
158 type = types.bool;
159 default = true;
160 description = "Replies to XMPP pings with pongs";
161 };
162
163 register = mkOption {
164 type = types.bool;
165 default = true;
166 description = "Allow users to register on this server using a client and change passwords";
167 };
168
169 mam = mkOption {
170 type = types.bool;
171 default = true;
172 description = "Store messages in an archive and allow users to access it";
173 };
174
175 smacks = mkOption {
176 type = types.bool;
177 default = true;
178 description = "Allow a client to resume a disconnected session, and prevent message loss";
179 };
180
181 # Admin interfaces
182 admin_adhoc = mkOption {
183 type = types.bool;
184 default = true;
185 description = "Allows administration via an XMPP client that supports ad-hoc commands";
186 };
187
188 http_files = mkOption {
189 type = types.bool;
190 default = true;
191 description = "Serve static files from a directory over HTTP";
192 };
193
194 proxy65 = mkOption {
195 type = types.bool;
196 default = true;
197 description = "Enables a file transfer proxy service which clients behind NAT can use";
198 };
199
200 admin_telnet = mkOption {
201 type = types.bool;
202 default = false;
203 description = "Opens telnet console interface on localhost port 5582";
204 };
205
206 # HTTP modules
207 bosh = mkOption {
208 type = types.bool;
209 default = false;
210 description = "Enable BOSH clients, aka 'Jabber over HTTP'";
211 };
212
213 websocket = mkOption {
214 type = types.bool;
215 default = false;
216 description = "Enable WebSocket support";
217 };
218
219 # Other specific functionality
220 limits = mkOption {
221 type = types.bool;
222 default = false;
223 description = "Enable bandwidth limiting for XMPP connections";
224 };
225
226 groups = mkOption {
227 type = types.bool;
228 default = false;
229 description = "Shared roster support";
230 };
231
232 server_contact_info = mkOption {
233 type = types.bool;
234 default = false;
235 description = "Publish contact information for this service";
236 };
237
238 announce = mkOption {
239 type = types.bool;
240 default = false;
241 description = "Send announcement to all online users";
242 };
243
244 welcome = mkOption {
245 type = types.bool;
246 default = false;
247 description = "Welcome users who register accounts";
248 };
249
250 watchregistrations = mkOption {
251 type = types.bool;
252 default = false;
253 description = "Alert admins of registrations";
254 };
255
256 motd = mkOption {
257 type = types.bool;
258 default = false;
259 description = "Send a message to users when they log in";
260 };
261
262 legacyauth = mkOption {
263 type = types.bool;
264 default = false;
265 description = "Legacy authentication. Only used by some old clients and bots";
266 };
267 };
268
269 toLua =
270 x:
271 if builtins.isString x then
272 ''"${x}"''
273 else if builtins.isBool x then
274 boolToString x
275 else if builtins.isInt x then
276 toString x
277 else if builtins.isList x then
278 "{ ${lib.concatMapStringsSep ", " toLua x} }"
279 else
280 throw "Invalid Lua value";
281
282 settingsToLua =
283 prefix: settings:
284 generators.toKeyValue {
285 listsAsDuplicateKeys = false;
286 mkKeyValue =
287 k:
288 generators.mkKeyValueDefault {
289 mkValueString = toLua;
290 } " = " (prefix + k);
291 } (filterAttrs (k: v: v != null) settings);
292
293 createSSLOptsStr = o: ''
294 ssl = {
295 cafile = "/etc/ssl/certs/ca-bundle.crt";
296 key = "${o.key}";
297 certificate = "${o.cert}";
298 ${concatStringsSep "\n" (
299 mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions
300 )}
301 };
302 '';
303
304 mucOpts =
305 { ... }:
306 {
307 options = {
308 domain = mkOption {
309 type = types.str;
310 description = "Domain name of the MUC";
311 };
312 name = mkOption {
313 type = types.str;
314 description = "The name to return in service discovery responses for the MUC service itself";
315 default = "Prosody Chatrooms";
316 };
317 restrictRoomCreation = mkOption {
318 type = types.enum [
319 true
320 false
321 "admin"
322 "local"
323 ];
324 default = false;
325 description = "Restrict room creation to server admins";
326 };
327 maxHistoryMessages = mkOption {
328 type = types.int;
329 default = 20;
330 description = "Specifies a limit on what each room can be configured to keep";
331 };
332 roomLocking = mkOption {
333 type = types.bool;
334 default = true;
335 description = ''
336 Enables room locking, which means that a room must be
337 configured before it can be used. Locked rooms are invisible
338 and cannot be entered by anyone but the creator
339 '';
340 };
341 roomLockTimeout = mkOption {
342 type = types.int;
343 default = 300;
344 description = ''
345 Timeout after which the room is destroyed or unlocked if not
346 configured, in seconds
347 '';
348 };
349 tombstones = mkOption {
350 type = types.bool;
351 default = true;
352 description = ''
353 When a room is destroyed, it leaves behind a tombstone which
354 prevents the room being entered or recreated. It also allows
355 anyone who was not in the room at the time it was destroyed
356 to learn about it, and to update their bookmarks. Tombstones
357 prevents the case where someone could recreate a previously
358 semi-anonymous room in order to learn the real JIDs of those
359 who often join there.
360 '';
361 };
362 tombstoneExpiry = mkOption {
363 type = types.int;
364 default = 2678400;
365 description = ''
366 This settings controls how long a tombstone is considered
367 valid. It defaults to 31 days. After this time, the room in
368 question can be created again.
369 '';
370 };
371 allowners_muc = mkOption {
372 type = types.bool;
373 default = false;
374 description = ''
375 Add module allowners, any user in chat is able to
376 kick other. Usefull in jitsi-meet to kick ghosts.
377 '';
378 };
379 vcard_muc = mkOption {
380 type = types.bool;
381 default = true;
382 description = "Adds the ability to set vCard for Multi User Chat rooms";
383 };
384
385 # Extra parameters. Defaulting to prosody default values.
386 # Adding them explicitly to make them visible from the options
387 # documentation.
388 #
389 # See https://prosody.im/doc/modules/mod_muc for more details.
390 roomDefaultPublic = mkOption {
391 type = types.bool;
392 default = true;
393 description = "If set, the MUC rooms will be public by default.";
394 };
395 roomDefaultMembersOnly = mkOption {
396 type = types.bool;
397 default = false;
398 description = "If set, the MUC rooms will only be accessible to the members by default.";
399 };
400 roomDefaultModerated = mkOption {
401 type = types.bool;
402 default = false;
403 description = "If set, the MUC rooms will be moderated by default.";
404 };
405 roomDefaultPublicJids = mkOption {
406 type = types.bool;
407 default = false;
408 description = "If set, the MUC rooms will display the public JIDs by default.";
409 };
410 roomDefaultChangeSubject = mkOption {
411 type = types.bool;
412 default = false;
413 description = "If set, the rooms will display the public JIDs by default.";
414 };
415 roomDefaultHistoryLength = mkOption {
416 type = types.int;
417 default = 20;
418 description = "Number of history message sent to participants by default.";
419 };
420 roomDefaultLanguage = mkOption {
421 type = types.str;
422 default = "en";
423 description = "Default room language.";
424 };
425 extraConfig = mkOption {
426 type = types.lines;
427 default = "";
428 description = "Additional MUC specific configuration";
429 };
430 };
431 };
432
433 uploadHttpOpts =
434 { ... }:
435 {
436 options = {
437 domain = mkOption {
438 type = types.nullOr types.str;
439 description = "Domain name for the http-upload service";
440 };
441 uploadFileSizeLimit = mkOption {
442 type = types.str;
443 default = "50 * 1024 * 1024";
444 description = "Maximum file size, in bytes. Defaults to 50MB.";
445 };
446 uploadExpireAfter = mkOption {
447 type = types.str;
448 default = "60 * 60 * 24 * 7";
449 description = "Max age of a file before it gets deleted, in seconds.";
450 };
451 userQuota = mkOption {
452 type = types.nullOr types.int;
453 default = null;
454 example = 1234;
455 description = ''
456 Maximum size of all uploaded files per user, in bytes. There
457 will be no quota if this option is set to null.
458 '';
459 };
460 httpUploadPath = mkOption {
461 type = types.str;
462 description = ''
463 Directory where the uploaded files will be stored when the http_upload module is used.
464 By default, uploaded files are put in a sub-directory of the default Prosody storage path (usually /var/lib/prosody).
465 '';
466 default = "/var/lib/prosody";
467 };
468 };
469 };
470
471 httpFileShareOpts =
472 { ... }:
473 {
474 freeformType =
475 with types;
476 let
477 atom = oneOf [
478 int
479 bool
480 str
481 (listOf atom)
482 ];
483 in
484 attrsOf (nullOr atom)
485 // {
486 description = "int, bool, string or list of them";
487 };
488 options.domain = mkOption {
489 type = with types; nullOr str;
490 description = "Domain name for a http_file_share service.";
491 };
492 };
493
494 vHostOpts =
495 { ... }:
496 {
497
498 options = {
499
500 # TODO: require attribute
501 domain = mkOption {
502 type = types.str;
503 description = "Domain name";
504 };
505
506 enabled = mkOption {
507 type = types.bool;
508 default = false;
509 description = "Whether to enable the virtual host";
510 };
511
512 ssl = mkOption {
513 type = types.nullOr (types.submodule sslOpts);
514 default = null;
515 description = "Paths to SSL files";
516 };
517
518 extraConfig = mkOption {
519 type = types.lines;
520 default = "";
521 description = "Additional virtual host specific configuration";
522 };
523
524 };
525
526 };
527
528in
529
530{
531
532 ###### interface
533
534 options = {
535
536 services.prosody = {
537
538 enable = mkOption {
539 type = types.bool;
540 default = false;
541 description = "Whether to enable the prosody server";
542 };
543
544 xmppComplianceSuite = mkOption {
545 type = types.bool;
546 default = true;
547 description = ''
548 The XEP-0423 defines a set of recommended XEPs to implement
549 for a server. It's generally a good idea to implement this
550 set of extensions if you want to provide your users with a
551 good XMPP experience.
552
553 This NixOS module aims to provide a "advanced server"
554 experience as per defined in the XEP-0423[1] specification.
555
556 Setting this option to true will prevent you from building a
557 NixOS configuration which won't comply with this standard.
558 You can explicitly decide to ignore this standard if you
559 know what you are doing by setting this option to false.
560
561 [1] https://xmpp.org/extensions/xep-0423.html
562 '';
563 };
564
565 package = mkPackageOption pkgs "prosody" {
566 example = ''
567 pkgs.prosody.override {
568 withExtraLibs = [ pkgs.luaPackages.lpty ];
569 withCommunityModules = [ "auth_external" ];
570 };
571 '';
572 };
573
574 dataDir = mkOption {
575 type = types.path;
576 default = "/var/lib/prosody";
577 description = ''
578 The prosody home directory used to store all data. If left as the default value
579 this directory will automatically be created before the prosody server starts, otherwise
580 you are responsible for ensuring the directory exists with appropriate ownership
581 and permissions.
582 '';
583 };
584
585 disco_items = mkOption {
586 type = types.listOf (types.submodule discoOpts);
587 default = [ ];
588 description = "List of discoverable items you want to advertise.";
589 };
590
591 user = mkOption {
592 type = types.str;
593 default = "prosody";
594 description = ''
595 User account under which prosody runs.
596
597 ::: {.note}
598 If left as the default value this user will automatically be created
599 on system activation, otherwise you are responsible for
600 ensuring the user exists before the prosody service starts.
601 :::
602 '';
603 };
604
605 group = mkOption {
606 type = types.str;
607 default = "prosody";
608 description = ''
609 Group account under which prosody runs.
610
611 ::: {.note}
612 If left as the default value this group will automatically be created
613 on system activation, otherwise you are responsible for
614 ensuring the group exists before the prosody service starts.
615 :::
616 '';
617 };
618
619 allowRegistration = mkOption {
620 type = types.bool;
621 default = false;
622 description = "Allow account creation";
623 };
624
625 # HTTP server-related options
626 httpPorts = mkOption {
627 type = types.listOf types.int;
628 description = "Listening HTTP ports list for this service.";
629 default = [ 5280 ];
630 };
631
632 httpInterfaces = mkOption {
633 type = types.listOf types.str;
634 default = [
635 "*"
636 "::"
637 ];
638 description = "Interfaces on which the HTTP server will listen on.";
639 };
640
641 httpsPorts = mkOption {
642 type = types.listOf types.int;
643 description = "Listening HTTPS ports list for this service.";
644 default = [ 5281 ];
645 };
646
647 httpsInterfaces = mkOption {
648 type = types.listOf types.str;
649 default = [
650 "*"
651 "::"
652 ];
653 description = "Interfaces on which the HTTPS server will listen on.";
654 };
655
656 c2sRequireEncryption = mkOption {
657 type = types.bool;
658 default = true;
659 description = ''
660 Force clients to use encrypted connections? This option will
661 prevent clients from authenticating unless they are using encryption.
662 '';
663 };
664
665 s2sRequireEncryption = mkOption {
666 type = types.bool;
667 default = true;
668 description = ''
669 Force servers to use encrypted connections? This option will
670 prevent servers from authenticating unless they are using encryption.
671 Note that this is different from authentication.
672 '';
673 };
674
675 s2sSecureAuth = mkOption {
676 type = types.bool;
677 default = false;
678 description = ''
679 Force certificate authentication for server-to-server connections?
680 This provides ideal security, but requires servers you communicate
681 with to support encryption AND present valid, trusted certificates.
682 For more information see https://prosody.im/doc/s2s#security
683 '';
684 };
685
686 s2sInsecureDomains = mkOption {
687 type = types.listOf types.str;
688 default = [ ];
689 example = [ "insecure.example.com" ];
690 description = ''
691 Some servers have invalid or self-signed certificates. You can list
692 remote domains here that will not be required to authenticate using
693 certificates. They will be authenticated using DNS instead, even
694 when s2s_secure_auth is enabled.
695 '';
696 };
697
698 s2sSecureDomains = mkOption {
699 type = types.listOf types.str;
700 default = [ ];
701 example = [ "jabber.org" ];
702 description = ''
703 Even if you leave s2s_secure_auth disabled, you can still require valid
704 certificates for some domains by specifying a list here.
705 '';
706 };
707
708 modules = moduleOpts;
709
710 extraModules = mkOption {
711 type = types.listOf types.str;
712 default = [ ];
713 description = "Enable custom modules";
714 };
715
716 extraPluginPaths = mkOption {
717 type = types.listOf types.path;
718 default = [ ];
719 description = "Additional path in which to look find plugins/modules";
720 };
721
722 uploadHttp = mkOption {
723 description = ''
724 Configures the old Prosody builtin HTTP server to handle user uploads.
725 '';
726 type = types.nullOr (types.submodule uploadHttpOpts);
727 default = null;
728 example = {
729 domain = "uploads.my-xmpp-example-host.org";
730 };
731 };
732
733 httpFileShare = mkOption {
734 description = ''
735 Configures the http_file_share module to handle user uploads.
736 '';
737 type = types.nullOr (types.submodule httpFileShareOpts);
738 default = null;
739 example = {
740 domain = "uploads.my-xmpp-example-host.org";
741 };
742 };
743
744 muc = mkOption {
745 type = types.listOf (types.submodule mucOpts);
746 default = [ ];
747 example = [
748 {
749 domain = "conference.my-xmpp-example-host.org";
750 }
751 ];
752 description = "Multi User Chat (MUC) configuration";
753 };
754
755 virtualHosts = mkOption {
756
757 description = "Define the virtual hosts";
758
759 type = with types; attrsOf (submodule vHostOpts);
760
761 example = {
762 myhost = {
763 domain = "my-xmpp-example-host.org";
764 enabled = true;
765 };
766 };
767
768 default = {
769 localhost = {
770 domain = "localhost";
771 enabled = true;
772 };
773 };
774
775 };
776
777 ssl = mkOption {
778 type = types.nullOr (types.submodule sslOpts);
779 default = null;
780 description = "Paths to SSL files";
781 };
782
783 admins = mkOption {
784 type = types.listOf types.str;
785 default = [ ];
786 example = [
787 "admin1@example.com"
788 "admin2@example.com"
789 ];
790 description = "List of administrators of the current host";
791 };
792
793 authentication = mkOption {
794 type = types.enum [
795 "internal_plain"
796 "internal_hashed"
797 "cyrus"
798 "anonymous"
799 ];
800 default = "internal_hashed";
801 example = "internal_plain";
802 description = "Authentication mechanism used for logins.";
803 };
804
805 extraConfig = mkOption {
806 type = types.lines;
807 default = "";
808 description = "Additional prosody configuration";
809 };
810
811 log = mkOption {
812 type = types.lines;
813 default = ''"*syslog"'';
814 description = "Logging configuration. See [](https://prosody.im/doc/logging) for more details";
815 example = ''
816 {
817 { min = "warn"; to = "*syslog"; };
818 }
819 '';
820 };
821
822 };
823 };
824
825 ###### implementation
826
827 config = mkIf cfg.enable {
828
829 assertions =
830 let
831 genericErrMsg = ''
832
833 Having a server not XEP-0423-compliant might make your XMPP
834 experience terrible. See the NixOS manual for further
835 information.
836
837 If you know what you're doing, you can disable this warning by
838 setting config.services.prosody.xmppComplianceSuite to false.
839 '';
840 errors = [
841 {
842 assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite;
843 message =
844 ''
845 You need to setup at least a MUC domain to comply with
846 XEP-0423.
847 ''
848 + genericErrMsg;
849 }
850 {
851 assertion = cfg.uploadHttp != null || cfg.httpFileShare != null || !cfg.xmppComplianceSuite;
852 message =
853 ''
854 You need to setup the http_upload or http_file_share modules through config.services.prosody.uploadHttp
855 or config.services.prosody.httpFileShare to comply with XEP-0423.
856 ''
857 + genericErrMsg;
858 }
859 ];
860 in
861 errors;
862
863 environment.systemPackages = [ cfg.package ];
864
865 environment.etc."prosody/prosody.cfg.lua".text =
866 let
867 httpDiscoItems =
868 optional (cfg.uploadHttp != null) {
869 url = cfg.uploadHttp.domain;
870 description = "HTTP upload endpoint";
871 }
872 ++ optional (cfg.httpFileShare != null) {
873 url = cfg.httpFileShare.domain;
874 description = "HTTP file share endpoint";
875 };
876 mucDiscoItems = builtins.foldl' (
877 acc: muc:
878 [
879 {
880 url = muc.domain;
881 description = "${muc.domain} MUC endpoint";
882 }
883 ]
884 ++ acc
885 ) [ ] cfg.muc;
886 discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems;
887 in
888 ''
889
890 pidfile = "/run/prosody/prosody.pid"
891
892 log = ${cfg.log}
893
894 data_path = "${cfg.dataDir}"
895 plugin_paths = {
896 ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths)}
897 }
898
899 ${optionalString (cfg.ssl != null) (createSSLOptsStr cfg.ssl)}
900
901 admins = ${toLua cfg.admins}
902
903 modules_enabled = {
904
905 ${lib.concatStringsSep "\n " (
906 lib.mapAttrsToList (name: val: optionalString val "${toLua name};") cfg.modules
907 )}
908 ${lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
909 ${lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
910 };
911
912 disco_items = {
913 ${lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)}
914 };
915
916 allow_registration = ${toLua cfg.allowRegistration}
917
918 c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
919
920 s2s_require_encryption = ${toLua cfg.s2sRequireEncryption}
921
922 s2s_secure_auth = ${toLua cfg.s2sSecureAuth}
923
924 s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains}
925
926 s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
927
928 authentication = ${toLua cfg.authentication}
929
930 http_interfaces = ${toLua cfg.httpInterfaces}
931
932 https_interfaces = ${toLua cfg.httpsInterfaces}
933
934 http_ports = ${toLua cfg.httpPorts}
935
936 https_ports = ${toLua cfg.httpsPorts}
937
938 ${cfg.extraConfig}
939
940 ${lib.concatMapStrings (muc: ''
941 Component ${toLua muc.domain} "muc"
942 modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";''} ${optionalString muc.allowners_muc ''"muc_allowners";''} }
943 name = ${toLua muc.name}
944 restrict_room_creation = ${toLua muc.restrictRoomCreation}
945 max_history_messages = ${toLua muc.maxHistoryMessages}
946 muc_room_locking = ${toLua muc.roomLocking}
947 muc_room_lock_timeout = ${toLua muc.roomLockTimeout}
948 muc_tombstones = ${toLua muc.tombstones}
949 muc_tombstone_expiry = ${toLua muc.tombstoneExpiry}
950 muc_room_default_public = ${toLua muc.roomDefaultPublic}
951 muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly}
952 muc_room_default_moderated = ${toLua muc.roomDefaultModerated}
953 muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids}
954 muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject}
955 muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength}
956 muc_room_default_language = ${toLua muc.roomDefaultLanguage}
957 ${muc.extraConfig}
958 '') cfg.muc}
959
960 ${lib.optionalString (cfg.uploadHttp != null) ''
961 Component ${toLua cfg.uploadHttp.domain} "http_upload"
962 http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
963 http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
964 ${lib.optionalString (
965 cfg.uploadHttp.userQuota != null
966 ) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"}
967 http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
968 ''}
969
970 ${lib.optionalString (cfg.httpFileShare != null) ''
971 Component ${toLua cfg.httpFileShare.domain} "http_file_share"
972 ${settingsToLua " http_file_share_" (cfg.httpFileShare // { domain = null; })}
973 ''}
974
975 ${lib.concatStringsSep "\n" (
976 lib.mapAttrsToList (n: v: ''
977 VirtualHost "${v.domain}"
978 enabled = ${boolToString v.enabled};
979 ${optionalString (v.ssl != null) (createSSLOptsStr v.ssl)}
980 ${v.extraConfig}
981 '') cfg.virtualHosts
982 )}
983 '';
984
985 users.users.prosody = mkIf (cfg.user == "prosody") {
986 uid = config.ids.uids.prosody;
987 description = "Prosody user";
988 inherit (cfg) group;
989 home = cfg.dataDir;
990 };
991
992 users.groups.prosody = mkIf (cfg.group == "prosody") {
993 gid = config.ids.gids.prosody;
994 };
995
996 systemd.services.prosody = {
997 description = "Prosody XMPP server";
998 after = [ "network-online.target" ];
999 wants = [ "network-online.target" ];
1000 wantedBy = [ "multi-user.target" ];
1001 restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
1002 serviceConfig = mkMerge [
1003 {
1004 User = cfg.user;
1005 Group = cfg.group;
1006 Type = "forking";
1007 RuntimeDirectory = [ "prosody" ];
1008 PIDFile = "/run/prosody/prosody.pid";
1009 ExecStart = "${cfg.package}/bin/prosodyctl start";
1010 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
1011
1012 MemoryDenyWriteExecute = true;
1013 PrivateDevices = true;
1014 PrivateMounts = true;
1015 PrivateTmp = true;
1016 ProtectControlGroups = true;
1017 ProtectHome = true;
1018 ProtectHostname = true;
1019 ProtectKernelModules = true;
1020 ProtectKernelTunables = true;
1021 RestrictNamespaces = true;
1022 RestrictRealtime = true;
1023 RestrictSUIDSGID = true;
1024 }
1025 (mkIf (cfg.dataDir == "/var/lib/prosody") {
1026 StateDirectory = "prosody";
1027 })
1028 ];
1029 };
1030
1031 };
1032
1033 meta.doc = ./prosody.md;
1034}