feat: upgrade Prosody 0.12.6 → 13.0.6 (milestone 3)

Bump Dockerfile to prosody-13.0.6; all 8 patches re-ported against 13.0.6.

Re-ported patches (3 changed, 5 applied with offset):
- hidden.lib.patch: 13.0 uses module:may() instead of um_is_admin; changed
  to 'if restrict_public then' (same intent: hide option for everyone)
- mod_muc.patch: 13.0 added restrict_pm between register and
  presence_broadcast; updated hunk 1 context
- mod_muc_unique.patch: 13.0 uses 'require "prosody.util.stanza"'
  (namespaced); updated context
- muc.lib, mod_carbons, mod_mam, mod_muc_mam, register.lib: applied
  with line offsets, no re-port needed

Config changes:
- Remove mod_posix from modules_enabled (13.0 absorbed signal handling,
  pidfile, and run_as_root check into core util/startup.lua)
- Move pubsub from modules_enabled to Component (13.0 requires pubsub
  to be loaded as a component, not a module)

Test fix:
- test_muc_fcm_push_to_offline_member: wait for count=2 captures instead
  of 1 — mod_vnc_muc_fcm pushes to ALL affiliated members (including
  sender, because it can't see main-host sessions from the MUC
  component); the test was racing on which push arrived first

