Prosody: Difference between revisions
first version with link to manual |
rm redundant config |
||
(7 intermediate revisions by the same user not shown) | |||
Line 3: | Line 3: | ||
It is documented in the [https://nixos.org/manual/nixos/stable/#module-services-prosody NixOS manual]. | It is documented in the [https://nixos.org/manual/nixos/stable/#module-services-prosody 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 [https://prosody.im/doc/dns]. | |||
* 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. | |||
<syntaxhighlight lang="nix"> | |||
services.prosody.disco_items = [ | |||
{ | |||
description = "http upload"; | |||
url = "upload.xmpp.${domainName}"; | |||
} | |||
]; | |||
</syntaxhighlight> | |||
* 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. | |||
<syntaxhighlight lang="nix"> | |||
{ | |||
config, | |||
... | |||
}: | |||
let | |||
sslCertDir = config.security.acme.certs."example.org".directory; | |||
domainName = "example.org"; | |||
in | |||
{ | |||
# further prosody & coturn configuration goes here | |||
... | |||
} | |||
</syntaxhighlight> | |||
= Set up Prosody = | |||
== Open firewall for client to server c2s == | |||
<syntaxhighlight lang="nix"> | |||
networking.firewall = { | |||
allowedTCPPorts = [ | |||
# prosody client to server | |||
5222 | |||
5223 | |||
]; | |||
}; | |||
</syntaxhighlight> | |||
== Authentication and XMPP modules == | |||
<syntaxhighlight lang="nix"> | |||
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; | |||
}; | |||
</syntaxhighlight> | |||
== Setup Multi User Conference module == | |||
<syntaxhighlight lang="nix"> | |||
services.prosody = { | |||
muc = [ | |||
{ | |||
domain = "muc.xmpp.${domainName}"; | |||
restrictRoomCreation = false; | |||
} | |||
]; | |||
} | |||
</syntaxhighlight> | |||
== Setup virtual host and ssl == | |||
<syntaxhighlight lang="nix"> | |||
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. | |||
"myvhost0" = { | |||
domain = "${domainName}"; | |||
enabled = true; | |||
}; | |||
}; | |||
}; | |||
</syntaxhighlight> | |||
You also need to set proper file permissions on the cert directory and key files. | |||
See [[ACME#Integration_with_service_modules]]. | |||
== Optional: use sqlite == | |||
<syntaxhighlight lang="nix"> | |||
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). | |||
} | |||
''; | |||
}; | |||
</syntaxhighlight> | |||
sqlite seems to be more performant than the default file-backed | |||
storage driver. | |||
= Set up coturn = | |||
== Open firewall ports == | |||
<syntaxhighlight lang="nix"> | |||
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; | |||
} | |||
]; | |||
}; | |||
</syntaxhighlight> | |||
== Set up coturn == | |||
<syntaxhighlight lang="nix"> | |||
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; | |||
}; | |||
</syntaxhighlight> | |||
== Connect to prosody == | |||
<syntaxhighlight lang="nix"> | |||
services.prosody.virtualHosts."myvhost0".extraConfig = '' | |||
turn_external_host = "turn.xmpp.${domainName}" | |||
turn_external_secret = "unfortunately this is a inline password" | |||
''; | |||
services.prosody.extraModules = [ "turn_external" ]; | |||
</syntaxhighlight> | |||
= 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. [https://github.com/ThomasLeister/prosody-filer/issues/39] | |||
== Open firewall ports == | |||
<syntaxhighlight lang="nix"> | |||
networking.firewall = { | |||
allowedTCPPorts = [ | |||
# prosody https upload | |||
443 | |||
]; | |||
}; | |||
</syntaxhighlight> | |||
== Setup prosody-filer service == | |||
<syntaxhighlight lang="nix"> | |||
# 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/"; | |||
}; | |||
}; | |||
</syntaxhighlight> | |||
== Set up nginx virtual host == | |||
<syntaxhighlight lang="nix"> | |||
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; | |||
} | |||
''; | |||
}; | |||
}; | |||
}; | |||
</syntaxhighlight> | |||
== Connect filer to Prosody == | |||
<syntaxhighlight lang="nix"> | |||
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 | |||
''; | |||
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" | |||
]; | |||
</syntaxhighlight> | |||
= Final Checkup = | |||
Run prosodyctl check to check for local configuration and DNS setup errors. | |||
[[Category:Server]] | [[Category:Server]] | ||
[[Category:NixOS Manual]] | [[Category:NixOS Manual]] |
Latest revision as of 02:16, 27 September 2025
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}";
}
];
- 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;
}
];
}
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.
"myvhost0" = {
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."myvhost0".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
'';
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.