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