{ config, lib, mylib, pkgs, ... }: let cfg = config.tv.ejabberd; gen-dhparam = pkgs.writeDash "gen-dhparam" '' set -efu path=$1 bits=2048 # TODO regenerate dhfile after some time? if ! test -e "$path"; then ${pkgs.openssl}/bin/openssl dhparam "$bits" > "$path" fi ''; settingsFormat = pkgs.formats.json {}; in { options.tv.ejabberd = { enable = lib.mkEnableOption "tv.ejabberd"; certfiles = lib.mkOption { type = lib.types.listOf mylib.types.absolute-pathname; default = [ (toString + "/ejabberd.pem") ]; }; configFile = lib.mkOption { type = lib.types.either lib.types.package mylib.types.absolute-pathname; default = settingsFormat.generate "ejabberd.yaml" cfg.settings; }; ciphers = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "ECDHE-ECDSA-AES256-GCM-SHA384" "ECDHE-RSA-AES256-GCM-SHA384" "ECDHE-ECDSA-CHACHA20-POLY1305" "ECDHE-RSA-CHACHA20-POLY1305" "ECDHE-ECDSA-AES128-GCM-SHA256" "ECDHE-RSA-AES128-GCM-SHA256" "ECDHE-ECDSA-AES256-SHA384" "ECDHE-RSA-AES256-SHA384" "ECDHE-ECDSA-AES128-SHA256" "ECDHE-RSA-AES128-SHA256" ]; }; credentials.certfiles = lib.mkOption { internal = true; readOnly = true; default = lib.imap (i: _: "/tmp/credentials/certfile${builtins.toJSON i}") cfg.certfiles; }; hosts = lib.mkOption { type = lib.types.listOf lib.types.str; }; pkgs.ejabberd = lib.mkOption { type = mylib.types.package; default = pkgs.symlinkJoin { name = "ejabberd-wrapper"; paths = [ (pkgs.writeDashBin "ejabberdctl" '' exec ${pkgs.ejabberd}/bin/ejabberdctl \ --config /etc/ejabberd/ejabberd.yaml \ --ctl-config /etc/ejabberd/ejabberdctl.cfg \ --logs ${cfg.stateDir} \ --spool ${cfg.stateDir} \ "$@" '') pkgs.ejabberd ]; }; }; protocol_options = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "no_sslv2" "no_sslv3" "no_tlsv1" "no_tlsv1_10" ]; }; registration_watchers = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ config.krebs.users.tv.mail ]; }; settings = lib.mkOption { type = settingsFormat.type; default = {}; }; stateDir = lib.mkOption { type = lib.types.addCheck mylib.types.absolute-pathname (path: lib.hasPrefix "/var/lib/" path && mylib.types.filename.check (lib.removePrefix "/var/lib/" path) ); default = "/var/lib/ejabberd"; }; }; config = lib.mkIf cfg.enable { environment.etc."ejabberd/ejabberd.yaml".source = cfg.configFile; environment.etc."ejabberd/ejabberdctl.cfg".source = builtins.toFile "ejabberdctl.cfg" /* sh */ '' ERL_OPTIONS='-setcookie ${cfg.stateDir}/.erlang.cookie' ''; environment.systemPackages = [ (pkgs.symlinkJoin { name = "ejabberd-sudo-wrapper"; paths = [ (pkgs.writeDashBin "ejabberdctl" '' exec ${pkgs.systemd}/bin/systemd-run \ --unit=ejabberdctl \ --property=StateDirectory=ejabberd \ --property=User=ejabberd \ --collect \ --pipe \ --quiet \ ${cfg.pkgs.ejabberd}/bin/ejabberdctl "$@" '') cfg.pkgs.ejabberd ]; }) ]; krebs.systemd.services.ejabberd.restartIfCredentialsChange = true; systemd.services.ejabberd = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; reloadTriggers = [ config.environment.etc."ejabberd/ejabberd.yaml".source config.environment.etc."ejabberd/ejabberdctl.cfg".source ]; serviceConfig = { ExecStartPre = [ "${pkgs.coreutils}/bin/ln -s \${CREDENTIALS_DIRECTORY} /tmp/credentials" "${gen-dhparam} ${cfg.stateDir}/dhfile" ]; ExecStart = "${cfg.pkgs.ejabberd}/bin/ejabberdctl foreground"; ExecStop = [ "${cfg.pkgs.ejabberd}/bin/ejabberdctl stop" "${cfg.pkgs.ejabberd}/bin/ejabberdctl stopped" ]; ExecReload = "${cfg.pkgs.ejabberd}/bin/ejabberdctl reload_config"; LoadCredential = lib.zipListsWith (dst: src: "${baseNameOf dst}:${src}") cfg.credentials.certfiles cfg.certfiles; LimitNOFILE = 65536; PrivateDevices = true; PrivateTmp = true; SyslogIdentifier = "ejabberd"; StateDirectory = "ejabberd"; User = "ejabberd"; DynamicUser = true; TimeoutSec = 60; RestartSec = 5; Restart = "on-failure"; Type = "notify"; NotifyAccess = "all"; WatchdogSec = 30; }; }; # preset config values tv.ejabberd.settings = { access_rules = { announce = lib.mkDefault [{ allow = "admin"; }]; local = lib.mkDefault [{ allow = "local"; }]; configure = lib.mkDefault [{ allow = "admin"; }]; register = lib.mkDefault ["allow"]; s2s = lib.mkDefault ["allow"]; trusted_network = lib.mkDefault [{ allow = "loopback"; }]; }; acl = { local.user_regexp = lib.mkDefault ""; loopback.ip = lib.mkDefault [ "127.0.0.0/8" "::1/128" "::FFFF:127.0.0.1/128" ]; }; certfiles = lib.mkDefault cfg.credentials.certfiles; hosts = lib.mkDefault cfg.hosts; language = lib.mkDefault "en"; listen = lib.mkDefault [ { port = 5222; ip = "::"; module = "ejabberd_c2s"; shaper = "c2s_shaper"; ciphers = lib.concatStringsSep ":" cfg.ciphers; protocol_options = cfg.protocol_options; starttls = true; starttls_required = true; tls = false; tls_compression = false; max_stanza_size = 65536; } { port = 5269; ip = "::"; module = "ejabberd_s2s_in"; shaper = "s2s_shaper"; dhfile = "${cfg.stateDir}/dhfile"; max_stanza_size = 131072; } ]; loglevel = lib.mkDefault "4"; modules = { mod_adhoc = lib.mkDefault {}; mod_admin_extra = lib.mkDefault {}; mod_announce.access = lib.mkDefault "announce"; mod_caps = lib.mkDefault {}; mod_carboncopy = lib.mkDefault {}; mod_client_state = lib.mkDefault {}; mod_configure = lib.mkDefault {}; mod_disco = lib.mkDefault {}; mod_echo = lib.mkDefault {}; mod_bosh = lib.mkDefault {}; mod_last = lib.mkDefault {}; mod_offline.access_max_user_messages = lib.mkDefault "max_user_offline_messages"; mod_ping = lib.mkDefault {}; mod_privacy = lib.mkDefault {}; mod_private = lib.mkDefault {}; mod_register = { access_from = lib.mkDefault "deny"; access = lib.mkDefault "register"; ip_access = lib.mkDefault "trusted_network"; registration_watchers = lib.mkDefault cfg.registration_watchers; }; mod_roster = lib.mkDefault {}; mod_shared_roster = lib.mkDefault {}; mod_stats = lib.mkDefault {}; mod_time = lib.mkDefault {}; mod_vcard.search = lib.mkDefault false; mod_version = lib.mkDefault {}; mod_http_api = lib.mkDefault {}; }; s2s_access = lib.mkDefault "s2s"; s2s_ciphers = lib.concatStringsSep ":" cfg.ciphers; s2s_dhfile = lib.mkDefault "${cfg.stateDir}/dhfile"; s2s_protocol_options = lib.mkDefault cfg.protocol_options; s2s_tls_compression = lib.mkDefault false; s2s_use_starttls = lib.mkDefault "required"; shaper_rules = { max_user_offline_messages = lib.mkDefault [ { "5000" = "admin"; } 100 ]; max_user_sessions = lib.mkDefault 10; c2s_shaper = lib.mkDefault [ { "none" = "admin"; } "normal" ]; s2s_shaper = lib.mkDefault "fast"; }; }; }; }