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