5.1 KiB
Production migration 0.11.9 → 13.0 (verified 2026-08-30, chat.nixg.ru / /opt/icq)
Live migration of the production XMPP server from Prosody 0.11.9 to 13.0
(image gitea.nixg.ru/hermes/icq-prosody:13.0). Complements the parallel-stand
recipe (prosody-13-parallel-stand.md) with the PROD-only steps and two
regressions that only surface with real bridges/users.
Steps
- Backup first:
cd /opt/icq && tar czf backups/pre-migration-13-$(date +%Y%m%d_%H%M%S).tar.gz config data certs modules webchat - Swap image in docker-compose.yml:
prosody/prosody:latest→gitea.nixg.ru/hermes/icq-prosody:13.0(also remove obsolete top-levelversion: "3.9"— Compose v2 warns). docker compose up -d prosodythendocker exec icq-prosody prosodyctl check config.
Config changes 0.11.9 → 13.0 (prod)
component_ports = { 5347 }— REMOVED in 13.0 (listener hardcoded on 5347; delete the line). The module-lessComponent "telegram.nixg.ru"block already raises the listener."pubsub"out ofmodules_enabledon the VirtualHost → must be a separateComponent "pubsub.<domain>" "pubsub". Without this, startup fails:Error initializing module 'pubsub' on '<host>': Pubsub should be loaded as a component.- HTTP Upload regression — Slidge gets "No upload slot":
- Symptom: slidge logs floods of
No upload slot in this IQ: <iq xmlns="jabber:client" id="0" />(~180 / 10 min on a busy bridge). Attachments (images/videos) silently fail. - Cause: the 13.0-era community mod_http_upload only hands out slots to
origin.type == "c2s"or JIDs inhttp_upload_access. A Slidge external component requests slots as a component → denied → empty result IQ. - Fix, inside the upload component block:
Component "upload.<domain>" "http_upload" http_upload_access = { "telegram.<domain>" } - After fix: 0 errors. (
docker logs icq-slidgram --since 2m | grep -c "No upload slot"= 0.)
- Symptom: slidge logs floods of
cross_domain_websocketis deprecated in 13.0 but STILL read by mod_websocket → keep it. (New mechanism ishttp_cors_override; no need to migrate yet.)- Single global
logblock (13.0 tolerates only one).
Post-migration verification (no client password needed)
prosodyctl check config→ All checks passed.
WebSocket SASL regression after 13.0: "no-auth-mech" / blank spinner
Symptom: Converse.js on https://chat.nixg.ru shows the login form for a split
second, then an infinite white spinner. Browser devtools on the WS frame shows
<failure><no-auth-mech/></failure> or the server offers NO SASL mechanisms.
Prosody logs flood every ~1s with:
warn No stream features to offer on insecure session. Check encryption and security settings.
Cause: TLS is terminated at the edge (Caddy → nginx → Prosody over plain HTTP
:5280), so the WebSocket session arrives at Prosody as insecure. In 13.0
mod_websocket only offers SASL on secure sessions; insecure → empty <stream:features>
→ client cannot authenticate and reconnects forever. (On 0.11.x this silently worked.)
Fix: in the GLOBAL config section (before VirtualHost), set
consider_websocket_secure = true
This is mod_websocket's own option (module:get_option_boolean("consider_websocket_secure"),
mod_websocket.lua:35, 286 — session.secure = consider_websocket_secure or request.secure or session.secure).
trusted_proxiesdoes NOT fix this — it only affects IP/logging, not session secure-ness.- Do NOT set
c2s_require_encryption = false— that would allow plaintext passwords on the external 5222 port. The webchat path is already TLS all the way to the browser.
Verify: after restart, prosody.log shows
Authenticated as user@nixg.ru [prosody:operator] for WS logins and the
No stream features... warnings stop. Headless check:
chromium --headless --no-sandbox --disable-gpu --virtual-time-budget=20000 --dump-dom https://chat.nixg.ru/ | grep -c converse-login-form → 1.
- Component auth:
grep "External component successfully authenticated" prosody.log(timestamp after restart). - mod_privilege XEP-0356 live:
grep "privilege" prosody.log | grep "Archiving stanza"— slidge-carbon-* stanza entries prove privileged message/roster delivery works on prod. - WebSocket path: raw handshake to the webchat port →
101 Switching Protocolsconfirms nginx → Prosody 13.0. - SASL logic: slixmpp/raw connect with a WRONG password →
AUTH FAILEDproves the auth chain works. (Do not connect a second component with the same JID as the live bridge — Prosody logsSecond component attempted to connect, denying connection; that is normal, not an error.)
Gotchas
docker exec icq-prosody sh -c 'ss/netstat...'may show nothing if net-tools absent — rely onprosody.log("Servers started", "Certificates loaded") and external handshakes instead.- Test-stand teardown for "stop until next update" (NOT delete):
docker stop icq-prosody-test && docker update --restart=no icq-prosody-test. - Prod was re-verified healthy after: prosody/slidgram/webchat all Up, 0 upload errors.