[2026-09-15] eagle: family/how-to/truenas-infrastructure.md personal/tech/vless-space-subscription-egress.md personal/tech/xray-outbound-subscription-3xui.md personal/tech/xray-reverse-tunnel-kraken-truenas.md

This commit is contained in:
Alexey Martemyanov
2026-09-15 16:13:51 +06:00
parent 65f515bf51
commit 05464f090a
4 changed files with 187 additions and 12 deletions
@@ -23,11 +23,22 @@ related:
# Xray outbound из внешней подписки в 3x-ui (`vless-space`)
> ## ❌ Статус 2026-09-15 (финал): НЕ ПРИМЕНЕНО, БД ОТКАЧЕНА
> Артефакты собраны и проверены в песочнице. Применение отбилось **семь раз** — **3x-ui не даёт править себя снаружи**. БД откачена Alex'ом из бэкапа, система в исходном состоянии. Задача ждёт доступа к панели.
> Разбор контейнера и клиентов — в [[family/how-to/truenas-infrastructure]] (разделы `xray-admin`). Полная история попыток — [[personal/tech/vless-space-subscription-egress]].
> ## ✅ ОБНОВЛЕНО 2026-09-15 (вечер): БАРЬЕР ПРЕОДОЛЁН — `xui_v7.db` ЗАЛИТА УСПЕШНО
>
> **Правило взаимодействия (Alex, 2026-09-15):** команды для Alex — **плоские однострочники без `ssh … '…'`-обёртки**, **без `sleep N`**, без `execute_code`/python. Alex выполняет их сам.
> **Прежний вывод «клиентов через SQLite добавить нельзя» — ОПРОВЕРГНУТ.** `xui_v7.db` залита, и клиент появился:
>
> | Проверка | Результат |
> |---|---|
> | `in-10095-tcp` clients | ✅ `user1,kraken-user,vless-space` **одной строкой** (без дубля, без порта 10096) |
> | outbounds | ✅ 13 |
> | подписка `/sub/24df9391356b48ff` | ✅ **HTTP 200** — QR появился |
> | `kraken-user` | ✅ **не отвалился** |
>
> **Что было настоящим барьером (а не «внутренняя память 3x-ui»):** в попытках 5-6 инбаунд `in-10095-tcp` лежал **внутри `xrayTemplateConfig`**. 3x-ui мёржит шаблон с таблицей `inbounds`, шаблонная версия того же тега перебивает панельную → клиенты исчезают. Как только инбаунд убран из шаблона (попытка 7) — SQLite-путь **работает штатно**, клиент и подписка в норме.
>
> **Остаточная проблема — НЕ в клиенте, а в стратегии балансировщика:** egress `vless-space` не идёт из-за `leastPing` (см. §«leastLoad»). Клиент создан, подписка работает, маршрут — нет. Собран `xui_v7_load.db` (`leastPing`→`leastLoad`), залит не был.
>
> ⛔ **Правило взаимодействия (Alex, 2026-09-15):** команды — **плоские однострочники без `ssh … '…'`-обёртки**, **без `sleep N`**, **без кириллицы в bash**, без `execute_code`/python. Alex выполняет их сам.
## Задача
@@ -46,9 +57,30 @@ related:
| **Развернуть серверы подписки в обычные outbound'ы** | Не зависит от версии панели. Работает сразу после `docker start`. |
| **НЕ полагаться на `inbounds` в SQL — только панель** | 🔴 **Урок сессии:** клиенты инбаунда через SQLite снаружи **не применяются**. 3x-ui держит БД открытой, пишет в `-wal`, и при рестарте берёт список клиентов из своей внутренней памяти, а не из файла. Четыре подхода (INSERT в `clients`, правка `inbounds.settings`, фикс `NULL`, дописать `security`) — все отбились. |
| **НЕ класть существующий инбаунд в `xrayTemplateConfig`** | 🔴 **Урок 6-й попытки (2026-09-15):** попытка протащить `in-10095-tcp` с тремя клиентами внутрь шаблона **сломала `kraken-user`** — 3x-ui мёржит шаблон с таблицей `inbounds`, и шаблонная версия того же тега перебивает панельную (клиенты исчезают, egress-правило по email не срабатывает). В шаблоне — **только outbound'ы/правила/балансировщик**. Инбаунды — эксклюзив панели. |
| **`leastPing` + `observatory`** | Alex: «авто брать» — все 10 серверов в пул, живой выбирается сам. `observatory` **обязателен**, иначе балансировщик не знает задержек. ✅ Оба ключа приняты Xray 26.7.28. |
| ~~**`leastPing` + `observatory`**~~ | 🔴 **ОПРОВЕРГНУТО 2026-09-15 (вечер).** `leastPing` в Xray 26.x **не работает** — балансировщик не создаётся, `non existing outTag: space-balancer` в рантайме. Рабочая стратегия: **`leastLoad`**. См. §«leastLoad» ниже. |
| **Правило по `user`, а не по клиентскому id** | Образец взят с рабочего `kraken-user-via-reverse`: Xray матчит клиента inbound'а по `email`. |
## 🔴 ОБЯЗАТЕЛЬНО: `leastLoad`, а НЕ `leastPing` (проверено 2026-09-15)
```jsonc
// ✅ РАБОТАЕТ в Xray 26.3.27 / 26.7.28
"balancers": [ { "tag": "space-balancer", "selector": ["space-"],
"strategy": { "type": "leastLoad" } } ]
"observatory": { "subjectSelector": ["space-"],
"probeUrl": "https://www.google.com/generate_204",
"probeInterval": "30s", "enableConcurrency": true }
```
```jsonc
// ❌ НЕ РАБОТАЕТ — тег не регистрируется, трафик молча теряется
"strategy": { "type": "leastPing" }
```
**A/B на минимальном конфиге (docker, TrueNAS):** `leastPing``non existing outTag`; `burstObservatory + leastPing` → то же; **`leastLoad` + `observatory``the outbound space-01 is alive:0.249`, `taking detour [space-02]`** ✅.
> ⚠️ **`xray -test` печатает `Configuration OK` и для `leastPing`** — валидация конфига ошибку НЕ ловит. Проверять только рантайм-логом + живым запросом.
> ⚠️ **`chmod 644` конфига** перед `docker run -v …` — иначе `permission denied`.
## Питфоллы (все проверены фактом)
1. **🔴 SSH к TrueNAS обрывается на `docker stop xray-admin`** — `closed by remote host`. Контейнер **сам поднялся** (`restart: unless-stopped`), БД не пострадала. Останавливать и применять SQL **одной короткой командой**, не серией вызовов.
@@ -135,7 +167,65 @@ ls -la /app/bin/config.json /etc/x-ui/x-ui.db
1. **`x-ui.db` не обновлялся** — правки, применённые в 15:53/16:02/16:06, в основной файл **не попадали**. 3x-ui держит базу и пишет в `-wal`; удаление `-wal` перед правкой уничтожало актуальные данные, после правки он перезаписывал файл из своего состояния.
2. **`config.json` перегенерирован из внутренней памяти** 3x-ui, а не из файла — отсюда парадокс: **13 outbound'ов применились** (шаблон `settings` он читает), **третий клиент исчез** (список клиентов берёт из своей памяти).
> 🔴 **ВЫВОД: `inbounds` и клиентов 3x-ui через SQLite снаружи править НЕЛЬЗЯ.** Работает только `settings.xrayTemplateConfig` (outbound'ы/правила/балансировщики). Всё, что касается клиентов инбаунда, идёт **исключительно через панель** (`/panel/api/inbounds/update/:id`) — а для API нужен **API-токен**.
> 🔴 **ВЫВОД (исправлен 2026-09-15 вечером): `inbounds` и клиентов 3x-ui через SQLite править МОЖНО — но при двух жёстких условиях.** Прежний вывод «нельзя вообще» был **неверен**: он был основан на сборках, где инбаунд лежал внутри `xrayTemplateConfig` (см. урок 6) и где копию БД клали рядом со старыми `-wal`/`-shm`.
>
> **Условия, при которых SQLite-путь РАБОТАЕТ (проверено заливкой `xui_v7.db`):**
> 1. Инбаунд `in-10095-tcp` **НЕ должен присутствовать в `xrayTemplateConfig`** (в шаблоне — только `api`).
> 2. БД правится **локально на Mac**, затем кладётся файлом при остановленном контейнере, с `rm -f x-ui.db-wal x-ui.db-shm` и `chown 950:root`.
>
> Результат: клиент появился в `config.json`, подписка отдала HTTP 200, `kraken-user` не пострадал.
>
> **Что при этом всё равно требует внимания:** `xray -test` не ловит семантические ошибки маршрутизации (пример: `leastPing` даёт `Configuration OK`, но в рантайме `non existing outTag`). Рантайм-лог обязателен.
### Рецепт, который сработал (`xui_v7.db`, 2026-09-15)
Сборка **от оригинала** (`xui_copy.db`), не от промежуточных копий:
```bash
# 1) шаблон: ТОЛЬКО outbounds + rule + balancer + observatory (инбаунд api уже есть)
sqlite3 xui_v6.db "SELECT value FROM settings WHERE key='xrayTemplateConfig';" | \
jq '[.outbounds[] | select(.tag | startswith("space-"))]' > /tmp/space_outs.json
jq --slurpfile sp /tmp/space_outs.json '.outbounds += $sp[0]
| .routing.rules += [{"type":"field","user":["vless-space"],"outboundTag":"space-balancer","ruleTag":"vless-space-via-subscription"}]
| .routing.balancers = [{"tag":"space-balancer","selector":["space-"],"strategy":{"type":"leastLoad"}}]
| .observatory = {"subjectSelector":["space-"],"probeUrl":"https://www.google.com/generate_204","probeInterval":"30s","enableConcurrency":true}' \
/tmp/base_orig.json > /tmp/tpl_final.json
# ⛔ .inbounds НЕ трогать — там остаётся только api
# 2) клиент в ТРИ места: inbounds.settings JSON, clients, client_inbounds
sqlite3 xui_v7.db "UPDATE inbounds SET settings = json_set(settings, '$.clients', json('<новый_массив>')) WHERE id=1;"
sqlite3 xui_v7.db "INSERT INTO clients (id,email,sub_id,uuid,password,auth,flow,security,reverse,...) VALUES (5,'vless-space',...);"
sqlite3 xui_v7.db "INSERT INTO client_inbounds (client_id,inbound_id,flow_override,created_at) VALUES (5,1,'',...);"
# client_traffics НЕ трогать — в оригинале пусто, 3x-ui заполняет сам
# 3) обязательная гигиена перед заливкой
sqlite3 xui_v7.db "PRAGMA journal_mode=DELETE;"; rm -f xui_v7.db-wal xui_v7.db-shm
sqlite3 xui_v7.db "PRAGMA integrity_check;" # → ok
```
**Контроль качества:** `diff <(sqlite3 xui_copy.db .dump) <(sqlite3 xui_v7.db .dump)` — должно быть ровно 4 изменения (клиент в `inbounds.settings`, строка в `clients`, строка в `client_inbounds`, шаблон + `sqlite_sequence`). Всё лишнее — ошибка.
**Заливка на TrueNAS:**
```bash
scp ~/tmp-xray-space/xui_v7.db truenas_admin@mallexxx.duckdns.org:/tmp/xui_v7.db
docker stop xray-admin
docker run --rm -v /mnt/RED_2TB/docker/xray-admin:/data -v /mnt/RED_2TB/docker/backups:/b -v /tmp:/src alpine sh -c 'TS=$(date +%Y%m%d-%H%M%S); mkdir -p /b/xray-admin-v7-$TS; cp -av /data/x-ui.db /b/xray-admin-v7-$TS/; rm -f /data/x-ui.db-wal /data/x-ui.db-shm; cp -av /src/xui_v7.db /data/x-ui.db; chown 950:root /data/x-ui.db; echo BACKUP=/b/xray-admin-v7-$TS'
docker start xray-admin
```
**Проверка после старта:**
```bash
docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -r '.inbounds[] | "\(.tag) port=\(.port) clients=\([.settings.clients[]?.email]|join(\",\"))"'
docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -c '{out:(.outbounds|length),bal:.routing.balancers[0].strategy}'
docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -r '.inbounds[].tag' # в шаблоне должен быть ТОЛЬКО api
curl -sk -o /dev/null -w "sub: %{http_code}\n" https://vpn-panel.mallexxx.duckdns.org/sub/24df9391356b48ff
```
### Историческая (ошибочная) формулировка — оставлена для контекста
> Работает только `settings.xrayTemplateConfig` (outbound'ы/правила/балансировщики). Всё, что касается клиентов инбаунда, идёт **исключительно через панель** (`/panel/api/inbounds/update/:id`) — а для API нужен **API-токен**.
**Почему это было неверно:** «внутреннюю память 3x-ui» обвиняли зря. Настоящая причина — инбаунд внутри шаблона. См. §«БАРЬЕР ПРЕОДОЛЁН» в шапке.
> **Как это делали раньше:** `kraken-user` добавлен 2026-09-02 **при участии панели** — в Zulip-записи прямо сказано «Temporary full-admin API token **не создавался**; `api_tokens` осталась пустой» + «Verified with the local `xray-test-client`». Это и есть подтверждение, что путь был через API панели, а не через SQL.