13 KiB
Развёртывание XMPP-мессенджера ICQ на chat.nixg.ru — Полный разбор
Итоговый документ опыта. Дата: 2026-08-28. Команда:
bigbox(внутренний сервер, /opt/icq) +vps02(публичный вход, Caddy + WireGuard). Домен: chat.nixg.ru · Протокол: XMPP · Сервер: Prosody 0.11.9 (Docker).
1. Почему XMPP, а не Telegram/Matrix/Snikket
- XMPP — открытый федеративный протокол (XML, RFC 6120/6121), как email:
у каждого пользователя JID
user@домен, серверы общаются между собой по s2s (порт 5269). Главное требование семьи — «общаться со всеми» — выполняется федерацией. - Telegram — закрытый централизованный протокол (MTProto, бинарный). Серверный код закрыт, клиенты захардкожены на официальные дата-центры. «Слямзить» протокол невозможно. Bot API — только HTTP+JSON поверх MTProto, федерации нет.
- Snikket-сервер отброшен: он спроектирован под прямой 443 (сам раздаёт TLS) и «капризничает» за реверс-прокси. Его КЛИЕНТЫ (iOS/Android) — отличные, используем их.
- Matrix/Conduit — мощные, но push (APNs/FCM) сложнее, нужен свой push-шлюз; для семьи XMPP проще.
2. Архитектура (почему так)
[Клиенты iOS/Android] ← c2s:5222 (TLS), wss:443 (веб)
│
▼
[vps02: 87.242.100.206] ← публичный IP, Caddy (TLS/443) + iptables DNAT (5222/5269)
│ WireGuard-туннель: bigbox=10.8.0.2, vps02=10.8.0.4
▼
[bigbox: 10.8.0.2] ← внутри, /opt/icq, Docker Compose
├─ icq-prosody — Prosody 0.11.9 (5222, 5269, 5280)
└─ icq-webchat — nginx (8081): статика Converse.js + прокси WebSocket на Prosody
- bigbox не имеет публичного IP — весь внешний трафик идёт через vps02.
- Caddy сам выпускает Let's Encrypt для 443 и проксирует WebSocket/HTTP.
- 5222/5269 (XMPP-клиенты и федерация) — DNAT в iptables на vps02 → 10.8.0.2.
- Порт 5280 (Prosody HTTP: WebSocket/BOSH) наружу НЕ торчит — веб-клиент ходит через Caddy на 443 (wss://), а боты/внутренние сервисы — напрямую.
3. Что сделано по шагам (с командами)
3.1 DNS (Jino, панель домена nixg.ru)
| Запись | Тип | Значение |
|---|---|---|
chat.nixg.ru |
A | 87.242.100.206 |
_xmpp-client._tcp.nixg.ru |
SRV | 0 5 5222 chat.nixg.ru |
_xmpp-server._tcp.nixg.ru |
SRV | 0 5 5269 chat.nixg.ru |
Проверка: host -t SRV _xmpp-client._tcp.nixg.ru
Засада Jino: панель склеивает поля в одну строку (_xmpp-client._tcp.nixg.ruIN SRV0 5 5222 chat.nixg.ru).
Решение: заполнять поля формы по отдельности: имя=_xmpp-client._tcp, приоритет=0, вес=5, порт=5222, цель=chat.nixg.ru. (FQDN с точкой). Результат в DNS всё равно корректный.
SRV-записи критичны для федерации: без них другие XMPP-серверы не найдут нас.
3.2 Prosody на bigbox (/opt/icq)
docker-compose.yml— сервисprosody(prosody/prosody:latest), порты 5222/5269/5280/5281, volumes: ./data, ./config, ./certs, ./modules, ./logs.- Конфиг
config/prosody.cfg.lua:- VirtualHost
chat.nixg.ru,authentication = "internal_hashed",allow_registration = true - модули: roster, saslauth, tls, dialback, disco, carbons, pep, private, blocklist, vcard4, vcard_legacy, version, uptime, time, ping, register, adhoc, admin_adhoc, csi, offline, presence, mam, websocket, server_contact_info
- MUC-компонент
conference.chat.nixg.ru cross_domain_websocket = { "https://chat.nixg.ru" }— разрешить WebSocket с нашего домена- Логи — в файлы (не консоль), т.к. docker-образ по умолчанию пишет в файлы.
https_portsпо умолчанию 5281 — даёт ошибку «No certificate present» (нет HTTPS-серта на 5281), не мешает.
- VirtualHost
- Регистрация админа:
docker exec icq-prosody prosodyctl register admin chat.nixg.ru ПАРОЛЬ - Проверка:
docker exec icq-prosody prosodyctl check config→ All checks passed.
Тонкости Prosody 0.11.9 в docker-образе:
- Нет community-модулей:
http_upload(XEP-0363),smacks,s2s_bidi,xmpp_component. Их нет в/usr/lib/prosody/modules/. Для http_upload нужна установка prosody-modules (см. раздел 7). - Порты 5222/5269/5280 слушаются на 0.0.0.0 — трафик снаружи приходит через WG.
3.3 Caddy на vps02
- Caddy в Docker (host-network), конфиг
/opt/caddy/Caddyfileмонтируется в контейнер. - Блок для chat.nixg.ru:
reverse_proxy 10.8.0.2:8081(веб-клиент+WS через nginx на bigbox). - Засада с bind-mount: при перезаписи Caddyfile через
sudo tee/>файл получает НОВЫЙ inode, а монтированный в контейнер старый inode остаётся — Caddy продолжает читать СТАРУЮ версию. Симптом: «конфиг изменил, а Caddy проксирует на старый адрес». Решение: перезапустить контейнер (docker compose restart caddy) или использоватьsudo sed -i(правит in-place, inode тот же). - Проверка:
docker exec caddy caddy validate --config /etc/caddy/Caddyfile+ reload/restart.
3.4 iptables DNAT на vps02 (форвард 5222/5269 на bigbox)
iptables -t nat -A PREROUTING -i enp3s0 -p tcp --dport 5222 -j DNAT --to-destination 10.8.0.2:5222
iptables -t nat -A PREROUTING -i enp3s0 -p tcp --dport 5269 -j DNAT --to 10..0.2:5269
iptables -A FORWARD -d 10.8.0.2 -p tcp --dport 5222 -j ACCEPT (и 5269)
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
- Обязательно добавить FORWARD ACCEPT (default policy DROP!) и MASQUERADE — иначе пакеты докодят до bigbox, но ответы не возвращаются (connection refused).
- Сохранение:
sudo sh -c "iptables-save > /etc/iptables/rules.v4"(переживает reboot). - Группа безопасности у провайдера (Timeweb): открыть TCP 5222, TCP 5269 (только TCP).
3.5 Веб-клиент (Converse.js v14 + nginx)
/opt/icq/webchat:index.html,converse.min.js(1.3MB, v14.0.0),converse.min.css(545KB) — скачаны локально с CDN (чтобы не зависеть от cdn.conversejs.org — может резаться из РФ).ngnix.conf:server 8081, root/usr/share/nginx/html;location /xmpp-websocket { proxy_pass http://prosody:5280/xmpp-websocket; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; };location / { try_files $uri $uri/ /index.html; }.- Docker-compose: сервис
webchat(nginx:alpine), ports8081:8081, зависит от prosody.
Критическая засада веб-клиента: в Converse.js v14 опция WebSocket называется websocket_url,
НЕ bosh_service_url! Если задать bosh_service_url с wss:// — Converse пытается подключиться по BOSH-протоколу (а у Prosody BOSH нет), падает, и UI вообще не рендерится (пустая страница!). В v14 view_mode: 'fullscreen' — дефолт.
4. Проверка работоспособности (извне, с реалного хоста — WSL/телефон)
nc -vz 87.242.100.206 5222 # xmpp-client: open
nc -vz 87.242.100.206 5269 # xmpp-server: open
curl -skI https://chat.nixg.ru/ # 200, HTML
curl -sk https://chat.nixg.ru/converse.min.js # 200, JS
# WebSocket-рукоятка (как у браузера):
# GET /xmpp-websocket, Upgrade: websocket, Sec-WebSocket-Protocol: xmpp, Origin: https://chat.nixg.ru
# → 101 Switching Protocols
Засада с hairpin NAT: с самого bigbox/vps02 нельзя проверить свой публичный IP —
маршрут <local>, пакет не проходит через DNAT. Нужен внешний хост (WSL, телефон по мобильной сети).
5. Веб-клиент Converse.js — настройки
converse.initialize({
websocket_url: 'wss://chat.nixg.ru/xmpp-websocket', // ОБЯЗАТЕЛЬНО websocket_url!
view_mode: 'fullscreen',
auto_login: false,
theme: 'concord',
i18n: 'ru',
show_controlbox_by_default: true,
});
Требования Prosody для WS: cross_domain_websocket = { "https://chat.nixg.ru" } в конфиге.
6. Ошибки, на которых мы обожглись (выжимка)
- SRV на Jino склеивается — заполнять поля отдельно, цель с точкой.
- FORWARD policy DROP + нет MASQUERADE → connection refused на 5222/5269.
- bind-mount Caddyfile «залипает» при перезаписи через tee (новый inode) — рестарт контейнера.
bosh_service_urlвместоwebsocket_url→ пустая страница Converse.js.- WebSocket-тест без Origin → 403, без
Sec-WebSocket-Protocol: xmpp→ 501. - hairpin NAT — нельзя тестировать свой pub IP с самого сервера.
- Кавычки в grep-командах блокируются терминалом — использовать Python для поиска в минифицированных JS.
- docker compose up -d/restart блокируется эвристикой — запускать в background + process wait.
7. TODO (следующие шаги)
⚡ Текущий блокер и пошаговый план: STATUS.md — пустая страница Converse.js.
- Починить пустую страницу веб-клиента (см. STATUS.md: разложить полный дистрибутив Converse.js с /dist-ассетами, получить console-ошибки headless-браузером).
- Установить модуль HTTP Upload (XEP-0363) для Prosody 0.11.9:
community-модуль mod_http_upload из prosody-modules (https://modules.prosody.im/).
Шаги: скачать модуль в ./modules, добавить
"http_upload"в modules_enabled, настроить размер/путь, перезапустить Prosody. Нужен для отправки файлов в чат (книги бота, фото). Также проверить mod_http_upload_external. - OMEMO (сквозное шифрование) — модуль + настройка клиентов.
- Бот-книгоискатель (slixmpp + OPDS) — Этап 3 PRD.
- Мосты mautrix-telegram / mautrix-whatsapp — Этап 4 (нужен реестр компонентов Prosody, модуль mod_component/external component, НЕ xmpp_component — его нет в 0.11.9).
- Настоящие сертификаты для 5222/5269 (dns-01) вместо самоподписанных.
- Push-уведомления APNs/FCM (публичный push-шлюз для Prosody).
8. Полезные команды
cd /opt/icq
docker compose ps
docker logs icq-prosody --tail 50
docker exec icq-prosody prosodyctl check config
docker exec icq-prosody prosodyctl register admin chat.nixg.ru ПАРОЛЬ
# рестарт
docker restart icq-prosody
# проверка WS рукоятки
python3 -c "...socket..."