Initial commit: Hermes skill xmpp-server-prosody

This commit is contained in:
estorozhenko
2026-09-06 13:51:11 +00:00
commit 3da6c8709b
28 changed files with 2016 additions and 0 deletions
@@ -0,0 +1,79 @@
# 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:
```lua
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
```lua
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.