]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/networking/prosody.nix
mermet: miniflux: fix LoadCredentialEncrypted= not supported by EnvironmentFile=
[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 {
271 listsAsDuplicateKeys = false;
272 mkKeyValue = k:
273 generators.mkKeyValueDefault
274 {
275 mkValueString = toLua;
276 } " = "
277 (prefix + k);
278 }
279 (filterAttrs (_k: v: v != null) settings);
280
281 createSSLOptsStr = o: ''
282 ssl = {
283 cafile = "/etc/ssl/certs/ca-bundle.crt";
284 key = "${o.key}";
285 certificate = "${o.cert}";
286 ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions)}
287 };
288 '';
289
290 mucOpts = { ... }: {
291 options = {
292 domain = mkOption {
293 type = types.str;
294 description = "Domain name of the MUC";
295 };
296 name = mkOption {
297 type = types.str;
298 description = "The name to return in service discovery responses for the MUC service itself";
299 default = "Prosody Chatrooms";
300 };
301 restrictRoomCreation = mkOption {
302 type = types.enum [ true false "admin" "local" ];
303 default = false;
304 description = "Restrict room creation to server admins";
305 };
306 maxHistoryMessages = mkOption {
307 type = types.int;
308 default = 20;
309 description = "Specifies a limit on what each room can be configured to keep";
310 };
311 roomLocking = mkOption {
312 type = types.bool;
313 default = true;
314 description = ''
315 Enables room locking, which means that a room must be
316 configured before it can be used. Locked rooms are invisible
317 and cannot be entered by anyone but the creator
318 '';
319 };
320 roomLockTimeout = mkOption {
321 type = types.int;
322 default = 300;
323 description = ''
324 Timout after which the room is destroyed or unlocked if not
325 configured, in seconds
326 '';
327 };
328 tombstones = mkOption {
329 type = types.bool;
330 default = true;
331 description = ''
332 When a room is destroyed, it leaves behind a tombstone which
333 prevents the room being entered or recreated. It also allows
334 anyone who was not in the room at the time it was destroyed
335 to learn about it, and to update their bookmarks. Tombstones
336 prevents the case where someone could recreate a previously
337 semi-anonymous room in order to learn the real JIDs of those
338 who often join there.
339 '';
340 };
341 tombstoneExpiry = mkOption {
342 type = types.int;
343 default = 2678400;
344 description = ''
345 This settings controls how long a tombstone is considered
346 valid. It defaults to 31 days. After this time, the room in
347 question can be created again.
348 '';
349 };
350
351 vcard_muc = mkOption {
352 type = types.bool;
353 default = true;
354 description = "Adds the ability to set vCard for Multi User Chat rooms";
355 };
356
357 # Extra parameters. Defaulting to prosody default values.
358 # Adding them explicitly to make them visible from the options
359 # documentation.
360 #
361 # See https://prosody.im/doc/modules/mod_muc for more details.
362 roomDefaultPublic = mkOption {
363 type = types.bool;
364 default = true;
365 description = "If set, the MUC rooms will be public by default.";
366 };
367 roomDefaultMembersOnly = mkOption {
368 type = types.bool;
369 default = false;
370 description = "If set, the MUC rooms will only be accessible to the members by default.";
371 };
372 roomDefaultModerated = mkOption {
373 type = types.bool;
374 default = false;
375 description = "If set, the MUC rooms will be moderated by default.";
376 };
377 roomDefaultPublicJids = mkOption {
378 type = types.bool;
379 default = false;
380 description = "If set, the MUC rooms will display the public JIDs by default.";
381 };
382 roomDefaultChangeSubject = mkOption {
383 type = types.bool;
384 default = false;
385 description = "If set, the rooms will display the public JIDs by default.";
386 };
387 roomDefaultHistoryLength = mkOption {
388 type = types.int;
389 default = 20;
390 description = "Number of history message sent to participants by default.";
391 };
392 roomDefaultLanguage = mkOption {
393 type = types.str;
394 default = "en";
395 description = "Default room language.";
396 };
397 extraConfig = mkOption {
398 type = types.lines;
399 default = "";
400 description = "Additional MUC specific configuration";
401 };
402 };
403 };
404
405 uploadHttpOpts = { ... }: {
406 options = {
407 domain = mkOption {
408 type = types.nullOr types.str;
409 description = "Domain name for the http-upload service";
410 };
411 uploadFileSizeLimit = mkOption {
412 type = types.str;
413 default = "50 * 1024 * 1024";
414 description = "Maximum file size, in bytes. Defaults to 50MB.";
415 };
416 uploadExpireAfter = mkOption {
417 type = types.str;
418 default = "60 * 60 * 24 * 7";
419 description = "Max age of a file before it gets deleted, in seconds.";
420 };
421 userQuota = mkOption {
422 type = types.nullOr types.int;
423 default = null;
424 example = 1234;
425 description = ''
426 Maximum size of all uploaded files per user, in bytes. There
427 will be no quota if this option is set to null.
428 '';
429 };
430 httpUploadPath = mkOption {
431 type = types.str;
432 description = ''
433 Directory where the uploaded files will be stored
434 when the http_upload module is used.
435 By default, uploaded files are put in a sub-directory of the
436 default Prosody storage path (usually /var/lib/prosody).
437 '';
438 default = "/var/lib/prosody";
439 };
440 };
441 };
442
443 httpFileShareOpts = { ... }: {
444 freeformType = with types;
445 let atom = oneOf [ int bool str (listOf atom) ]; in
446 attrsOf (nullOr atom);
447 options.domain = mkOption {
448 type = with types; nullOr str;
449 description = "Domain name for a http_file_share service.";
450 };
451 };
452
453 vHostOpts = { ... }: {
454
455 options = {
456
457 # TODO: require attribute
458 domain = mkOption {
459 type = types.str;
460 description = "Domain name";
461 };
462
463 enabled = mkOption {
464 type = types.bool;
465 default = false;
466 description = "Whether to enable the virtual host";
467 };
468
469 ssl = mkOption {
470 type = types.nullOr (types.submodule sslOpts);
471 default = null;
472 description = "Paths to SSL files";
473 };
474
475 extraConfig = mkOption {
476 type = types.lines;
477 default = "";
478 description = "Additional virtual host specific configuration";
479 };
480
481 };
482
483 };
484
485 in
486
487 {
488
489 ###### interface
490
491 options = {
492
493 services.prosody = {
494
495 enable = mkOption {
496 type = types.bool;
497 default = false;
498 description = "Whether to enable the prosody server";
499 };
500
501 xmppComplianceSuite = mkOption {
502 type = types.bool;
503 default = true;
504 description = ''
505 The XEP-0423 defines a set of recommended XEPs to implement
506 for a server. It's generally a good idea to implement this
507 set of extensions if you want to provide your users with a
508 good XMPP experience.
509
510 This NixOS module aims to provide a "advanced server"
511 experience as per defined in the XEP-0423[1] specification.
512
513 Setting this option to true will prevent you from building a
514 NixOS configuration which won't comply with this standard.
515 You can explicitely decide to ignore this standard if you
516 know what you are doing by setting this option to false.
517
518 [1] https://xmpp.org/extensions/xep-0423.html
519 '';
520 };
521
522 package = mkOption {
523 type = types.package;
524 description = "Prosody package to use";
525 default = pkgs.prosody;
526 defaultText = literalExpression "pkgs.prosody";
527 example = literalExpression ''
528 pkgs.prosody.override {
529 withExtraLibs = [ pkgs.luaPackages.lpty ];
530 withCommunityModules = [ "auth_external" ];
531 };
532 '';
533 };
534
535 dataDir = mkOption {
536 type = types.path;
537 description = "Directory where Prosody stores its data";
538 default = "/var/lib/prosody";
539 };
540
541 disco_items = mkOption {
542 type = types.listOf (types.submodule discoOpts);
543 default = [ ];
544 description = "List of discoverable items you want to advertise.";
545 };
546
547 user = mkOption {
548 type = types.str;
549 default = "prosody";
550 description = "User account under which prosody runs.";
551 };
552
553 group = mkOption {
554 type = types.str;
555 default = "prosody";
556 description = "Group account under which prosody runs.";
557 };
558
559 allowRegistration = mkOption {
560 type = types.bool;
561 default = false;
562 description = "Allow account creation";
563 };
564
565 # HTTP server-related options
566 httpPorts = mkOption {
567 type = types.listOf types.int;
568 description = "Listening HTTP ports list for this service.";
569 default = [ 5280 ];
570 };
571
572 httpInterfaces = mkOption {
573 type = types.listOf types.str;
574 default = [ "*" "::" ];
575 description = "Interfaces on which the HTTP server will listen on.";
576 };
577
578 httpsPorts = mkOption {
579 type = types.listOf types.int;
580 description = "Listening HTTPS ports list for this service.";
581 default = [ 5281 ];
582 };
583
584 httpsInterfaces = mkOption {
585 type = types.listOf types.str;
586 default = [ "*" "::" ];
587 description = "Interfaces on which the HTTPS server will listen on.";
588 };
589
590 c2sRequireEncryption = mkOption {
591 type = types.bool;
592 default = true;
593 description = ''
594 Force clients to use encrypted connections? This option will
595 prevent clients from authenticating unless they are using encryption.
596 '';
597 };
598
599 s2sRequireEncryption = mkOption {
600 type = types.bool;
601 default = true;
602 description = ''
603 Force servers to use encrypted connections? This option will
604 prevent servers from authenticating unless they are using encryption.
605 Note that this is different from authentication.
606 '';
607 };
608
609 s2sSecureAuth = mkOption {
610 type = types.bool;
611 default = false;
612 description = ''
613 Force certificate authentication for server-to-server connections?
614 This provides ideal security, but requires servers you communicate
615 with to support encryption AND present valid, trusted certificates.
616 For more information see https://prosody.im/doc/s2s#security
617 '';
618 };
619
620 s2sInsecureDomains = mkOption {
621 type = types.listOf types.str;
622 default = [ ];
623 example = [ "insecure.example.com" ];
624 description = ''
625 Some servers have invalid or self-signed certificates. You can list
626 remote domains here that will not be required to authenticate using
627 certificates. They will be authenticated using DNS instead, even
628 when s2s_secure_auth is enabled.
629 '';
630 };
631
632 s2sSecureDomains = mkOption {
633 type = types.listOf types.str;
634 default = [ ];
635 example = [ "jabber.org" ];
636 description = ''
637 Even if you leave s2s_secure_auth disabled, you can still require valid
638 certificates for some domains by specifying a list here.
639 '';
640 };
641
642
643 modules = moduleOpts;
644
645 extraModules = mkOption {
646 type = types.listOf types.str;
647 default = [ ];
648 description = "Enable custom modules";
649 };
650
651 extraPluginPaths = mkOption {
652 type = types.listOf types.path;
653 default = [ ];
654 description = "Addtional path in which to look find plugins/modules";
655 };
656
657 uploadHttp = mkOption {
658 description = ''
659 Configures the old Prosody builtin HTTP server to handle user uploads.
660 '';
661 type = types.nullOr (types.submodule uploadHttpOpts);
662 default = null;
663 example = {
664 domain = "uploads.my-xmpp-example-host.org";
665 };
666 };
667
668 httpFileShare = mkOption {
669 description = ''
670 Configures the http_file_share module to handle user uploads.
671 '';
672 type = types.nullOr (types.submodule httpFileShareOpts);
673 default = null;
674 example = {
675 domain = "uploads.my-xmpp-example-host.org";
676 };
677 };
678
679 muc = mkOption {
680 type = types.listOf (types.submodule mucOpts);
681 default = [ ];
682 example = [{
683 domain = "conference.my-xmpp-example-host.org";
684 }];
685 description = "Multi User Chat (MUC) configuration";
686 };
687
688 virtualHosts = mkOption {
689
690 description = "Define the virtual hosts";
691
692 type = with types; attrsOf (submodule vHostOpts);
693
694 example = {
695 myhost = {
696 domain = "my-xmpp-example-host.org";
697 enabled = true;
698 };
699 };
700
701 default = {
702 localhost = {
703 domain = "localhost";
704 enabled = true;
705 };
706 };
707
708 };
709
710 ssl = mkOption {
711 type = types.nullOr (types.submodule sslOpts);
712 default = null;
713 description = "Paths to SSL files";
714 };
715
716 admins = mkOption {
717 type = types.listOf types.str;
718 default = [ ];
719 example = [ "admin1@example.com" "admin2@example.com" ];
720 description = "List of administrators of the current host";
721 };
722
723 authentication = mkOption {
724 type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ];
725 default = "internal_hashed";
726 example = "internal_plain";
727 description = "Authentication mechanism used for logins.";
728 };
729
730 extraConfig = mkOption {
731 type = types.lines;
732 default = "";
733 description = "Additional prosody configuration";
734 };
735
736 };
737 };
738
739
740 ###### implementation
741
742 config = mkIf cfg.enable {
743
744 assertions =
745 let
746 genericErrMsg = ''
747
748 Having a server not XEP-0423-compliant might make your XMPP
749 experience terrible. See the NixOS manual for further
750 informations.
751
752 If you know what you're doing, you can disable this warning by
753 setting config.services.prosody.xmppComplianceSuite to false.
754 '';
755 errors = [
756 {
757 assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite;
758 message = ''
759 You need to setup at least a MUC domain to comply with
760 XEP-0423.
761 '' + genericErrMsg;
762 }
763 {
764 assertion = cfg.uploadHttp != null || cfg.httpFileShare != null || !cfg.xmppComplianceSuite;
765 message = ''
766 You need to setup the http_upload or http_file_share modules through
767 config.services.prosody.uploadHttp
768 or config.services.prosody.httpFileShare
769 to comply with XEP-0423.
770 '' + genericErrMsg;
771 }
772 ];
773 in
774 errors;
775
776 environment.systemPackages = [ cfg.package ];
777
778 environment.etc."prosody/prosody.cfg.lua".text =
779 let
780 httpDiscoItems =
781 optional (cfg.uploadHttp != null)
782 { url = cfg.uploadHttp.domain; description = "HTTP upload endpoint"; } ++
783 optional (cfg.httpFileShare != null)
784 { url = cfg.httpFileShare.domain; description = "HTTP file share endpoint"; };
785 mucDiscoItems = builtins.foldl'
786 (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint"; }] ++ acc)
787 [ ]
788 cfg.muc;
789 discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems;
790 in
791 ''
792
793 pidfile = "/run/prosody/prosody.pid"
794
795 log = "*syslog"
796
797 data_path = "${cfg.dataDir}"
798 plugin_paths = {
799 ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths) }
800 }
801
802 ${ optionalString (cfg.ssl != null) (createSSLOptsStr cfg.ssl) }
803
804 admins = ${toLua cfg.admins}
805
806 -- we already build with libevent, so we can just enable it for a more performant server
807 use_libevent = true
808
809 modules_enabled = {
810
811 ${ lib.concatStringsSep "\n " (lib.mapAttrsToList
812 (name: val: optionalString val "${toLua name};")
813 cfg.modules) }
814 ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
815 ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
816 };
817
818 disco_items = {
819 ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)}
820 };
821
822 allow_registration = ${toLua cfg.allowRegistration}
823
824 c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
825
826 s2s_require_encryption = ${toLua cfg.s2sRequireEncryption}
827
828 s2s_secure_auth = ${toLua cfg.s2sSecureAuth}
829
830 s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains}
831
832 s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
833
834 authentication = ${toLua cfg.authentication}
835
836 http_interfaces = ${toLua cfg.httpInterfaces}
837
838 https_interfaces = ${toLua cfg.httpsInterfaces}
839
840 http_ports = ${toLua cfg.httpPorts}
841
842 https_ports = ${toLua cfg.httpsPorts}
843
844 ${ cfg.extraConfig }
845
846 ${lib.concatMapStrings (muc: ''
847 Component ${toLua muc.domain} "muc"
848 modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } }
849 name = ${toLua muc.name}
850 restrict_room_creation = ${toLua muc.restrictRoomCreation}
851 max_history_messages = ${toLua muc.maxHistoryMessages}
852 muc_room_locking = ${toLua muc.roomLocking}
853 muc_room_lock_timeout = ${toLua muc.roomLockTimeout}
854 muc_tombstones = ${toLua muc.tombstones}
855 muc_tombstone_expiry = ${toLua muc.tombstoneExpiry}
856 muc_room_default_public = ${toLua muc.roomDefaultPublic}
857 muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly}
858 muc_room_default_moderated = ${toLua muc.roomDefaultModerated}
859 muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids}
860 muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject}
861 muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength}
862 muc_room_default_language = ${toLua muc.roomDefaultLanguage}
863 ${ muc.extraConfig }
864 '') cfg.muc}
865
866 ${ lib.optionalString (cfg.uploadHttp != null) ''
867 Component ${toLua cfg.uploadHttp.domain} "http_upload"
868 http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
869 http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
870 ${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"}
871 http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
872 ''}
873
874 ${ lib.optionalString (cfg.httpFileShare != null) ''
875 Component ${toLua cfg.httpFileShare.domain} "http_file_share"
876 ${settingsToLua " http_file_share_" (cfg.httpFileShare // { domain = null; })}
877 ''}
878
879 ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (_n: v: ''
880 VirtualHost "${v.domain}"
881 enabled = ${boolToString v.enabled};
882 ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) }
883 ${ v.extraConfig }
884 '') cfg.virtualHosts) }
885 '';
886
887 users.users.prosody = mkIf (cfg.user == "prosody") {
888 uid = config.ids.uids.prosody;
889 description = "Prosody user";
890 createHome = true;
891 inherit (cfg) group;
892 home = "${cfg.dataDir}";
893 };
894
895 users.groups.prosody = mkIf (cfg.group == "prosody") {
896 gid = config.ids.gids.prosody;
897 };
898
899 systemd.services.prosody = {
900 description = "Prosody XMPP server";
901 after = [ "network-online.target" ];
902 wants = [ "network-online.target" ];
903 wantedBy = [ "multi-user.target" ];
904 restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
905 serviceConfig = {
906 User = cfg.user;
907 Group = cfg.group;
908 Type = "forking";
909 RuntimeDirectory = [ "prosody" ];
910 PIDFile = "/run/prosody/prosody.pid";
911 ExecStart = "${cfg.package}/bin/prosodyctl start";
912 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
913
914 MemoryDenyWriteExecute = true;
915 PrivateDevices = true;
916 PrivateMounts = true;
917 PrivateTmp = true;
918 ProtectControlGroups = true;
919 ProtectHome = true;
920 ProtectHostname = true;
921 ProtectKernelModules = true;
922 ProtectKernelTunables = true;
923 RestrictNamespaces = true;
924 RestrictRealtime = true;
925 RestrictSUIDSGID = true;
926 };
927 };
928
929 };
930 meta.doc = ./prosody.xml;
931 }