Merge pull request #86067 from NinjaTrappeur/nin-sane-prosody-defaults
nixos/prosody: make module defaults comply with XEP-0423
This commit is contained in:
commit
e148a72377
@ -1,9 +1,7 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
cfg = config.services.prosody;
|
cfg = config.services.prosody;
|
||||||
|
|
||||||
sslOpts = { ... }: {
|
sslOpts = { ... }: {
|
||||||
@ -30,8 +28,21 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
discoOpts = {
|
||||||
|
options = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "URL of the endpoint you want to make discoverable";
|
||||||
|
};
|
||||||
|
description = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "A short description of the endpoint you want to advertise";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
moduleOpts = {
|
moduleOpts = {
|
||||||
# Generally required
|
# Required for compliance with https://compliance.conversations.im/about/
|
||||||
roster = mkOption {
|
roster = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
@ -69,6 +80,18 @@ let
|
|||||||
description = "Keep multiple clients in sync";
|
description = "Keep multiple clients in sync";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
csi = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Implements the CSI protocol that allows clients to report their active/inactive state to the server";
|
||||||
|
};
|
||||||
|
|
||||||
|
cloud_notify = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online";
|
||||||
|
};
|
||||||
|
|
||||||
pep = mkOption {
|
pep = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
@ -89,10 +112,22 @@ let
|
|||||||
|
|
||||||
vcard = mkOption {
|
vcard = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = false;
|
||||||
description = "Allow users to set vCards";
|
description = "Allow users to set vCards";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vcard_legacy = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Converts users profiles and Avatars between old and new formats";
|
||||||
|
};
|
||||||
|
|
||||||
|
bookmarks = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP";
|
||||||
|
};
|
||||||
|
|
||||||
# Nice to have
|
# Nice to have
|
||||||
version = mkOption {
|
version = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
@ -126,10 +161,16 @@ let
|
|||||||
|
|
||||||
mam = mkOption {
|
mam = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = true;
|
||||||
description = "Store messages in an archive and allow users to access it";
|
description = "Store messages in an archive and allow users to access it";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
smacks = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Allow a client to resume a disconnected session, and prevent message loss";
|
||||||
|
};
|
||||||
|
|
||||||
# Admin interfaces
|
# Admin interfaces
|
||||||
admin_adhoc = mkOption {
|
admin_adhoc = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
@ -137,6 +178,18 @@ let
|
|||||||
description = "Allows administration via an XMPP client that supports ad-hoc commands";
|
description = "Allows administration via an XMPP client that supports ad-hoc commands";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
http_files = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Serve static files from a directory over HTTP";
|
||||||
|
};
|
||||||
|
|
||||||
|
proxy65 = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enables a file transfer proxy service which clients behind NAT can use";
|
||||||
|
};
|
||||||
|
|
||||||
admin_telnet = mkOption {
|
admin_telnet = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
@ -156,12 +209,6 @@ let
|
|||||||
description = "Enable WebSocket support";
|
description = "Enable WebSocket support";
|
||||||
};
|
};
|
||||||
|
|
||||||
http_files = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Serve static files from a directory over HTTP";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Other specific functionality
|
# Other specific functionality
|
||||||
limits = mkOption {
|
limits = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
@ -210,13 +257,6 @@ let
|
|||||||
default = false;
|
default = false;
|
||||||
description = "Legacy authentication. Only used by some old clients and bots";
|
description = "Legacy authentication. Only used by some old clients and bots";
|
||||||
};
|
};
|
||||||
|
|
||||||
proxy65 = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Enables a file transfer proxy service which clients behind NAT can use";
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
toLua = x:
|
toLua = x:
|
||||||
@ -235,6 +275,153 @@ let
|
|||||||
};
|
};
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
mucOpts = { ... }: {
|
||||||
|
options = {
|
||||||
|
domain = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Domain name of the MUC";
|
||||||
|
};
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "The name to return in service discovery responses for the MUC service itself";
|
||||||
|
default = "Prosody Chatrooms";
|
||||||
|
};
|
||||||
|
restrictRoomCreation = mkOption {
|
||||||
|
type = types.enum [ true false "admin" "local" ];
|
||||||
|
default = false;
|
||||||
|
description = "Restrict room creation to server admins";
|
||||||
|
};
|
||||||
|
maxHistoryMessages = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 20;
|
||||||
|
description = "Specifies a limit on what each room can be configured to keep";
|
||||||
|
};
|
||||||
|
roomLocking = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Enables room locking, which means that a room must be
|
||||||
|
configured before it can be used. Locked rooms are invisible
|
||||||
|
and cannot be entered by anyone but the creator
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
roomLockTimeout = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 300;
|
||||||
|
description = ''
|
||||||
|
Timout after which the room is destroyed or unlocked if not
|
||||||
|
configured, in seconds
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
tombstones = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
When a room is destroyed, it leaves behind a tombstone which
|
||||||
|
prevents the room being entered or recreated. It also allows
|
||||||
|
anyone who was not in the room at the time it was destroyed
|
||||||
|
to learn about it, and to update their bookmarks. Tombstones
|
||||||
|
prevents the case where someone could recreate a previously
|
||||||
|
semi-anonymous room in order to learn the real JIDs of those
|
||||||
|
who often join there.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
tombstoneExpiry = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2678400;
|
||||||
|
description = ''
|
||||||
|
This settings controls how long a tombstone is considered
|
||||||
|
valid. It defaults to 31 days. After this time, the room in
|
||||||
|
question can be created again.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
vcard_muc = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Adds the ability to set vCard for Multi User Chat rooms";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Extra parameters. Defaulting to prosody default values.
|
||||||
|
# Adding them explicitly to make them visible from the options
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# See https://prosody.im/doc/modules/mod_muc for more details.
|
||||||
|
roomDefaultPublic = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "If set, the MUC rooms will be public by default.";
|
||||||
|
};
|
||||||
|
roomDefaultMembersOnly = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "If set, the MUC rooms will only be accessible to the members by default.";
|
||||||
|
};
|
||||||
|
roomDefaultModerated = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "If set, the MUC rooms will be moderated by default.";
|
||||||
|
};
|
||||||
|
roomDefaultPublicJids = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "If set, the MUC rooms will display the public JIDs by default.";
|
||||||
|
};
|
||||||
|
roomDefaultChangeSubject = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "If set, the rooms will display the public JIDs by default.";
|
||||||
|
};
|
||||||
|
roomDefaultHistoryLength = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 20;
|
||||||
|
description = "Number of history message sent to participants by default.";
|
||||||
|
};
|
||||||
|
roomDefaultLanguage = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "en";
|
||||||
|
description = "Default room language.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
uploadHttpOpts = { ... }: {
|
||||||
|
options = {
|
||||||
|
domain = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
description = "Domain name for the http-upload service";
|
||||||
|
};
|
||||||
|
uploadFileSizeLimit = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "50 * 1024 * 1024";
|
||||||
|
description = "Maximum file size, in bytes. Defaults to 50MB.";
|
||||||
|
};
|
||||||
|
uploadExpireAfter = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "60 * 60 * 24 * 7";
|
||||||
|
description = "Max age of a file before it gets deleted, in seconds.";
|
||||||
|
};
|
||||||
|
userQuota = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
example = 1234;
|
||||||
|
description = ''
|
||||||
|
Maximum size of all uploaded files per user, in bytes. There
|
||||||
|
will be no quota if this option is set to null.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
httpUploadPath = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Directory where the uploaded files will be stored. By
|
||||||
|
default, uploaded files are put in a sub-directory of the
|
||||||
|
default Prosody storage path (usually /var/lib/prosody).
|
||||||
|
'';
|
||||||
|
default = "/var/lib/prosody";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
vHostOpts = { ... }: {
|
vHostOpts = { ... }: {
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
@ -283,6 +470,27 @@ in
|
|||||||
description = "Whether to enable the prosody server";
|
description = "Whether to enable the prosody server";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
xmppComplianceSuite = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
The XEP-0423 defines a set of recommended XEPs to implement
|
||||||
|
for a server. It's generally a good idea to implement this
|
||||||
|
set of extensions if you want to provide your users with a
|
||||||
|
good XMPP experience.
|
||||||
|
|
||||||
|
This NixOS module aims to provide a "advanced server"
|
||||||
|
experience as per defined in the XEP-0423[1] specification.
|
||||||
|
|
||||||
|
Setting this option to true will prevent you from building a
|
||||||
|
NixOS configuration which won't comply with this standard.
|
||||||
|
You can explicitely decide to ignore this standard if you
|
||||||
|
know what you are doing by setting this option to false.
|
||||||
|
|
||||||
|
[1] https://xmpp.org/extensions/xep-0423.html
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
package = mkOption {
|
package = mkOption {
|
||||||
type = types.package;
|
type = types.package;
|
||||||
description = "Prosody package to use";
|
description = "Prosody package to use";
|
||||||
@ -302,6 +510,12 @@ in
|
|||||||
default = "/var/lib/prosody";
|
default = "/var/lib/prosody";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
disco_items = mkOption {
|
||||||
|
type = types.listOf (types.submodule discoOpts);
|
||||||
|
default = [];
|
||||||
|
description = "List of discoverable items you want to advertise.";
|
||||||
|
};
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "prosody";
|
default = "prosody";
|
||||||
@ -320,6 +534,31 @@ in
|
|||||||
description = "Allow account creation";
|
description = "Allow account creation";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# HTTP server-related options
|
||||||
|
httpPorts = mkOption {
|
||||||
|
type = types.listOf types.int;
|
||||||
|
description = "Listening HTTP ports list for this service.";
|
||||||
|
default = [ 5280 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
httpInterfaces = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "*" "::" ];
|
||||||
|
description = "Interfaces on which the HTTP server will listen on.";
|
||||||
|
};
|
||||||
|
|
||||||
|
httpsPorts = mkOption {
|
||||||
|
type = types.listOf types.int;
|
||||||
|
description = "Listening HTTPS ports list for this service.";
|
||||||
|
default = [ 5281 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
httpsInterfaces = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "*" "::" ];
|
||||||
|
description = "Interfaces on which the HTTPS server will listen on.";
|
||||||
|
};
|
||||||
|
|
||||||
c2sRequireEncryption = mkOption {
|
c2sRequireEncryption = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
@ -387,6 +626,26 @@ in
|
|||||||
description = "Addtional path in which to look find plugins/modules";
|
description = "Addtional path in which to look find plugins/modules";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uploadHttp = mkOption {
|
||||||
|
description = ''
|
||||||
|
Configures the Prosody builtin HTTP server to handle user uploads.
|
||||||
|
'';
|
||||||
|
type = types.nullOr (types.submodule uploadHttpOpts);
|
||||||
|
default = null;
|
||||||
|
example = {
|
||||||
|
domain = "uploads.my-xmpp-example-host.org";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
muc = mkOption {
|
||||||
|
type = types.listOf (types.submodule mucOpts);
|
||||||
|
default = [ ];
|
||||||
|
example = [ {
|
||||||
|
domain = "conference.my-xmpp-example-host.org";
|
||||||
|
} ];
|
||||||
|
description = "Multi User Chat (MUC) configuration";
|
||||||
|
};
|
||||||
|
|
||||||
virtualHosts = mkOption {
|
virtualHosts = mkOption {
|
||||||
|
|
||||||
description = "Define the virtual hosts";
|
description = "Define the virtual hosts";
|
||||||
@ -443,9 +702,44 @@ in
|
|||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
|
assertions = let
|
||||||
|
genericErrMsg = ''
|
||||||
|
|
||||||
|
Having a server not XEP-0423-compliant might make your XMPP
|
||||||
|
experience terrible. See the NixOS manual for further
|
||||||
|
informations.
|
||||||
|
|
||||||
|
If you know what you're doing, you can disable this warning by
|
||||||
|
setting config.services.prosody.xmppComplianceSuite to false.
|
||||||
|
'';
|
||||||
|
errors = [
|
||||||
|
{ assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite;
|
||||||
|
message = ''
|
||||||
|
You need to setup at least a MUC domain to comply with
|
||||||
|
XEP-0423.
|
||||||
|
'' + genericErrMsg;}
|
||||||
|
{ assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite;
|
||||||
|
message = ''
|
||||||
|
You need to setup the uploadHttp module through
|
||||||
|
config.services.prosody.uploadHttp to comply with
|
||||||
|
XEP-0423.
|
||||||
|
'' + genericErrMsg;}
|
||||||
|
];
|
||||||
|
in errors;
|
||||||
|
|
||||||
environment.systemPackages = [ cfg.package ];
|
environment.systemPackages = [ cfg.package ];
|
||||||
|
|
||||||
environment.etc."prosody/prosody.cfg.lua".text = ''
|
environment.etc."prosody/prosody.cfg.lua".text =
|
||||||
|
let
|
||||||
|
httpDiscoItems = if (cfg.uploadHttp != null)
|
||||||
|
then [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}]
|
||||||
|
else [];
|
||||||
|
mucDiscoItems = builtins.foldl'
|
||||||
|
(acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc)
|
||||||
|
[]
|
||||||
|
cfg.muc;
|
||||||
|
discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems;
|
||||||
|
in ''
|
||||||
|
|
||||||
pidfile = "/run/prosody/prosody.pid"
|
pidfile = "/run/prosody/prosody.pid"
|
||||||
|
|
||||||
@ -472,6 +766,10 @@ in
|
|||||||
${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
|
${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
disco_items = {
|
||||||
|
${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)}
|
||||||
|
};
|
||||||
|
|
||||||
allow_registration = ${toLua cfg.allowRegistration}
|
allow_registration = ${toLua cfg.allowRegistration}
|
||||||
|
|
||||||
c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
|
c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
|
||||||
@ -486,6 +784,42 @@ in
|
|||||||
|
|
||||||
authentication = ${toLua cfg.authentication}
|
authentication = ${toLua cfg.authentication}
|
||||||
|
|
||||||
|
http_interfaces = ${toLua cfg.httpInterfaces}
|
||||||
|
|
||||||
|
https_interfaces = ${toLua cfg.httpsInterfaces}
|
||||||
|
|
||||||
|
http_ports = ${toLua cfg.httpPorts}
|
||||||
|
|
||||||
|
https_ports = ${toLua cfg.httpsPorts}
|
||||||
|
|
||||||
|
${lib.concatMapStrings (muc: ''
|
||||||
|
Component ${toLua muc.domain} "muc"
|
||||||
|
modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } }
|
||||||
|
name = ${toLua muc.name}
|
||||||
|
restrict_room_creation = ${toLua muc.restrictRoomCreation}
|
||||||
|
max_history_messages = ${toLua muc.maxHistoryMessages}
|
||||||
|
muc_room_locking = ${toLua muc.roomLocking}
|
||||||
|
muc_room_lock_timeout = ${toLua muc.roomLockTimeout}
|
||||||
|
muc_tombstones = ${toLua muc.tombstones}
|
||||||
|
muc_tombstone_expiry = ${toLua muc.tombstoneExpiry}
|
||||||
|
muc_room_default_public = ${toLua muc.roomDefaultPublic}
|
||||||
|
muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly}
|
||||||
|
muc_room_default_moderated = ${toLua muc.roomDefaultModerated}
|
||||||
|
muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids}
|
||||||
|
muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject}
|
||||||
|
muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength}
|
||||||
|
muc_room_default_language = ${toLua muc.roomDefaultLanguage}
|
||||||
|
|
||||||
|
'') cfg.muc}
|
||||||
|
|
||||||
|
${ lib.optionalString (cfg.uploadHttp != null) ''
|
||||||
|
Component ${toLua cfg.uploadHttp.domain} "http_upload"
|
||||||
|
http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
|
||||||
|
http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
|
||||||
|
${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"}
|
||||||
|
http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
|
||||||
|
''}
|
||||||
|
|
||||||
${ cfg.extraConfig }
|
${ cfg.extraConfig }
|
||||||
|
|
||||||
${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
|
${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
|
||||||
@ -522,9 +856,22 @@ in
|
|||||||
PIDFile = "/run/prosody/prosody.pid";
|
PIDFile = "/run/prosody/prosody.pid";
|
||||||
ExecStart = "${cfg.package}/bin/prosodyctl start";
|
ExecStart = "${cfg.package}/bin/prosodyctl start";
|
||||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||||
|
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateMounts = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
meta.doc = ./prosody.xml;
|
||||||
}
|
}
|
||||||
|
88
nixos/modules/services/networking/prosody.xml
Normal file
88
nixos/modules/services/networking/prosody.xml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<chapter xmlns="http://docbook.org/ns/docbook"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
version="5.0"
|
||||||
|
xml:id="module-services-prosody">
|
||||||
|
<title>Prosody</title>
|
||||||
|
<para>
|
||||||
|
<link xlink:href="https://prosody.im/">Prosody</link> is an open-source, modern XMPP server.
|
||||||
|
</para>
|
||||||
|
<section xml:id="module-services-prosody-basic-usage">
|
||||||
|
<title>Basic usage</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A common struggle for most XMPP newcomers is to find the right set
|
||||||
|
of XMPP Extensions (XEPs) to setup. Forget to activate a few of
|
||||||
|
those and your XMPP experience might turn into a nightmare!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The XMPP community tackles this problem by creating a meta-XEP
|
||||||
|
listing a decent set of XEPs you should implement. This meta-XEP
|
||||||
|
is issued every year, the 2020 edition being
|
||||||
|
<link xlink:href="https://xmpp.org/extensions/xep-0423.html">XEP-0423</link>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The NixOS Prosody module will implement most of these recommendend XEPs out of
|
||||||
|
the box. That being said, two components still require some
|
||||||
|
manual configuration: the
|
||||||
|
<link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
|
||||||
|
and the <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link> ones.
|
||||||
|
You'll need to create a DNS subdomain for each of those. The current convention is to name your
|
||||||
|
MUC endpoint <literal>conference.example.org</literal> and your HTTP upload domain <literal>upload.example.org</literal>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
A good configuration to start with, including a
|
||||||
|
<link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
|
||||||
|
endpoint as well as a <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link>
|
||||||
|
endpoint will look like this:
|
||||||
|
<programlisting>
|
||||||
|
services.prosody = {
|
||||||
|
<link linkend="opt-services.prosody.enable">enable</link> = true;
|
||||||
|
<link linkend="opt-services.prosody.admins">admins</link> = [ "root@example.org" ];
|
||||||
|
<link linkend="opt-services.prosody.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
|
||||||
|
<link linkend="opt-services.prosody.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
|
||||||
|
<link linkend="opt-services.prosody.virtualHosts">virtualHosts</link>."example.org" = {
|
||||||
|
<link linkend="opt-services.prosody.virtualHosts._name__.enabled">enabled</link> = true;
|
||||||
|
<link linkend="opt-services.prosody.virtualHosts._name__.domain">domain</link> = "example.org";
|
||||||
|
<link linkend="opt-services.prosody.virtualHosts._name__.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
|
||||||
|
<link linkend="opt-services.prosody.virtualHosts._name__.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
|
||||||
|
};
|
||||||
|
<link linkend="opt-services.prosody.muc">muc</link> = [ {
|
||||||
|
<link linkend="opt-services.prosody.muc">domain</link> = "conference.example.org";
|
||||||
|
} ];
|
||||||
|
<link linkend="opt-services.prosody.uploadHttp">uploadHttp</link> = {
|
||||||
|
<link linkend="opt-services.prosody.uploadHttp.domain">domain</link> = "upload.example.org";
|
||||||
|
};
|
||||||
|
};</programlisting>
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
<section xml:id="module-services-prosody-letsencrypt">
|
||||||
|
<title>Let's Encrypt Configuration</title>
|
||||||
|
<para>
|
||||||
|
As you can see in the code snippet from the
|
||||||
|
<link linkend="module-services-prosody-basic-usage">previous section</link>,
|
||||||
|
you'll need a single TLS certificate covering your main endpoint,
|
||||||
|
the MUC one as well as the HTTP Upload one. We can generate such a
|
||||||
|
certificate by leveraging the ACME
|
||||||
|
<link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains</link> module option.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Provided the setup detailed in the previous section, you'll need the following acme configuration to generate
|
||||||
|
a TLS certificate for the three endponits:
|
||||||
|
<programlisting>
|
||||||
|
security.acme = {
|
||||||
|
<link linkend="opt-security.acme.email">email</link> = "root@example.org";
|
||||||
|
<link linkend="opt-security.acme.acceptTerms">acceptTerms</link> = true;
|
||||||
|
<link linkend="opt-security.acme.certs">certs</link> = {
|
||||||
|
"example.org" = {
|
||||||
|
<link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/example.org";
|
||||||
|
<link linkend="opt-security.acme.certs._name_.email">email</link> = "root@example.org";
|
||||||
|
<link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."conference.example.org"</link> = null;
|
||||||
|
<link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."upload.example.org"</link> = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};</programlisting>
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
</chapter>
|
@ -1,27 +1,80 @@
|
|||||||
import ../make-test-python.nix {
|
let
|
||||||
name = "prosody";
|
cert = pkgs: pkgs.runCommandNoCC "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
|
||||||
|
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=uploads.example.com/CN=conference.example.com'
|
||||||
|
mkdir -p $out
|
||||||
|
cp key.pem cert.pem $out
|
||||||
|
'';
|
||||||
|
createUsers = pkgs: pkgs.writeScriptBin "create-prosody-users" ''
|
||||||
|
#!${pkgs.bash}/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Creates and set password for the 2 xmpp test users.
|
||||||
|
#
|
||||||
|
# Doing that in a bash script instead of doing that in the test
|
||||||
|
# script allow us to easily provision the users when running that
|
||||||
|
# test interactively.
|
||||||
|
|
||||||
|
prosodyctl register cthon98 example.com nothunter2
|
||||||
|
prosodyctl register azurediamond example.com hunter2
|
||||||
|
'';
|
||||||
|
delUsers = pkgs: pkgs.writeScriptBin "delete-prosody-users" ''
|
||||||
|
#!${pkgs.bash}/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Deletes the test users.
|
||||||
|
#
|
||||||
|
# Doing that in a bash script instead of doing that in the test
|
||||||
|
# script allow us to easily provision the users when running that
|
||||||
|
# test interactively.
|
||||||
|
|
||||||
|
prosodyctl deluser cthon98@example.com
|
||||||
|
prosodyctl deluser azurediamond@example.com
|
||||||
|
'';
|
||||||
|
in import ../make-test-python.nix {
|
||||||
|
name = "prosody";
|
||||||
nodes = {
|
nodes = {
|
||||||
client = { nodes, pkgs, ... }: {
|
client = { nodes, pkgs, config, ... }: {
|
||||||
|
security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
|
||||||
|
console.keyMap = "fr-bepo";
|
||||||
|
networking.extraHosts = ''
|
||||||
|
${nodes.server.config.networking.primaryIPAddress} example.com
|
||||||
|
${nodes.server.config.networking.primaryIPAddress} conference.example.com
|
||||||
|
${nodes.server.config.networking.primaryIPAddress} uploads.example.com
|
||||||
|
'';
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
(pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; })
|
(pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; })
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
server = { config, pkgs, ... }: {
|
server = { config, pkgs, ... }: {
|
||||||
|
security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
|
||||||
|
console.keyMap = "fr-bepo";
|
||||||
networking.extraHosts = ''
|
networking.extraHosts = ''
|
||||||
${config.networking.primaryIPAddress} example.com
|
${config.networking.primaryIPAddress} example.com
|
||||||
|
${config.networking.primaryIPAddress} conference.example.com
|
||||||
|
${config.networking.primaryIPAddress} uploads.example.com
|
||||||
'';
|
'';
|
||||||
networking.firewall.enable = false;
|
networking.firewall.enable = false;
|
||||||
|
environment.systemPackages = [
|
||||||
|
(createUsers pkgs)
|
||||||
|
(delUsers pkgs)
|
||||||
|
];
|
||||||
services.prosody = {
|
services.prosody = {
|
||||||
enable = true;
|
enable = true;
|
||||||
# TODO: use a self-signed certificate
|
ssl.cert = "${cert pkgs}/cert.pem";
|
||||||
c2sRequireEncryption = false;
|
ssl.key = "${cert pkgs}/key.pem";
|
||||||
extraConfig = ''
|
virtualHosts.example = {
|
||||||
storage = "sql"
|
|
||||||
'';
|
|
||||||
virtualHosts.test = {
|
|
||||||
domain = "example.com";
|
domain = "example.com";
|
||||||
enabled = true;
|
enabled = true;
|
||||||
|
ssl.cert = "${cert pkgs}/cert.pem";
|
||||||
|
ssl.key = "${cert pkgs}/key.pem";
|
||||||
|
};
|
||||||
|
muc = [
|
||||||
|
{
|
||||||
|
domain = "conference.example.com";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
uploadHttp = {
|
||||||
|
domain = "uploads.example.com";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -31,16 +84,8 @@ import ../make-test-python.nix {
|
|||||||
server.wait_for_unit("prosody.service")
|
server.wait_for_unit("prosody.service")
|
||||||
server.succeed('prosodyctl status | grep "Prosody is running"')
|
server.succeed('prosodyctl status | grep "Prosody is running"')
|
||||||
|
|
||||||
# set password to 'nothunter2' (it's asked twice)
|
server.succeed("create-prosody-users")
|
||||||
server.succeed("yes nothunter2 | prosodyctl adduser cthon98@example.com")
|
client.succeed('send-message 2>&1 | grep "XMPP SCRIPT TEST SUCCESS"')
|
||||||
# set password to 'y'
|
server.succeed("delete-prosody-users")
|
||||||
server.succeed("yes | prosodyctl adduser azurediamond@example.com")
|
|
||||||
# correct password to "hunter2"
|
|
||||||
server.succeed("yes hunter2 | prosodyctl passwd azurediamond@example.com")
|
|
||||||
|
|
||||||
client.succeed("send-message")
|
|
||||||
|
|
||||||
server.succeed("prosodyctl deluser cthon98@example.com")
|
|
||||||
server.succeed("prosodyctl deluser azurediamond@example.com")
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,61 @@
|
|||||||
{ writeScriptBin, python3, connectTo ? "localhost" }:
|
{ writeScriptBin, writeText, python3, connectTo ? "localhost" }:
|
||||||
writeScriptBin "send-message" ''
|
let
|
||||||
#!${(python3.withPackages (ps: [ ps.sleekxmpp ])).interpreter}
|
dummyFile = writeText "dummy-file" ''
|
||||||
# Based on the sleekxmpp send_client example, look there for more details:
|
Dear dog,
|
||||||
# https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
class SendMsgBot(sleekxmpp.ClientXMPP):
|
Please find this *really* important attachment.
|
||||||
"""
|
|
||||||
A basic SleekXMPP bot that will log in, send a message,
|
|
||||||
and then log out.
|
|
||||||
"""
|
|
||||||
def __init__(self, jid, password, recipient, message):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
self.recipient = recipient
|
Yours truly,
|
||||||
self.msg = message
|
John
|
||||||
|
'';
|
||||||
|
in writeScriptBin "send-message" ''
|
||||||
|
#!${(python3.withPackages (ps: [ ps.slixmpp ])).interpreter}
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from types import MethodType
|
||||||
|
|
||||||
self.add_event_handler("session_start", self.start, threaded=True)
|
from slixmpp import ClientXMPP
|
||||||
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
def start(self, event):
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
self.send_message(mto=self.recipient,
|
|
||||||
mbody=self.msg,
|
|
||||||
mtype='chat')
|
|
||||||
|
|
||||||
self.disconnect(wait=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
class CthonTest(ClientXMPP):
|
||||||
xmpp = SendMsgBot("cthon98@example.com", "nothunter2", "azurediamond@example.com", "hey, if you type in your pw, it will show as stars")
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# TODO: verify certificate
|
def __init__(self, jid, password):
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
ClientXMPP.__init__(self, jid, password)
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
self.add_event_handler("session_start", self.session_start)
|
||||||
|
|
||||||
if xmpp.connect(('${connectTo}', 5222)):
|
async def session_start(self, event):
|
||||||
xmpp.process(block=True)
|
log = logging.getLogger(__name__)
|
||||||
else:
|
self.send_presence()
|
||||||
print("Unable to connect.")
|
self.get_roster()
|
||||||
sys.exit(1)
|
# Sending a test message
|
||||||
|
self.send_message(mto="azurediamond@example.com", mbody="Hello, this is dog.", mtype="chat")
|
||||||
|
log.info('Message sent')
|
||||||
|
|
||||||
|
# Test http upload (XEP_0363)
|
||||||
|
def timeout_callback(arg):
|
||||||
|
log.error("ERROR: Cannot upload file. XEP_0363 seems broken")
|
||||||
|
sys.exit(1)
|
||||||
|
url = await self['xep_0363'].upload_file("${dummyFile}",timeout=10, timeout_callback=timeout_callback)
|
||||||
|
log.info('Upload success!')
|
||||||
|
# Test MUC
|
||||||
|
self.plugin['xep_0045'].join_muc('testMucRoom', 'cthon98', wait=True)
|
||||||
|
log.info('MUC join success!')
|
||||||
|
log.info('XMPP SCRIPT TEST SUCCESS')
|
||||||
|
self.disconnect(wait=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
ct = CthonTest('cthon98@example.com', 'nothunter2')
|
||||||
|
ct.register_plugin('xep_0071')
|
||||||
|
ct.register_plugin('xep_0128')
|
||||||
|
# HTTP Upload
|
||||||
|
ct.register_plugin('xep_0363')
|
||||||
|
# MUC
|
||||||
|
ct.register_plugin('xep_0045')
|
||||||
|
ct.connect(("server", 5222))
|
||||||
|
ct.process(forever=False)
|
||||||
''
|
''
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{ stdenv, fetchurl, libidn, openssl, makeWrapper, fetchhg
|
{ stdenv, fetchurl, lib, libidn, openssl, makeWrapper, fetchhg
|
||||||
, lua5, luasocket, luasec, luaexpat, luafilesystem, luabitop
|
, lua5, luasocket, luasec, luaexpat, luafilesystem, luabitop
|
||||||
, withLibevent ? true, luaevent ? null
|
, withLibevent ? true, luaevent ? null
|
||||||
, withDBI ? true, luadbi ? null
|
, withDBI ? true, luadbi ? null
|
||||||
@ -16,7 +16,16 @@ with stdenv.lib;
|
|||||||
stdenv.mkDerivation rec {
|
stdenv.mkDerivation rec {
|
||||||
version = "0.11.5"; # also update communityModules
|
version = "0.11.5"; # also update communityModules
|
||||||
pname = "prosody";
|
pname = "prosody";
|
||||||
|
# The following community modules are necessary for the nixos module
|
||||||
|
# prosody module to comply with XEP-0423 and provide a working
|
||||||
|
# default setup.
|
||||||
|
nixosModuleDeps = [
|
||||||
|
"bookmarks"
|
||||||
|
"cloud_notify"
|
||||||
|
"vcard_muc"
|
||||||
|
"smacks"
|
||||||
|
"http_upload"
|
||||||
|
];
|
||||||
src = fetchurl {
|
src = fetchurl {
|
||||||
url = "https://prosody.im/downloads/source/${pname}-${version}.tar.gz";
|
url = "https://prosody.im/downloads/source/${pname}-${version}.tar.gz";
|
||||||
sha256 = "12s0hn6hvjbi61cdw3165l6iw0878971dmlvfg663byjsmjvvy2m";
|
sha256 = "12s0hn6hvjbi61cdw3165l6iw0878971dmlvfg663byjsmjvvy2m";
|
||||||
@ -52,7 +61,7 @@ stdenv.mkDerivation rec {
|
|||||||
postInstall = ''
|
postInstall = ''
|
||||||
${concatMapStringsSep "\n" (module: ''
|
${concatMapStringsSep "\n" (module: ''
|
||||||
cp -r $communityModules/mod_${module} $out/lib/prosody/modules/
|
cp -r $communityModules/mod_${module} $out/lib/prosody/modules/
|
||||||
'') (withCommunityModules ++ withOnlyInstalledCommunityModules)}
|
'') (lib.lists.unique(nixosModuleDeps ++ withCommunityModules ++ withOnlyInstalledCommunityModules))}
|
||||||
wrapProgram $out/bin/prosody \
|
wrapProgram $out/bin/prosody \
|
||||||
--prefix LUA_PATH ';' "$LUA_PATH" \
|
--prefix LUA_PATH ';' "$LUA_PATH" \
|
||||||
--prefix LUA_CPATH ';' "$LUA_CPATH"
|
--prefix LUA_CPATH ';' "$LUA_CPATH"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user