waylyrics: 0.3.16 -> 0.3.20 (#364626)
[NixPkgs.git] / nixos / modules / services / networking / prosody.nix
blob9e54dfc17dfe7b716d552fdb2c504d3b5585fb21
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 = "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 = "Path to the certificate file.";
20       };
22       extraOptions = mkOption {
23         type = types.attrs;
24         default = {};
25         description = "Extra SSL configuration options.";
26       };
28     };
29   };
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   };
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     };
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     };
58     tls = mkOption {
59       type = types.bool;
60       default = true;
61       description = "Add support for secure TLS on c2s/s2s connections";
62     };
64     dialback = mkOption {
65       type = types.bool;
66       default = true;
67       description = "s2s dialback support";
68     };
70     disco = mkOption {
71       type = types.bool;
72       default = true;
73       description = "Service discovery";
74     };
76     # Not essential, but recommended
77     carbons = mkOption {
78       type = types.bool;
79       default = true;
80       description = "Keep multiple clients in sync";
81     };
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     };
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     };
95     pep = mkOption {
96       type = types.bool;
97       default = true;
98       description = "Enables users to publish their mood, activity, playing music and more";
99     };
101     private = mkOption {
102       type = types.bool;
103       default = true;
104       description = "Private XML storage (for room bookmarks, etc.)";
105     };
107     blocklist = mkOption {
108       type = types.bool;
109       default = true;
110       description = "Allow users to block communications with other users";
111     };
113     vcard = mkOption {
114       type = types.bool;
115       default = false;
116       description = "Allow users to set vCards";
117     };
119     vcard_legacy = mkOption {
120       type = types.bool;
121       default = true;
122       description = "Converts users profiles and Avatars between old and new formats";
123     };
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     };
131     # Nice to have
132     version = mkOption {
133       type = types.bool;
134       default = true;
135       description = "Replies to server version requests";
136     };
138     uptime = mkOption {
139       type = types.bool;
140       default = true;
141       description = "Report how long server has been running";
142     };
144     time = mkOption {
145       type = types.bool;
146       default = true;
147       description = "Let others know the time here on this server";
148     };
150     ping = mkOption {
151       type = types.bool;
152       default = true;
153       description = "Replies to XMPP pings with pongs";
154     };
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     };
162     mam = mkOption {
163       type = types.bool;
164       default = true;
165       description = "Store messages in an archive and allow users to access it";
166     };
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     };
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     };
181     http_files = mkOption {
182       type = types.bool;
183       default = true;
184       description = "Serve static files from a directory over HTTP";
185     };
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     };
193     admin_telnet = mkOption {
194       type = types.bool;
195       default = false;
196       description = "Opens telnet console interface on localhost port 5582";
197     };
199     # HTTP modules
200     bosh = mkOption {
201       type = types.bool;
202       default = false;
203       description = "Enable BOSH clients, aka 'Jabber over HTTP'";
204     };
206     websocket = mkOption {
207       type = types.bool;
208       default = false;
209       description = "Enable WebSocket support";
210     };
212     # Other specific functionality
213     limits = mkOption {
214       type = types.bool;
215       default = false;
216       description = "Enable bandwidth limiting for XMPP connections";
217     };
219     groups = mkOption {
220       type = types.bool;
221       default = false;
222       description = "Shared roster support";
223     };
225     server_contact_info = mkOption {
226       type = types.bool;
227       default = false;
228       description = "Publish contact information for this service";
229     };
231     announce = mkOption {
232       type = types.bool;
233       default = false;
234       description = "Send announcement to all online users";
235     };
237     welcome = mkOption {
238       type = types.bool;
239       default = false;
240       description = "Welcome users who register accounts";
241     };
243     watchregistrations = mkOption {
244       type = types.bool;
245       default = false;
246       description = "Alert admins of registrations";
247     };
249     motd = mkOption {
250       type = types.bool;
251       default = false;
252       description = "Send a message to users when they log in";
253     };
255     legacyauth = mkOption {
256       type = types.bool;
257       default = false;
258       description = "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.concatMapStringsSep ", " toLua x} }"
267     else throw "Invalid Lua value";
269   settingsToLua = prefix: settings: generators.toKeyValue {
270     listsAsDuplicateKeys = false;
271     mkKeyValue = k: generators.mkKeyValueDefault {
272       mkValueString = toLua;
273     } " = " (prefix + k);
274   } (filterAttrs (k: v: v != null) settings);
276   createSSLOptsStr = o: ''
277     ssl = {
278       cafile = "/etc/ssl/certs/ca-bundle.crt";
279       key = "${o.key}";
280       certificate = "${o.cert}";
281       ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions)}
282     };
283   '';
285   mucOpts = { ... }: {
286     options = {
287       domain = mkOption {
288         type = types.str;
289         description = "Domain name of the MUC";
290       };
291       name = mkOption {
292         type = types.str;
293         description = "The name to return in service discovery responses for the MUC service itself";
294         default = "Prosody Chatrooms";
295       };
296       restrictRoomCreation = mkOption {
297         type = types.enum [ true false "admin" "local" ];
298         default = false;
299         description = "Restrict room creation to server admins";
300       };
301       maxHistoryMessages = mkOption {
302         type = types.int;
303         default = 20;
304         description = "Specifies a limit on what each room can be configured to keep";
305       };
306       roomLocking = mkOption {
307         type = types.bool;
308         default = true;
309         description = ''
310           Enables room locking, which means that a room must be
311           configured before it can be used. Locked rooms are invisible
312           and cannot be entered by anyone but the creator
313         '';
314       };
315       roomLockTimeout = mkOption {
316         type = types.int;
317         default = 300;
318         description = ''
319           Timeout after which the room is destroyed or unlocked if not
320           configured, in seconds
321        '';
322       };
323       tombstones = mkOption {
324         type = types.bool;
325         default = true;
326         description = ''
327           When a room is destroyed, it leaves behind a tombstone which
328           prevents the room being entered or recreated. It also allows
329           anyone who was not in the room at the time it was destroyed
330           to learn about it, and to update their bookmarks. Tombstones
331           prevents the case where someone could recreate a previously
332           semi-anonymous room in order to learn the real JIDs of those
333           who often join there.
334         '';
335       };
336       tombstoneExpiry = mkOption {
337         type = types.int;
338         default = 2678400;
339         description = ''
340           This settings controls how long a tombstone is considered
341           valid. It defaults to 31 days. After this time, the room in
342           question can be created again.
343         '';
344       };
346       vcard_muc = mkOption {
347         type = types.bool;
348         default = true;
349       description = "Adds the ability to set vCard for Multi User Chat rooms";
350       };
352       # Extra parameters. Defaulting to prosody default values.
353       # Adding them explicitly to make them visible from the options
354       # documentation.
355       #
356       # See https://prosody.im/doc/modules/mod_muc for more details.
357       roomDefaultPublic = mkOption {
358         type = types.bool;
359         default = true;
360         description = "If set, the MUC rooms will be public by default.";
361       };
362       roomDefaultMembersOnly = mkOption {
363         type = types.bool;
364         default = false;
365         description = "If set, the MUC rooms will only be accessible to the members by default.";
366       };
367       roomDefaultModerated = mkOption {
368         type = types.bool;
369         default = false;
370         description = "If set, the MUC rooms will be moderated by default.";
371       };
372       roomDefaultPublicJids = mkOption {
373         type = types.bool;
374         default = false;
375         description = "If set, the MUC rooms will display the public JIDs by default.";
376       };
377       roomDefaultChangeSubject = mkOption {
378         type = types.bool;
379         default = false;
380         description = "If set, the rooms will display the public JIDs by default.";
381       };
382       roomDefaultHistoryLength = mkOption {
383         type = types.int;
384         default = 20;
385         description = "Number of history message sent to participants by default.";
386       };
387       roomDefaultLanguage = mkOption {
388         type = types.str;
389         default = "en";
390         description = "Default room language.";
391       };
392       extraConfig = mkOption {
393         type = types.lines;
394         default = "";
395         description = "Additional MUC specific configuration";
396       };
397     };
398   };
400   uploadHttpOpts = { ... }: {
401     options = {
402       domain = mkOption {
403         type = types.nullOr types.str;
404         description = "Domain name for the http-upload service";
405       };
406       uploadFileSizeLimit = mkOption {
407         type = types.str;
408         default = "50 * 1024 * 1024";
409         description = "Maximum file size, in bytes. Defaults to 50MB.";
410       };
411       uploadExpireAfter = mkOption {
412         type = types.str;
413         default = "60 * 60 * 24 * 7";
414         description = "Max age of a file before it gets deleted, in seconds.";
415       };
416       userQuota = mkOption {
417         type = types.nullOr types.int;
418         default = null;
419         example = 1234;
420         description = ''
421           Maximum size of all uploaded files per user, in bytes. There
422           will be no quota if this option is set to null.
423         '';
424       };
425       httpUploadPath = mkOption {
426         type = types.str;
427         description = ''
428           Directory where the uploaded files will be stored when the http_upload module is used.
429           By default, uploaded files are put in a sub-directory of the default Prosody storage path (usually /var/lib/prosody).
430         '';
431         default = "/var/lib/prosody";
432       };
433     };
434   };
436   httpFileShareOpts = { ... }: {
437     freeformType = with types;
438       let atom = oneOf [ int bool str (listOf atom) ]; in
439       attrsOf (nullOr atom) // {
440         description = "int, bool, string or list of them";
441       };
442     options.domain = mkOption {
443       type = with types; nullOr str;
444       description = "Domain name for a http_file_share service.";
445     };
446   };
448   vHostOpts = { ... }: {
450     options = {
452       # TODO: require attribute
453       domain = mkOption {
454         type = types.str;
455         description = "Domain name";
456       };
458       enabled = mkOption {
459         type = types.bool;
460         default = false;
461         description = "Whether to enable the virtual host";
462       };
464       ssl = mkOption {
465         type = types.nullOr (types.submodule sslOpts);
466         default = null;
467         description = "Paths to SSL files";
468       };
470       extraConfig = mkOption {
471         type = types.lines;
472         default = "";
473         description = "Additional virtual host specific configuration";
474       };
476     };
478   };
484   ###### interface
486   options = {
488     services.prosody = {
490       enable = mkOption {
491         type = types.bool;
492         default = false;
493         description = "Whether to enable the prosody server";
494       };
496       xmppComplianceSuite = mkOption {
497         type = types.bool;
498         default = true;
499         description = ''
500           The XEP-0423 defines a set of recommended XEPs to implement
501           for a server. It's generally a good idea to implement this
502           set of extensions if you want to provide your users with a
503           good XMPP experience.
505           This NixOS module aims to provide a "advanced server"
506           experience as per defined in the XEP-0423[1] specification.
508           Setting this option to true will prevent you from building a
509           NixOS configuration which won't comply with this standard.
510           You can explicitly decide to ignore this standard if you
511           know what you are doing by setting this option to false.
513           [1] https://xmpp.org/extensions/xep-0423.html
514         '';
515       };
517       package = mkPackageOption pkgs "prosody" {
518         example = ''
519           pkgs.prosody.override {
520             withExtraLibs = [ pkgs.luaPackages.lpty ];
521             withCommunityModules = [ "auth_external" ];
522           };
523         '';
524       };
526       dataDir = mkOption {
527         type = types.path;
528         default = "/var/lib/prosody";
529         description = ''
530           The prosody home directory used to store all data. If left as the default value
531           this directory will automatically be created before the prosody server starts, otherwise
532           you are responsible for ensuring the directory exists with appropriate ownership
533           and permissions.
534         '';
535       };
537       disco_items = mkOption {
538         type = types.listOf (types.submodule discoOpts);
539         default = [];
540         description = "List of discoverable items you want to advertise.";
541       };
543       user = mkOption {
544         type = types.str;
545         default = "prosody";
546         description = ''
547           User account under which prosody runs.
549           ::: {.note}
550           If left as the default value this user will automatically be created
551           on system activation, otherwise you are responsible for
552           ensuring the user exists before the prosody service starts.
553           :::
554         '';
555       };
557       group = mkOption {
558         type = types.str;
559         default = "prosody";
560         description = ''
561           Group account under which prosody runs.
563           ::: {.note}
564           If left as the default value this group will automatically be created
565           on system activation, otherwise you are responsible for
566           ensuring the group exists before the prosody service starts.
567           :::
568         '';
569       };
571       allowRegistration = mkOption {
572         type = types.bool;
573         default = false;
574         description = "Allow account creation";
575       };
577       # HTTP server-related options
578       httpPorts = mkOption {
579         type = types.listOf types.int;
580         description = "Listening HTTP ports list for this service.";
581         default = [ 5280 ];
582       };
584       httpInterfaces = mkOption {
585         type = types.listOf types.str;
586         default = [ "*" "::" ];
587         description = "Interfaces on which the HTTP server will listen on.";
588       };
590       httpsPorts = mkOption {
591         type = types.listOf types.int;
592         description = "Listening HTTPS ports list for this service.";
593         default = [ 5281 ];
594       };
596       httpsInterfaces = mkOption {
597         type = types.listOf types.str;
598         default = [ "*" "::" ];
599         description = "Interfaces on which the HTTPS server will listen on.";
600       };
602       c2sRequireEncryption = mkOption {
603         type = types.bool;
604         default = true;
605         description = ''
606           Force clients to use encrypted connections? This option will
607           prevent clients from authenticating unless they are using encryption.
608         '';
609       };
611       s2sRequireEncryption = mkOption {
612         type = types.bool;
613         default = true;
614         description = ''
615           Force servers to use encrypted connections? This option will
616           prevent servers from authenticating unless they are using encryption.
617           Note that this is different from authentication.
618         '';
619       };
621       s2sSecureAuth = mkOption {
622         type = types.bool;
623         default = false;
624         description = ''
625           Force certificate authentication for server-to-server connections?
626           This provides ideal security, but requires servers you communicate
627           with to support encryption AND present valid, trusted certificates.
628           For more information see https://prosody.im/doc/s2s#security
629         '';
630       };
632       s2sInsecureDomains = mkOption {
633         type = types.listOf types.str;
634         default = [];
635         example = [ "insecure.example.com" ];
636         description = ''
637           Some servers have invalid or self-signed certificates. You can list
638           remote domains here that will not be required to authenticate using
639           certificates. They will be authenticated using DNS instead, even
640           when s2s_secure_auth is enabled.
641         '';
642       };
644       s2sSecureDomains = mkOption {
645         type = types.listOf types.str;
646         default = [];
647         example = [ "jabber.org" ];
648         description = ''
649           Even if you leave s2s_secure_auth disabled, you can still require valid
650           certificates for some domains by specifying a list here.
651         '';
652       };
655       modules = moduleOpts;
657       extraModules = mkOption {
658         type = types.listOf types.str;
659         default = [];
660         description = "Enable custom modules";
661       };
663       extraPluginPaths = mkOption {
664         type = types.listOf types.path;
665         default = [];
666         description = "Additional path in which to look find plugins/modules";
667       };
669       uploadHttp = mkOption {
670         description = ''
671           Configures the old Prosody builtin HTTP server to handle user uploads.
672         '';
673         type = types.nullOr (types.submodule uploadHttpOpts);
674         default = null;
675         example = {
676           domain = "uploads.my-xmpp-example-host.org";
677         };
678       };
680       httpFileShare = mkOption {
681         description = ''
682           Configures the http_file_share module to handle user uploads.
683         '';
684         type = types.nullOr (types.submodule httpFileShareOpts);
685         default = null;
686         example = {
687           domain = "uploads.my-xmpp-example-host.org";
688         };
689       };
691       muc = mkOption {
692         type = types.listOf (types.submodule mucOpts);
693         default = [ ];
694         example = [ {
695           domain = "conference.my-xmpp-example-host.org";
696         } ];
697         description = "Multi User Chat (MUC) configuration";
698       };
700       virtualHosts = mkOption {
702         description = "Define the virtual hosts";
704         type = with types; attrsOf (submodule vHostOpts);
706         example = {
707           myhost = {
708             domain = "my-xmpp-example-host.org";
709             enabled = true;
710           };
711         };
713         default = {
714           localhost = {
715             domain = "localhost";
716             enabled = true;
717           };
718         };
720       };
722       ssl = mkOption {
723         type = types.nullOr (types.submodule sslOpts);
724         default = null;
725         description = "Paths to SSL files";
726       };
728       admins = mkOption {
729         type = types.listOf types.str;
730         default = [];
731         example = [ "admin1@example.com" "admin2@example.com" ];
732         description = "List of administrators of the current host";
733       };
735       authentication = mkOption {
736         type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ];
737         default = "internal_hashed";
738         example = "internal_plain";
739         description = "Authentication mechanism used for logins.";
740       };
742       extraConfig = mkOption {
743         type = types.lines;
744         default = "";
745         description = "Additional prosody configuration";
746       };
748       log = mkOption {
749         type = types.lines;
750         default = ''"*syslog"'';
751         description = "Logging configuration. See [](https://prosody.im/doc/logging) for more details";
752         example = ''
753           {
754             { min = "warn"; to = "*syslog"; };
755           }
756         '';
757       };
759     };
760   };
763   ###### implementation
765   config = mkIf cfg.enable {
767     assertions = let
768       genericErrMsg = ''
770           Having a server not XEP-0423-compliant might make your XMPP
771           experience terrible. See the NixOS manual for further
772           information.
774           If you know what you're doing, you can disable this warning by
775           setting config.services.prosody.xmppComplianceSuite to false.
776       '';
777       errors = [
778         { assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite;
779           message = ''
780             You need to setup at least a MUC domain to comply with
781             XEP-0423.
782           '' + genericErrMsg;}
783         { assertion = cfg.uploadHttp != null || cfg.httpFileShare != null || !cfg.xmppComplianceSuite;
784           message = ''
785             You need to setup the http_upload or http_file_share modules through config.services.prosody.uploadHttp
786             or config.services.prosody.httpFileShare to comply with XEP-0423.
787           '' + genericErrMsg;}
788       ];
789     in errors;
791     environment.systemPackages = [ cfg.package ];
793     environment.etc."prosody/prosody.cfg.lua".text =
794       let
795         httpDiscoItems = optional (cfg.uploadHttp != null) {
796           url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";
797         } ++ optional (cfg.httpFileShare != null) {
798           url = cfg.httpFileShare.domain; description = "HTTP file share endpoint";
799         };
800         mucDiscoItems = builtins.foldl'
801             (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc)
802             []
803             cfg.muc;
804         discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems;
805       in ''
807       pidfile = "/run/prosody/prosody.pid"
809       log = ${cfg.log}
811       data_path = "${cfg.dataDir}"
812       plugin_paths = {
813         ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths) }
814       }
816       ${ optionalString  (cfg.ssl != null) (createSSLOptsStr cfg.ssl) }
818       admins = ${toLua cfg.admins}
820       modules_enabled = {
822         ${ lib.concatStringsSep "\n  " (lib.mapAttrsToList
823           (name: val: optionalString val "${toLua name};")
824         cfg.modules) }
825         ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
826         ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
827       };
829       disco_items = {
830       ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)}
831       };
833       allow_registration = ${toLua cfg.allowRegistration}
835       c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
837       s2s_require_encryption = ${toLua cfg.s2sRequireEncryption}
839       s2s_secure_auth = ${toLua cfg.s2sSecureAuth}
841       s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains}
843       s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
845       authentication = ${toLua cfg.authentication}
847       http_interfaces = ${toLua cfg.httpInterfaces}
849       https_interfaces = ${toLua cfg.httpsInterfaces}
851       http_ports = ${toLua cfg.httpPorts}
853       https_ports = ${toLua cfg.httpsPorts}
855       ${ cfg.extraConfig }
857       ${lib.concatMapStrings (muc: ''
858         Component ${toLua muc.domain} "muc"
859             modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } }
860             name = ${toLua muc.name}
861             restrict_room_creation = ${toLua muc.restrictRoomCreation}
862             max_history_messages = ${toLua muc.maxHistoryMessages}
863             muc_room_locking = ${toLua muc.roomLocking}
864             muc_room_lock_timeout = ${toLua muc.roomLockTimeout}
865             muc_tombstones = ${toLua muc.tombstones}
866             muc_tombstone_expiry = ${toLua muc.tombstoneExpiry}
867             muc_room_default_public = ${toLua muc.roomDefaultPublic}
868             muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly}
869             muc_room_default_moderated = ${toLua muc.roomDefaultModerated}
870             muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids}
871             muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject}
872             muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength}
873             muc_room_default_language = ${toLua muc.roomDefaultLanguage}
874             ${ muc.extraConfig }
875         '') cfg.muc}
877       ${ lib.optionalString (cfg.uploadHttp != null) ''
878         Component ${toLua cfg.uploadHttp.domain} "http_upload"
879             http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
880             http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
881             ${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"}
882             http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
883       ''}
885       ${lib.optionalString (cfg.httpFileShare != null) ''
886         Component ${toLua cfg.httpFileShare.domain} "http_file_share"
887         ${settingsToLua "  http_file_share_" (cfg.httpFileShare // { domain = null; })}
888       ''}
890       ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
891         VirtualHost "${v.domain}"
892           enabled = ${boolToString v.enabled};
893           ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) }
894           ${ v.extraConfig }
895         '') cfg.virtualHosts) }
896     '';
898     users.users.prosody = mkIf (cfg.user == "prosody") {
899       uid = config.ids.uids.prosody;
900       description = "Prosody user";
901       inherit (cfg) group;
902       home = cfg.dataDir;
903     };
905     users.groups.prosody = mkIf (cfg.group == "prosody") {
906       gid = config.ids.gids.prosody;
907     };
909     systemd.services.prosody = {
910       description = "Prosody XMPP server";
911       after = [ "network-online.target" ];
912       wants = [ "network-online.target" ];
913       wantedBy = [ "multi-user.target" ];
914       restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
915       serviceConfig = mkMerge [
916         {
917           User = cfg.user;
918           Group = cfg.group;
919           Type = "forking";
920           RuntimeDirectory = [ "prosody" ];
921           PIDFile = "/run/prosody/prosody.pid";
922           ExecStart = "${cfg.package}/bin/prosodyctl start";
923           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
925           MemoryDenyWriteExecute = true;
926           PrivateDevices = true;
927           PrivateMounts = true;
928           PrivateTmp = true;
929           ProtectControlGroups = true;
930           ProtectHome = true;
931           ProtectHostname = true;
932           ProtectKernelModules = true;
933           ProtectKernelTunables = true;
934           RestrictNamespaces = true;
935           RestrictRealtime = true;
936           RestrictSUIDSGID = true;
937         }
938         (mkIf (cfg.dataDir == "/var/lib/prosody") {
939           StateDirectory = "prosody";
940         })
941       ];
942     };
944   };
946   meta.doc = ./prosody.md;