Files
xmpp-server-prosody/references/prosody-13-production-migration.md
2026-09-06 13:51:11 +00:00

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

  1. 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
  2. Swap image in docker-compose.yml: prosody/prosody:latest → gitea.nixg.ru/hermes/icq-prosody:13.0 (also remove obsolete top-level version: "3.9" — Compose v2 warns).
  3. docker compose up -d prosody then docker exec icq-prosody prosodyctl check config.

Config changes 0.11.9 → 13.0 (prod)

  1. component_ports = { 5347 } — REMOVED in 13.0 (listener hardcoded on 5347; delete the line). The module-less Component "telegram.nixg.ru" block already raises the listener.
  2. "pubsub" out of modules_enabled on the VirtualHost → must be a separate Component "pubsub.<domain>" "pubsub". Without this, startup fails: Error initializing module 'pubsub' on '<host>': Pubsub should be loaded as a component.
  3. 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 in http_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.)
  4. cross_domain_websocket is deprecated in 13.0 but STILL read by mod_websocket → keep it. (New mechanism is http_cors_override; no need to migrate yet.)
  5. Single global log block (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_proxies does 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 Protocols confirms nginx → Prosody 13.0.
  • SASL logic: slixmpp/raw connect with a WRONG password → AUTH FAILED proves the auth chain works. (Do not connect a second component with the same JID as the live bridge — Prosody logs Second 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 on prosody.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.