summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Fennell <matthew@fennell.dev>2026-11-12 19:24:34 +0000
committerMatthew Fennell <matthew@fennell.dev>2026-11-12 19:24:34 +0000
commitfb49c9e2bc80598a8e53b8d477c795a396fadb79 (patch)
tree29dd9f2cad5379ef19489ed89c3189bcd43d4bec
parent456fbc037b0ba473e20a8ca2e3223405d4e8cb11 (diff)
Use xmpp-server role from ansible-roles repoHEADmaster
Now that the role has been migrated, we can remove the manual steps and just use it here.
-rw-r--r--files/nginx_conf.j259
-rw-r--r--files/prosody.cfg.lua.j2165
-rw-r--r--files/turnserver.conf.j212
-rw-r--r--files/virtual_host.cfg.lua.j2107
-rw-r--r--playbook.yaml426
5 files changed, 1 insertions, 768 deletions
diff --git a/files/nginx_conf.j2 b/files/nginx_conf.j2
deleted file mode 100644
index 29cd59d..0000000
--- a/files/nginx_conf.j2
+++ /dev/null
@@ -1,59 +0,0 @@
-# SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
-#
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-server {
- listen 443 ssl;
- listen [::]:443 ssl;
-
- ssl_certificate /etc/prosody/certs/{{ virtual_host }}.crt;
- ssl_certificate_key /etc/prosody/certs/{{ virtual_host }}.key;
-
- server_name chat.{{ virtual_host }};
-
- location / {
- proxy_pass https://localhost:5281;
- proxy_set_header Host "chat.{{ virtual_host }}";
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_buffering off;
- tcp_nodelay on;
- }
-}
-
-server {
- listen 80;
- listen [::]:80;
-
- server_name chat.{{ virtual_host }};
-
- return 301 https://$host$request_uri;
-}
-
-server {
- listen 443 ssl;
- listen [::]:443 ssl;
-
- ssl_certificate /etc/prosody/certs/{{ virtual_host }}.crt;
- ssl_certificate_key /etc/prosody/certs/{{ virtual_host }}.key;
-
- server_name {{ virtual_host }};
-
- location / {
- proxy_pass https://localhost:5281;
- proxy_set_header Host "{{ virtual_host }}";
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_buffering off;
- tcp_nodelay on;
- }
-}
-
-server {
- listen 80;
- listen [::]:80;
-
- server_name {{ virtual_host }};
-
- return 301 https://$host$request_uri;
-}
diff --git a/files/prosody.cfg.lua.j2 b/files/prosody.cfg.lua.j2
deleted file mode 100644
index 4b79a82..0000000
--- a/files/prosody.cfg.lua.j2
+++ /dev/null
@@ -1,165 +0,0 @@
--- SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
---
--- SPDX-License-Identifier: AGPL-3.0-or-later
-
-plugin_paths = {
- "/usr/local/lib/prosody/modules"
-}
-
-modules_enabled = {
-
- -- Core
- "account_activity";
- "admin_adhoc";
- "admin_shell";
- "blocklist";
- "bookmarks";
- "bosh";
- "carbons";
- "cloud_notify";
- "csi_battery_saver";
- "disco";
- "external_services";
- "lastactivity";
- "lastlog2";
- "limits";
- "mam";
- "measure_active_users";
- "mimicking";
- "pep";
- "ping";
- "private";
- "roster";
- "sasl2";
- "sasl2_bind2";
- "sasl2_fast";
- "sasl2_sm";
- "sasl_ssdp";
- "saslauth";
- "server_contact_info";
- "server_info";
- "smacks";
- "stanza_debug";
- "time";
- "tls";
- "uptime";
- "vcard4";
- "vcard_legacy";
- "version";
- "websocket";
-
-{% if not is_transport_server %}
- -- Invites
- "http";
- "http_libjs";
-{% endif %}
-
-{% if not is_transport_server %}
- -- Turn
- "turn_external";
-{% endif %}
-
-{% if not is_transport_server %}
- -- Spam
- "anti_spam";
- "report_forward";
- "spam_reporting";
- "watch_spam_reports";
-{% endif %}
-
-{% if not is_transport_server %}
- -- S2S
- "s2s_auth_dane_in";
- "s2s_bidi";
-{% endif %}
-
-{% if is_transport_server %}
- -- Privileged components
- "privilege";
-{% endif %}
-
-}
-
-modules_disabled = {
-
-{% if is_transport_server %}
- "s2s";
-{% endif %}
-
-}
-
-pidfile = "/run/prosody/prosody.pid";
-
-log = {
- info = "/var/log/prosody/prosody.log";
- error = "/var/log/prosody/prosody.err";
- {
- levels = {
- "error"
- };
- to = "syslog";
- };
-}
-
-{% if not is_transport_server %}
-s2s_secure_auth = true
-{% endif %}
-
-c2s_direct_tls_ports = {
- 5223
-}
-
-{% if not is_transport_server %}
-s2s_direct_tls_ports = {
- 5270
-}
-use_dane = true
-{% endif %}
-
-{% if not is_transport_server %}
-anti_spam_services = {
- "xmppbl.org"
-}
-report_forward_to = {
- "submit@reports.xmppbl.org";
-}
-{% endif %}
-
-{% if not is_transport_server %}
-turn_external_host = "{{ turn_server }}"
-turn_external_secret = "{{ turn_secret }}"
-{% endif %}
-
--- From Monal considerations for XMPP server admins
-smacks_hibernation_time = 86400
-
-{% if not is_transport_server %}
-allow_registration = true
-registration_invite_only = true
-{% endif %}
-
-{% if not is_transport_server %}
-firewall_scripts = {
- "module:scripts/spam-blocking.pfw";
- "module:script/spam-blocklists.pfw";
-}
-{% endif %}
-
-limits = {
- c2s = {
- rate = "10kb/s";
- };
- s2sin = {
- rate = "30kb/s";
- };
-}
-
-storage = "sql"
-sql = {
- driver = "PostgreSQL";
- database = "prosody";
- username = "prosody";
- password = "{{ postgres_password }}";
-}
-
-Include "conf.d/*.cfg.lua"
diff --git a/files/turnserver.conf.j2 b/files/turnserver.conf.j2
deleted file mode 100644
index de158d2..0000000
--- a/files/turnserver.conf.j2
+++ /dev/null
@@ -1,12 +0,0 @@
-# SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
-#
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-syslog
-no-rfc5780
-no-stun-backward-compatibility
-response-origin-only-with-rfc5780
-realm={{ turn_server }}
-use-auth-secret
-static-auth-secret={{ turn_secret }}
-external-ip={{ ipv4 }}
diff --git a/files/virtual_host.cfg.lua.j2 b/files/virtual_host.cfg.lua.j2
deleted file mode 100644
index 389e98e..0000000
--- a/files/virtual_host.cfg.lua.j2
+++ /dev/null
@@ -1,107 +0,0 @@
--- SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
---
--- SPDX-License-Identifier: AGPL-3.0-or-later
-
-VirtualHost "{{ virtual_host }}"
-
-admins = {
-{% for admin in admins %}
- "{{ admin }}"
-{% endfor %}
-}
-
-modules_enabled = {
-
- -- Core
- "groups";
-
-{% if not is_transport_server %}
- -- Invites
- "invites";
- "invites_adhoc";
- "invites_api";
- "invites_page";
- "invites_register";
- "invites_register_web";
- "invites_tracking";
- "register";
- "register_apps";
-{% endif %}
-
-}
-
-{% if not is_transport_server %}
-site_apps_show = {
- "conversations";
- "dino";
- "gajim";
- "monal";
-}
-{% endif %}
-
-{% if not is_transport_server %}
-allow_user_invites = true
-site_name = "{{ virtual_host }}"
-{% endif %}
-
-privileged_entities = {
-{% for transport in transports | default([]) %}
- ["{{ transport.subdomain }}.{{ virtual_host }}"] = {
- roster = "both";
- message = "outgoing";
- iq = {
- ["http://jabber.org/protocol/pubsub"] = "both";
- ["http://jabber.org/protocol/pubsub#owner"] = "set";
- };
- };
-{% endfor %}
-}
-
-Component "upload.{{ virtual_host }}" "http_file_share"
-http_file_share_size_limit = 300 * 1024 * 1024
-http_file_share_global_quota = 5 * 1024 * 1024 * 1024
-
-{% if is_transport_server %}
-http_file_share_access = {
-{% for transport in transports | default([]) %}
- "{{ transport.subdomain }}.{{ virtual_host }}";
-{% endfor %}
-}
-{% endif %}
-
-Component "proxy.{{ virtual_host }}" "proxy65"
-
-proxy65_address = "chat.{{ virtual_host }}"
-
-modules_disabled = {
- "s2s";
-}
-
-Component "conference.{{ virtual_host }}" "muc"
-modules_enabled = {
- "muc";
- "muc_mam";
-
- -- Spam
-{% if not is_transport_server %}
- "muc_moderation";
- "muc_rtbl";
-{% endif %}
-}
-
-log_all_rooms = true
-restrict_room_creation = "local"
-muc_room_default_public = false
-muc_room_default_public_jids = true
-
-{% if not is_transport_server %}
-muc_rtbl_jid = "xmppbl.org"
-{% endif %}
-
-{% for transport in transports | default([]) %}
-Component "{{ transport.subdomain }}.{{ virtual_host }}"
-component_secret = "{{ transport.secret }}"
-modules_enabled = {
- "privilege";
-}
-{% endfor %}
diff --git a/playbook.yaml b/playbook.yaml
index 5b6231a..d1aae6a 100644
--- a/playbook.yaml
+++ b/playbook.yaml
@@ -10,432 +10,8 @@
roles:
- bootstrap
- unattended-upgrades
+ - xmpp-server
- role: munin-node
vars:
extra_profiles:
- prosody
- tasks:
-
- - name: Retrieve DANE hash
- ansible.builtin.shell:
- cmd: >
- set -o pipefail && openssl x509 -in
- ~/.lego/certificates/{{ virtual_host }}.crt -noout -pubkey | openssl
- pkey -pubin -outform DER | openssl dgst -sha256 -binary | hexdump -ve
- '/1 "%02x"'
- register: dane_result
- changed_when: false
- delegate_to: localhost
-
- - name: Take note of DANE hash
- ansible.builtin.set_fact:
- dane_hash: "{{ dane_result.stdout }}"
- tags:
- - dns
-
- - name: Ensure common records exist
- ansible.builtin.uri:
- url: "https://api.mythic-beasts.com/dns/v2/zones/{{ domain }}/records\
- ?select=host%3Dchat{{ env_suffix }}%26type%3DA\
- &select=host%3Dchat{{ env_suffix }}%26type%3DAAAA\
- &select=host%3Dconference{{ env_suffix }}%26type%3DCNAME\
- &select=host%3Dupload{{ env_suffix }}%26type%3DCNAME\
- &select=host%3D_xmpp-client._tcp{{ env_suffix }}%26type%3DSRV\
- &select=host%3D_xmpps-client._tcp{{ env_suffix }}%26type%3DSRV\
- &select=host%3D_5222._tcp.chat{{ env_suffix }}%26type%3DTLSA\
- %26data%3D{{ dane_hash }}\
- &select=host%3D_5223._tcp.chat{{ env_suffix }}%26type%3DTLSA\
- %26data%3D{{ dane_hash }}"
- method: PUT
- body_format: json
- body:
- records:
- - host: "chat{{ env_suffix }}"
- type: "A"
- data: "{{ ipv4 }}"
- - host: "chat{{ env_suffix }}"
- type: "AAAA"
- data: "{{ ipv6 }}"
- - host: "conference{{ env_suffix }}"
- type: "CNAME"
- data: "chat{{ env_suffix }}.{{ domain }}."
- - host: "upload{{ env_suffix }}"
- type: "CNAME"
- data: "chat{{ env_suffix }}.{{ domain }}."
- - host: "_xmpp-client._tcp{{ env_suffix }}"
- type: "SRV"
- data: "chat{{ env_suffix }}.{{ domain }}."
- srv_priority: 0
- srv_weight: 5
- srv_port: 5222
- - host: "_xmpps-client._tcp{{ env_suffix }}"
- type: "SRV"
- data: "chat{{ env_suffix }}.{{ domain }}."
- srv_priority: "0"
- srv_weight: "5"
- srv_port: "5223"
- - host: "_5222._tcp.chat{{ env_suffix }}"
- type: "TLSA"
- data: "{{ dane_hash }}"
- tlsa_usage: "3"
- tlsa_selector: "1"
- tlsa_matching: "1"
- - host: "_5223._tcp.chat{{ env_suffix }}"
- type: "TLSA"
- data: "{{ dane_hash }}"
- tlsa_usage: "3"
- tlsa_selector: "1"
- tlsa_matching: "1"
- delegate_to: localhost
- tags:
- - dns
-
- - name: Ensure non-transport records exist
- ansible.builtin.uri:
- url: "https://api.mythic-beasts.com/dns/v2/zones/{{ domain }}/records\
- ?select=host%3D_xmpp-server._tcp{{ env_suffix }}%26type%3DSRV\
- &select=host%3D_xmpps-server._tcp{{ env_suffix }}%26type%3DSRV\
- &select=host%3D_xmpps-server._tcp.conference{{ env_suffix }}\
- %26type%3DSRV\
- &select=host%3D_xmpps-server._tcp.upload{{ env_suffix }}%26type%3DSRV\
- &select=host%3D_5269._tcp.chat{{ env_suffix }}%26type%3DTLSA\
- %26data%3D{{ dane_hash }}\
- &select=host%3D_5270._tcp.chat{{ env_suffix }}%26type%3DTLSA\
- %26data%3D{{ dane_hash }}"
- method: PUT
- body_format: json
- body:
- records:
- - host: "_xmpp-server._tcp{{ env_suffix }}"
- type: "SRV"
- data: "chat{{ env_suffix }}.{{ domain }}."
- srv_priority: "0"
- srv_weight: "5"
- srv_port: "5269"
- - host: "_xmpps-server._tcp{{ env_suffix }}"
- type: "SRV"
- data: "chat{{ env_suffix }}.{{ domain }}."
- srv_priority: "0"
- srv_weight: "5"
- srv_port: "5270"
- - host: "_xmpps-server._tcp.conference{{ env_suffix }}"
- type: "SRV"
- data: "chat{{ env_suffix }}.{{ domain }}."
- srv_priority: "0"
- srv_weight: "5"
- srv_port: "5270"
- - host: "_xmpps-server._tcp.upload{{ env_suffix }}"
- type: "SRV"
- data: "chat{{ env_suffix }}.{{ domain }}."
- srv_priority: "0"
- srv_weight: "5"
- srv_port: "5270"
- - host: "_5269._tcp.chat{{ env_suffix }}"
- type: "TLSA"
- data: "{{ dane_hash }}"
- tlsa_usage: "3"
- tlsa_selector: "1"
- tlsa_matching: "1"
- - host: "_5270._tcp.chat{{ env_suffix }}"
- type: "TLSA"
- data: "{{ dane_hash }}"
- tlsa_usage: "3"
- tlsa_selector: "1"
- tlsa_matching: "1"
- delegate_to: localhost
- when: not is_transport_server
- tags:
- - dns
-
- # We specifically use apt instead of the more general package module here,
- # because we want to ensure the cache is updated before we try and install
- # anything. This is needed because, on a freh Debian install on AWS
- # Lightsail (as of 2024-02-08), nothing was returned after running apt
- # search borgmatic. Updating the cache before running apt install solved
- # this issue, but the package module does not support this functionality.
- - name: Ensure required packages are installed
- ansible.builtin.apt:
- name:
- - lua-dbi-postgresql # Prosody postgres connection
- - lua-unbound # Prosody DNS resolution
- - postgresql # Database
- - prosody # XMPP server
- - prosody-modules # Extra addons
- - python3-psycopg2 # Used by ansible postgres role
- state: present
- update_cache: true
- become: true
-
- - name: Ensure invite-specific packages are installed
- ansible.builtin.apt:
- name:
- - libjs-bootstrap4 # Used by invite webpage
- - libjs-jquery # Used by invite webpage
- - nginx # Serve invite webpages
- state: present
- update_cache: true
- become: true
- when: not is_transport_server
-
- - name: Ensure turn-specific packages are installed
- ansible.builtin.apt:
- name:
- - coturn # Audio / video calling server
- state: present
- update_cache: true
- become: true
- when: not is_transport_server
-
- - name: Ensure required ports with ufw applications are open
- community.general.ufw:
- rule: allow
- name: "{{ item }}"
- state: enabled
- loop:
- - OpenSSH
- become: true
-
- - name: Ensure full XMPP ports are open
- community.general.ufw:
- rule: allow
- name: "{{ item }}"
- state: enabled
- loop:
- - XMPP
- become: true
- when: not is_transport_server
-
- - name: Ensure only c2s ports are open
- community.general.ufw:
- rule: allow
- port: "{{ item }}"
- proto: tcp
- state: enabled
- loop:
- - 5222
- become: true
- when: is_transport_server
-
- - name: Ensure invite-specific ports with ufw applications are open
- community.general.ufw:
- rule: allow
- name: "{{ item }}"
- state: enabled
- loop:
- - WWW Full
- become: true
- when: not is_transport_server
-
- - name: Ensure turn-specific ports with ufw applications are open
- community.general.ufw:
- rule: allow
- name: "{{ item }}"
- state: enabled
- loop:
- - Turnserver
- become: true
- when: not is_transport_server
-
- - name: Ensure other required tcp ports are open
- community.general.ufw:
- rule: allow
- port: "{{ item }}"
- proto: tcp
- state: enabled
- loop:
- - 5000 # XEP-0065
- - 5223 # XEP-0368
- - 5270 # XEP-0368
- - 5280 # XEP-0363
- - 5281 # XEP-0363
- # - 5432 # Postgres
- become: true
-
- - name: Ensure other udp ports are open
- community.general.ufw:
- rule: allow
- port: "{{ item }}"
- proto: udp
- state: enabled
- loop:
- - 5000 # XEP-0065
- - 5280 # XEP-0363
- - 5281 # XEP-0363
- become: true
-
- - name: Ensure default nginx config is removed
- ansible.builtin.file:
- path: "/etc/nginx/sites-enabled/default"
- state: absent
- become: true
- notify: Restart nginx
- when: not is_transport_server
-
- - name: Ensure nginx config is installed
- ansible.builtin.template:
- src: "{{ playbook_dir }}/files/nginx_conf.j2"
- dest: /etc/nginx/sites-available/{{ virtual_host }}
- owner: root
- group: root
- mode: "0644"
- become: true
- notify: Restart nginx
- when: not is_transport_server
-
- - name: Ensure nginx config is enabled
- ansible.builtin.file:
- src: /etc/nginx/sites-available/{{ virtual_host }}
- dest: /etc/nginx/sites-enabled/{{ virtual_host }}
- owner: root
- group: root
- state: link
- become: true
- notify: Restart nginx
- when: not is_transport_server
-
- - name: Ensure turn is configured
- ansible.builtin.template:
- src: "{{ playbook_dir }}/files/turnserver.conf.j2"
- dest: /etc/turnserver.conf
- owner: root
- group: prosody
- mode: "0640"
- become: true
- notify: Restart coturn
- when: not is_transport_server
-
- - name: Ensure prosody database is created
- community.postgresql.postgresql_db:
- name: prosody
- become: true
- become_user: postgres
-
- - name: Ensure prosody role is created
- community.postgresql.postgresql_user:
- login_db: prosody
- name: prosody
- become: true
- become_user: postgres
-
- - name: Ensure prosody schema is created
- community.postgresql.postgresql_schema:
- login_db: prosody
- name: prosody
- owner: prosody
- become: true
- become_user: postgres
- register: my_result
-
- - name: Ensure prosody user exists on database
- community.postgresql.postgresql_user:
- name: prosody
- become: true
- become_user: postgres
-
- - name: Ensure prosody user has permissions on database
- community.postgresql.postgresql_privs:
- type: database
- login_db: prosody
- privs: ALL
- roles: prosody
- become: true
- become_user: postgres
-
- - name: Ensure prosody user has permissions on schema
- community.postgresql.postgresql_privs:
- type: table
- login_db: prosody
- objs: ALL_IN_SCHEMA
- privs: ALL
- roles: prosody
- become: true
- become_user: postgres
-
- - name: Ensure top-level prosody configuration is installed
- ansible.builtin.template:
- src: "{{ playbook_dir }}/files/prosody.cfg.lua.j2"
- dest: /etc/prosody/prosody.cfg.lua
- owner: root
- group: prosody
- mode: "0640"
- become: true
- notify: Restart prosody
-
- - name: Ensure host-specific prosody configuration is available
- ansible.builtin.template:
- src: "{{ playbook_dir }}/files/virtual_host.cfg.lua.j2"
- dest: "/etc/prosody/conf.avail/{{ virtual_host }}.cfg.lua"
- owner: root
- group: prosody
- mode: "0644"
- become: true
- notify: Restart prosody
-
- - name: Ensure host-specific prosody configuration is set
- ansible.builtin.file:
- src: "/etc/prosody/conf.avail/{{ virtual_host }}.cfg.lua"
- dest: "/etc/prosody/conf.d/{{ virtual_host }}.cfg.lua"
- owner: root
- group: prosody
- state: link
- become: true
- notify: Restart prosody
-
- - name: Ensure localhost prosody configuration is removed
- ansible.builtin.file:
- path: "/etc/prosody/conf.d/localhost.cfg.lua"
- state: absent
- become: true
- notify: Restart prosody
-
- - name: Ensure localhost prosody configuration is not available
- ansible.builtin.file:
- path: "/etc/prosody/conf.avail/localhost.cfg.lua"
- state: absent
- become: true
-
- - name: Ensure example prosody configuration is not available
- ansible.builtin.file:
- path: "/etc/prosody/conf.avail/example.com.cfg.lua"
- state: absent
- become: true
-
- - name: Ensure prosody is enabled
- ansible.builtin.service:
- name: prosody
- enabled: true
- become: true
-
- handlers:
-
- - name: Restart prosody
- ansible.builtin.service:
- name: prosody
- state: restarted
- become: true
-
- - name: Restart coturn
- ansible.builtin.service:
- name: coturn
- state: restarted
- become: true
-
- - name: Restart nginx
- ansible.builtin.service:
- name: nginx
- state: restarted
- become: true
-
- vars:
-
- env_prefix: >-
- {{ "" if env == "" else env + "." }}
-
- env_suffix: >-
- {{ "" if env == "" else "." + env }}
-
- virtual_host: "{{ env_prefix }}{{ domain }}"
-
- turn_server: "chat.{{ env_prefix }}{{ turn_domain }}"
-
- is_transport_server: "{{ transports | default([]) | length > 0 }}"