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

169 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Развёртывание 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 —
маршрут `<local>`, пакет не проходит через 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..."
```