at 24.11-pre 27 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4let 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.concatMapStringsSep ", " toLua x} }" 267 else throw "Invalid Lua value"; 268 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 ''; 277 278 mucOpts = { ... }: { 279 options = { 280 domain = mkOption { 281 type = types.str; 282 description = "Domain name of the MUC"; 283 }; 284 name = mkOption { 285 type = types.str; 286 description = "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 = "Restrict room creation to server admins"; 293 }; 294 maxHistoryMessages = mkOption { 295 type = types.int; 296 default = 20; 297 description = "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 = '' 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 = '' 312 Timeout 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 = '' 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 = '' 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 }; 338 339 vcard_muc = mkOption { 340 type = types.bool; 341 default = true; 342 description = "Adds the ability to set vCard for Multi User Chat rooms"; 343 }; 344 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 = "If set, the MUC rooms will be public by default."; 354 }; 355 roomDefaultMembersOnly = mkOption { 356 type = types.bool; 357 default = false; 358 description = "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 = "If set, the MUC rooms will be moderated by default."; 364 }; 365 roomDefaultPublicJids = mkOption { 366 type = types.bool; 367 default = false; 368 description = "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 = "If set, the rooms will display the public JIDs by default."; 374 }; 375 roomDefaultHistoryLength = mkOption { 376 type = types.int; 377 default = 20; 378 description = "Number of history message sent to participants by default."; 379 }; 380 roomDefaultLanguage = mkOption { 381 type = types.str; 382 default = "en"; 383 description = "Default room language."; 384 }; 385 extraConfig = mkOption { 386 type = types.lines; 387 default = ""; 388 description = "Additional MUC specific configuration"; 389 }; 390 }; 391 }; 392 393 uploadHttpOpts = { ... }: { 394 options = { 395 domain = mkOption { 396 type = types.nullOr types.str; 397 description = "Domain name for the http-upload service"; 398 }; 399 uploadFileSizeLimit = mkOption { 400 type = types.str; 401 default = "50 * 1024 * 1024"; 402 description = "Maximum file size, in bytes. Defaults to 50MB."; 403 }; 404 uploadExpireAfter = mkOption { 405 type = types.str; 406 default = "60 * 60 * 24 * 7"; 407 description = "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 = '' 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 = '' 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 }; 429 430 vHostOpts = { ... }: { 431 432 options = { 433 434 # TODO: require attribute 435 domain = mkOption { 436 type = types.str; 437 description = "Domain name"; 438 }; 439 440 enabled = mkOption { 441 type = types.bool; 442 default = false; 443 description = "Whether to enable the virtual host"; 444 }; 445 446 ssl = mkOption { 447 type = types.nullOr (types.submodule sslOpts); 448 default = null; 449 description = "Paths to SSL files"; 450 }; 451 452 extraConfig = mkOption { 453 type = types.lines; 454 default = ""; 455 description = "Additional virtual host specific configuration"; 456 }; 457 458 }; 459 460 }; 461 462in 463 464{ 465 466 ###### interface 467 468 options = { 469 470 services.prosody = { 471 472 enable = mkOption { 473 type = types.bool; 474 default = false; 475 description = "Whether to enable the prosody server"; 476 }; 477 478 xmppComplianceSuite = mkOption { 479 type = types.bool; 480 default = true; 481 description = '' 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. 486 487 This NixOS module aims to provide a "advanced server" 488 experience as per defined in the XEP-0423[1] specification. 489 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 explicitly decide to ignore this standard if you 493 know what you are doing by setting this option to false. 494 495 [1] https://xmpp.org/extensions/xep-0423.html 496 ''; 497 }; 498 499 package = mkPackageOption pkgs "prosody" { 500 example = '' 501 pkgs.prosody.override { 502 withExtraLibs = [ pkgs.luaPackages.lpty ]; 503 withCommunityModules = [ "auth_external" ]; 504 }; 505 ''; 506 }; 507 508 dataDir = mkOption { 509 type = types.path; 510 default = "/var/lib/prosody"; 511 description = '' 512 The prosody home directory used to store all data. If left as the default value 513 this directory will automatically be created before the prosody server starts, otherwise 514 you are responsible for ensuring the directory exists with appropriate ownership 515 and permissions. 516 ''; 517 }; 518 519 disco_items = mkOption { 520 type = types.listOf (types.submodule discoOpts); 521 default = []; 522 description = "List of discoverable items you want to advertise."; 523 }; 524 525 user = mkOption { 526 type = types.str; 527 default = "prosody"; 528 description = '' 529 User account under which prosody runs. 530 531 ::: {.note} 532 If left as the default value this user will automatically be created 533 on system activation, otherwise you are responsible for 534 ensuring the user exists before the prosody service starts. 535 ::: 536 ''; 537 }; 538 539 group = mkOption { 540 type = types.str; 541 default = "prosody"; 542 description = '' 543 Group account under which prosody runs. 544 545 ::: {.note} 546 If left as the default value this group will automatically be created 547 on system activation, otherwise you are responsible for 548 ensuring the group exists before the prosody service starts. 549 ::: 550 ''; 551 }; 552 553 allowRegistration = mkOption { 554 type = types.bool; 555 default = false; 556 description = "Allow account creation"; 557 }; 558 559 # HTTP server-related options 560 httpPorts = mkOption { 561 type = types.listOf types.int; 562 description = "Listening HTTP ports list for this service."; 563 default = [ 5280 ]; 564 }; 565 566 httpInterfaces = mkOption { 567 type = types.listOf types.str; 568 default = [ "*" "::" ]; 569 description = "Interfaces on which the HTTP server will listen on."; 570 }; 571 572 httpsPorts = mkOption { 573 type = types.listOf types.int; 574 description = "Listening HTTPS ports list for this service."; 575 default = [ 5281 ]; 576 }; 577 578 httpsInterfaces = mkOption { 579 type = types.listOf types.str; 580 default = [ "*" "::" ]; 581 description = "Interfaces on which the HTTPS server will listen on."; 582 }; 583 584 c2sRequireEncryption = mkOption { 585 type = types.bool; 586 default = true; 587 description = '' 588 Force clients to use encrypted connections? This option will 589 prevent clients from authenticating unless they are using encryption. 590 ''; 591 }; 592 593 s2sRequireEncryption = mkOption { 594 type = types.bool; 595 default = true; 596 description = '' 597 Force servers to use encrypted connections? This option will 598 prevent servers from authenticating unless they are using encryption. 599 Note that this is different from authentication. 600 ''; 601 }; 602 603 s2sSecureAuth = mkOption { 604 type = types.bool; 605 default = false; 606 description = '' 607 Force certificate authentication for server-to-server connections? 608 This provides ideal security, but requires servers you communicate 609 with to support encryption AND present valid, trusted certificates. 610 For more information see https://prosody.im/doc/s2s#security 611 ''; 612 }; 613 614 s2sInsecureDomains = mkOption { 615 type = types.listOf types.str; 616 default = []; 617 example = [ "insecure.example.com" ]; 618 description = '' 619 Some servers have invalid or self-signed certificates. You can list 620 remote domains here that will not be required to authenticate using 621 certificates. They will be authenticated using DNS instead, even 622 when s2s_secure_auth is enabled. 623 ''; 624 }; 625 626 s2sSecureDomains = mkOption { 627 type = types.listOf types.str; 628 default = []; 629 example = [ "jabber.org" ]; 630 description = '' 631 Even if you leave s2s_secure_auth disabled, you can still require valid 632 certificates for some domains by specifying a list here. 633 ''; 634 }; 635 636 637 modules = moduleOpts; 638 639 extraModules = mkOption { 640 type = types.listOf types.str; 641 default = []; 642 description = "Enable custom modules"; 643 }; 644 645 extraPluginPaths = mkOption { 646 type = types.listOf types.path; 647 default = []; 648 description = "Additional path in which to look find plugins/modules"; 649 }; 650 651 uploadHttp = mkOption { 652 description = '' 653 Configures the Prosody builtin HTTP server to handle user uploads. 654 ''; 655 type = types.nullOr (types.submodule uploadHttpOpts); 656 default = null; 657 example = { 658 domain = "uploads.my-xmpp-example-host.org"; 659 }; 660 }; 661 662 muc = mkOption { 663 type = types.listOf (types.submodule mucOpts); 664 default = [ ]; 665 example = [ { 666 domain = "conference.my-xmpp-example-host.org"; 667 } ]; 668 description = "Multi User Chat (MUC) configuration"; 669 }; 670 671 virtualHosts = mkOption { 672 673 description = "Define the virtual hosts"; 674 675 type = with types; attrsOf (submodule vHostOpts); 676 677 example = { 678 myhost = { 679 domain = "my-xmpp-example-host.org"; 680 enabled = true; 681 }; 682 }; 683 684 default = { 685 localhost = { 686 domain = "localhost"; 687 enabled = true; 688 }; 689 }; 690 691 }; 692 693 ssl = mkOption { 694 type = types.nullOr (types.submodule sslOpts); 695 default = null; 696 description = "Paths to SSL files"; 697 }; 698 699 admins = mkOption { 700 type = types.listOf types.str; 701 default = []; 702 example = [ "admin1@example.com" "admin2@example.com" ]; 703 description = "List of administrators of the current host"; 704 }; 705 706 authentication = mkOption { 707 type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ]; 708 default = "internal_hashed"; 709 example = "internal_plain"; 710 description = "Authentication mechanism used for logins."; 711 }; 712 713 extraConfig = mkOption { 714 type = types.lines; 715 default = ""; 716 description = "Additional prosody configuration"; 717 }; 718 719 }; 720 }; 721 722 723 ###### implementation 724 725 config = mkIf cfg.enable { 726 727 assertions = let 728 genericErrMsg = '' 729 730 Having a server not XEP-0423-compliant might make your XMPP 731 experience terrible. See the NixOS manual for further 732 information. 733 734 If you know what you're doing, you can disable this warning by 735 setting config.services.prosody.xmppComplianceSuite to false. 736 ''; 737 errors = [ 738 { assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite; 739 message = '' 740 You need to setup at least a MUC domain to comply with 741 XEP-0423. 742 '' + genericErrMsg;} 743 { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite; 744 message = '' 745 You need to setup the uploadHttp module through 746 config.services.prosody.uploadHttp to comply with 747 XEP-0423. 748 '' + genericErrMsg;} 749 ]; 750 in errors; 751 752 environment.systemPackages = [ cfg.package ]; 753 754 environment.etc."prosody/prosody.cfg.lua".text = 755 let 756 httpDiscoItems = optionals (cfg.uploadHttp != null) 757 [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}]; 758 mucDiscoItems = builtins.foldl' 759 (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc) 760 [] 761 cfg.muc; 762 discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems; 763 in '' 764 765 pidfile = "/run/prosody/prosody.pid" 766 767 log = "*syslog" 768 769 data_path = "${cfg.dataDir}" 770 plugin_paths = { 771 ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths) } 772 } 773 774 ${ optionalString (cfg.ssl != null) (createSSLOptsStr cfg.ssl) } 775 776 admins = ${toLua cfg.admins} 777 778 modules_enabled = { 779 780 ${ lib.concatStringsSep "\n " (lib.mapAttrsToList 781 (name: val: optionalString val "${toLua name};") 782 cfg.modules) } 783 ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)} 784 ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)} 785 }; 786 787 disco_items = { 788 ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)} 789 }; 790 791 allow_registration = ${toLua cfg.allowRegistration} 792 793 c2s_require_encryption = ${toLua cfg.c2sRequireEncryption} 794 795 s2s_require_encryption = ${toLua cfg.s2sRequireEncryption} 796 797 s2s_secure_auth = ${toLua cfg.s2sSecureAuth} 798 799 s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains} 800 801 s2s_secure_domains = ${toLua cfg.s2sSecureDomains} 802 803 authentication = ${toLua cfg.authentication} 804 805 http_interfaces = ${toLua cfg.httpInterfaces} 806 807 https_interfaces = ${toLua cfg.httpsInterfaces} 808 809 http_ports = ${toLua cfg.httpPorts} 810 811 https_ports = ${toLua cfg.httpsPorts} 812 813 ${ cfg.extraConfig } 814 815 ${lib.concatMapStrings (muc: '' 816 Component ${toLua muc.domain} "muc" 817 modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } } 818 name = ${toLua muc.name} 819 restrict_room_creation = ${toLua muc.restrictRoomCreation} 820 max_history_messages = ${toLua muc.maxHistoryMessages} 821 muc_room_locking = ${toLua muc.roomLocking} 822 muc_room_lock_timeout = ${toLua muc.roomLockTimeout} 823 muc_tombstones = ${toLua muc.tombstones} 824 muc_tombstone_expiry = ${toLua muc.tombstoneExpiry} 825 muc_room_default_public = ${toLua muc.roomDefaultPublic} 826 muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly} 827 muc_room_default_moderated = ${toLua muc.roomDefaultModerated} 828 muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids} 829 muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject} 830 muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength} 831 muc_room_default_language = ${toLua muc.roomDefaultLanguage} 832 ${ muc.extraConfig } 833 '') cfg.muc} 834 835 ${ lib.optionalString (cfg.uploadHttp != null) '' 836 -- TODO: think about migrating this to mod-http_file_share instead. 837 Component ${toLua cfg.uploadHttp.domain} "http_upload" 838 http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit} 839 http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter} 840 ${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"} 841 http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath} 842 ''} 843 844 ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: '' 845 VirtualHost "${v.domain}" 846 enabled = ${boolToString v.enabled}; 847 ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) } 848 ${ v.extraConfig } 849 '') cfg.virtualHosts) } 850 ''; 851 852 users.users.prosody = mkIf (cfg.user == "prosody") { 853 uid = config.ids.uids.prosody; 854 description = "Prosody user"; 855 inherit (cfg) group; 856 home = cfg.dataDir; 857 }; 858 859 users.groups.prosody = mkIf (cfg.group == "prosody") { 860 gid = config.ids.gids.prosody; 861 }; 862 863 systemd.services.prosody = { 864 description = "Prosody XMPP server"; 865 after = [ "network-online.target" ]; 866 wants = [ "network-online.target" ]; 867 wantedBy = [ "multi-user.target" ]; 868 restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ]; 869 serviceConfig = mkMerge [ 870 { 871 User = cfg.user; 872 Group = cfg.group; 873 Type = "forking"; 874 RuntimeDirectory = [ "prosody" ]; 875 PIDFile = "/run/prosody/prosody.pid"; 876 ExecStart = "${cfg.package}/bin/prosodyctl start"; 877 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 878 879 MemoryDenyWriteExecute = true; 880 PrivateDevices = true; 881 PrivateMounts = true; 882 PrivateTmp = true; 883 ProtectControlGroups = true; 884 ProtectHome = true; 885 ProtectHostname = true; 886 ProtectKernelModules = true; 887 ProtectKernelTunables = true; 888 RestrictNamespaces = true; 889 RestrictRealtime = true; 890 RestrictSUIDSGID = true; 891 } 892 (mkIf (cfg.dataDir == "/var/lib/prosody") { 893 StateDirectory = "prosody"; 894 }) 895 ]; 896 }; 897 898 }; 899 900 meta.doc = ./prosody.md; 901}