Swap all lua5.2-* Alpine packages to lua5.4-* in both Dockerfile stages
and add --lua-version=5.4 to ./configure.
Dropped packages:
- lua5.2-bitop: Prosody's util.bitcompat uses util.bit53 (native Lua
5.3/5.4 bitwise operators) when bit32 is unavailable
- lua5.2-lpeg_patterns, lua5.2-rapidjson, lua5.2-redis: not required by
any require in Prosody 0.12.6 or any vnctalk module
- luarocks5.2: installed but never used
- Duplicate lua5.2-socket entry: removed
Code audit for 5.2→5.4 breakage: clean.
- No bit32/bitop usage in vnctalk modules
- Every Prosody file using unpack has 'local unpack = table.unpack or
unpack' (resolves to table.unpack on 5.4)
- No string.format('%d', float) patterns in vnctalk modules
Verified: image builds, prosodyctl check config passes, compose-harness
testsuite green (80 passed, 5 skipped — identical to M1).
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Three changes to complete the repo-level work for Milestone 1 (0.11.6 →
0.12.6) plus a howto for the remaining manual/external tasks:
1. config/prosody.cfg.lua.template: re-add run_as_root = true. Production
and the compose harness run as root (startup.sh writes into root-owned
/etc/prosody/). Without it, mod_posix calls prosody.shutdown() during
startup, which deactivates c2s (port 5222) before the shutdown itself
errors out (prosody.main_thread is nil during module init), leaving
Prosody running without c2s.
2. tests/test_08_image_patches.py: replace 3 stale patch-marker entries
that checked for patches M1 intentionally dropped (moduleapi,
mod_admin_telnet, muc.lib dumpTable) with markers that verify their
config-based replacements (console_interfaces, http_interfaces) and
the storagemanager.open() rewrite in mod_vnc_muc_fcm.lua.
3. upgrade-plan.md: update M1 status to reflect the post-M1 fixes
(run_as_root, default_storage, stale tests), mark manual steps (DB
migration, telnet console regression, external testsuite) with
cross-references to m1-manual-tasks.md, and correct 0.12.5 → 0.12.6
throughout.
4. m1-manual-tasks.md: new file documenting the three manual tasks that
require external infrastructure — DB schema migration (one-way, with
rehearse-on-copy procedure), telnet console regression test (0.12
reimplemented the console on mod_admin_shell), and external testsuite
run against a dev deployment.
Verified: compose-harness testsuite — 80 passed, 5 skipped, 1 pre-existing
failure (test_vcard_fallback: mod_vnc_vcard_fallback not enabled in config,
unrelated to M1).
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
The M1 upgrade (0ea4d0a) removed default_storage = "sql" as seemingly
redundant with storage = "sql". However, the MUC component sets
storage = { muc_log = "sql" } (a table), and storagemanager.get_driver
falls back to default_storage (or "internal") for stores not listed in
the table. Without default_storage = "sql", the kick store on the MUC
component falls back to internal storage, whose archive driver requires
stanza objects — mod_vnc_track_kicks passes a plain string, causing
"unsupported-datatype" errors and kick data not being persisted.
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Extend tests/README.md with:
- Running with docker-compose: make test one-shot, host-run flow, mock
capture/log inspection, teardown.
- Importing an existing database into the compose postgres (psql/pg_restore,
fresh-volume flow, auth-backend caveat).
- Running against a standalone local prosody container (build + docker run,
port mapping, optional separate mock for side-effect tests).
- Updated env table (MOCK_URL, SMACKS_HIBERNATION_TIME), test coverage table
(test_08-12), prerequisites (tests/requirements.txt), and notes reflecting
the new healthcheck execution, REST Host header, and XMPP_HOST/PORT fix.
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
- mod_vnc_lastactivity: the jabber:iq:last handler returns <forbidden> when
the query has no 'to'; send it to another existing user's bare JID and
assert the result carries a 'seconds' attribute
- set body via msg["body"] and do not await message send() (returns None)
- use unique uuid room names instead of a fixed duplicated localpart
- unlock the freshly-created (locked) room with configure_muc before a
second user joins / is affiliated
- the unregister IQ is fire-and-forget (no reply); tolerate IqTimeout rather
than failing the kick-tracking smoke test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Prosody keeps every store in just two tables — prosody (keyval/map) and
prosodyarchive (archive) — distinguished by a `store` column. The previous
tests looked for separate tables named like %muc_log%, %kick%, %activity%,
which never exist, so they failed.
- check stores via the `store` column in prosody/prosodyarchive, not by
table name: muc_log + kick are archive stores, activity + vcard are
keyval stores (matching the modules' open_store calls)
- assert the two real tables (prosody, prosodyarchive) exist
- verify MUC archives are stored under the conference component host
- store_user inversion: 'key' is the generated archive UID, not the message
id, so match the REST-injected message on value LIKE and then assert the
owning 'user' is the sender and 'with' is the recipient
Verified against the live database (schema and sample rows inspected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
A half-open PostgreSQL tunnel accepts TCP but never completes the PG
handshake; asyncpg.connect() with no timeout then hangs the entire
test_06 suite indefinitely. Wrap connect in wait_for(timeout=10) and skip
with a clear message when the DB is unreachable, and only close the
connection in the finally if it was actually established (the previous
unconditional close raised NameError when connect failed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
- set body via msg["body"] / read via msg["body"]; do not await message
send() (returns None in slixmpp)
- str(msg["from"]) / str(pres["from"]) before .startswith (JID not str)
- mod_muc_unique: query a bare JID on the MUC component (a room JID), not a
user on the main host. The item-not-found handler is registered on the
component; a query to the user host is merely service-unavailable.
- offline-affiliate test: unlock the freshly-created room with configure_muc
before setting affiliations
Verified the portmanager network_default_read_size patch end-to-end: chat
bodies up to 12000 bytes are delivered intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
conftest:
- send initial presence after connect so the server routes directed
messages to the resource (chats to a bare JID otherwise go to offline
storage, breaking the timestamp/receipt tests)
- configure_muc: include FORM_TYPE on non-empty config submits, else
Prosody rejects them ("Form is not of type room configuration")
test_03_vnctalk:
- set body via msg["body"] and do not await presence/message send()
- read body via msg["body"], not msg.body
- replace nonexistent slixmpp.jid.nodeprep / slixmpp.util.id.short with uuid
- disco_info form lookup uses .// (form nested under <query>)
- test_broadcast_component rewritten to exercise real fanout: the
component is message-only (no disco), so post a <vncTalkBroadcast> with a
<to> recipient and assert that recipient receives the cast copy
- config-change broadcast test: unlock the room first, trigger via a real
config submit (not a subject change), and catch the bodyless
<x xmlns='xmpp:vnctalk:update'> with a low-level handler (slixmpp's
message event needs a <body>)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
conftest:
- presence.send()/message.send() return None in slixmpp; do not await them
- JID objects are not str; use str(pres["from"]) before .startswith
- disco_info/disco_items take a timeout so a non-responding server fails
fast instead of hanging the suite
- query_mam_and_collect takes a `to` arg to query a MUC archive (MUC MAM
lives at the room, not the user)
- add configure_muc() to submit the owner config form; a freshly-created
Prosody room is locked until configured and other users cannot join
test_02_muc:
- set body via msg["body"], not msg.body (the latter sets a Python attr
and produces a bodyless stanza that is neither reflected nor archived)
- MUC MAM test queries the room and matches the inner forwarded message by
local name (Prosody may omit xmlns='jabber:client')
- unlock rooms with configure_muc before a second user joins / is affiliated
- broadcast test rewritten as test_muc_broadcast_strips_spoofed_stanza_id:
the real contract is anti-spoofing (a client-injected by-room stanza-id is
stripped) while the room still adds its own legitimate XEP-0359 stanza-id
- unregister IQ handler is fire-and-forget (sends no reply); tolerate the
IqTimeout and assert the side effect (affiliation removed)
- hidden-override test skips when the server does not restrict public rooms
- disco_info form lookups use .// (form is nested under <query>)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
mod_vnc_e2ehints put the raw boolean returned by get_e2e() (false when
unset) into the disco#info roominfo formdata. muc#roominfo_e2e is a
text-single field, and util.dataforms/util.stanza cannot serialize a
boolean field value, so room:get_disco_info() raised an error and the
server silently dropped the disco#info reply for every existing room
(confirmed at the wire level: non-existent rooms returned item-not-found,
existing rooms returned nothing). Coerce the value to a string, matching
the working mod_vnc_muc_data pattern. get_e2e() itself is left returning a
boolean since the muc-config-form boolean field needs it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
slixmpp's high-level "message" event only fires for stanzas with a
top-level <body> (matcher '{jabber:client}message/{jabber:client}body').
MAM result wrappers (<message><result xmlns='urn:xmpp:mam:2'>...) have no
<body> of their own, so the event never fired and the MAM tests saw zero
results even though the server correctly returned them (<fin complete>).
Capture results with a low-level Callback + MatchXPath on the result
element instead, the same mechanism slixmpp's own xep_0313 plugin uses.
Also filter MAM queries by a start timestamp so a large archive does not
push fresh messages off the first page, and match the inner forwarded
<message> by local name to tolerate Prosody omitting xmlns='jabber:client'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Archiving triggered by REST injection is asynchronous: the HTTP endpoint
returns 201 before archive:append necessarily completes. A single 1s sleep
was often too short, causing flaky failures even though the message was
present in the database moments later.
- Add _poll_mam_for_id helper that queries MAM up to 10 times with 1s
intervals until the target message id appears
- Replace fixed sleeps in both REST MAM roundtrip tests with polling
- Improves reliability on slower or more loaded Prosody instances
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
For REST-injected messages, mod_mam.lua's patched message_handler sets
both store_user and the 'with' field to the sender's JID (because c2s is
false for REST and the vnc_rest branch only overrides store_user, not
'with'). Filtering by recipient to_jid therefore returns no results.
- Remove with_jid=to_jid from both REST MAM roundtrip tests
- Query the full sender archive and scan for the message id instead
- This is more robust regardless of the 'with' field semantics
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
XEP-0313 requires the MAM query data form to include a hidden FORM_TYPE
field with value 'urn:xmpp:mam:2'. Without it, Prosody's dataform
validation may reject or ignore the 'with' filter, causing the query to
return empty results even when messages exist in the archive.
- Introduce _add_mam_query_form helper that always injects FORM_TYPE
- Update query_mam and query_mam_and_collect to use the helper
- Also support start/end filters for completeness
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Prosody's mod_mam sends <result> elements as individual messages but the
<fin> element inside the IQ response stanza. The old code waited for a
<message> containing <fin>, which never arrives, causing TimeoutError.
- Remove on_mam_fin message handler
- Use iq.send() timeout instead of waiting for a message <fin>
- Add 0.5s grace period after IQ response for trailing <result> messages
- Clean up only the message handler in finally
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
mod_mam.lua advertises urn:xmpp:mam:2 and urn:xmpp:sid:0 via the
account-disco-info hook (user bare JID), not on the server domain.
The previous test incorrectly expected them from the server domain.
- test_disco_info_server: only assert basic disco#info / disco#items
- test_disco_info_account: new test querying bare JID for MAM + sid
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
- slixmpp 1.16 (asyncio) has no process() method; connect() already
schedules background processing. Remove the call that caused
AttributeError: 'VNCXmppClient' object has no attribute 'process'
- REST endpoint returns 422 (not 415) for invalid XML; accept both
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
slixmpp 1.16.0 connect() is not a coroutine; it returns an asyncio.Future
and takes (host, port) kwargs, not (address, use_ssl). The previous code
awaited connect() with wrong args causing TypeError inside the async fixture,
which pytest-asyncio surfaced as ERROR at setup.
- Pass host/port/ssl_context to super().__init__() so XMLStream stores them
- Remove manual connect_address and use_ssl tracking
- async_connect: simply await self.connect() with no args (host/port already
configured in __init__)
- Keep ssl_context creation with verify_mode=CERT_NONE for self-signed certs
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
pytest-asyncio 1.4 with asyncio_mode=auto requires async fixtures to be
decorated with @pytest_asyncio.fixture instead of @pytest.fixture.
Without this, async generator fixtures raise AssertionError during setup
at plugin.py:558, causing ERROR before any test code runs.
- Add import pytest_asyncio
- Replace @pytest.fixture with @pytest_asyncio.fixture for xmpp_client,
second_client, and pg_connection async fixtures
- Leave sync fixtures (xmpp_config, rest_injector) unchanged
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Prosody containers use self-generated self-signed certs. slixmpp with
use_ssl=True verifies certificates by default, causing
SSLCertVerificationError and pytest ERROR during fixture setup.
- VNCXmppClient: add verify_ssl parameter (default False) that creates
an ssl.SSLContext with verify_mode=CERT_NONE when disabled
- conftest.py: add --verify-ssl CLI option / VERIFY_SSL env var;
pass verify_ssl through xmpp_config to both client fixtures
- docs: document --verify-ssl in README.md and AGENTS.md
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
The /rest endpoint is typically protected by a reverse proxy. The
RESTInjector helper now accepts optional auth_user/auth_password and
sends an Authorization: Basic header when configured.
- conftest.py: add --rest-user/--rest-password CLI options and env vars;
update RESTInjector to include Basic auth headers
- test_01_core.py: refactor TestRestInjection to use the authenticated
rest_injector fixture instead of raw aiohttp calls
- tests/README.md + AGENTS.md: document REST_USER/REST_PASSWORD
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
- test_02_muc.py: add test_muc_mam_presence_not_archived (negative assertion
that presence stanzas are excluded from MUC MAM) and
test_hidden_lib_rejects_public_override (config form absence + raw override
submission with xfail for admin accounts)
- test_06_postgres.py: add TestPostgresStoreUserInversion which injects via
REST and queries the archive table directly to assert the 'user' column is
the sender; add TestPostgresKickStore and TestPostgresActivityStore for
mod_vnc_track_kicks and mod_vnc_lastactivity store tables
- test_07_module_load.py: new file with smoke tests for the five previously
zero-coverage modules (lastactivity IQ, delfile message hook, remotemucstore
message hook, remotemucinvite event, track_kicks event)
- MANUAL_TESTS.md: expand mod_auth_http_async section with mock-HTTP-server
procedure; add container build verification steps
- AUDIT.md: revise verdict to 'adequate for upgrade safety'; update all
coverage tables to reflect 55 tests across 7 files
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
- Add AGENTS.md with repo-specific conventions, build steps, and quirks
- Add PATCHES_AND_MODULES.md documenting every upstream deviation
- Add tests/ with pytest/slixmpp integration suite for core, MUC,
vnctalk extensions, and infrastructure verification
- Include pytest.ini and .gitignore
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Re-port all source patches onto 0.12.6; three are gone entirely:
moduleapi (the two vnc_muc_fcm modules call core.storagemanager
directly now), mod_admin_telnet and portmanager (replaced by
console_interfaces/http_interfaces config). The muc.lib fork keeps its
four functional changes including the externally consumed
muc-config-sub-mitted event; the operator-precedence hunk was fixed
upstream. mod_muc_mam shrinks to keep-archive-on-room-destroy since
muc_log_expires_after="never" disables cleanup upstream in 0.12.
Delete the bundled mod_smacks fork and the no-op mod_smacks_offline;
core 0.12 smacks supersedes them (options audited, dead smacks_max_old
corrected to smacks_max_old_sessions).
Config/startup: drop legacyauth, run_as_root, daemonize; bosh_ports ->
http_ports; cross_domain_* -> http_cors_override; randomize
component_secret at startup (env-overridable); export
log_slow_events_threshold fallback (latent render bug).
Found while smoke-testing: pin --idn-library=idn (0.12's ICU default
segfaults without ICU data in the image); http became a private
service in 0.12 so 5280 needs http_interfaces to stay public; the
telnet console now depends on mod_admin_socket, whose socket moves to
/var/log/prosody.
Verified: prosodyctl check config clean; boots against Postgres with
empty error log and same port bindings as 0.11; healthcheck green;
telnet console and SQL storage round-trip; 0.11->0.12 schema upgrade
rehearsed on a 0.11-created database with data intact.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
Phase 0 of upgrade-plan.md. The 11 forked copies of 0.11.6 files in
patches/ are replaced by unified .patch files applied in the Dockerfile
builder stage with patch -p1 --fuzz=0 before make install, so upstream
drift fails the build instead of silently shipping stale forks. The
cp-over-installed-files block in the final stage is gone and /vnc/patches
no longer ships in the image. Stale patches.list replaced by a README
documenting each patch's intent.
Verified: installed prosody tree (269 files) is byte-identical to the
image built from the previous mechanism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>