521 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			521 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| { config, lib, pkgs, ... }:
 | |
| 
 | |
| with lib;
 | |
| 
 | |
| let
 | |
| 
 | |
|   cfg = config.services.prosody;
 | |
| 
 | |
|   sslOpts = { ... }: {
 | |
| 
 | |
|     options = {
 | |
| 
 | |
|       key = mkOption {
 | |
|         type = types.path;
 | |
|         description = "Path to the key file.";
 | |
|       };
 | |
| 
 | |
|       # TODO: rename to certificate to match the prosody config
 | |
|       cert = mkOption {
 | |
|         type = types.path;
 | |
|         description = "Path to the certificate file.";
 | |
|       };
 | |
| 
 | |
|       extraOptions = mkOption {
 | |
|         type = types.attrs;
 | |
|         default = {};
 | |
|         description = "Extra SSL configuration options.";
 | |
|       };
 | |
| 
 | |
|     };
 | |
|   };
 | |
| 
 | |
|   moduleOpts = {
 | |
|     # Generally required
 | |
|     roster = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Allow users to have a roster";
 | |
|     };
 | |
| 
 | |
|     saslauth = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Authentication for clients and servers. Recommended if you want to log in.";
 | |
|     };
 | |
| 
 | |
|     tls = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Add support for secure TLS on c2s/s2s connections";
 | |
|     };
 | |
| 
 | |
|     dialback = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "s2s dialback support";
 | |
|     };
 | |
| 
 | |
|     disco = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Service discovery";
 | |
|     };
 | |
| 
 | |
|     # Not essential, but recommended
 | |
|     carbons = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Keep multiple clients in sync";
 | |
|     };
 | |
| 
 | |
|     pep = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Enables users to publish their mood, activity, playing music and more";
 | |
|     };
 | |
| 
 | |
|     private = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Private XML storage (for room bookmarks, etc.)";
 | |
|     };
 | |
| 
 | |
|     blocklist = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Allow users to block communications with other users";
 | |
|     };
 | |
| 
 | |
|     vcard = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Allow users to set vCards";
 | |
|     };
 | |
| 
 | |
|     # Nice to have
 | |
|     version = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Replies to server version requests";
 | |
|     };
 | |
| 
 | |
|     uptime = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Report how long server has been running";
 | |
|     };
 | |
| 
 | |
|     time = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Let others know the time here on this server";
 | |
|     };
 | |
| 
 | |
|     ping = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Replies to XMPP pings with pongs";
 | |
|     };
 | |
| 
 | |
|     register = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Allow users to register on this server using a client and change passwords";
 | |
|     };
 | |
| 
 | |
|     mam = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Store messages in an archive and allow users to access it";
 | |
|     };
 | |
| 
 | |
|     # Admin interfaces
 | |
|     admin_adhoc = mkOption {
 | |
|       type = types.bool;
 | |
|       default = true;
 | |
|       description = "Allows administration via an XMPP client that supports ad-hoc commands";
 | |
|     };
 | |
| 
 | |
|     admin_telnet = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Opens telnet console interface on localhost port 5582";
 | |
|     };
 | |
| 
 | |
|     # HTTP modules
 | |
|     bosh = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Enable BOSH clients, aka 'Jabber over HTTP'";
 | |
|     };
 | |
| 
 | |
|     websocket = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Enable WebSocket support";
 | |
|     };
 | |
| 
 | |
|     http_files = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Serve static files from a directory over HTTP";
 | |
|     };
 | |
| 
 | |
|     # Other specific functionality
 | |
|     limits = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Enable bandwidth limiting for XMPP connections";
 | |
|     };
 | |
| 
 | |
|     groups = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Shared roster support";
 | |
|     };
 | |
| 
 | |
|     server_contact_info = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Publish contact information for this service";
 | |
|     };
 | |
| 
 | |
|     announce = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Send announcement to all online users";
 | |
|     };
 | |
| 
 | |
|     welcome = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Welcome users who register accounts";
 | |
|     };
 | |
| 
 | |
|     watchregistrations = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Alert admins of registrations";
 | |
|     };
 | |
| 
 | |
|     motd = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Send a message to users when they log in";
 | |
|     };
 | |
| 
 | |
|     legacyauth = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Legacy authentication. Only used by some old clients and bots";
 | |
|     };
 | |
| 
 | |
|     proxy65 = mkOption {
 | |
|       type = types.bool;
 | |
|       default = false;
 | |
|       description = "Enables a file transfer proxy service which clients behind NAT can use";
 | |
|     };
 | |
| 
 | |
|   };
 | |
| 
 | |
|   toLua = x:
 | |
|     if builtins.isString x then ''"${x}"''
 | |
|     else if builtins.isBool x then (if x == true then "true" else "false")
 | |
|     else if builtins.isInt x then toString x
 | |
|     else if builtins.isList x then ''{ ${lib.concatStringsSep ", " (map (n: toLua n) x) } }''
 | |
|     else throw "Invalid Lua value";
 | |
| 
 | |
