init
This commit is contained in:
commit
883f31932e
169 changed files with 5676 additions and 0 deletions
10
ansible/roles/prosody/defaults/main.yml
Normal file
10
ansible/roles/prosody/defaults/main.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
install_dir: /opt/prosody
|
||||
bind_address: "0.0.0.0"
|
||||
bosh_port: 5280
|
||||
c2s_port: 5222
|
||||
s2s_port: 5269
|
||||
proxy65_port: 5000
|
||||
proxy65_address: "{{ bind_address }}"
|
||||
http_upload_file_size_limit: 10485760
|
||||
http_upload_expire_after: 604800
|
||||
153
ansible/roles/prosody/files/mod_auth_oauth_external.lua
Normal file
153
ansible/roles/prosody/files/mod_auth_oauth_external.lua
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
-- Based on https://hg.prosody.im/prosody-modules/file/tip/mod_auth_oauth_external/mod_auth_oauth_external.lua
|
||||
-- Patched to store email from userinfo in accounts store for mod_offline_email
|
||||
local http = require "net.http";
|
||||
local async = require "util.async";
|
||||
local jid = require "util.jid";
|
||||
local json = require "util.json";
|
||||
local sasl = require "util.sasl";
|
||||
|
||||
local issuer_identity = module:get_option_string("oauth_external_issuer");
|
||||
local oidc_discovery_url = module:get_option_string("oauth_external_discovery_url",
|
||||
issuer_identity and issuer_identity .. "/.well-known/oauth-authorization-server" or nil);
|
||||
local validation_endpoint = module:get_option_string("oauth_external_validation_endpoint");
|
||||
local token_endpoint = module:get_option_string("oauth_external_token_endpoint");
|
||||
|
||||
local username_field = module:get_option_string("oauth_external_username_field", "preferred_username");
|
||||
local allow_plain = module:get_option_boolean("oauth_external_resource_owner_password", true);
|
||||
|
||||
local client_id = module:get_option_string("oauth_external_client_id");
|
||||
local client_secret = module:get_option_string("oauth_external_client_secret");
|
||||
local scope = module:get_option_string("oauth_external_scope", "openid");
|
||||
|
||||
local accounts = module:open_store("accounts");
|
||||
|
||||
local host = module.host;
|
||||
local provider = {};
|
||||
|
||||
local function not_implemented()
|
||||
return nil, "method not implemented"
|
||||
end
|
||||
|
||||
provider.test_password = not_implemented;
|
||||
provider.get_password = not_implemented;
|
||||
provider.set_password = not_implemented;
|
||||
provider.create_user = not_implemented;
|
||||
|
||||
function provider.delete_user(username)
|
||||
return accounts:set(username, nil);
|
||||
end
|
||||
|
||||
function provider.get_account_info(username)
|
||||
local account, err = accounts:get(username);
|
||||
if not account then return nil, err or "Account not available"; end
|
||||
return {
|
||||
created = account.created;
|
||||
password_updated = account.updated;
|
||||
enabled = not account.disabled;
|
||||
};
|
||||
end
|
||||
|
||||
function provider.user_exists(username)
|
||||
local account, err = accounts:get(username);
|
||||
if err then
|
||||
return nil, err;
|
||||
elseif account then
|
||||
return true;
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function provider.users()
|
||||
return accounts:users();
|
||||
end
|
||||
|
||||
local function save_account(username, response)
|
||||
local account_data = { exists = true };
|
||||
if type(response) == "table" and type(response.email) == "string" then
|
||||
account_data.email = response.email;
|
||||
end
|
||||
accounts:set(username, account_data);
|
||||
end
|
||||
|
||||
function provider.get_sasl_handler()
|
||||
local profile = {};
|
||||
profile.http_client = http.default:new({ connection_pooling = true });
|
||||
local extra = { oidc_discovery_url = oidc_discovery_url };
|
||||
if token_endpoint and allow_plain then
|
||||
local map_username = function (username, _realm) return username; end;
|
||||
function profile:plain_test(username, password, realm)
|
||||
username = jid.unescape(username);
|
||||
local tok, err = async.wait_for(self.profile.http_client:request(token_endpoint, {
|
||||
headers = { ["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"; ["Accept"] = "application/json" };
|
||||
body = http.formencode({
|
||||
grant_type = "password";
|
||||
client_id = client_id;
|
||||
client_secret = client_secret;
|
||||
username = map_username(username, realm);
|
||||
password = password;
|
||||
scope = scope;
|
||||
});
|
||||
}))
|
||||
if err or not (tok.code >= 200 and tok.code < 300) then
|
||||
return false, nil;
|
||||
end
|
||||
local token_resp = json.decode(tok.body);
|
||||
if not token_resp or string.lower(token_resp.token_type or "") ~= "bearer" then
|
||||
return false, nil;
|
||||
end
|
||||
if not validation_endpoint then
|
||||
self.username = jid.escape(username);
|
||||
self.token_info = token_resp;
|
||||
save_account(self.username, nil);
|
||||
return true, true;
|
||||
end
|
||||
local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint,
|
||||
{ headers = { ["Authorization"] = "Bearer " .. token_resp.access_token; ["Accept"] = "application/json" } }));
|
||||
if err then
|
||||
return false, nil;
|
||||
end
|
||||
if not (ret.code >= 200 and ret.code < 300) then
|
||||
return false, nil;
|
||||
end
|
||||
local response = json.decode(ret.body);
|
||||
if type(response) ~= "table" then
|
||||
return false, nil, nil;
|
||||
elseif type(response[username_field]) ~= "string" then
|
||||
return false, nil, nil;
|
||||
end
|
||||
self.username = jid.escape(response[username_field]);
|
||||
self.token_info = response;
|
||||
save_account(self.username, response);
|
||||
return true, true;
|
||||
end
|
||||
end
|
||||
if validation_endpoint then
|
||||
function profile:oauthbearer(token)
|
||||
if token == "" then
|
||||
return false, nil, extra;
|
||||
end
|
||||
|
||||
local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, {
|
||||
headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" };
|
||||
}));
|
||||
if err then
|
||||
return false, nil, extra;
|
||||
end
|
||||
local response = ret and json.decode(ret.body);
|
||||
if not (ret.code >= 200 and ret.code < 300) then
|
||||
return false, nil, response or extra;
|
||||
end
|
||||
if type(response) ~= "table" or type(response[username_field]) ~= "string" then
|
||||
return false, nil, nil;
|
||||
end
|
||||
|
||||
local username = jid.escape(response[username_field]);
|
||||
save_account(username, response);
|
||||
return username, true, response;
|
||||
end
|
||||
end
|
||||
return sasl.new(host, profile);
|
||||
end
|
||||
|
||||
module:provides("auth", provider);
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
-- Strip PLAIN from SASL mechanisms for BOSH and WebSocket connections.
|
||||
-- Direct c2s connections keep PLAIN for native XMPP client auth.
|
||||
|
||||
module:hook("stream-features", function(event)
|
||||
local origin, features = event.origin, event.features;
|
||||
|
||||
local is_bosh = origin.bosh_version ~= nil;
|
||||
local is_websocket = origin.websocket_request ~= nil;
|
||||
|
||||
if not (is_bosh or is_websocket) then
|
||||
return;
|
||||
end
|
||||
|
||||
for i, child in ipairs(features.tags) do
|
||||
if child.name == "mechanisms" then
|
||||
local dominated = {};
|
||||
for j, mech in ipairs(child.tags) do
|
||||
if mech:get_text() == "PLAIN" then
|
||||
table.insert(dominated, j);
|
||||
end
|
||||
end
|
||||
for k = #dominated, 1, -1 do
|
||||
local idx = dominated[k];
|
||||
table.remove(child.tags, idx);
|
||||
for m = #child, 1, -1 do
|
||||
if type(child[m]) == "table" and child[m].name == "mechanism" and child[m]:get_text() == "PLAIN" then
|
||||
table.remove(child, m);
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
break;
|
||||
end
|
||||
end
|
||||
end, -1);
|
||||
16
ansible/roles/prosody/files/mod_session_timeout.lua
Normal file
16
ansible/roles/prosody/files/mod_session_timeout.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- Disconnect c2s sessions after a configurable timeout to force re-authentication.
|
||||
-- This ensures that expired credentials (e.g. app passwords) are caught promptly.
|
||||
|
||||
local timeout = module:get_option_number("session_timeout", 1800); -- default 30 minutes
|
||||
|
||||
module:hook("resource-bind", function(event)
|
||||
local session = event.session;
|
||||
if not session then return; end
|
||||
|
||||
session._timeout_timer = module:add_timer(timeout, function()
|
||||
if session.type == "c2s" and not session.destroyed then
|
||||
module:log("info", "Session timeout for %s, forcing re-authentication", session.full_jid);
|
||||
session:close({ condition = "policy-violation", text = "Session expired, please reconnect" });
|
||||
end
|
||||
end);
|
||||
end);
|
||||
4
ansible/roles/prosody/meta/main.yml
Normal file
4
ansible/roles/prosody/meta/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
84
ansible/roles/prosody/tasks/deploy-prosody.yml
Normal file
84
ansible/roles/prosody/tasks/deploy-prosody.yml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Ensure modules directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/modules"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
when: oauth_client_id is defined or default_contacts is defined or smtp_host is defined or session_timeout is defined
|
||||
|
||||
-
|
||||
name: Deploy mod_auth_oauth_external
|
||||
copy:
|
||||
src: mod_auth_oauth_external.lua
|
||||
dest: "{{ install_dir }}/modules/mod_auth_oauth_external.lua"
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Deploy mod_sasl_oauthbearer_only_bosh
|
||||
copy:
|
||||
src: mod_sasl_oauthbearer_only_bosh.lua
|
||||
dest: "{{ install_dir }}/modules/mod_sasl_oauthbearer_only_bosh.lua"
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Deploy mod_default_contacts
|
||||
template:
|
||||
src: mod_default_contacts.lua.j2
|
||||
dest: "{{ install_dir }}/modules/mod_default_contacts.lua"
|
||||
when: default_contacts is defined
|
||||
|
||||
-
|
||||
name: Deploy mod_session_timeout
|
||||
copy:
|
||||
src: mod_session_timeout.lua
|
||||
dest: "{{ install_dir }}/modules/mod_session_timeout.lua"
|
||||
when: session_timeout is defined
|
||||
|
||||
-
|
||||
name: Deploy mod_offline_email
|
||||
template:
|
||||
src: mod_offline_email.lua.j2
|
||||
dest: "{{ install_dir }}/modules/mod_offline_email.lua"
|
||||
when: smtp_host is defined
|
||||
|
||||
-
|
||||
name: Deploy prosody configuration
|
||||
template:
|
||||
src: prosody.cfg.lua.j2
|
||||
dest: "{{ install_dir }}/prosody.cfg.lua"
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start prosody stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Deploy prosody backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: prosody
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- prosody_prosody_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ install_dir }}/prosody.cfg.lua"
|
||||
4
ansible/roles/prosody/tasks/main.yml
Normal file
4
ansible/roles/prosody/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy prosody
|
||||
ansible.builtin.include_tasks: deploy-prosody.yml
|
||||
36
ansible/roles/prosody/tasks/restore.yml
Normal file
36
ansible/roles/prosody/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_prosody_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/prosody"
|
||||
|
||||
-
|
||||
name: Stop prosody stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore config files
|
||||
copy:
|
||||
src: "{{ _prosody_backup_dir }}/{{ item }}"
|
||||
dest: "{{ install_dir }}/{{ item }}"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
loop:
|
||||
- docker-compose.yml
|
||||
- prosody.cfg.lua
|
||||
|
||||
-
|
||||
name: Restore prosody data volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v prosody_prosody_data:/data
|
||||
-v {{ _prosody_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/prosody_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start prosody stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
18
ansible/roles/prosody/templates/docker-compose.yml.j2
Normal file
18
ansible/roles/prosody/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
services:
|
||||
prosody:
|
||||
image: prosodyim/prosody:{{ version }}
|
||||
network_mode: host
|
||||
volumes:
|
||||
- prosody_data:/var/lib/prosody
|
||||
- ./prosody.cfg.lua:/etc/prosody/prosody.cfg.lua:ro
|
||||
{% if oauth_client_id is defined or default_contacts is defined %}
|
||||
- ./modules:/usr/lib/prosody/custom-modules:ro
|
||||
{% endif %}
|
||||
{% if ssl_cert is defined %}
|
||||
- {{ ssl_cert }}:/etc/prosody/certs/fullchain.pem:ro
|
||||
- {{ ssl_key }}:/etc/prosody/certs/privkey.pem:ro
|
||||
{% endif %}
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
prosody_data:
|
||||
40
ansible/roles/prosody/templates/mod_default_contacts.lua.j2
Normal file
40
ansible/roles/prosody/templates/mod_default_contacts.lua.j2
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local rostermanager = require "core.rostermanager";
|
||||
local host = module.host;
|
||||
|
||||
local default_contacts = module:get_option("default_contacts", {});
|
||||
|
||||
module:hook("resource-bind", function(event)
|
||||
local user = event.session.username;
|
||||
local user_jid = user .. "@" .. host;
|
||||
local roster = rostermanager.load_roster(user, host);
|
||||
|
||||
for _, contact in ipairs(default_contacts) do
|
||||
local contact_user = contact.jid:match("^([^@]+)@");
|
||||
if contact_user ~= user and not roster[contact.jid] then
|
||||
-- Add contact to this user's roster
|
||||
local groups = {};
|
||||
for _, gname in ipairs(contact.groups or {}) do
|
||||
groups[gname] = true;
|
||||
end
|
||||
roster[contact.jid] = {
|
||||
subscription = "both";
|
||||
name = contact.name;
|
||||
groups = groups;
|
||||
};
|
||||
rostermanager.save_roster(user, host);
|
||||
rostermanager.roster_push(user, host, contact.jid);
|
||||
|
||||
-- Add this user to the contact's roster (for bidirectional presence)
|
||||
local contact_roster = rostermanager.load_roster(contact_user, host);
|
||||
if contact_roster and not contact_roster[user_jid] then
|
||||
contact_roster[user_jid] = {
|
||||
subscription = "both";
|
||||
name = user;
|
||||
groups = {};
|
||||
};
|
||||
rostermanager.save_roster(contact_user, host);
|
||||
rostermanager.roster_push(contact_user, host, user_jid);
|
||||
end
|
||||
end
|
||||
end
|
||||
end);
|
||||
144
ansible/roles/prosody/templates/mod_offline_email.lua.j2
Normal file
144
ansible/roles/prosody/templates/mod_offline_email.lua.j2
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
local socket = require "socket";
|
||||
local ssl = require "ssl";
|
||||
local b64 = require "prosody.util.encodings".base64.encode;
|
||||
|
||||
local smtp_server = module:get_option_string("offline_email_smtp_server");
|
||||
local smtp_port = module:get_option_number("offline_email_smtp_port", 587);
|
||||
local smtp_username = module:get_option_string("offline_email_smtp_username");
|
||||
local smtp_password = module:get_option_string("offline_email_smtp_password");
|
||||
local smtp_from = module:get_option_string("offline_email_smtp_from", smtp_username);
|
||||
|
||||
local accounts = module:open_store("accounts");
|
||||
|
||||
local function read_response(sock)
|
||||
local lines = {};
|
||||
while true do
|
||||
local line, err = sock:receive("*l");
|
||||
if not line then return nil, err; end
|
||||
table.insert(lines, line);
|
||||
if line:sub(4, 4) == " " then break; end
|
||||
end
|
||||
local code = tonumber(lines[1]:sub(1, 3));
|
||||
return code, table.concat(lines, "\n");
|
||||
end
|
||||
|
||||
local function send_command(sock, cmd)
|
||||
sock:send(cmd .. "\r\n");
|
||||
return read_response(sock);
|
||||
end
|
||||
|
||||
local function send_email(to_email, subject, body_text)
|
||||
local sock = socket.tcp();
|
||||
sock:settimeout(10);
|
||||
|
||||
local ok, err = sock:connect(smtp_server, smtp_port);
|
||||
if not ok then return nil, "connect: " .. tostring(err); end
|
||||
|
||||
local code, resp = read_response(sock);
|
||||
if not code or code ~= 220 then
|
||||
sock:close();
|
||||
return nil, "greeting: " .. tostring(resp);
|
||||
end
|
||||
|
||||
code = send_command(sock, "EHLO localhost");
|
||||
if code ~= 250 then sock:close(); return nil, "EHLO failed"; end
|
||||
|
||||
code = send_command(sock, "STARTTLS");
|
||||
if code ~= 220 then sock:close(); return nil, "STARTTLS rejected"; end
|
||||
|
||||
sock = ssl.wrap(sock, { mode = "client", protocol = "any", verify = "none" });
|
||||
ok = sock:dohandshake();
|
||||
if not ok then sock:close(); return nil, "TLS handshake failed"; end
|
||||
|
||||
code = send_command(sock, "EHLO localhost");
|
||||
if code ~= 250 then sock:close(); return nil, "EHLO after TLS failed"; end
|
||||
|
||||
local auth_str = b64("\0" .. smtp_username .. "\0" .. smtp_password);
|
||||
code = send_command(sock, "AUTH PLAIN " .. auth_str);
|
||||
if code ~= 235 then sock:close(); return nil, "AUTH failed"; end
|
||||
|
||||
code = send_command(sock, "MAIL FROM:<" .. smtp_from .. ">");
|
||||
if code ~= 250 then sock:close(); return nil, "MAIL FROM failed"; end
|
||||
|
||||
code = send_command(sock, "RCPT TO:<" .. to_email .. ">");
|
||||
if code ~= 250 then sock:close(); return nil, "RCPT TO failed"; end
|
||||
|
||||
code = send_command(sock, "DATA");
|
||||
if code ~= 354 then sock:close(); return nil, "DATA failed"; end
|
||||
|
||||
local message = "From: " .. smtp_from .. "\r\n" ..
|
||||
"To: " .. to_email .. "\r\n" ..
|
||||
"Subject: " .. subject .. "\r\n" ..
|
||||
"Content-Type: text/plain; charset=UTF-8\r\n" ..
|
||||
"\r\n" ..
|
||||
body_text .. "\r\n.\r\n";
|
||||
sock:send(message);
|
||||
code = read_response(sock);
|
||||
if code ~= 250 then sock:close(); return nil, "message rejected"; end
|
||||
|
||||
send_command(sock, "QUIT");
|
||||
sock:close();
|
||||
return true;
|
||||
end
|
||||
|
||||
local function notify_offline(to_user, stanza)
|
||||
local body = stanza:get_child_text("body");
|
||||
local is_encrypted = stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") ~= nil;
|
||||
|
||||
if not is_encrypted and (not body or body == "") then return; end
|
||||
|
||||
local account = accounts:get(to_user);
|
||||
if not account or not account.email then return; end
|
||||
|
||||
local from_jid = stanza.attr.from or "unknown";
|
||||
local from_name = from_jid:match("^([^@]+)") or from_jid;
|
||||
local subject = "New message from " .. from_name;
|
||||
local email_body;
|
||||
if is_encrypted then
|
||||
email_body = "Ahoy,\r\n\r\n" ..
|
||||
"while offline, you've received an encrypted message from " .. from_name .. ".\r\n\r\n" ..
|
||||
"This message was sent using end-to-end encryption (OMEMO).\r\n" ..
|
||||
"To view it, log in from the same device where your encryption keys are stored. " ..
|
||||
"The message cannot be read on a different device or client.\r\n\r\n" ..
|
||||
"Kind regards,\r\n\r\n" ..
|
||||
"Tiara";
|
||||
else
|
||||
email_body = "Ahoy,\r\n\r\n" ..
|
||||
"while offline, you received a plain-text message from " .. from_name .. ":\r\n\r\n" ..
|
||||
">> " .. body .. "\r\n\r\n" ..
|
||||
"Kind regards,\r\n\r\n" ..
|
||||
"Tiara";
|
||||
end
|
||||
|
||||
local ok, err = send_email(account.email, subject, email_body);
|
||||
if ok then
|
||||
module:log("info", "Sent offline email notification to %s for user %s", account.email, to_user);
|
||||
else
|
||||
module:log("warn", "Failed to send offline email to %s: %s", account.email, tostring(err));
|
||||
end
|
||||
end
|
||||
|
||||
local bare_sessions = prosody.bare_sessions;
|
||||
local jid = require "util.jid";
|
||||
|
||||
local function has_hibernating_session(username)
|
||||
local bare = username .. "@" .. module.host;
|
||||
local user = bare_sessions[bare];
|
||||
if not user then return false; end
|
||||
for _, session in pairs(user.sessions) do
|
||||
if session.hibernating then return true; end
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
-- Fired when user is offline (no sessions) or when smacks gives up on a hibernating session
|
||||
module:hook("message/offline/handle", function(event)
|
||||
local to_user = event.username or (event.stanza.attr.to and event.stanza.attr.to:match("^([^@]+)"));
|
||||
if not to_user then return; end
|
||||
-- Skip if smacks is still hibernating — we'll get called again when it expires
|
||||
if has_hibernating_session(to_user) then
|
||||
module:log("debug", "Skipping email for %s — smacks session still hibernating", to_user);
|
||||
return;
|
||||
end
|
||||
notify_offline(to_user, event.stanza);
|
||||
end, 1);
|
||||
111
ansible/roles/prosody/templates/prosody.cfg.lua.j2
Normal file
111
ansible/roles/prosody/templates/prosody.cfg.lua.j2
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
admins = { "{{ admin_jid }}" }
|
||||
|
||||
{% if oauth_client_id is defined or default_contacts is defined or smtp_host is defined or session_timeout is defined %}
|
||||
plugin_paths = { "/usr/lib/prosody/custom-modules" }
|
||||
{% endif %}
|
||||
|
||||
modules_enabled = {
|
||||
"roster";
|
||||
"saslauth";
|
||||
"tls";
|
||||
"dialback";
|
||||
"disco";
|
||||
"carbons";
|
||||
"pep";
|
||||
"private";
|
||||
"blocklist";
|
||||
"vcard4";
|
||||
"vcard_legacy";
|
||||
"version";
|
||||
"uptime";
|
||||
"time";
|
||||
"ping";
|
||||
"register";
|
||||
"admin_adhoc";
|
||||
"bosh";
|
||||
"websocket";
|
||||
"smacks";
|
||||
"csi_simple";
|
||||
"mam";
|
||||
{% if oauth_client_id is defined %}
|
||||
"sasl_oauthbearer_only_bosh";
|
||||
{% endif %}
|
||||
{% if session_timeout is defined %}
|
||||
"session_timeout";
|
||||
{% endif %}
|
||||
{% if smtp_host is defined %}
|
||||
"offline";
|
||||
"offline_email";
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
allow_registration = false
|
||||
|
||||
http_ports = { 5280 }
|
||||
http_interfaces = { "127.0.0.1" }
|
||||
|
||||
https_ports = {}
|
||||
|
||||
proxy65_ports = { {{ proxy65_port }} }
|
||||
|
||||
consider_bosh_secure = true
|
||||
consider_websocket_secure = true
|
||||
|
||||
VirtualHost "{{ domain }}"
|
||||
{% if oauth_client_id is defined %}
|
||||
authentication = "oauth_external"
|
||||
oauth_external_validation_endpoint = "{{ oauth_userinfo_url }}"
|
||||
oauth_external_username_field = "preferred_username"
|
||||
oauth_external_client_id = "{{ oauth_ropc_client_id | default(oauth_client_id) }}"
|
||||
{% if oauth_ropc_client_secret is defined %}
|
||||
oauth_external_client_secret = "{{ oauth_ropc_client_secret }}"
|
||||
oauth_external_token_endpoint = "{{ oauth_token_url }}"
|
||||
oauth_external_resource_owner_password = true
|
||||
oauth_external_scope = "openid profile email"
|
||||
{% endif %}
|
||||
{% else %}
|
||||
authentication = "internal_hashed"
|
||||
{% endif %}
|
||||
{% if session_timeout is defined %}
|
||||
session_timeout = {{ session_timeout }}
|
||||
{% endif %}
|
||||
{% if smtp_host is defined %}
|
||||
offline_email_smtp_server = "{{ smtp_host }}"
|
||||
offline_email_smtp_port = {{ smtp_port | default(587) }}
|
||||
offline_email_smtp_username = "{{ smtp_username }}"
|
||||
offline_email_smtp_password = "{{ smtp_password }}"
|
||||
offline_email_smtp_from = "{{ smtp_from | default(smtp_username) }}"
|
||||
{% endif %}
|
||||
{% if default_contacts is defined %}
|
||||
modules_enabled = { "default_contacts" }
|
||||
default_contacts = {
|
||||
{% for contact in default_contacts %}
|
||||
{ jid = "{{ contact.jid }}"; name = "{{ contact.name }}"; groups = { "{{ contact.group | default('Contacts') }}" } };
|
||||
{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if ssl_cert is defined %}
|
||||
ssl = {
|
||||
certificate = "/etc/prosody/certs/fullchain.pem";
|
||||
key = "/etc/prosody/certs/privkey.pem";
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
Component "conference.{{ domain }}" "muc"
|
||||
modules_enabled = { "muc_mam" }
|
||||
restrict_room_creation = true
|
||||
muc_room_default_public = false
|
||||
muc_room_default_members_only = true
|
||||
muc_room_default_change_subject = true
|
||||
muc_room_default_history_length = 50
|
||||
muc_room_locking = false
|
||||
|
||||
Component "upload.{{ domain }}" "http_file_share"
|
||||
http_file_share_size_limit = {{ http_upload_file_size_limit }}
|
||||
http_file_share_expires_after = {{ http_upload_expire_after }}
|
||||
http_host = "upload.{{ domain }}"
|
||||
http_external_url = "https://upload.{{ domain }}"
|
||||
|
||||
Component "proxy.{{ domain }}" "proxy65"
|
||||
proxy65_address = "{{ proxy65_address }}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue