From 83640139889a07c68f00e1b565aee7bd573eb759 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 15 Sep 2026 14:54:31 +0600 Subject: [PATCH] [2026-09-15] eagle: family/how-to/truenas-infrastructure.md personal/tech/vless-space-subscription-egress.md --- family/how-to/truenas-infrastructure.md | 2 +- .../tech/vless-space-subscription-egress.md | 263 ++++++++++++++++++ 2 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 personal/tech/vless-space-subscription-egress.md diff --git a/family/how-to/truenas-infrastructure.md b/family/how-to/truenas-infrastructure.md index 67dfb328..48624f2a 100644 --- a/family/how-to/truenas-infrastructure.md +++ b/family/how-to/truenas-infrastructure.md @@ -13,7 +13,7 @@ > - **⚠️ «Камера на TrueNAS» — ИСПРАВЛЕНО (2026-09-14, вечер-10) + ✅ РЕШЕНО ОКОНЧАТЕЛЬНО (вечер-13):** прежняя запись «камера = USB-вебка на t610, upstream `cam.*:8090` к камере отношения не имеет» — **❌ НЕВЕРНА**. Поиск в `Caddyfile.bak` доказал: **`cam.mallexxx.duckdns.org → 192.168.2.197:8090` — ЭТО И БЫЛА камера** на TrueNAS: отдельный HTTP-MJPEG-сервис (`ustreamer`/`mjpg-streamer`, порт 8090 — канон для «USB-вебка → MJPEG»). **Контейнер УТРАЧЕН** при пересоздании пула (локальный образ не пережил `.ix-apps`; из живого Caddyfile строка удалена — `cam.*` больше нет). **✅ ФИНАЛ (вечер-13):** вебка `046d:0825` физически в **t610**, работает через **аддон `a889bffc_go2rtc-hardware`** → RTSP H.264 `rtsp://192.168.2.176:8554/usb_camera_h264` → **Generic Camera** `camera.192_168_2_176` (зона `kotelnaia`, `unique_id`, **WebRTC работает**, поворот `#rotate=90`). Схема `camera: platform: ffmpeg` в Core — **❌ ОТВЕРГНУТА** (`Resource busy` + нет `unique_id`). Подробно — [[family/how-to/home-automation]] §5-кватер-И-3/И-6. > - **Не перенесено с TrueNAS:** ~~погашение TrueNAS-стека (Этап 4 п.6 — заблокировано: Caddy на TrueNAS держит точку входа)~~ → **✅ ЗАКРЫТО 2026-09-14 (ночь): стек автоматизации ПОГАШЕН, Caddy остался на TrueNAS (так и задумано).** См. §5-кватер-Л в [[family/how-to/home-automation]]. -> Обновлено: 2026-09-15 (ночь-2: **`vless-space` — артефакты СОБРАНЫ И ПРОВЕРЕНЫ, применение в живую БД остановлено** (SSH к TrueNAS оборвался ровно на `docker stop`; контейнер сам поднялся, БД не тронута). Путь через БД подтверждён как рабочий (доступ к панели НЕ нужен), шаг «нужны креды панели» **снят как неверный**. Готовы `apply_space.sql` (проверен в песочнице), `template_config.new.json` (10 outbound + правило + `space-balancer` leastPing), UUID клиента. Разделы `xray-admin` обновлены: питфолл `x-ui.db`+WAL, `xrayTemplateConfig` как точка правки, `outbound_subscriptions` **НЕ задействуется** (выбран путь «outbound'ы из подписки, развёрнутые в шаблон»). Ранее 2026-09-15 (вечер: черновой план `vless-space`, шаг 1 — подписка проверена). Ранее: 2026-09-15 (subscription-канал 3x-ui: ссылки `/sub/` работают, base64 vless-список). Ранее: 2026-09-02 (vpn.mallexxx:443 = РАБОЧИЙ Xray-сервер, проверено end-to-end; vless-proxy outbound мёртв) +> Обновлено: 2026-09-15 (ночь-3: **`vless-space` — попытка 1 применения на живую БД ПРОВАЛИЛАСЬ (`database disk image is malformed`), причина найдена и исправлена.** Причина: SQL применялся к **копии** `x-ui.db`, созданной без её `-wal`/`-shm`, и копия клалась рядом со **старыми** WAL/SHM → SQLite применяла чужой журнал. 🔴 **ПИТФОЛЛ:** для WAL-базы нельзя копировать только `.db`; либо работать напрямую при остановленном процессе, либо удалять `-wal`/`-shm` до открытия. Исправленная процедура — в `~/tmp-xray-space/apply_space_v2.sql` (с `PRAGMA journal_mode=DELETE` в начале и `WAL` в конце, запись НАПРЯМУЮ в `/data/x-ui.db`). **✅ Также в этой сессии: compose-файл `xray-admin` ВОССТАНОВЛЕН** из `docker inspect` (утрачен 2026-09-01, см. [[family/plans/reverse-xray-3xui-kraken]]). Полный разбор задачи — [[personal/tech/vless-space-subscription-egress]]. Ранее 2026-09-15 (ночь-2: артефакты `vless-space` собраны и проверены в песочнице; путь через БД подтверждён рабочим, шаг «нужны креды панели» **снят как неверный**). Ранее 2026-09-15 (вечер: черновой план `vless-space`, подписка проверена). Ранее: 2026-09-15 (subscription-канал 3x-ui: ссылки `/sub/` работают, base64 vless-список). Ранее: 2026-09-02 (vpn.mallexxx:443 = РАБОЧИЙ Xray-сервер, проверено end-to-end; vless-proxy outbound мёртв) > ## ⚠️ ПРИОРИТЕТ 2026-09-01: VPS qentra.top УДАЛЁН → vless-proxy и сетевые туннели сломаны > - **VPS `91.207.28.205` больше НЕ существует** (вся его Xray/OpenVPN/WG-инфраструктура утрачена). Подробно: [[family/how-to/vps-qentra]]. diff --git a/personal/tech/vless-space-subscription-egress.md b/personal/tech/vless-space-subscription-egress.md new file mode 100644 index 00000000..f9e26ea3 --- /dev/null +++ b/personal/tech/vless-space-subscription-egress.md @@ -0,0 +1,263 @@ +--- +title: vless-space — клиент 3x-ui с egress через external-подписку +created: 2026-09-15 +updated: 2026-09-15 +type: tech +namespace: personal +tags: [xray, 3x-ui, x-ui, subscription, outbound, profilegrid, truenas, space] +confidence: high +status: in-progress +related: + - "[[family/plans/reverse-xray-3xui-kraken]]" + - "[[personal/tech/xray-reverse-tunnel-kraken-truenas]]" + - "[[family/how-to/truenas-infrastructure]]" +--- + +# `vless-space` — клиент 3x-ui, egress через внешнюю подписку + +> Задача поставлена Alex 2026-09-15. **Статус: НЕ применено** — SQL готов и проверен в песочнице, применение на живой БД провалилось (см. «Провал попытки 1») и требует повтора по исправленной процедуре. + +## Что хотели + +Добавить в 3x-ui (`xray-admin` на TrueNAS) **третьего клиента** `vless-space`, чей трафик уходит в интернет **не** через TrueNAS (`direct`) и **не** через Кра́кена (`via-kraken`), а через **внешнюю подписку** провайдера `profilegrid.net` (28 строк → 10 уникальных серверов). + +Требование Alex (дословно): «не трогая `user1` и `kraken-user` (reverse)», «авто брать» — т.е. **балансировщик по всем серверам подписки**, а не один конкретный. + +## Ключевое различие, которое я сначала понял неверно + +**Подписка — функция КЛИЕНТА, не сервера.** 3x-ui — это сервер (принимает входящие), он **не может** «ходить через чужую подписку» в inbound-режиме. + +**НО** у 3x-ui есть `outbound_subscriptions` — функция, которая **скачивает подписку и превращает её серверы в свои outbound'ы**. Тогда сервер может: принять клиента → отправить его трафик через сервер из подписки. Это и есть нужный механизм. + +**Схема:** + +```text +[клиент vless-space] → vpn.mallexxx.duckdns.org:443 → Caddy → xray-admin:10095 + │ + routing: user=[vless-space] + ▼ + space-balancer (leastPing) + ▼ + 10 × VLESS+REALITY outbound (mirrorgrid.net) + ▼ + интернет +``` + +## Подписка (источник серверов) + +- **URL:** `https://go.profilegrid.net/sub/djMsNDc3MjgsMTc4OTQ1OTg5NA.WjZEVaps9Xl3s5dkD6x7KRyNIGi7MlDUNAVPMKBUOdk` +- **Проверено 2026-09-15 с TrueNAS:** `HTTP 200`, 12 412 байт, `text/plain`, base64 +- **28 строк** `vless://`, из них **10 уникальных серверов** (остальное — дубли одного UUID на тех же хостах) +- **Один UUID на весь список:** `c67ce742-d94f-4e56-889e-9f894ae59940` (на разных хостах встречаются варианты `…-4e56-…` и `…-0008-…`) +- **Формат:** VLESS + `security=reality` + `type=tcp` + `flow=xtls-rprx-vision` + `fp=firefox` +- **Уникальность** определяется парой `host` + `pbk` (publicKey) + `sid` (shortId) — у каждого сервера свои. + +### 10 уникальных серверов + +| tag | имя | host | +|---|---|---| +| `space-01` | 🇩🇪 Germany | `edge-de.mirrorgrid.net:443` | +| `space-02` | 🇱🇻 Latvia \| YT | `edge-lv.mirrorgrid.net:443` | +| `space-03` | 🇳🇱 Netherlands \| YT | `edge-nl.mirrorgrid.net:443` | +| `space-04` | 🇳🇱 Netherlands #2 | `packages-nl.repocache.com:443` | +| `space-05` | 🇪🇪 Estonia \| YT | `edge-ee.mirrorgrid.net:443` | +| `space-06` | 🇸🇪 Sweden \| YT | `packages-se.repocache.com:443` | +| `space-07` | 🇲🇩 Moldova \| Torrent | `md.repodelivery.com:443` | +| `space-08` | 🇵🇱 Poland \| YT | `edge-pl.mirrorgrid.net:443` | +| `space-09` | 🇫🇷 France \| YT | `edge-fr.mirrorgrid.net:443` | +| `space-10` | 🇺🇸 USA \| YT | `edge-us.mirrorgrid.net:443` | + +## Артефакты (на Mac) + +``` +~/tmp-xray-space/ +├── apply_space_v2.sql ← ИСПРАВЛЕННЫЙ SQL (применять этот) +├── apply_space.sql ← первая (битая) версия — НЕ использовать +├── make_sql.py ← генератор v1 (битый) +├── make_sql_v2.py ← генератор v2 (с PRAGMA journal_mode) +├── build_template.py ← сборка xrayTemplateConfig из подписки +├── sub_decoded.txt ← развёрнутая подписка (28 строк vless://) +├── space_outbounds.json ← 10 новых outbound (справочно) +├── template_config.new.json ← новый xrayTemplateConfig (13 outbound) +├── template_config.json ← исходный шаблон из БД +├── runtime_config.json ← снимок /app/bin/config.json +├── xui_copy.db ← копия БД до правок +└── docker-compose.yml ← восстановленный compose xray-admin +``` + +## Параметры клиента + +| | | +|---|---| +| email | `vless-space` | +| UUID | `a792c483-07e2-4723-9c50-78054c0abc07` | +| subId | `24df9391356b48ff` | +| sub-ссылка | `https://vpn-panel.mallexxx.duckdns.org/sub/24df9391356b48ff` | +| inbound | `in-10095-tcp` (id=1), port 10095, VLESS-WS | +| egress | `space-balancer` → 10 серверов подписки | + +## Изменения в `xrayTemplateConfig` + +Шаблон хранится в БД: таблица `settings`, ключ **`xrayTemplateConfig`**. 3x-ui генерирует из него `/app/bin/config.json` при старте. + +Добавлено (существующее не тронуто): + +**1. Routing-правило** (после `kraken-user-via-reverse`): + +```json +{ + "type": "field", + "user": ["vless-space"], + "outboundTag": "space-balancer", + "ruleTag": "vless-space-via-subscription" +} +``` + +**2. Балансировщик:** + +```json +"balancers": [ + { + "tag": "space-balancer", + "selector": ["space-"], + "strategy": { "type": "leastPing" } + } +] +``` + +**3. Observatory** (нужен для `leastPing`): + +```json +"observatory": { + "subjectSelector": ["space-"], + "probeUrl": "https://www.google.com/generate_204", + "probeInterval": "30s", + "enableConcurrency": true +} +``` + +**4. 10 outbound'ов** `space-01`…`space-10`, формат: + +```json +{ + "tag": "space-NN", + "protocol": "vless", + "settings": { "vnext": [ { "address": "", "port": 443, "users": [ + { "id": "c67ce742-…", "encryption": "none", "flow": "xtls-rprx-vision" } ] } ] }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "serverName": "", "fingerprint": "firefox", + "publicKey": "", "shortId": "", "spiderX": "" + } + } +} +``` + +**Итого:** было 3 outbound (`direct`, `blocked`, `via-kraken`) → стало 13. Правил было 4 → стало 5. + +## 🔴 ПРОВАЛ ПОПЫТКИ 1 (2026-09-15) — `database disk image is malformed` + +**Симптом:** после применения `apply_space.sql` запрос к БД вернул `Parse error in 2nd command line argument: database disk image is malformed (11)`. + +**Причина (моя ошибка в процедуре, не в SQL):** + +1. SQL применялся к **копии** базы (`/tmp/x-ui.db.work`), созданной **без** её `-wal`/`-shm`. +2. Копия клалась обратно как `/data/x-ui.db`. +3. Рядом на хосте оставались **СТАРЫЕ** `x-ui.db-wal` и `x-ui.db-shm` от предыдущего процесса. +4. SQLite при открытии увидела WAL-файл, попыталась применить **чужой журнал** к новой базе → `malformed`. + +**Дополнительно:** в SQL не было `PRAGMA journal_mode=DELETE` — WAL-режим не отключался на время записи. + +> 🔴 **ПИТФОЛЛ (повторяемый):** при правке SQLite-базы, работающей в WAL-режиме, **нельзя** копировать только `.db` и класть рядом со старыми `-wal`/`-shm`. Либо работать **напрямую** с базой при остановленном процессе, либо удалять `-wal`/`-shm` до открытия. + +## ✅ ИСПРАВЛЕННАЯ ПРОЦЕДУРА (`apply_space_v2.sql`) + +Отличия от v1: +- `PRAGMA journal_mode=DELETE;` в начале → WAL отключён на время сессии +- `PRAGMA journal_mode=WAL;` в конце → возврат к режиму, которого ждёт 3x-ui +- `INSERT OR REPLACE` вместо `INSERT` (идемпотентность) +- **Работа идёт НАПРЯМУЮ с `/data/x-ui.db`**, не через копию + +### Команды + +```bash +# 1) залить SQL +scp ~/tmp-xray-space/apply_space_v2.sql truenas_admin@mallexxx.duckdns.org:/tmp/ + +# 2) стоп (3x-ui сам сбросит WAL) +ssh truenas_admin@mallexxx.duckdns.org 'docker stop xray-admin' + +# 3) бэкап +ssh truenas_admin@mallexxx.duckdns.org ' +TS=$(date +%Y%m%d-%H%M%S) +docker run --rm -v /mnt/RED_2TB/docker/xray-admin:/d -v /mnt/RED_2TB/docker/backups:/b alpine sh -c " + mkdir -p /b/xray-admin-v2-\$TS && cp -av /d/x-ui.db /b/xray-admin-v2-\$TS/ + echo BACKUP_DIR=/b/xray-admin-v2-\$TS"' + +# 4) применить — ВАЖНО: rm WAL до SQL, работа напрямую +ssh truenas_admin@mallexxx.duckdns.org ' +docker run --rm -v /mnt/RED_2TB/docker/xray-admin:/data -v /tmp:/src alpine sh -c " + apk add --no-cache sqlite >/dev/null 2>&1 + cd /data + rm -f x-ui.db-wal x-ui.db-shm + echo \"before=\$(sqlite3 x-ui.db \\\"PRAGMA integrity_check;\\\")\" + sqlite3 x-ui.db < /src/apply_space_v2.sql + echo \"sql_exit=\$?\" + echo \"after=\$(sqlite3 x-ui.db \\\"PRAGMA integrity_check;\\\")\" + sqlite3 x-ui.db \"select email, sub_id from clients order by id;\" + chown 950:root x-ui.db"' + +# 5) старт +ssh truenas_admin@mallexxx.duckdns.org 'docker start xray-admin && sleep 6 && docker logs --tail 12 xray-admin' + +# 6) проверка +ssh truenas_admin@mallexxx.duckdns.org ' +docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -r ".inbounds[].settings.clients[]?.email" +docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -r ".outbounds | length" +docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -c ".routing.balancers" +curl -sk -o /dev/null -w "HTTP %{http_code}\n" "https://vpn-panel.mallexxx.duckdns.org/sub/24df9391356b48ff"' +``` + +**Ожидаемо:** клиенты `user1`, `kraken-user`, `vless-space` / outbounds `13` / balancer `space-balancer` / `HTTP 200`. + +### Откат + +```bash +# A — из бэкапа шага 3 +ssh truenas_admin@mallexxx.duckdns.org ' +docker stop xray-admin +docker run --rm -v /mnt/RED_2TB/docker/xray-admin:/d -v /mnt/RED_2TB/docker/backups:/b alpine sh -c " + cp -av /b/xray-admin-v2-/x-ui.db /d/x-ui.db + rm -f /d/x-ui.db-wal /d/x-ui.db-shm && chown 950:root /d/x-ui.db" +docker start xray-admin' + +# B — из исходного бэкапа (до всех правок) +# /mnt/RED_2TB/docker/backups/xray-admin-before-vless-space-20260915-012057/ +# (только x-ui.db; -wal/-shm удалить, не копировать) +``` + +`user1` и `kraken-user` **не изменяются ни в одной версии SQL** — откат нужен только если сломалась БД. + +## Открытые риски (не проверено) + +1. **`leastPing` + `observatory`** — могут не поддерживаться 3x-ui 3.7.0 (Xray 26.7.28). Если контейнер не поднимется → `docker logs xray-admin`, откат по A. +2. Если ругается на `subjectSelector` — попробовать старый ключ `selector`. +3. **Ссылка подписки `vpn-panel…/sub/` открыта БЕЗ авторизации** — любой, кто знает `subId`, получит ключ. Кандидат на включение подписки-по-токену в панели (не сделано). + +## Что НЕ сделано / следующие шаги + +- [ ] Применить `apply_space_v2.sql` по исправленной процедуре +- [ ] Проверить end-to-end: тестовый клиент с UUID `vless-space` → `curl` → egress IP ≠ `90.189.160.148` +- [ ] Проверить, что `user1` и `kraken-user` продолжают работать +- [ ] Решить вопрос с авторизацией на sub-ссылках +- [ ] Обновить [[family/how-to/truenas-infrastructure]] (новый клиент + балансировщик) +- [ ] Отдельная задача: `hermes-taiga` ходит через `vless-proxy`, чей outbound указывает на **мёртвый** `v.qentra.top` (VPS удалён). Нужно переключить на `vpn.mallexxx.duckdns.org` (клиент `user1`) или на `vless-space`/Kraken. + +## Связанные заметки + +- [[family/plans/reverse-xray-3xui-kraken]] — reverse-туннель к Кра́кену, история compose-инцидента +- [[personal/tech/xray-reverse-tunnel-kraken-truenas]] — архитектура reverse +- [[family/how-to/truenas-infrastructure]] — инфраструктура TrueNAS, контейнер `xray-admin` +- [[family/how-to/vps-qentra]] — удалённый VPS (объясняет, почему `vless-proxy` мёртв)