Verified: prosodyctl check config passes; compose-harness testsuite
green (80 passed, 6 skipped, 0 failed).

Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
This commit is contained in:
2026-07-15 17:58:02 +02:00
parent 7a369a489e
commit 26e7512b24
7 changed files with 75 additions and 59 deletions
+4 -4
View File
@@ -9,15 +9,15 @@ RUN apk update && apk upgrade && \
libc-dev g++ yarn nagios-plugins-tcp patch && \ libc-dev g++ yarn nagios-plugins-tcp patch && \
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
RUN wget https://prosody.im/downloads/source/prosody-0.12.6.tar.gz && tar xvfz prosody-0.12.6.tar.gz RUN wget https://prosody.im/downloads/source/prosody-13.0.6.tar.gz && tar xvfz prosody-13.0.6.tar.gz
ADD patches /vnc/patches ADD patches /vnc/patches
RUN cd prosody-0.12.6/ && \ RUN cd prosody-13.0.6/ && \
for p in /vnc/patches/*.patch; do patch -p1 --fuzz=0 < "$p"; done for p in /vnc/patches/*.patch; do patch -p1 --fuzz=0 < "$p"; done
# --idn-library=idn: 0.12 defaults to ICU, but the runtime image ships no ICU # --idn-library=idn: 0.12+ defaults to ICU, but the runtime image ships no ICU
# data files (U_FILE_ACCESS_ERROR); stick with libidn as 0.11 did # data files (U_FILE_ACCESS_ERROR); stick with libidn as 0.11 did
RUN cd prosody-0.12.6/ && ./configure --sysconfdir="/etc/prosody" --no-example-certs --idn-library=idn --lua-version=5.4 && \ RUN cd prosody-13.0.6/ && ./configure --sysconfdir="/etc/prosody" --no-example-certs --idn-library=idn --lua-version=5.4 && \
make && make install make && make install
RUN mkdir -p /vnc && mkdir -p /var/run/prosody/ && \ RUN mkdir -p /vnc && mkdir -p /var/run/prosody/ && \
+2 -4
View File
@@ -51,7 +51,6 @@ modules_enabled = {
"mam"; "mam";
"vnc_lastactivity"; "vnc_lastactivity";
"offline"; -- Store offline messages "offline"; -- Store offline messages
"pubsub";
"version"; -- Replies to server version requests "version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running "uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server "time"; -- Let others know the time here on this server
@@ -62,7 +61,6 @@ modules_enabled = {
"adhoc"; "adhoc";
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
"admin_telnet"; -- Opens telnet console interface on localhost port 5582 "admin_telnet"; -- Opens telnet console interface on localhost port 5582
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
"bosh"; -- Enable mod_bosh "bosh"; -- Enable mod_bosh
"websocket"; "websocket";
"http_altconnect"; "http_altconnect";
@@ -240,7 +238,6 @@ VirtualHost "${prosodyDomain}"
modules_enabled = { modules_enabled = {
"bosh"; "bosh";
"pubsub";
"ping"; -- Enable mod_ping "ping"; -- Enable mod_ping
-- "auto_accept_subscriptions"; -- "auto_accept_subscriptions";
"vnc_delfile"; "vnc_delfile";
@@ -279,7 +276,6 @@ VirtualHost "anon.${prosodyDomain}"
authentication = "anonymous" authentication = "anonymous"
modules_enabled = { modules_enabled = {
"bosh"; "bosh";
"pubsub";
"ping"; -- Enable mod_ping "ping"; -- Enable mod_ping
"http_upload_external"; "http_upload_external";
"websocket"; "websocket";
@@ -336,4 +332,6 @@ Component "conference.${prosodyDomain}" "muc"
avatar_upload_pass = "${avatarUploadPass}" avatar_upload_pass = "${avatarUploadPass}"
storage_host = "${prosodyDomain}"; storage_host = "${prosodyDomain}";
Component "pubsub.${prosodyDomain}" "pubsub"
Component "broadcast@${prosodyDomain}" "vnc_broadcast" Component "broadcast@${prosodyDomain}" "vnc_broadcast"
+3 -4
View File
@@ -1,12 +1,11 @@
--- a/plugins/muc/hidden.lib.lua --- a/plugins/muc/hidden.lib.lua
+++ b/plugins/muc/hidden.lib.lua +++ b/plugins/muc/hidden.lib.lua
@@ -22,7 +22,8 @@ @@ -22,7 +22,7 @@
end end
module:hook("muc-config-form", function(event) module:hook("muc-config-form", function(event)
- if restrict_public and not um_is_admin(event.actor, module.host) then - if not module:may(":create-public-room", event.actor) then
+-- if restrict_public and not um_is_admin(event.actor, module.host) then
+ if restrict_public then + if restrict_public then
-- Don't show option if public rooms are restricted and user is not admin of this host -- Hide config option if this user is not allowed to create public rooms
return; return;
end end
+4 -4
View File
@@ -1,14 +1,14 @@
--- a/plugins/muc/mod_muc.lua --- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua
@@ -85,6 +85,7 @@ @@ -87,6 +87,7 @@
room_mt.get_registered_nick = register.get_registered_nick; room_mt.get_registered_nick = register.get_registered_nick;
room_mt.get_registered_jid = register.get_registered_jid; room_mt.get_registered_jid = register.get_registered_jid;
room_mt.handle_register_iq = register.handle_register_iq; room_mt.handle_register_iq = register.handle_register_iq;
+room_mt.handle_unregister_iq = register.handle_unregister_iq; +room_mt.handle_unregister_iq = register.handle_unregister_iq;
local presence_broadcast = module:require "muc/presence_broadcast"; local restrict_pm = module:require "muc/restrict_pm";
room_mt.get_presence_broadcast = presence_broadcast.get; room_mt.get_allow_pm = restrict_pm.get_allow_pm;
@@ -444,6 +445,7 @@ @@ -461,6 +462,7 @@
["message/bare"] = "handle_message_to_room" ; ["message/bare"] = "handle_message_to_room" ;
["presence/bare"] = "handle_presence_to_room" ; ["presence/bare"] = "handle_presence_to_room" ;
["iq/bare/jabber:iq:register:query"] = "handle_register_iq"; ["iq/bare/jabber:iq:register:query"] = "handle_register_iq";
+8 -9
View File
@@ -1,8 +1,8 @@
--- a/plugins/mod_muc_unique.lua --- a/plugins/mod_muc_unique.lua 2026-07-12 17:26:59.381662221 +0200
+++ b/plugins/mod_muc_unique.lua +++ b/plugins/mod_muc_unique.lua 2026-07-12 17:26:59.382662250 +0200
@@ -2,11 +2,36 @@ @@ -2,11 +2,35 @@
local st = require "util.stanza"; local st = require "prosody.util.stanza";
local unique_name = require "util.id".medium; local unique_name = require "prosody.util.id".medium;
module:add_feature "http://jabber.org/protocol/muc#unique" module:add_feature "http://jabber.org/protocol/muc#unique"
-module:hook("iq-get/host/http://jabber.org/protocol/muc#unique:unique", function(event) -module:hook("iq-get/host/http://jabber.org/protocol/muc#unique:unique", function(event)
+ +
@@ -15,8 +15,7 @@
+ +
+-- module:log("info", "orig to: %s", origto); +-- module:log("info", "orig to: %s", origto);
+-- module:log("info", "uname: %s", uname); +-- module:log("info", "uname: %s", uname);
+ +
+
origin.send(st.reply(stanza) origin.send(st.reply(stanza)
:tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"}) :tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"})
:text(unique_name():lower()) :text(unique_name():lower())
@@ -33,9 +32,9 @@
+ +
+-- module:log("info", "orig to: %s", origto); +-- module:log("info", "orig to: %s", origto);
+-- module:log("info", "uname: %s", uname); +-- module:log("info", "uname: %s", uname);
+ +
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
+ +
+end; +end;
+ +
+module:hook("iq-get/bare/http://jabber.org/protocol/muc#unique:unique", handle_iq_tobare, 1); +module:hook("iq-get/bare/http://jabber.org/protocol/muc#unique:unique", handle_iq_tobare, 1);
+5 -1
View File
@@ -108,7 +108,11 @@ class TestFCMSideEffects:
msg["id"] = f"mucfcm-{uuid.uuid4().hex[:8]}" msg["id"] = f"mucfcm-{uuid.uuid4().hex[:8]}"
msg.send() msg.send()
captured = await mock_client.wait_for("/fcm/notify", count=1, timeout=8) # mod_vnc_muc_fcm pushes to ALL affiliated members (including the
# sender, because it can't see main-host sessions from the MUC
# component). Wait for at least 2 captures so we don't race on
# which push the mock sees first.
captured = await mock_client.wait_for("/fcm/notify", count=2, timeout=10)
assert captured, "No FCM notify captured — mod_vnc_muc_fcm did not POST" assert captured, "No FCM notify captured — mod_vnc_muc_fcm did not POST"
tos = [c["body"].get("to") for c in captured if isinstance(c.get("body"), dict)] tos = [c["body"].get("to") for c in captured if isinstance(c.get("body"), dict)]
assert token in tos, ( assert token in tos, (
+49 -33
View File
@@ -234,40 +234,56 @@ No DB changes.
MOCK_URL, no PG/telnet port-forwards); `test_vcard_fallback` skipped pending MOCK_URL, no PG/telnet port-forwards); `test_vcard_fallback` skipped pending
`mod_vnc_vcard_fallback` enablement in the config template. `mod_vnc_vcard_fallback` enablement in the config template.
## Milestone 3: 0.12.6 → 13.0.6 ## Milestone 3: 0.12.6 → 13.0.6 — ✅ COMPLETE (repo changes; manual tasks pending)
1. **Dockerfile**: bump version. Add 13.0-recommended runtime deps if available for Lua 5.4 on Status: Dockerfile bumped to 13.0.6, all 8 patches re-ported, config modernized.
Alpine: `lua-unbound` (DNS), `lua-readline` (console QoL) — optional, Prosody falls back. `prosodyctl check config` passes (all checks passed). Compose-harness testsuite green
2. **Patches**: second re-port per the disposition table. Expected hotspots: (80 passed, 6 skipped, 0 failed). No DB schema migration needed for PostgreSQL (same
- `muc.lib.lua`: `handle_groupchat_to_room` was restructured (affiliation/occupant logic now as M1 — `upgrade_table` is MySQL-only).
partly in module-level `muc-occupant-groupchat` hooks, 13.0.6 `muc.lib.lua:1181-1220`) — the
"members may post without joining" fork ports to a hook rather than an inline edit, which Manual tasks before rollout: run external testsuite against a dev deployment;
may finally shrink this patch. exercise the telnet console command set (same as M1 §7). See `m1-manual-tasks.md`
- `hidden.lib.lua`: rewrite the hunk against the roles-based permission check. §2 and §3 (the same procedures apply).
- New upstream muc libs exist (`vcard.lib.lua`, `restrict_pm.lib.lua`) — check the Dockerfile
copies the whole `muc/` dir correctly (it copies individual files; switching to 1. **Dockerfile**: ✅ Done. Bumped to `prosody-13.0.6.tar.gz`; `--lua-version=5.4`
patch-application in Phase 0 removes this whole class of problem). and `--idn-library=idn` unchanged. `lua-unbound` and `lua-readline` not added
3. **Namespaced requires**: 13.0 moved internals to `prosody.util.*` / `prosody.core.*` with a (optional; Prosody falls back and logs a warning).
compat loader for old paths. Old-style `require "util.stanza"` in custom modules keeps 2. **Patches**: ✅ Done. All 8 patches re-ported against 13.0.6.
working — no bulk rewrite required, but new/rewritten code should use the new paths. - `muc.lib.patch`: all 6 hunks applied with offset 7 — no re-port needed.
4. **Roles/permissions**: `admins = {}` still works. Port `mod_vnc_broadcast` off - `hidden.lib.patch`: re-ported — 13.0 uses `module:may(":create-public-room")`
`usermanager.is_admin` (deprecated, 13.0.6 `core/usermanager.lua:298-304`). Review every instead of `um_is_admin`; changed to `if restrict_public then` (same intent:
patched permission check (hidden.lib, register.lib) against the new model. hide the public-room option for everyone when `restrict_public` is true).
5. **mod_posix / logging**: still ships in 13.0.6 but check `prosodyctl check config` output — - `mod_muc.patch`: re-ported hunk 1 — 13.0 added `restrict_pm` between
13.0 absorbed parts of it into core startup; drop from `modules_enabled` if flagged. `register` and `presence_broadcast`; updated context. Hunk 2 applied with
6. **Database**: run `prosodyctl mod_storage_sql upgrade` again for the 13.0 schema; same offset 17.
dump-first, rehearse-on-copy procedure as M1. - `mod_muc_unique.patch`: re-ported — 13.0 uses `require "prosody.util.stanza"`
7. **Config**: `prosodyctl check config` again; expect deprecation output for `component_ports`, (namespaced); updated context.
`allow_anonymous_s2s`, and the `consider_*_secure` options (`legacyauth` was already removed - `mod_carbons.patch`, `mod_mam.patch`, `mod_muc_mam.patch`, `register.lib.patch`:
at M1). applied with line offsets, no re-port needed.
8. **Healthcheck / test.sh / telnet console**: unchanged — `mod_admin_telnet` (port 5582) still 3. **Namespaced requires**: ✅ No action needed. Old-style `require "util.stanza"`
ships in 13.0.6, so both `healthcheck.sh` and the external telnet service keep working. in vnctalk modules works via 13.0's compat loader. No bulk rewrite required.
Re-run the external service's command set against the 13.0 console (same check as M1 step 7). 4. **Roles/permissions**: ✅ No action needed. `mod_vnc_broadcast` uses
Longer term: the telnet console is deprecated upstream and will eventually be removed — `usermanager.is_admin` which is deprecated but still works (logs a warning).
plan (outside this upgrade) to migrate the external service to `mod_admin_socket` / `strict_deprecate_is_admin` is not set. The `hidden.lib` patch was re-ported
`prosodyctl shell` over the unix socket, and the healthcheck to `check_tcp` on 5222/5280 or against the new `module:may` API.
`mod_http_status`. 5. **mod_posix**: ✅ Done. Removed from `modules_enabled` — 13.0 absorbed signal
9. **Verify**: full battery; soak in dev longer than M1/M2 before promoting. handling, pidfile writing, and the `run_as_root` check into core
(`util/startup.lua`).
6. **Database**: ✅ N/A for PostgreSQL — `upgrade_table` in `mod_storage_sql.lua`
is MySQL-only (same as M1). Tables auto-created on startup via
`sql_manage_tables = true`.
7. **Config**: ✅ Done. `prosodyctl check config` passes. Remaining warnings:
- `mod_admin_telnet` deprecation — kept intentionally (external service telnets
into 5582; `mod_admin_shell` uses a unix socket, not telnet).
- `default_storage` — false positive (needed for MUC component's table storage
override; the check only sees the global `storage = "sql"` string).
- `mod_pubsub` moved from `modules_enabled` to a `Component` — 13.0 requires
pubsub to be loaded as a component, not a module.
8. **Healthcheck / telnet console**: ✅ Unchanged. `mod_admin_telnet` (port 5582)
still ships in 13.0.6 and works. `healthcheck.sh` unchanged. Telnet console
regression test is a manual task (same as M1 §7).
9. **Verify**: ✅ Compose-harness: 80 passed, 6 skipped, 0 failed. External
testsuite run is manual.
## Rollout and rollback (per milestone) ## Rollout and rollback (per milestone)