# Развёртывание 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/телефон) ```bash 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 — маршрут ``, пакет не проходит через DNAT. Нужен внешний хост (WSL, телефон по мобильной сети). ## 5. Веб-клиент Converse.js — настройки ```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](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. Полезные команды ```bash 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..." ```