{ 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 = { cafile = "/etc/ssl/certs/ca-bundle.crt"; 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.path; 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"; }; authentication = mkOption { type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ]; default = "internal_hashed"; example = "internal_plain"; description = "Authentication mechanism used for logins."; }; 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} authentication = ${toLua cfg.authentication} ${ 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"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; }; }; }; }