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