|   createSSLOptsStr = o: ''
 | |
|     ssl = {
 | |
|       key = "${o.key}";
 | |
|       certificate = "${o.cert}";
 | |
|       ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions)}
 | |
|     };
 | |
|   '';
 | |
| 
 | |
|   vHostOpts = { ... }: {
 | |
| 
 | |
|     options = {
 | |
| 
 | |
|       # TODO: require attribute
 | |
|       domain = mkOption {
 | |
|         type = types.str;
 | |
|         description = "Domain name";
 | |
|       };
 | |
| 
 | |
|       enabled = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = "Whether to enable the virtual host";
 | |
|       };
 | |
| 
 | |
|       ssl = mkOption {
 | |
|         type = types.nullOr (types.submodule sslOpts);
 | |
|         default = null;
 | |
|         description = "Paths to SSL files";
 | |
|       };
 | |
| 
 | |
|       extraConfig = mkOption {
 | |
|         type = types.lines;
 | |
|         default = "";
 | |
|         description = "Additional virtual host specific configuration";
 | |
|       };
 | |
| 
 | |
|     };
 | |
| 
 | |
|   };
 | |
| 
 | |
| in
 | |
| 
 | |
| {
 | |
| 
 | |
|   ###### interface
 | |
| 
 | |
|   options = {
 | |
| 
 | |
|     services.prosody = {
 | |
| 
 | |
|       enable = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = "Whether to enable the prosody server";
 | |
|       };
 | |
| 
 | |
|       package = mkOption {
 | |
|         type = types.package;
 | |
|         description = "Prosody package to use";
 | |
|         default = pkgs.prosody;
 | |
|         defaultText = "pkgs.prosody";
 | |
|         example = literalExample ''
 | |
|           pkgs.prosody.override {
 | |
|             withExtraLibs = [ pkgs.luaPackages.lpty ];
 | |
|             withCommunityModules = [ "auth_external" ];
 | |
|           };
 | |
|         '';
 | |
|       };
 | |
| 
 | |
|       dataDir = mkOption {
 | |
|         type = types.string;
 | |
|         description = "Directory where Prosody stores its data";
 | |
|         default = "/var/lib/prosody";
 | |
|       };
 | |
| 
 | |
|       user = mkOption {
 | |
|         type = types.str;
 | |
|         default = "prosody";
 | |
|         description = "User account under which prosody runs.";
 | |
|       };
 | |
| 
 | |
|       group = mkOption {
 | |
|         type = types.str;
 | |
|         default = "prosody";
 | |
|         description = "Group account under which prosody runs.";
 | |
|       };
 | |
| 
 | |
|       allowRegistration = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = "Allow account creation";
 | |
|       };
 | |
| 
 | |
|       c2sRequireEncryption = mkOption {
 | |
|         type = types.bool;
 | |
|         default = true;
 | |
|         description = ''
 | |
|           Force clients to use encrypted connections? This option will
 | |
|           prevent clients from authenticating unless they are using encryption.
 | |
|         '';
 | |
|       };
 | |
| 
 | |
|       s2sRequireEncryption = mkOption {
 | |
|         type = types.bool;
 | |
|         default = true;
 | |
|         description = ''
 | |
|           Force servers to use encrypted connections? This option will
 | |
|           prevent servers from authenticating unless they are using encryption.
 | |
|           Note that this is different from authentication.
 | |
|         '';
 | |
|       };
 | |
| 
 | |
|       s2sSecureAuth = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = ''
 | |
|           Force certificate authentication for server-to-server connections?
 | |
|           This provides ideal security, but requires servers you communicate
 | |
|           with to support encryption AND present valid, trusted certificates.
 | |
|           For more information see https://prosody.im/doc/s2s#security
 | |
|         '';
 | |
|       };
 | |
| 
 | |
|       s2sInsecureDomains = mkOption {
 | |
|         type = types.listOf types.str;
 | |
|         default = [];
 | |
|         example = [ "insecure.example.com" ];
 | |
|         description = ''
 | |
|           Some servers have invalid or self-signed certificates. You can list
 | |
|           remote domains here that will not be required to authenticate using
 | |
|           certificates. They will be authenticated using DNS instead, even
 | |
|           when s2s_secure_auth is enabled.
 | |
|         '';
 | |
|       };
 | |
| 
 | |
|       s2sSecureDomains = mkOption {
 | |
|         type = types.listOf types.str;
 | |
|         default = [];
 | |
|         example = [ "jabber.org" ];
 | |
|         description = ''
 | |
|           Even if you leave s2s_secure_auth disabled, you can still require valid
 | |
|           certificates for some domains by specifying a list here.
 | |
|         '';
 | |
|       };
 | |
| 
 | |
| 
 | |
|       modules = moduleOpts;
 | |
| 
 | |
|       extraModules = mkOption {
 | |
|         type = types.listOf types.str;
 | |
|         default = [];
 | |
|         description = "Enable custom modules";
 | |
|       };
 | |
| 
 | |
