Prosody
Prosody is an open-source, modern XMPP server.
It is documented in the NixOS manual.
This page describes how to setup a walled-off Prosody instance for your organisation or family, with STUN/TURN support and http upload. This setup has server-to-server communication disabled.
Set up DNS records for Prosody
See official Prosody documentation here [1].
- Domain of the xmpp address is the bare top level domain example.org. The bare top level domain has SRV records pointing to XMPP server xmpp.example.org.
- XMPP server is hosted on xmpp.example.org.
- XMPP services, such as STUN/TURN server, Multi-User Chat and HTTP upload are hosted at *.xmpp.example.org. As *.xmpp.example.org is not a direct subdomain of example.org, we need to set mod_disco to let XMPP client to discover the services.
services.prosody.disco_items = [
{
description = "http upload";
url = "upload.xmpp.${domainName}";
}
{
description = "multi user chat";
url = "muc.xmpp.${domainName}";
}
];
- STUN/TURN server is hosted on turn.xmpp.example.org.
- Multi-User Chat is hosted on muc.xmpp.example.org.
- HTTP upload server is hosted on upload.xmpp.example.org.
SSL Certificate with ACME
This article assumes a working ACME configuration for certificate renewal.
{
config,
...
}:
let
sslCertDir = config.security.acme.certs."example.org".directory;
domainName = "example.org";
in
{
# further prosody & coturn configuration goes here
...
}
Set up Prosody
Open firewall for client to server c2s
networking.firewall = {
allowedTCPPorts = [
# prosody client to server
5222
5223
];
};
Authentication and XMPP modules
services.prosody = {
# this is a minimal server config with turn and http upload
# all server to server connection are blocked
enable = true;
admins = [ "admin@${domainName}" ];
allowRegistration = false;
authentication = "internal_plain";
s2sSecureAuth = true;
c2sRequireEncryption = true;
modules = {
admin_adhoc = false;
cloud_notify = false;
pep = false;
blocklist = false;
bookmarks = false;
dialback = false;
ping = false;
private = false;
register = false;
vcard_legacy = false;
};
xmppComplianceSuite = false;
};
Setup Multi User Conference module
services.prosody = {
muc = [
{
domain = "muc.xmpp.${domainName}";
restrictRoomCreation = false;
}
];
disco_items = [
{
description = "multi user chat";
url = "muc.xmpp.${domainName}";
}
];
}
Setup virtual host and ssl
services.prosody = {
ssl = {
cert = "${sslCertDir}/fullchain.pem";
key = "${sslCertDir}/key.pem";
};
virtualHosts = {
# xmpp server for "@example.org" is hosted on "xmpp.example.org"
# use SRV records.
"xmpp.${domainName}" = {
domain = "${domainName}";
enabled = true;
};
};
};
You also need to set proper file permissions on the cert directory and key files. See ACME#Integration_with_service_modules.
Optional: use sqlite
services.prosody = {
extraConfig = ''
storage = "sql"
sql = {
driver = "SQLite3";
database = "prosody.sqlite"; -- The database name to use. For SQLite3 this the database filename (relative to the data storage directory).
}
'';
};
sqlite seems to be more performant than the default file-backed storage driver.
Set up coturn
Open firewall ports
networking.firewall = {
allowedTCPPorts = [
# prosody turn/stun with coturn
# see /run/coturn/turnserver.cfg
3478
3479
5349
5350
];
allowedUDPPorts = [
# prosody turn/stun with coturn
3478
3479
5349
5350
];
allowedUDPPortRanges = [
# coturn
{
from = 49152;
to = 65535;
}
];
};
Set up coturn
age.secrets.prosody-coturn = {
file = ./prosody-coturn.age;
# -rw-r--r--
mode = "644";
};
services.coturn = {
# https://prosody.im/doc/coturn
enable = true;
listening-port = 3478;
pkey = "${sslCertDir}/key.pem";
cert = "${sslCertDir}/fullchain.pem";
realm = "turn.xmpp.${domainName}";
static-auth-secret-file = config.age.secrets.prosody-coturn.path;
use-auth-secret = true;
};
Connect to prosody
services.prosody.virtualHosts."xmpp.${domainName}".extraConfig = ''
turn_external_host = "turn.xmpp.${domainName}"
turn_external_secret = "unfortunately this is a inline password"
'';
services.prosody.extraModules = [ "turn_external" ];
http upload with prosody-filer and nginx
Although newer prosody versions does not require external http upload to run, prosody-filer is still more performant than the built-in server. [2]
Open firewall ports
networking.firewall = {
allowedTCPPorts = [
# prosody https upload
443
];
};
Setup prosody-filer service
# set up directory for storing uploaded file
systemd.services.prosody-filer.serviceConfig = {
StateDirectory = "prosody-filer";
RuntimeDirectory = "prosody-filer";
RuntimeDirectoryMode = "0750";
};
services.prosody-filer = {
enable = true;
settings = {
secret = "plain in line password";
storeDir = "/var/lib/prosody-filer/uploads/";
# this option refers to the upload url
# https://upload.xmpp.${domainName}/upload/
# <------->
# do not change this, else 404
uploadSubDir = "upload/";
};
};
Set up nginx virtual host
services.nginx = {
clientMaxBodySize = "50m";
virtualHosts."upload.xmpp.${domainName}" = {
forceSSL = true;
useACMEHost = "${domainName}";
locations."/upload/" = {
proxyPass = "http://localhost:5050/upload/";
# config copied from https://github.com/ThomasLeister/prosody-filer/blob/develop/README.md
extraConfig = ''
proxy_request_buffering off;
if ( $request_method = OPTIONS ) {
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Methods 'PUT, GET, OPTIONS, HEAD';
add_header Access-Control-Allow-Headers 'Authorization, Content-Type';
add_header Access-Control-Allow-Credentials 'true';
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
'';
};
};
};
Connect filer to Prosody
services.prosody.extraConfig = ''
Component "upload.xmpp.${domainName}" "http_upload_external"
http_upload_external_secret = "should be the same pwd as above";
http_upload_external_base_url = "https://upload.xmpp.${domainName}/upload/"
http_upload_external_file_size_limit = 52428800
-- Grant permissions for users on the '${domainName}' VirtualHost
parent_host = "${domainName}"
'';
services.prosody.disco_items = [
{
description = "http upload";
url = "upload.xmpp.${domainName}";
}
];
# mod_http_upload_external is a community module
# has to be installed via
# https://modules.prosody.im/mod_http_upload_external.html#installation
## Clone Prosody Community Modules Repo
# cd /var/lib/prosody
# hg clone https://hg.prosody.im/prosody-modules/ prosody-modules
# update plugins via hg pull --update
services.prosody.extraPluginPaths = [
"/var/lib/prosody/prosody-modules/mod_http_upload_external"
];
Final Checkup
Run prosodyctl check to check for local configuration and DNS setup errors.