Files
icq/WALKTHROUGH.md
T
2026-08-28 19:36:21 +00:00

13 KiB
Raw Blame History

Развёртывание 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), не мешает.
  • Регистрация админа: 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), ports 8081: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. Ошибки, на которых мы обожглись (выжимка)

  1. SRV на Jino склеивается — заполнять поля отдельно, цель с точкой.
  2. FORWARD policy DROP + нет MASQUERADE → connection refused на 5222/5269.
  3. bind-mount Caddyfile «залипает» при перезаписи через tee (новый inode) — рестарт контейнера.
  4. bosh_service_url вместо websocket_url → пустая страница Converse.js.
  5. WebSocket-тест без Origin → 403, без Sec-WebSocket-Protocol: xmpp → 501.
  6. hairpin NAT — нельзя тестировать свой pub IP с самого сервера.
  7. Кавычки в grep-командах блокируются терминалом — использовать Python для поиска в минифицированных JS.
  8. 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..."