|       extraPluginPaths = mkOption {
 | |
|         type = types.listOf types.path;
 | |
|         default = [];
 | |
|         description = "Addtional path in which to look find plugins/modules";
 | |
|       };
 | |
| 
 | |
|       virtualHosts = mkOption {
 | |
| 
 | |
|         description = "Define the virtual hosts";
 | |
| 
 | |
|         type = with types; loaOf (submodule vHostOpts);
 | |
| 
 | |
|         example = {
 | |
|           myhost = {
 | |
|             domain = "my-xmpp-example-host.org";
 | |
|             enabled = true;
 | |
|           };
 | |
|         };
 | |
| 
 | |
|         default = {
 | |
|           localhost = {
 | |
|             domain = "localhost";
 | |
|             enabled = true;
 | |
|           };
 | |
|         };
 | |
| 
 | |
|       };
 | |
| 
 | |
|       ssl = mkOption {
 | |
|         type = types.nullOr (types.submodule sslOpts);
 | |
|         default = null;
 | |
|         description = "Paths to SSL files";
 | |
|       };
 | |
| 
 | |
|       admins = mkOption {
 | |
|         type = types.listOf types.str;
 | |
|         default = [];
 | |
|         example = [ "admin1@example.com" "admin2@example.com" ];
 | |
|         description = "List of administrators of the current host";
 | |
|       };
 | |
| 
 | |
|       extraConfig = mkOption {
 | |
|         type = types.lines;
 | |
|         default = "";
 | |
|         description = "Additional prosody configuration";
 | |
|       };
 | |
| 
 | |
|     };
 | |
|   };
 | |
| 
 | |
| 
 | |
|   ###### implementation
 | |
| 
 | |
|   config = mkIf cfg.enable {
 | |
| 
 | |
|     environment.systemPackages = [ cfg.package ];
 | |
| 
 | |
|     environment.etc."prosody/prosody.cfg.lua".text = ''
 | |
| 
 | |
|       pidfile = "/run/prosody/prosody.pid"
 | |
| 
 | |
|       log = "*syslog"
 | |
| 
 | |
|       data_path = "${cfg.dataDir}"
 | |
|       plugin_paths = {
 | |
|         ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths) }
 | |
|       }
 | |
| 
 | |
|       ${ optionalString  (cfg.ssl != null) (createSSLOptsStr cfg.ssl) }
 | |
| 
 | |
|       admins = ${toLua cfg.admins}
 | |
| 
 | |
|       -- we already build with libevent, so we can just enable it for a more performant server
 | |
|       use_libevent = true
 | |
| 
 | |
|       modules_enabled = {
 | |
| 
 | |
|         ${ lib.concatStringsSep "\n\ \ " (lib.mapAttrsToList
 | |
|           (name: val: optionalString val "${toLua name};")
 | |
|         cfg.modules) }
 | |
|         ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
 | |
|         ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
 | |
|       };
 | |
| 
 | |
|       allow_registration = ${toLua cfg.allowRegistration}
 | |
| 
 | |
|       c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
 | |
| 
 | |
|       s2s_require_encryption = ${toLua cfg.s2sRequireEncryption}
 | |
| 
 | |
|       s2s_secure_auth = ${toLua cfg.s2sSecureAuth}
 | |
| 
 | |
|       s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains}
 | |
| 
 | |
|       s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
 | |
| 
 | |
| 
 | |
|       ${ cfg.extraConfig }
 | |
| 
 | |
|       ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
 | |
|         VirtualHost "${v.domain}"
 | |
|           enabled = ${boolToString v.enabled};
 | |
|           ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) }
 | |
|           ${ v.extraConfig }
 | |
|         '') cfg.virtualHosts) }
 | |
|     '';
 | |
| 
 | |
|     users.users.prosody = mkIf (cfg.user == "prosody") {
 | |
|       uid = config.ids.uids.prosody;
 | |
|       description = "Prosody user";
 | |
|       createHome = true;
 | |
|       inherit (cfg) group;
 | |
|       home = "${cfg.dataDir}";
 | |
|     };
 | |
| 
 | |
|     users.groups.prosody = mkIf (cfg.group == "prosody") {
 | |
|       gid = config.ids.gids.prosody;
 | |
|     };
 | |
| 
 | |
|     systemd.services.prosody = {
 | |
|       description = "Prosody XMPP server";
 | |
|       after = [ "network-online.target" ];
 | |
|       wants = [ "network-online.target" ];
 | |
|       wantedBy = [ "multi-user.target" ];
 | |
|       restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
 | |
|       serviceConfig = {
 | |
|         User = cfg.user;
 | |
|         Group = cfg.group;
 | |
|         Type = "forking";
 | |
|         RuntimeDirectory = [ "prosody" ];
 | |
|         PIDFile = "/run/prosody/prosody.pid";
 | |
|         ExecStart = "${cfg.package}/bin/prosodyctl start";
 | |
|       };
 | |
|     };
 | |
| 
 | |
|   };
 | |
| 
 | |
| }
 | 
