Files
obsidian-vault/personal/tech/xray-outbound-subscription-3xui.md
T

221 lines
17 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.
---
title: Xray outbound — внешняя подписка в 3x-ui (vless-space)
created: '2026-09-15'
updated: '2026-09-15'
type: tech
namespace: personal
tags:
- xray
- 3x-ui
- subscription
- outbound
- truenas
- reality
- balancer
confidence: high
status: blocked-no-panel-access
related:
- '[[family/how-to/truenas-infrastructure]]'
- '[[personal/tech/xray-reverse-tunnel-kraken-truenas]]'
- '[[personal/tech/vless-space-subscription-egress]]'
- '[[family/how-to/vps-qentra]]'
---
# 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]].
## Задача
Добавить в `xray-admin` (3x-ui на TrueNAS) **третьего клиента** `vless-space`, чей трафик идёт не напрямую (как `user1`) и не через reverse-Кра́кен (как `kraken-user`), а **через серверы внешней Xray-подписки** `profilegrid.net`, с автоматическим выбором живого сервера. `user1` и `kraken-user` не трогаются.
```
[клиент vless-space] → vpn.mallexxx.duckdns.org:443 → xray-admin
└→ space-01…space-10 (leastPing) → интернет (DE/LV/NL/EE/PL/FR/US/SE/MD)
```
## Ключевые решения и почему
| Решение | Почему |
|---|---|
| **Править `settings.xrayTemplateConfig` в `x-ui.db`, а не `config.json`** | ✅ **Подтверждено фактом:** 3x-ui генерирует `/app/bin/config.json` из БД. `/app/bin/config.json` править бесполезно (перезапишется). Из шаблона применились все 13 outbound'ов + балансировщик. |
| **Развернуть серверы подписки в обычные outbound'ы** | Не зависит от версии панели. Работает сразу после `docker start`. |
| **НЕ полагаться на `inbounds` в SQL — только панель** | 🔴 **Урок сессии:** клиенты инбаунда через SQLite снаружи **не применяются**. 3x-ui держит БД открытой, пишет в `-wal`, и при рестарте берёт список клиентов из своей внутренней памяти, а не из файла. Четыре подхода (INSERT в `clients`, правка `inbounds.settings`, фикс `NULL`, дописать `security`) — все отбились. |
| **`leastPing` + `observatory`** | Alex: «авто брать» — все 10 серверов в пул, живой выбирается сам. `observatory` **обязателен**, иначе балансировщик не знает задержек. ✅ Оба ключа приняты Xray 26.7.28. |
| **Правило по `user`, а не по клиентскому id** | Образец взят с рабочего `kraken-user-via-reverse`: Xray матчит клиента inbound'а по `email`. |
## Питфоллы (все проверены фактом)
1. **🔴 SSH к TrueNAS обрывается на `docker stop xray-admin`** — `closed by remote host`. Контейнер **сам поднялся** (`restart: unless-stopped`), БД не пострадала. Останавливать и применять SQL **одной короткой командой**, не серией вызовов.
2. **WAL:** `docker stop` у 3x-ui корректно закрывает БД и **сливает WAL в основной файл**. Удалять `-wal`/`-shm` можно только **после** остановки. Правка при живом WAL = риск порчи базы.
3. **`sqlite3` внутри контейнера ОТСУТСТВУЕТ** (`which sqlite3` пусто). Использовать: `docker cp` БД на хост, либо хостовый `sqlite3`, либо `alpine`-контейнер.
4. **Дедуп подписки:** 28 строк → **10 уникальных серверов**. Одна подписка отдаёт несколько записей на один хост с разными `pbk`/`sid` (перебор ключей). Ключ дедупа — `host + publicKey + shortId`.
5. **REALITY в outbound:** параметры кладутся в `streamSettings.realitySettings` (`serverName`, `publicKey`, `shortId`, `spiderX`, `fingerprint`), а `flow: xtls-rprx-vision` — в `users[0].flow` (в ссылке он приходит query-параметром).
6. **`spx` в ссылке URL-энкодирован** (`%2F…`) — обязателен `unquote`, иначе Xray падает на `spiderX`.
7. **`routing.balancers.selector` — префиксный матч:** `["space-"]` подхватывает `space-01``space-10`.
8. **Правки в `routing.rules` добавлять В КОНЕЦ** — существующие правила (`api`, `geoip:private→blocked`, `bittorrent→blocked`, `kraken-user→via-kraken`) должны сохранить приоритет.
## Шаблон конфига — что добавляется
Существующее (outbound'ы `direct`/`blocked`/`via-kraken`, правила, inbound) **не трогается**. Добавляется:
```jsonc
// в outbounds[]
{ "tag": "space-01", "protocol": "vless",
"settings": { "vnext": [{ "address": "edge-de.mirrorgrid.net", "port": 443,
"users": [{ "id": "c67ce742-d94f-4e56-889e-9f894ae59940",
"encryption": "none", "flow": "xtls-rprx-vision" }] }] },
"streamSettings": { "network": "tcp", "security": "reality",
"realitySettings": { "serverName": "edge-de.mirrorgrid.net",
"fingerprint": "firefox", "publicKey": "<pbk>",
"shortId": "<sid>", "spiderX": "/" } } }
// … space-02 … space-10
// в routing.rules[] (В КОНЕЦ)
{ "type": "field", "user": ["vless-space"],
"outboundTag": "space-balancer", "ruleTag": "vless-space-via-subscription" }
// новый ключ routing.balancers
[ { "tag": "space-balancer", "selector": ["space-"],
"strategy": { "type": "leastPing" } } ]
// новый ключ observatory (ОБЯЗАТЕЛЕН для leastPing)
{ "subjectSelector": ["space-"],
"probeUrl": "https://www.google.com/generate_204",
"probeInterval": "30s", "enableConcurrency": true }
```
## Артефакты (Mac)
Рабочая папка: **`~/tmp-xray-space/`**
| Файл | Назначение |
|---|---|
| `build_template.py` | парсер `vless://` → xray-outbound; сборка шаблона (outbounds + rule + balancer + observatory) |
| `make_sql.py` | генератор `apply_space.sql` по живой БД |
| `apply_space.sql` | **готовый SQL** для применения (3 операции) |
| `template_config.new.json` | итоговый шаблон, 11 140 б, JSON VALID |
| `space_outbounds.json` | 10 новых outbound'ов (отчёт) |
| `sub_decoded.txt` | подписка, base64 развёрнут — 28 строк |
| `xui_copy.db` / `test_apply.db` | копия живой БД / песочница с применённым SQL |
**Параметры клиента (сгенерированы):**
| | |
|---|---|
| email | `vless-space` |
| UUID | `a792c483-07e2-4723-9c50-78054c0abc07` |
| `subId` | `24df9391356b48ff` |
| sub-ссылка | `https://vpn-panel.mallexxx.duckdns.org/sub/24df9391356b48ff` |
## 🔴 Почему внешняя правка НЕ работает (главный урок сессии)
Проверено четырьмя подходами, все отбились:
| # | Подход | Результат |
|---|---|---|
| 1 | SQL в **копию** БД → копия на место старых `-wal`/`-shm` | `database disk image is malformed` |
| 2 | SQL v2 напрямую (`PRAGMA journal_mode=DELETE`, `rm -wal` до правки) | SQL прошёл, БД корректна → в `config.json` клиента нет |
| 3 | Фикс `auth`/`reverse` (NULL → `''`) | применено → клиента нет |
| 4 | Дописать `security:"auto"` в `inbounds.settings` | применено (`3 × security:auto`) → клиента нет |
**Что закрыло вопрос:**
```
ls -la /app/bin/config.json /etc/x-ui/x-ui.db
→ config.json = 2978 б (16:06) ← схлопнулся с 11 990
→ x-ui.db = 262144 б (15:00) ← СТАРАЯ дата, правки не попали
```
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-токен**.
> **Как это делали раньше:** `kraken-user` добавлен 2026-09-02 **при участии панели** — в Zulip-записи прямо сказано «Temporary full-admin API token **не создавался**; `api_tokens` осталась пустой» + «Verified with the local `xray-test-client`». Это и есть подтверждение, что путь был через API панели, а не через SQL.
## §6 Как довести до конца — ждёт доступа к панели
**Блокер:** логин панели `vpn-admin`, пароль — bcrypt-хэш (нечитаем). `secret` из БД (`RuERf2DTzPTw3CoMcVjk7tGXKCQOk0Z4`) для логина дал 403, API-путь — 404.
**Вариант А — панель вручную** (2 минуты):
```
https://vpn-panel.mallexxx.duckdns.org/ (логин vpn-admin)
→ Inbounds → vless-ws (10095) → Edit
→ Clients → Add Client:
Email: vless-space
ID: a792c483-07e2-4723-9c50-78054c0abc07
Sub ID: 24df9391356b48ff
Enable: ✅
→ Save → Restart Xray (в шапке)
```
Затем routing-правило на `space-balancer` — тоже через панель (Xray → Routing).
**Вариант Б — API-токен:** Settings → API Tokens → Create, отдать агенту → он делает всё сам через `/panel/api/inbounds/update/1`.
**Готовый SQL на Mac (не применён):** `~/tmp-xray-space/apply_final.sql` (13 447 б) — делает 4 операции одной транзакцией: клиент в `clients`, клиент в `inbounds.settings`, шаблон с 10 серверами + правило + балансировщик, запись в `outbound_subscriptions`. ⚠️ Учитывая урок сессии, **сработавшей будет только 3-я операция** (шаблон) — клиента всё равно придётся добавлять через панель.
**Откат:** `/mnt/RED_2TB/docker/backups/xray-admin-before-vless-space-20260915-012057/` → скопировать `x-ui.db` при остановленном контейнере → `docker start xray-admin`.
## Ограничение: «auto» здесь НЕ автоматическое
Выбранный путь разворачивает серверы подписки в **статические** outbound'ы. Если провайдер сменит список серверов, шаблон надо **пересобрать вручную**: `build_template.py``make_sql.py` → применить.
**Для настоящего авто-обновления нужен cron-скрипт на TrueNAS:**
```
curl подписки → base64 -d → build_template.py → пересобрать xrayTemplateConfig
→ стоп xray-admin → SQL → старт
```
Не реализован. Альтернатива без скрипта — использовать штатную таблицу `outbound_subscriptions` через панель (даёт `update_interval` из коробки, но требует доступа к UI).
## Открытый вопрос безопасности (не решён)
`/sub/<subId>` отдаётся **без авторизации** (`/sub/abc` тоже 200). Новая ссылка `vless-space` будет так же открыта. Варианты: подписка по токену, ротация `subId`. Задано Alex 2026-09-15, решения нет.
## Связанные заметки
- [[family/how-to/truenas-infrastructure]] — контейнер `xray-admin`, клиенты, sub-канал
- [[personal/tech/xray-reverse-tunnel-kraken-truenas]] — reverse-туннель (отдельный механизм)
- [[family/how-to/vps-qentra]] — удалённый VPS (источник мёртвого `v.qentra.top`)
## 🔴 2026-09-15: попутные находки этой сессии
### 1. `hermes-taiga` ходит НЕ через `xray-admin`, а через `vless-proxy` (мёртвый)
Разбор путаницы «таига настроена через user1?» — **нет**:
| | |
|---|---|
| Env `hermes-taiga` | `TELEGRAM_PROXY=socks5://vless-proxy:1080`, `DISCORD_PROXY=socks5://vless-proxy:1080` |
| Сети `hermes-taiga` | `hermes_taiga_net`, `ha_default` |
| `vless-proxy` | образ `teddysun/xray:latest`, SOCKS `:1080` + HTTP `:1081`, outbound → **`v.qentra.top:443` (мёртв, VPS удалён)** |
| Сеть `xray-admin` | `caddy_default`с `hermes-taiga` **не пересекается** |
| `user1` | id `ce320965-…` — клиент inbound `in-10095-tcp`, `hermes-taiga` к нему **не обращается** |
**Следствие:** Telegram/Discord у `hermes-taiga` сейчас без сети (цепочка `hermes-taiga → vless-proxy:1080 → v.qentra.top → timeout`).
**Как чинить (обсуждалось, НЕ выполнено):** переписать outbound `/mnt/RED_2TB/docker/vless-proxy/config.json` с мёртвого `v.qentra.top` на живой сервер. 4 поля:
| Поле | Сейчас | Станет |
|---|---|---|
| `address` | `v.qentra.top` | `vpn.mallexxx.duckdns.org` |
| `users[0].id` | `2D9F24C4-21FE-4784-9843-F11C384DA67A` | `ce320965-6956-4759-84bb-7cb71cfc6252` (`user1`) |
| `tlsSettings.serverName` | `v.qentra.top` | `vpn.mallexxx.duckdns.org` |
| `wsSettings.headers.Host` | `v.qentra.top` | `vpn.mallexxx.duckdns.org` |
| `wsSettings.path` | `/qentra` | `/vless` |
Порт `443`, `security: tls`, `network: ws` — без изменений. `xray-admin` **не имеет SOCKS-входа** (он сервер) — поэтому `vless-proxy` нужен как локальный клиент-мостик, его нельзя просто «выкинуть».
> ⚠️ Побочный эффект: трафик пойдёт `vless-proxy → vpn.mallexxx.duckdns.org:443 → Caddy → xray-admin` на том же хосте → egress = IP TrueNAS `90.189.160.148`. Петля внутри TrueNAS.
### 2. Compose-файл `xray-admin` утрачен — подробный разбор
См. [[family/plans/reverse-xray-3xui-kraken]] (шапка). Кратко: `rm -f /etc/x-ui/docker-compose.yml` внутри контейнера 2026-09-01 05:40:08 UTC снёс файл на хосте через bind-mount. Восстанавливать из `docker inspect`.
### 3. «Auto» в Xray-контейнере = только скрипт
Ещё раз зафиксировано: **Xray-бинарник подписки не умеет.** Умеет один/несколько статических outbound'ов + `routing.balancers`. Авто-подписка (`update_interval`) — только GUI-клиенты (Happ/v2rayN/NekoBox) **или** штатная таблица `outbound_subscriptions` 3x-ui через панель. Для docker-контейнера (`hermes-taiga`, `vless-proxy`) «auto» = cron-скрипт.