# example configs: # - official: # - nixos: # config options: # - # # modules: # - main: # - community: # # debugging: # - logging: # - enable `stanza_debug` module # - enable `log.debug = "*syslog"` in extraConfig # - interactive: # - `telnet localhost 5582` (this is equal to `prosodyctl shell` -- but doesn't hang) # - `watch:stanzas(target_spec, filter)` -> to log stanzas, for version > 0.12 # - console docs: # - can modify/inspect arbitrary internals (lua) by prefixing line with `> ` # - e.g. `> _G` to print all globals # # sanity checks: # - `sudo -u prosody -g prosody prosodyctl check connectivity` # - `sudo -u prosody -g prosody prosodyctl check turn` # - `sudo -u prosody -g prosody prosodyctl check turn -v --ping=stun.conversations.im` # - checks that my stun/turn server is usable by clients of conversations.im (?) # - `sudo -u prosody -g prosody prosodyctl check` (dns, config, certs) # # # create users with: # - `sudo -u prosody prosodyctl adduser colin@uninsane.org` # # # federation/support matrix: # - nixnet.services (runs ejabberd): # - WORKS: sending and receiving PMs and calls (2023/10/15) # - N.B.: it didn't originally work; was solved by disabling the lua-unbound DNS option & forcing the system/local resolver # - cheogram (XMPP <-> SMS gateway): # - WORKS: sending and receiving PMs, images (2023/10/15) # - PARTIAL: calls (xmpp -> tel works; tel -> xmpp fails) # - maybe i need to setup stun/turn # # TODO: # - enable push notifications (mod_cloud_notify) # - optimize coturn (e.g. move off of the VPN!) # - ensure muc is working # - enable file uploads # - "upload.xmpp.uninsane.org:http_upload: URL: - Ensure this can be reached by users" # - disable or fix bosh (jabber over http): # - "certmanager: No certificate/key found for client_https port 0" { lib, pkgs, ... }: let # enables very verbose logging enableDebug = false; in { sane.persist.sys.byStore.plaintext = [ { user = "prosody"; group = "prosody"; path = "/var/lib/prosody"; method = "bind"; } ]; sane.ports.ports."5000" = { protocol = [ "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; description = "colin-xmpp-prosody-fileshare-proxy65"; }; sane.ports.ports."5222" = { protocol = [ "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; description = "colin-xmpp-client-to-server"; }; sane.ports.ports."5223" = { protocol = [ "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; description = "colin-xmpps-client-to-server"; # XMPP over TLS }; sane.ports.ports."5269" = { protocol = [ "tcp" ]; visibleTo.wan = true; description = "colin-xmpp-server-to-server"; }; sane.ports.ports."5270" = { protocol = [ "tcp" ]; visibleTo.wan = true; description = "colin-xmpps-server-to-server"; # XMPP over TLS }; sane.ports.ports."5280" = { protocol = [ "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; description = "colin-xmpp-bosh"; }; sane.ports.ports."5281" = { protocol = [ "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; description = "colin-xmpp-prosody-https"; # necessary? }; users.users.prosody.extraGroups = [ "nginx" # provide access to certs "ntfy-sh" # access to secret ntfy topic ]; security.acme.certs."uninsane.org".extraDomainNames = [ "xmpp.uninsane.org" "conference.xmpp.uninsane.org" "upload.xmpp.uninsane.org" ]; # exists so the XMPP server's cert can obtain altNames for all its resources services.nginx.virtualHosts."xmpp.uninsane.org" = { useACMEHost = "uninsane.org"; }; services.nginx.virtualHosts."conference.xmpp.uninsane.org" = { useACMEHost = "uninsane.org"; }; services.nginx.virtualHosts."upload.xmpp.uninsane.org" = { useACMEHost = "uninsane.org"; }; sane.dns.zones."uninsane.org".inet = { # XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs A."xmpp" = "%ANATIVE%"; CNAME."conference.xmpp" = "xmpp"; CNAME."upload.xmpp" = "xmpp"; # _Service._Proto.Name TTL Class SRV Priority Weight Port Target # - # something's requesting the SRV records for conference.xmpp, so let's include it # nothing seems to request XMPP SRVs for the other records (except @) # lower numerical priority field tells clients to prefer this method SRV."_xmpps-client._tcp.conference.xmpp" = "3 50 5223 xmpp"; SRV."_xmpps-server._tcp.conference.xmpp" = "3 50 5270 xmpp"; SRV."_xmpp-client._tcp.conference.xmpp" = "5 50 5222 xmpp"; SRV."_xmpp-server._tcp.conference.xmpp" = "5 50 5269 xmpp"; SRV."_xmpps-client._tcp" = "3 50 5223 xmpp"; SRV."_xmpps-server._tcp" = "3 50 5270 xmpp"; SRV."_xmpp-client._tcp" = "5 50 5222 xmpp"; SRV."_xmpp-server._tcp" = "5 50 5269 xmpp"; }; # help Prosody find its certificates. # pointing it to /var/lib/acme doesn't quite work because it expects the private key # to be named `privkey.pem` instead of acme's `key.pem` # sane.fs."/etc/prosody/certs/uninsane.org/fullchain.pem" = { symlink.target = "/var/lib/acme/uninsane.org/fullchain.pem"; wantedBeforeBy = [ "prosody.service" ]; }; sane.fs."/etc/prosody/certs/uninsane.org/privkey.pem" = { symlink.target = "/var/lib/acme/uninsane.org/key.pem"; wantedBeforeBy = [ "prosody.service" ]; }; services.prosody = { enable = true; package = pkgs.prosody.override { # XXX(2023/10/15): build without lua-unbound support. # this forces Prosody to fall back to the default Lua DNS resolver, which seems more reliable. # fixes errors like "unbound.queryXYZUV: Resolver error: out of memory" # related: lua.withPackages = selector: pkgs.lua.withPackages (p: selector (p // { luaunbound = null; }) ); # withCommunityModules = [ "turncredentials" ]; }; admins = [ "colin@uninsane.org" ]; # allowRegistration = false; # defaults to false muc = [ { domain = "conference.xmpp.uninsane.org"; } ]; uploadHttp.domain = "upload.xmpp.uninsane.org"; virtualHosts = { # "Prosody requires at least one enabled VirtualHost to function. You can # safely remove or disable 'localhost' once you have added another." # localhost = { # domain = "localhost"; # enabled = true; # }; "xmpp.uninsane.org" = { domain = "uninsane.org"; enabled = true; }; }; ## modules: # these are enabled by default, via # - cloud_notify # - http_upload # - vcard_muc # these are enabled by the module defaults (services.prosody.modules.) # - admin_adhoc # - blocklist # - bookmarks # - carbons # - cloud_notify # - csi # - dialback # - disco # - http_files # - mam # - pep # - ping # - private # - XEP-0049: let clients store arbitrary (private) data on the server # - proxy65 # - XEP-0065: allow server to proxy file transfers between two clients who are behind NAT # - register # - roster # - saslauth # - smacks # - time # - tls # - uptime # - vcard_legacy # - version extraPluginPaths = [ ./modules ]; extraModules = [ # admin_shell: allows `prosodyctl shell` to work # see: # see: "admin_shell" "admin_telnet" #< needed by admin_shell # lastactivity: XEP-0012: allow users to query how long another user has been idle for # - not sure why i enabled this; think it was in someone's config i referenced "lastactivity" # allows prosody to share TURN/STUN secrets with XMPP clients to provide them access to the coturn server. # see: "turn_external" # legacy coturn integration # see: # "turncredentials" "sane_ntfy" ] ++ lib.optionals enableDebug [ "stanza_debug" #< logs EVERY stanza as debug: ]; extraConfig = '' local function readAll(file) local f = assert(io.open(file, "rb")) local content = f:read("*all") f:close() -- remove trailing newline return string.gsub(content, "%s+", "") end -- logging docs: -- - -- - -- levels: debug, info, warn, error log = { ${if enableDebug then "debug" else "info"} = "*syslog"; } -- see: -- try to solve: "certmanager: Error indexing certificate directory /etc/prosody/certs: cannot open /etc/prosody/certs: No such file or directory" -- only, this doesn't work because prosody doesn't like acme's naming scheme -- certificates = "/var/lib/acme" c2s_direct_tls_ports = { 5223 } s2s_direct_tls_ports = { 5270 } turn_external_host = "turn.uninsane.org" turn_external_secret = readAll("/var/lib/coturn/shared_secret.bin") -- turn_external_user = "prosody" -- legacy mod_turncredentials integration -- turncredentials_host = "turn.uninsane.org" -- turncredentials_secret = readAll("/var/lib/coturn/shared_secret.bin") ntfy_binary = "${pkgs.ntfy-sh}/bin/ntfy" ntfy_topic = readAll("/run/secrets/ntfy-sh-topic") -- s2s_require_encryption = true -- c2s_require_encryption = true ''; }; }