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