diff --git a/MIGRATION.md b/MIGRATION.md
index fb1b312..530f550 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,7 +1,7 @@
# Миграция XMPP: chat.nixg.ru → nixg.ru (юзеры user@nixg.ru, XMPP-домен через xmpp.nixg.ru)
> Дата: 2026-08-28 (вечер) · bigbox (/opt/icq)
-> Статус: ВЫПОЛНЕНО (кроме SRV-DNS у пользователя + замена самоподписанного cert)
+> Статус: **ВЫПОЛНЕНО ПОЛНОСТЬЮ ✅** (2026-08-28): SRV → xmpp.nixg.ru (проверено dig), LE-серт Prosody (dns-01) получен и установлен, HTTP Upload настроен.
> Цель: JID юзеров `user@nixg.ru`, Prosody на VirtualHost nixg.ru, веб-клиент остаётся на https://chat.nixg.ru (страница),
> WebSocket клиентов → **wss://xmpp.nixg.ru/xmpp-websocket** (поддомен, визитка на apex НЕ тронута).
diff --git a/PRD.md b/PRD.md
index 1f8449f..80cdd7e 100644
--- a/PRD.md
+++ b/PRD.md
@@ -1,6 +1,8 @@
-# PRD: Семейный мессенджер ICQ (XMPP) на chat.nixg.ru
+# PRD: Семейный мессенджер ICQ (XMPP) на nixg.ru
-> Версия: 0.2 · Дата: 2026-08-28 · Статус: в разработке (Этап 1 завершён, Этап 2 начат)
+> Версия: 0.2 · Дата: 2026-08-28 · Статус: в разработке (Этапы 1-2 выполнены ✅, Этап 3 бот — следующий)
+> ⚠️ Актуальный домен: **nixg.ru** (JID user@nixg.ru). В тексте ниже старый домен chat.nixg.ru
+> из ранней версии PRD — при реализации везде используется nixg.ru (см. WALKTHROUGH.md).
>
> **Практический разбор всего сделанного: [WALKTHROUGH.md](WALKTHROUGH.md)** —
> пошаговое описание с командами, почему выбрана такая архитектура, и все «засады».
diff --git a/STATUS.md b/STATUS.md
index c19e64a..c1943e4 100644
--- a/STATUS.md
+++ b/STATUS.md
@@ -1,8 +1,8 @@
# ICQ XMPP — ТЕКУЩИЙ СТАТУС И ПЛАН (точка входа для новой сессии)
-> Обновлено: 2026-08-28 ≈ 19:00 UTC · Сессия: bigbox (/opt/icq)
+> Обновлено: 2026-08-28 ≈ 20:00 UTC · Сессия: bigbox (/opt/icq)
> ⚠️ МИГРАЦИЯ НА nixg.ru (2026-08-28): JID юзеров теперь `user@nixg.ru`, веб-клиент остаётся на chat.nixg.ru, WS → wss://xmpp.nixg.ru. См. MIGRATION.md.
-> Читать вместе с: [PRD.md](PRD.md) (требования/архитектура), [WALKTHROUGH.md](WALKTHROUGH.md) (опыт, команды, засады), [MIGRATION.md](MIGRATION.md) (переезд).
+> ✅ Документация полная и актуальная: [WALKTHROUGH.md](WALKTHROUGH.md) (все этапы, команды, засады, TLS, HTTP Upload) · [PRD.md](PRD.md) (требования/архитектура) · [MIGRATION.md](MIGRATION.md) (переезд).
----------------------------------------------------------------------
## 1. КРИТИЧНЫЕ ДАННЫЕ (для продолжения работы)
diff --git a/WALKTHROUGH.md b/WALKTHROUGH.md
index 3508ca4..d3d75ad 100644
--- a/WALKTHROUGH.md
+++ b/WALKTHROUGH.md
@@ -1,8 +1,10 @@
-# Развёртывание XMPP-мессенджера ICQ на chat.nixg.ru — Полный разбор
+# Развёртывание XMPP-мессенджера ICQ на nixg.ru — Полный разбор (WALKTHROUGH)
-> Итоговый документ опыта. Дата: 2026-08-28.
-> Команда: `bigbox` (внутренний сервер, /opt/icq) + `vps02` (публичный вход, Caddy + WireGuard).
-> Домен: **chat.nixg.ru** · Протокол: **XMPP** · Сервер: **Prosody 0.11.9** (Docker).
+> Документ опыта. Дата последнего обновления: 2026-08-28.
+> Команда: `bigbox` (внутренний сервер, /opt/icq) + `vps02` (публичный вход, Caddy + WireGuard + DNAT).
+> Домен: **nixg.ru** (JID юзеров `user@nixg.ru`) · Протокол: **XMPP** · Сервер: **Prosody 0.11.9** (Docker).
+> Веб-клиент: **Converse.js v14.0.0** на https://chat.nixg.ru · WebSocket: wss://xmpp.nixg.ru
+> См. также: [STATUS.md](STATUS.md) — текущее состояние и TODO · [PRD.md](PRD.md) — требования/архитектура · [MIGRATION.md](MIGRATION.md) — переезд chat.nixg.ru → nixg.ru.
----------------------------------------------------------------------
## 1. Почему XMPP, а не Telegram/Matrix/Snikket
@@ -20,62 +22,75 @@
## 2. Архитектура (почему так)
```
-[Клиенты iOS/Android] ← c2s:5222 (TLS), wss:443 (веб)
+[Клиенты iOS/Android] ← c2s:5222 (TLS/LE), wss:443 (веб), s2s:5269 (федерация)
│
▼
-[vps02: 87.242.100.206] ← публичный IP, Caddy (TLS/443) + iptables DNAT (5222/5269)
+[vps02: 87.242.100.206] ← публичный IP, Caddy (TLS/443) + iptables DNAT (5222/5269/5281)
│ 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
+ ├─ icq-prosody — Prosody 0.11.9 (5222 c2s, 5269 s2s, 5280 http, 5281 https)
+ ├─ icq-webchat — nginx (8081): статика Converse.js + прокси WebSocket на Prosody
+ └─ (отдельно на vps02) Caddy — 443: chat/xmpp/upload.nixg.ru → 10.8.0.2
```
- **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://), а боты/внутренние сервисы — напрямую.
+- **5281** (Prosody HTTPS: HTTP Upload) — DNAT НЕ проброшен наружу; Caddy на 443 (upload.nixg.ru) → 10.8.0.2:5281.
+- Порт **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` |
+| `nixg.ru` | A | `81.177.135.175` (визитка Jino — НЕ трогаем) |
+| `chat.nixg.ru` | A | `87.242.100.206` (веб-клиент Converse.js) |
+| `xmpp.nixg.ru` | A | `87.242.100.206` (WebSocket и всё XMPP) |
+| `upload.nixg.ru` | A | `87.242.100.206` (HTTP Upload, XEP-0363) |
+| `_xmpp-client._tcp.nixg.ru` | SRV | `0 5 5222 xmpp.nixg.ru.` |
+| `_xmpp-server._tcp.nixg.ru` | SRV | `0 5 5269 xmpp.nixg.ru.` |
+| `_xmpp-client-websocket._tcp.nixg.ru` | SRV | `0 5 443 xmpp.nixg.ru.` |
-Проверка: `host -t SRV _xmpp-client._tcp.nixg.ru`
+Проверка: `dig +short @1.1.1.1 _xmpp-client._tcp.nixg.ru SRV`, `dig +short @1.1.1.1 upload.nixg.ru A`.
**Засада Jino**: панель склеивает поля в одну строку (`_xmpp-client._tcp.nixg.ruIN SRV0 5 5222 chat.nixg.ru`).
-Решение: заполнять поля формы по отдельности: имя=`_xmpp-client._tcp`, приоритет=`0`, вес=`5`, порт=`5222`, цель=`chat.nixg.ru.` (FQDN с точкой). Результат в DNS всё равно корректный.
+Решение: заполнять поля формы по отдельности: имя=`_xmpp-client._tcp`, приоритет=`0`, вес=`5`, порт=`5222`,
+цель=`xmpp.nixg.ru.` (FQDN с точкой). Результат в DNS всё равно корректный.
SRV-записи критичны для федерации: без них другие XMPP-серверы не найдут нас.
+**Засада с DNS-кэшем**: после смены A-записи старый IP (визитка 81.177.135.175) висит в кэшах до TTL (10800 сек).
+Авторитативные NS отдают сразу правильное: `dig @ns1.jino.ru upload.nixg.ru A`.
+На bigbox кэш systemd-resolved сбрасывается ТОЛЬКО полным рестартом: `sudo systemctl restart systemd-resolved`
+(`resolvectl flush-caches` НЕ помогает!).
+
### 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`
+- `docker-compose.yml` — сервисы `prosody` (prosody/prosody:latest) и `webchat` (nginx:alpine); см. файл.
+- `config/prosody.cfg.lua`:
+ - VirtualHost **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 ПАРОЛЬ`
+ - MUC-компонент `conference.nixg.ru` (групповые чаты, max_history 200)
+ - `cross_domain_websocket = { "https://chat.nixg.ru", "https://xmpp.nixg.ru" }`
+ - HTTP: `http_ports = { 5280 }`, HTTPS: `https_ports = { 5281 }` + глобальный ssl (LE-серт)
+ - Component `upload.nixg.ru` (http_upload) — см. раздел 7
+ - Логи — в файлы (/var/log/prosody, монтируется ./logs)
+- Регистрация админа: `docker exec icq-prosody prosodyctl register admin 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.
+- Нет community-модулей по умолчанию. Установка: Ubuntu-пакет `prosody-modules` (apt) даёт их
+ в `/usr/lib/prosody/modules/` — копируем нужный в ./modules (монтируется в /etc/prosody/modules).
+- Порты слушаются на 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).
+- Блоки (см. актуальный файл): chat.nixg.ru и xmpp.nixg.ru → `10.8.0.2:8081` (nginx веб-чат),
+ upload.nixg.ru → `10.8.0.2:5281` (Prosody HTTPS, HTTP Upload) с `tls_insecure_skip_verify` (см. 7),
+ плюс netbox/gitea/homepage/vwar/ollama/webui/search/hermes (другие сервисы).
- **Засада с bind-mount**: при перезаписи Caddyfile через `sudo tee`/`>` файл получает НОВЫЙ inode,
а монтированный в контейнер старый inode остаётся — Caddy продолжает читать СТАРУЮ версию.
Симптом: «конфиг изменил, а Caddy проксирует на старый адрес». Решение: перезапустить контейнер
@@ -85,29 +100,106 @@ SRV-записи критичны для федерации: без них др
### 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 -t nat -A PREROUTING -i enp3s0 -p tcp --dport 5269 -j DNAT --to 10.8.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).
+ доходят до bigbox, но ответы не возвращаются (connection refused).
- Сохранение: `sudo sh -c "iptables-save > /etc/iptables/rules.v4"` (переживает reboot).
-- Группа безопасности у провайдера (Timeweb): открыть TCP 5222, TCP 5269 (только TCP).
+- Группа безопасности у провайдера (Timeweb): открыть TCP 5222, TCP 5269 (и 443 для Caddy).
-### 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.
+### 3.5 Веб-клиент (Converse.js v14.0.0 + nginx)
+- `/opt/icq/webchat`: `index.html`, `dist/` (полный дистрибутив v14.0.0), `nginx.conf`.
+ dist скачан из официального релиза `converse.js-14.0.0.tgz` (GitHub) — это важно:
+ CDN (cdn.conversejs.org) может резаться из РФ.
+- `nginx.conf`: server 8081, root /usr/share/nginx/html;
+ - `location /xmpp-websocket { proxy_pass http://prosody:5280/xmpp-websocket; upgrade-заголовки; proxy_read_timeout 3600s; }`
+ - `location /http-bind { proxy_pass http://prosody:5280/http-bind; }`
+ - `location = /.well-known/host-meta` и `.json` — JSON XEP-0156 (см. 6.4)
+ - `location / { try_files $uri $uri/ /index.html; }`
+- Docker-compose: сервис `webchat` (nginx:alpine), ports `8081:8081`, volume `./webchat:/usr/share/nginx/html:ro`.
**Критическая засада веб-клиента**: в Converse.js v14 опция WebSocket называется **`websocket_url`**,
-НЕ `bosh_service_url`! Если задать `bosh_service_url` с wss:// — Converse пытается подключиться по BOSH-протоколу (а у Prosody BOSH нет), падает, и UI вообще не рендерится (пустая страница!). В v14 `view_mode: 'fullscreen'` — дефолт.
+НЕ `bosh_service_url`! Если задать `bosh_service_url` с wss:// — Converse пытается подключиться по BOSH
+(а у Prosody BOSH нет), падает, UI не рендерится (пустая страница!).
+
+**КРИТИЧНО про v14 + ESM**: Converse v14 — ES-модуль (`import.meta.url`, `export{c as default}`).
+Подключение обычным `
+
+```
+Обязателен ПОЛНЫЙ dist из релиза (чанки locales/, libomemo.esm.min.js, curve25519_compiled.wasm, sounds, webfonts).
+
+## 4. TLS / Let's Encrypt для Prosody (c2s/s2s 5222/5269) — dns-01
+
+**Проблема**: Prosody (5222/5269) отдавал самоподписанный серт → АБСОЛЮТНО не работала федерация
+(другие серверы не принимали). Нужен настоящий LE-серт с SAN `nixg.ru` + `xmpp.nixg.ru`.
+
+**Почему dns-01**: http-01 невозможен (A nixg.ru → визитка 81.177.135.175, а не наш сервер),
+TLS-ALPN не подходит для портов 5222/5269. Jino не имеет публичного DNS API → TXT добавляется
+ВРУЧНУЮ в панели.
+
+**Команды (выполнено 2026-08-28)**:
+```bash
+# 0. Установить certbot (2.9.0, apt). Если apt сломан dpkg — сначала:
+sudo dpkg --configure -a
+sudo apt-get install --reinstall chromium-browser # был сломан snap-transitional, exit 127
+
+# 1. Первый запуск — печатает TXT-значения (ДВА вызова или один с двумя -d):
+sudo certbot certonly --manual --preferred-challenges dns-01 \
+ -d nixg.ru -d xmpp.nixg.ru \
+ --email kpa39l@yandex.ru --agree-tos --no-eff-email
+
+# 2. Добавить в Jino: TXT _acme-challenge.nixg.ru = <значение>, _acme-challenge.xmpp.nixg.ru = <значение>.
+
+# 3. Дождаться DNS (проверка: dig @1.1.1.1 TXT _acme-challenge.nixg.ru)
+# и повторить с хуками (чтобы не ждал интерактивно):
+sudo certbot certonly --manual --preferred-challenges dns-01 \
+ -d nixg.ru -d xmpp.nixg.ru \
+ --manual-auth-hook /bin/true --manual-cleanup-hook /bin/true
+
+# 4. Скопировать в /opt/icq/certs/ (ВАЖНО: cp -L, т.к. live/ — симлинки на archive/)
+sudo cp -L /etc/letsencrypt/live/nixg.ru/fullchain.pem /opt/icq/certs/nixg.ru.crt
+sudo cp -L /etc/letsencrypt/live/nixg.ru/privkey.pem /opt/icq/certs/nixg.ru.key
+
+# 5. Рестарт Prosody и проверка серта
+cd /opt/icq && docker compose restart prosody
+openssl x509 -in certs/nixg.ru.crt -noout -subject -issuer -dates -ext subjectAltName
+```
+
+**Проверка снаружи**: python-скрипт (openssl 3 STARTTLS XMPP не поддерживает!) — см. раздел 8.
+
+**ПРОДЛЕНИЕ (важно!)**: certbot создал таймер автопродления, но при dns-01 он БЕСПОЛЕЗЕН —
+каждый раз нужно ВРУЧНУЮ добавлять новый TXT в Jino (панель, без API). Процедура:
+```bash
+sudo certbot renew --manual-auth-hook /bin/true --manual-cleanup-hook /bin/true
+# ↑ напечатает новый TXT → добавить в Jino → снова запустить (создаст серт)
+# затем: sudo cp -L ... в /opt/icq/certs → docker compose restart prosody
+```
+Срок: выпущено 2026-08-28, expires 2026-11-26. Напоминание: cron-задача Hermes 2026-10-15 10:00 (job ca2a23a34905).
+
+## 5. Проверка работоспособности (извне)
-## 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
+curl -sSI https://chat.nixg.ru/ # 200, HTML (Converse.js)
+curl -sS https://upload.nixg.ru/upload # 200 (HTTP Upload endpoint)
+curl -sS https://xmpp.nixg.ru/.well-known/host-meta # 200 JSON XEP-0156
# WebSocket-рукоятка (как у браузера):
# GET /xmpp-websocket, Upgrade: websocket, Sec-WebSocket-Protocol: xmpp, Origin: https://chat.nixg.ru
# → 101 Switching Protocols
@@ -115,19 +207,6 @@ curl -sk https://chat.nixg.ru/converse.min.js # 200, JS
**Засада с 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.
@@ -137,33 +216,109 @@ converse.initialize({
6. **hairpin NAT** — нельзя тестировать свой pub IP с самого сервера.
7. **Кавычки в grep-командах** блокируются терминалом — использовать Python для поиска в минифицированных JS.
8. **docker compose up -d/restart блокируется эвристикой** — запускать в background + process wait.
+9. **Converse v14 = ESM**: без `type="module"` и полного /dist — `import.meta` SyntaxError, пустая страница.
+10. **DNS-кэш systemd-resolved**: после смены A-записи `resolvectl flush-caches` не помогает —
+ только `sudo systemctl restart systemd-resolved`.
+11. **openssl 3 не умеет `-starttls xmpp`** (XMPP-STARTTLS) — проверять TLS на 5222/5269 только python-скриптом.
+12. **dpkg прерван / apt exit 127** — сломан `chromium-browser` (snap-transitional); чинить `dpkg --configure -a` + переустановка.
+13. **git: .venv попал в историю** — venv НЕ коммитить (в .gitignore), удалять из истории
+ filter-branch + force-push (репо похудел с 22MB до 216K).
-## 7. TODO (следующие шаги)
-> ⚡ Текущий блокер и пошаговый план: **[STATUS.md](STATUS.md)** — пустая страница Converse.js.
+## 7. HTTP Upload (XEP-0363) — mod_http_upload
-- [ ] **Починить пустую страницу веб-клиента** (см. 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).
+**Зачем**: отправка файлов (фото, книги бота) в чаты. Converse умеет из коробки через disco-фичу
+`urn:xmpp:http:upload:0`.
-## 8. Полезные команды
+**Модуль**: в docker-образе Prosody 0.11.9 модуля НЕТ (и GitHub/hg/CDN с bigbox недоступны).
+Установка — Ubuntu-пакет:
+```bash
+sudo apt-get install -y prosody-modules
+cp -r /usr/lib/prosody/modules/mod_http_upload /opt/icq/modules/
+```
+(./modules монтируется в контейнер как /etc/prosody/modules.)
+
+**Конфиг** (в prosody.cfg.lua, как **Component**, НЕ в modules_enabled!):
+```lua
+-- HTTPS-порт для mod_http_upload (требует TLS)
+https_ports = { 5281 }
+https_interfaces = { "0.0.0.0" }
+ssl = { key = "/etc/prosody/certs/nixg.ru.key"; certificate = "/etc/prosody/certs/nixg.ru.crt" }
+
+Component "upload.nixg.ru" "http_upload"
+ http_upload_file_size_limit = 10 * 1024 * 1024 -- 10 MB
+ http_upload_expire_after = 7 * 24 * 60 * 60 -- 7 дней
+ http_upload_require_authentication = true
+ http_upload_path = "/var/lib/prosody/http_upload"
+ http_external_url = "https://upload.nixg.ru" -- внешний URL (без :5281!)
+```
+
+**Засады**:
+- Модуль требует TLS: «File upload MUST happen with TLS but it isn't enabled» →
+ включить https_ports={5281} + глобальный ssl с LE-сертом.
+- Лимит 100 MB → Prosody режет до 10485760 B (потолок HTTP-парсера) — ставить ≤10 MB.
+- `http_external_url` обязателен, иначе клиент получит URL с :5281 (недоступный снаружи).
+
+**Caddy (vps02)**:
+```
+upload.nixg.ru {
+ reverse_proxy 10.8.0.2:5281 {
+ header_up Host {host}
+ transport http {
+ tls_insecure_skip_verify # серт backend'а — для nixg.ru, не upload.nixg.ru!
+ }
+ }
+}
+```
+Без skip_verify Caddy рвёт соединение (502): backend-серт prosody — SAN nixg.ru+xmpp.nixg.ru,
+не upload.nixg.ru. Внутри WG-туннеля это безопасно.
+
+**DNS**: A `upload.nixg.ru` → 87.242.100.206 (Jino).
+
+**Проверка** (slixmpp 1.17 + aiohttp в .venv):
+```python
+# disco: фичи urn:xmpp:http:upload (+:0) на upload.nixg.ru
+url = await bot.plugin["xep_0363"].upload_file(
+ filename="test.txt", size=len(payload), content_type="text/plain",
+ input_file=io.BytesIO(payload), timeout=25)
+# → https://upload.nixg.ru/upload//test.txt ; GET → 200, содержимое цело
+```
+Файлы хранятся: `/opt/icq/data/http_upload//` (в контейнере /var/lib/prosody/http_upload).
+
+## 8. Проверка TLS (c2s/s2s) python-скриптом
+
+openssl 3 НЕ поддерживает `-starttls xmpp` (даёт ложную тревогу «Cipher is NONE / no peer cert»).
+Правильная проверка — вручную:
+```python
+import socket, ssl
+for port in (5222, 5269):
+ s = socket.create_connection(("87.242.100.206", port), 10)
+ s.send(b"")
+ print(port, "->", s.recv(4096)[:200]) # features:
+ s.send(b"")
+ print(port, "->", s.recv(4096)[:100]) #
+ ctx = ssl.create_default_context()
+ tls = ctx.wrap_socket(s, server_hostname="nixg.ru")
+ print(port, "TLS:", tls.version(), tls.getpeercert()["issuer"])
+ tls.close()
+# Ожидание: TLSv1.3, issuer Let's Encrypt
+```
+
+## 9. Полезные команды
```bash
cd /opt/icq
docker compose ps
-docker logs icq-prosody --tail 50
+docker logs icq-prosody --tail 50 # для логов: файлы ./logs/prosody.{log,err}
docker exec icq-prosody prosodyctl check config
-docker exec icq-prosody prosodyctl register admin chat.nixg.ru ПАРОЛЬ
-# рестарт
+docker exec icq-prosody prosodyctl register admin nixg.ru 'ПАРОЛЬ'
docker restart icq-prosody
-# проверка WS рукоятки
-python3 -c "...socket..."
-```
\ No newline at end of file
+# внешняя проверка
+python3 -c "import socket,ssl; ..." # см. раздел 8
+curl -sS https://upload.nixg.ru/upload
+```
+
+## 10. TODO (следующие шаги)
+- [ ] OMEMO (сквозное шифрование, XEP-0384) — клиенты Converse уже умеют (libomemo в dist); проверить на сервере.
+- [ ] Бот-книгоискатель (slixmpp + OPDS) — Этап 3 PRD. venv уже создан (/opt/icq/.venv: aiohttp, slixmpp).
+- [ ] Мосты mautrix-telegram / mautrix-whatsapp — Этап 4 (нужен Prosody mod_component / external component).
+- [ ] Push-уведомления APNs/FCM (публичный push-шлюз для Prosody).
+- [ ] Продление LE-серта nixg.ru — 2026-10-15 10:00 (cron Hermes, job ca2a23a34905); процедура в разделе 4.
\ No newline at end of file