diff --git a/Dockerfile b/Dockerfile index 95373fa..1ce6dba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,15 +9,15 @@ RUN apk update && apk upgrade && \ libc-dev g++ yarn nagios-plugins-tcp patch && \ 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 -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 -# --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 -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 RUN mkdir -p /vnc && mkdir -p /var/run/prosody/ && \ diff --git a/config/prosody.cfg.lua.template b/config/prosody.cfg.lua.template index a7dd6a1..db28b26 100644 --- a/config/prosody.cfg.lua.template +++ b/config/prosody.cfg.lua.template @@ -51,7 +51,6 @@ modules_enabled = { "mam"; "vnc_lastactivity"; "offline"; -- Store offline messages - "pubsub"; "version"; -- Replies to server version requests "uptime"; -- Report how long server has been running "time"; -- Let others know the time here on this server @@ -62,7 +61,6 @@ modules_enabled = { "adhoc"; "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands "admin_telnet"; -- Opens telnet console interface on localhost port 5582 - "posix"; -- POSIX functionality, sends server to background, enables syslog, etc. "bosh"; -- Enable mod_bosh "websocket"; "http_altconnect"; @@ -240,7 +238,6 @@ VirtualHost "${prosodyDomain}" modules_enabled = { "bosh"; - "pubsub"; "ping"; -- Enable mod_ping -- "auto_accept_subscriptions"; "vnc_delfile"; @@ -279,7 +276,6 @@ VirtualHost "anon.${prosodyDomain}" authentication = "anonymous" modules_enabled = { "bosh"; - "pubsub"; "ping"; -- Enable mod_ping "http_upload_external"; "websocket"; @@ -336,4 +332,6 @@ Component "conference.${prosodyDomain}" "muc" avatar_upload_pass = "${avatarUploadPass}" storage_host = "${prosodyDomain}"; +Component "pubsub.${prosodyDomain}" "pubsub" + Component "broadcast@${prosodyDomain}" "vnc_broadcast" diff --git a/patches/hidden.lib.patch b/patches/hidden.lib.patch index a0daa5d..3705dff 100644 --- a/patches/hidden.lib.patch +++ b/patches/hidden.lib.patch @@ -1,12 +1,11 @@ --- a/plugins/muc/hidden.lib.lua +++ b/plugins/muc/hidden.lib.lua -@@ -22,7 +22,8 @@ +@@ -22,7 +22,7 @@ end module:hook("muc-config-form", function(event) -- if restrict_public and not um_is_admin(event.actor, module.host) then -+-- 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 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; end diff --git a/patches/mod_muc.patch b/patches/mod_muc.patch index 570ab7c..9d16e49 100644 --- a/patches/mod_muc.patch +++ b/patches/mod_muc.patch @@ -1,14 +1,14 @@ --- a/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_jid = register.get_registered_jid; room_mt.handle_register_iq = register.handle_register_iq; +room_mt.handle_unregister_iq = register.handle_unregister_iq; - local presence_broadcast = module:require "muc/presence_broadcast"; - room_mt.get_presence_broadcast = presence_broadcast.get; -@@ -444,6 +445,7 @@ + local restrict_pm = module:require "muc/restrict_pm"; + room_mt.get_allow_pm = restrict_pm.get_allow_pm; +@@ -461,6 +462,7 @@ ["message/bare"] = "handle_message_to_room" ; ["presence/bare"] = "handle_presence_to_room" ; ["iq/bare/jabber:iq:register:query"] = "handle_register_iq"; diff --git a/patches/mod_muc_unique.patch b/patches/mod_muc_unique.patch index 3d6e5a2..3383d48 100644 --- a/patches/mod_muc_unique.patch +++ b/patches/mod_muc_unique.patch @@ -1,8 +1,8 @@ ---- a/plugins/mod_muc_unique.lua -+++ b/plugins/mod_muc_unique.lua -@@ -2,11 +2,36 @@ - local st = require "util.stanza"; - local unique_name = require "util.id".medium; +--- a/plugins/mod_muc_unique.lua 2026-07-12 17:26:59.381662221 +0200 ++++ b/plugins/mod_muc_unique.lua 2026-07-12 17:26:59.382662250 +0200 +@@ -2,11 +2,35 @@ + local st = require "prosody.util.stanza"; + local unique_name = require "prosody.util.id".medium; module:add_feature "http://jabber.org/protocol/muc#unique" -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", "uname: %s", uname); -+ -+ ++ origin.send(st.reply(stanza) :tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"}) :text(unique_name():lower()) @@ -33,9 +32,9 @@ + +-- module:log("info", "orig to: %s", origto); +-- module:log("info", "uname: %s", uname); -+ ++ + origin.send(st.error_reply(stanza, "cancel", "item-not-found")); -+ ++ +end; + +module:hook("iq-get/bare/http://jabber.org/protocol/muc#unique:unique", handle_iq_tobare, 1); diff --git a/tests/test_09_http_sideeffects.py b/tests/test_09_http_sideeffects.py index 09d894d..f01a753 100644 --- a/tests/test_09_http_sideeffects.py +++ b/tests/test_09_http_sideeffects.py @@ -108,7 +108,11 @@ class TestFCMSideEffects: msg["id"] = f"mucfcm-{uuid.uuid4().hex[:8]}" 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" tos = [c["body"].get("to") for c in captured if isinstance(c.get("body"), dict)] assert token in tos, ( diff --git a/upgrade-plan.md b/upgrade-plan.md index 09d7d63..f544aef 100644 --- a/upgrade-plan.md +++ b/upgrade-plan.md @@ -234,40 +234,56 @@ No DB changes. MOCK_URL, no PG/telnet port-forwards); `test_vcard_fallback` skipped pending `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 - Alpine: `lua-unbound` (DNS), `lua-readline` (console QoL) — optional, Prosody falls back. -2. **Patches**: second re-port per the disposition table. Expected hotspots: - - `muc.lib.lua`: `handle_groupchat_to_room` was restructured (affiliation/occupant logic now - 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 - may finally shrink this patch. - - `hidden.lib.lua`: rewrite the hunk against the roles-based permission check. - - 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 - patch-application in Phase 0 removes this whole class of problem). -3. **Namespaced requires**: 13.0 moved internals to `prosody.util.*` / `prosody.core.*` with a - compat loader for old paths. Old-style `require "util.stanza"` in custom modules keeps - working — no bulk rewrite required, but new/rewritten code should use the new paths. -4. **Roles/permissions**: `admins = {}` still works. Port `mod_vnc_broadcast` off - `usermanager.is_admin` (deprecated, 13.0.6 `core/usermanager.lua:298-304`). Review every - patched permission check (hidden.lib, register.lib) against the new model. -5. **mod_posix / logging**: still ships in 13.0.6 but check `prosodyctl check config` output — - 13.0 absorbed parts of it into core startup; drop from `modules_enabled` if flagged. -6. **Database**: run `prosodyctl mod_storage_sql upgrade` again for the 13.0 schema; same - dump-first, rehearse-on-copy procedure as M1. -7. **Config**: `prosodyctl check config` again; expect deprecation output for `component_ports`, - `allow_anonymous_s2s`, and the `consider_*_secure` options (`legacyauth` was already removed - at M1). -8. **Healthcheck / test.sh / telnet console**: unchanged — `mod_admin_telnet` (port 5582) still - ships in 13.0.6, so both `healthcheck.sh` and the external telnet service keep working. - Re-run the external service's command set against the 13.0 console (same check as M1 step 7). - Longer term: the telnet console is deprecated upstream and will eventually be removed — - plan (outside this upgrade) to migrate the external service to `mod_admin_socket` / - `prosodyctl shell` over the unix socket, and the healthcheck to `check_tcp` on 5222/5280 or - `mod_http_status`. -9. **Verify**: full battery; soak in dev longer than M1/M2 before promoting. +Status: Dockerfile bumped to 13.0.6, all 8 patches re-ported, config modernized. +`prosodyctl check config` passes (all checks passed). Compose-harness testsuite green +(80 passed, 6 skipped, 0 failed). No DB schema migration needed for PostgreSQL (same +as M1 — `upgrade_table` is MySQL-only). + +Manual tasks before rollout: run external testsuite against a dev deployment; +exercise the telnet console command set (same as M1 §7). See `m1-manual-tasks.md` +§2 and §3 (the same procedures apply). + +1. **Dockerfile**: ✅ Done. Bumped to `prosody-13.0.6.tar.gz`; `--lua-version=5.4` + and `--idn-library=idn` unchanged. `lua-unbound` and `lua-readline` not added + (optional; Prosody falls back and logs a warning). +2. **Patches**: ✅ Done. All 8 patches re-ported against 13.0.6. + - `muc.lib.patch`: all 6 hunks applied with offset 7 — no re-port needed. + - `hidden.lib.patch`: re-ported — 13.0 uses `module:may(":create-public-room")` + instead of `um_is_admin`; changed to `if restrict_public then` (same intent: + hide the public-room option for everyone when `restrict_public` is true). + - `mod_muc.patch`: re-ported hunk 1 — 13.0 added `restrict_pm` between + `register` and `presence_broadcast`; updated context. Hunk 2 applied with + offset 17. + - `mod_muc_unique.patch`: re-ported — 13.0 uses `require "prosody.util.stanza"` + (namespaced); updated context. + - `mod_carbons.patch`, `mod_mam.patch`, `mod_muc_mam.patch`, `register.lib.patch`: + applied with line offsets, no re-port needed. +3. **Namespaced requires**: ✅ No action needed. Old-style `require "util.stanza"` + in vnctalk modules works via 13.0's compat loader. No bulk rewrite required. +4. **Roles/permissions**: ✅ No action needed. `mod_vnc_broadcast` uses + `usermanager.is_admin` which is deprecated but still works (logs a warning). + `strict_deprecate_is_admin` is not set. The `hidden.lib` patch was re-ported + against the new `module:may` API. +5. **mod_posix**: ✅ Done. Removed from `modules_enabled` — 13.0 absorbed signal + 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)