diff options
siloed rooms
Diffstat (limited to 'ejabberd_kai.nix')
| -rw-r--r-- | ejabberd_kai.nix | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/ejabberd_kai.nix b/ejabberd_kai.nix new file mode 100644 index 0000000..aed1acc --- /dev/null +++ b/ejabberd_kai.nix @@ -0,0 +1,607 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.ejabberd; + inherit (config.security.acme) certs; + + runtimeDir = "/run/ejabberd"; + + # wouldn’t it be cool if we could attach extra data? + biboumiCfg = rec { + user = "biboumi"; + group = "biboumi"; + database = { + user = user; + name = "biboumi"; + }; + } // config.services.biboumi; + + fqdn = "toastal.in.th"; + + database = { + name = "ejabberd"; + user = cfg.user; + }; + + ports = { + mqtt = 1883; + c2s = 5222; + c2ss = 5223; + s2s = 5269; + s2ss = 5270; + http = 5280; + https = 5443; + irc = 6667; + ircs = 6697; + proxy65 = 7777; + matrix = 8448; + }; + + pow = lib.fix ( + self: base: power: + if power != 0 + then base * (self base (power - 1)) + else 1 + ); + + ejabberd_config = { + # Use systemd EnvironmentFile + EJABBERD_MACRO_* to define + define_macro = { + BIBOUMI_SECRET = null; + MATRIX_SECRET = null; + TURN_SECRET = null; + }; + loglevel = "notice"; + log_rotate_size = 1 * (pow 2 30); + log_rotate_count = 1; + hide_sensitive_log_data = true; + hosts = [ fqdn ]; + language = "en"; + default_db = "mnesia"; + acme.auto = false; + ca_file = "${config.environment.etc."ssl/certs/ca-certificates.crt".source}"; + certfiles = [ + "${certs.${fqdn}.directory}/*.pem" + ]; + c2s_tls_compression = true; + s2s_access = "s2s"; + s2s_tls_compression = true; + s2s_use_starttls = true; + new_sql_schema = true; + captcha_cmd = "${cfg.package.out}/lib/ejabberd-${cfg.package.version}/priv/bin/captcha.sh"; + captcha_url = "https://xmpp.@HOST@/captcha"; + acl = { + admin = [ + { user = "admin@${fqdn}"; } + { user = "toastal@${fqdn}"; } + ]; + local.user_regexp = ""; + loopback.ip = [ + "127.0.0.1/8" + "::1/128" + ]; + }; + access_rules = { + c2s = { + deny = "blocked"; + allow = "all"; + }; + s2s = { + allow = "all"; + }; + local.allow = "local"; + announce.allow = "admin"; + configure.allow = "admin"; + muc_create.allow = "local"; + pubsub_createnode.allow = "local"; + trusted_network.allow = "loopback"; + }; + api_permissions = { + "console commands" = { + from = [ "ejabberd_ctl" ]; + who = "all"; + what = "*"; + }; + "admin access" = { + who = { + access.allow = [ + { acl = "local"; } + { acl = "admin"; } + ]; + oauth = { + scope = "ejabberd:admin"; + access.allow = [ + { acl = "local"; } + { acl = "admin"; } + ]; + }; + }; + what = [ "*" "!stop" "!start" ]; + }; + "public commands" = { + who.ip = "127.0.0.1/8"; + what = [ "status" "connected_users_number" ]; + }; + }; + shaper = { + normal = { + rate = 3000; + burst_size = 20000; + }; + fast = 100000; + }; + modules = { + mod_adhoc = { }; + mod_admin_extra = { }; + mod_announce = { + access = "announce"; + }; + mod_avatar = { }; + mod_blocking = { }; + mod_bosh = { }; + mod_caps = { }; + mod_carboncopy = { }; + mod_client_state = { }; + mod_configure = { }; + mod_disco = { + server_info = [ + { + modules = "all"; + name = "abuse-addresses"; + urls = [ "mailto:toastal+abuse@posteo.net" ]; + } + ]; + }; + mod_host_meta = { + bosh_service_url = "https://xmpp.@HOST@/bosh"; + websocket_url = "wss://xmpp.@HOST@/ws"; + }; + mod_http_api = { }; + #mod_http_fileserver.docroot = "${cfg.spoolDir}/http"; + mod_http_upload = { + docroot = "${cfg.spoolDir}/uploads"; + dir_mode = "0755"; + file_mode = "0644"; + get_url = "https://xmpp.@HOST@/upload"; + put_url = "https://xmpp.@HOST@/upload"; + max_size = 4 * (pow 2 30); + custom_headers = { + Access-Control-Allow-Origin = "https://@HOST@,https://xmpp.@HOST@,https://social.@HOST@"; + Access-Control-Allow-Methods = "GET,HEAD,PUT,OPTIONS"; + Access-Control-Allow-Headers = "Content-Type"; + }; + }; + mod_http_upload_quota = { + max_days = 2 * 365; + }; + mod_last = { }; + mod_matrix_gw = { + host = "matrix.@HOST@"; + key_name = "use_xmpp"; + key = "MATRIX_SECRET"; + }; + mod_mam = { + assume_mam_usage = true; + default = "always"; + db_type = "sql"; + compress_xml = true; + }; + mod_mqtt = { }; + mod_muc = { + hosts = [ "chat.@HOST@" ]; + access = [ "allow" ]; + access_admin = [ + { allow = "admin"; } + ]; + access_create = "muc_create"; + access_persistent = "muc_create"; + access_mam = [ "allow" ]; + default_room_options = { + allow_change_subj = true; + allow_private_messages_from_visitors = "moderators"; + allow_subscription = true; + allow_user_invites = true; + allowpm = "participants"; + lang = "en"; + mam = true; + max_users = 512; + moderated = true; + }; + }; + mod_muc_admin = { }; + mod_offline = { + access_max_user_messages = "max_user_offline_messages"; + use_mam_for_storage = true; + }; + mod_ping = { }; + mod_private = { + db_type = "sql"; + }; + mod_privacy = { + db_type = "sql"; + }; + mod_proxy65 = { + hosts = [ + "proxy.@HOST@" + ]; + port = ports.proxy65; + access = "local"; + max_connections = 8; + }; + mod_pubsub = { + hosts = [ + "tidings.@HOST@" + ]; + access_createnode = "pubsub_createnode"; + ignore_pep_from_offline = false; + last_item_cache = false; + max_items_node = 2048; + default_node_config = { + max_items = 2048; + }; + plugins = [ "flat" "pep" ]; + force_node_config = { + "storage:bookmarks".access_model = "whitelist"; + "eu.siacs.conversations.axolotl.*".access_model = "open"; + "urn:xmpp:bookmarks:0" = { + access_model = "whitelist"; + send_last_published_item = "never"; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:bookmarks:1" = { + access_model = "whitelist"; + send_last_published_item = "never"; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:pubsub:movim-public-subscription" = { + access_model = "whitelist"; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:microblog:0" = { + notify_retract = true; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:microblog:0:comments*" = { + access_model = "open"; + notify_retract = true; + max_items = "infinity"; + persist_items = true; + }; + }; + }; + mod_push = { }; + mod_push_keepalive = { }; + mod_register = { + ip_access = "trusted_network"; + }; + mod_roster = { + versioning = true; + }; + mod_s2s_dialback = { }; + mod_shared_roster = { }; + mod_stream_mgmt = { }; + mod_stun_disco = { + services = map (type: { inherit type; host = "turn.${fqdn}"; port = 3478; }) [ "turn" "turns" ]; + secret = "TURN_SECRET"; + }; + mod_time = { }; + mod_vcard = { + db_type = "sql"; + }; + mod_vcard_xupdate = { }; + }; + listen = [ + { + module = "ejabberd_c2s"; + port = ports.c2s; + max_stanza_size = 262144; + #shaper = "c2s_shaper"; + access = "c2s"; + starttls_required = true; + } + { + module = "ejabberd_c2s"; + port = ports.c2ss; + max_stanza_size = 262144; + #shaper = "c2s_shaper"; + access = "c2s"; + tls = true; + starttls_required = true; + } + { + module = "ejabberd_s2s_in"; + port = ports.s2s; + max_stanza_size = 524288; + shaper = "fast"; + } + { + module = "ejabberd_s2s_in"; + port = ports.s2ss; + tls = true; + max_stanza_size = 524288; + shaper = "fast"; + } + { + module = "ejabberd_http"; + port = ports.http; + tls = false; + request_handlers = { }; + } + { + module = "ejabberd_http"; + port = ports.https; + tls = true; + request_handlers = { + "/admin" = "ejabberd_web_admin"; + "/api" = "mod_http_api"; + "/bosh" = "mod_bosh"; + "/captcha" = "ejabberd_captcha"; + "/upload" = "mod_http_upload"; + "/ws" = "ejabberd_http_ws"; + "/.well-known/host-meta" = "mod_host_meta"; + "/.well-known/host-meta.json" = "mod_host_meta"; + }; + } + { + module = "mod_mqtt"; + port = ports.mqtt; + backlog = 1024; + } + { + module = "ejabberd_service"; + port = biboumiCfg.settings.port; + hosts = { + "${biboumiCfg.settings.hostname}" = { + password = "BIBOUMI_SECRET"; + }; + }; + } + { + module = "ejabberd_http"; + port = ports.matrix; + tls = true; + request_handlers = { + "/_matrix" = "mod_matrix_gw"; + }; + } + ]; + host_config = { + "${fqdn}" = { + auth_method = "sql"; + auth_password_format = "scram"; + sql_type = "pgsql"; + sql_server = "localhost"; + sql_port = config.services.postgresql.settings.port; + sql_database = database.name; + sql_username = database.user; + }; + }; + }; + + ejabberd_config_file = + let + settingsFormat = pkgs.formats.yaml { }; + in + settingsFormat.generate "ejabberd.yml" ejabberd_config; +in +{ + users = { + groups = { + ${biboumiCfg.group} = { }; + xmpp = { }; + }; + users = { + ${cfg.user} = { + extraGroups = [ + "xmpp" + certs.${fqdn}.group + ]; + }; + ${biboumiCfg.user} = { + isSystemUser = true; + group = biboumiCfg.group; + extraGroups = [ "xmpp" ]; + }; + }; + }; + + security.acme.certs.${fqdn} = { + postRun = lib.mkAfter "systemctl restart ejabberd.service"; + }; + + services.ejabberd = { + enable = true; + package = pkgs.ejabberd.override (old: { + withImagemagick = true; + withPgsql = true; + withTools = true; + withZlib = true; + }); + configFile = ejabberd_config_file; + ctlConfig = '' + CONFIG_DIR=${runtimeDir} + ERL_CRASH_DUMP=${cfg.logsDir}/erl_crash.dump + ''; + imagemagick = true; + }; + + systemd.services.ejabberd-data-setup = { + description = "Ejabberd Setup: creates EnvironmentFile & so on"; + wantedBy = [ "multi-user.target" ]; + before = [ "ejabberd.service" ]; + requiredBy = [ "ejabberd.service" ]; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + UMask = "077"; + RuntimeDirectory = lib.removePrefix "/run/" runtimeDir; + RuntimeDirectoryMode = "700"; + RemainAfterExit = true; + ProtectHome = true; + PrivateTmp = true; + }; + script = /* sh */ '' + mkdir -p "${runtimeDir}" + touch "${runtimeDir}/.env" "${runtimeDir}/inetrc" + chmod 600 "${runtimeDir}/.env" + old_umask=$(umask) + umask 0177 + cat << EOF > "${runtimeDir}/.env" + EJABBERD_MACRO_BIBOUMI_SECRET="$(${lib.getExe pkgs.gawk} -F '=' '{a[$1]=$2} END {print(a["password"])}' "${biboumiCfg.credentialsFile}")" + EJABBERD_MACRO_MATRIX_SECRET="$(cat "/var/secrets/ejabberd/matrix.key")" + EJABBERD_MACRO_TURN_SECRET="$(cat "/var/secrets/turn-server/static-auth-secret.txt")" + EOF + ''; + }; + + systemd.services.ejabberd = { + requires = [ "ejabberd-data-setup.service" "postgresql.service" ]; + wantedBy = [ "biboumi.service" ]; + after = [ "ejabberd-data-setup.service" "postgresql.service" ]; + serviceConfig = { + StartupMemoryMax = "12G"; + MemoryMax = "8G"; + RuntimeDirectory = lib.removePrefix "/run/" runtimeDir; + RuntimeDirectoryMode = "700"; + RuntimeDirectoryPreserve = "yes"; + EnvironmentFile = "${runtimeDir}/.env"; + ProtectHome = true; + PrivateTmp = true; + }; + }; + + services.biboumi = { + enable = true; + package = pkgs.biboumi.override { + withPostgreSQL = true; + withSQLite = false; + }; + settings = { + admin = map (u: u.user) ejabberd_config.acl.admin; + hostname = "irc.${fqdn}"; + db_name = "postgresql://${biboumiCfg.database.user}@localhost:${builtins.toString config.services.postgresql.settings.port}/"; + password = null; + ca_file = "${config.environment.etc."ssl/certs/ca-certificates.crt".source}"; + }; + credentialsFile = "/var/secrets/biboumi/biboumi.cfg"; + openFirewall = true; + }; + + systemd.services.biboumi = { + partOf = [ "ip-change@enp2s0.target" ]; + before = [ "ip-change@enp2s0.target" ]; + requires = [ + "ejabberd.service" + "postgresql.service" + ]; + after = [ "ejabberd.service" ]; + serviceConfig = { + MemoryMax = "256M"; + }; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [ + database.name + biboumiCfg.database.name + ]; + ensureUsers = [ + { + name = database.user; + ensureDBOwnership = true; + } + { + name = biboumiCfg.database.user; + ensureDBOwnership = true; + } + ]; + authentication = '' + host ${database.name} ${database.user} localhost trust + host ${biboumiCfg.database.name} ${biboumiCfg.database.user} localhost trust + ''; + }; + + services.h2o = { + enable = true; + hosts = { + "matrix.${fqdn}" = { + tls.policy = "only"; + acme.useHost = fqdn; + settings = { + paths."/" = { + "proxy.reverse.url" = "http://matrix.${fqdn}:${builtins.toString ports.matrix}"; + "proxy.ssl.verify-peer" = "OFF"; + "proxy.tunnel" = "ON"; + }; + }; + }; + "proxy.${fqdn}" = { + tls.policy = "only"; + acme.useHost = fqdn; + settings = { + paths."/" = { + "proxy.reverse.url" = "http://proxy.${fqdn}:${builtins.toString ports.proxy65}"; + "proxy.ssl.verify-peer" = "OFF"; + "proxy.tunnel" = "ON"; + }; + }; + }; + "http.xmpp.${fqdn}" = { + serverName = "xmpp.${fqdn}"; + settings = { + paths."/" = { + "proxy.reverse.url" = "https://xmpp.${fqdn}:${builtins.toString ports.http}"; + "proxy.tunnel" = "ON"; + }; + }; + }; + "tls.xmpp.${fqdn}" = { + serverName = "xmpp.${fqdn}"; + tls.policy = "only"; + acme.useHost = fqdn; + settings = { + paths."/" = { + "proxy.reverse.url" = "https://xmpp.${fqdn}:${builtins.toString ports.https}"; + "proxy.ssl.verify-peer" = "OFF"; + "proxy.tunnel" = "ON"; + }; + }; + }; + }; + }; + + systemd.services.h2o.wants = [ "ejabberd.service" ]; + + systemd.tmpfiles.settings."10-ejabberd" = { + "${runtimeDir}".d = { inherit (cfg) user group; mode = "0700"; }; + "${runtimeDir}/.env".f = { inherit (cfg) user group; mode = "0600"; }; + "${runtimeDir}/inetrc".f = { inherit (cfg) user group; mode = "0600"; }; + "${runtimeDir}/vm.args".f = { inherit (cfg) user group; mode = "0600"; }; + "/run/biboumi".d = { inherit (biboumiCfg) user group; mode = "0740"; }; + }; + + networking = { + firewall.allowedTCPPorts = with ports; [ + c2s + c2ss + s2s + c2ss + http + https + irc + ircs + matrix + mqtt + proxy65 + ]; + nftables.ruleset = '' + add rule inet filter output meta skuid biboumi tcp accept + ''; + }; +} |
