29 KiB
title, created, updated, type, namespace, tags, confidence, status, related
| title | created | updated | type | namespace | tags | confidence | status | related | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Xray outbound — внешняя подписка в 3x-ui (vless-space) | 2026-09-15 | 2026-09-15T22:30:00.000Z | tech | personal |
|
high | done-egress-working |
|
Xray outbound из внешней подписки в 3x-ui (vless-space)
🏁 2026-09-15 22:30 — EGRESS РАБОТАЕТ.
xui_v9.dbЗАЛИТА.IP через
vless-space=194.x / 104.28.225.223(Стокгольм, SE) ≠90.189.160.148(TrueNAS). Проверено Xray-клиентом в докере на NAS, 3 запроса подряд.Формула, которая работает (все три пункта обязательны):
{ "type":"field", "user":["vless-space"], "balancerTag":"space-balancer" } // ← balancerTag, НЕ outboundTag "balancers": [ { "tag":"space-balancer","selector":["space-"], "strategy":{"type":"leastLoad"} } ] // ← leastLoad, НЕ leastPing "observatory": { "subjectSelector":["space-"], ..., "sampling":3 } // ← sampling обязателенБез
balancerTag→non existing outTag: space-balancer. Безsampling→ то же молча,-test=Configuration OK. БезleastLoad→ то же.⚠️
xray -testНЕ ловит эти ошибки — проверять только рантайм-логом и живым запросом.Остаточный риск:
strategy: leastLoadвыбран по A/B-тесту (единственный рабочий). Авто-обновление списка серверов подписки по-прежнему требует cron-скрипта — outbound'ы статические. Отдельная задача, не начата.
✅ БАРЬЕР ПРЕОДОЛЁН — xui_v7.db ЗАЛИТА УСПЕШНО
Прежний вывод «клиентов через SQLite добавить нельзя» — ОПРОВЕРГНУТ.
xui_v7.dbзалита, и клиент появился:
Проверка Результат in-10095-tcpclients✅ 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 выполняет их сам.
Задача
Добавить в 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) — все отбились. |
НЕ класть существующий инбаунд в xrayTemplateConfig |
🔴 Урок 6-й попытки (2026-09-15): попытка протащить in-10095-tcp с тремя клиентами внутрь шаблона сломала kraken-user — 3x-ui мёржит шаблон с таблицей inbounds, и шаблонная версия того же тега перебивает панельную (клиенты исчезают, egress-правило по email не срабатывает). В шаблоне — только outbound'ы/правила/балансировщик. Инбаунды — эксклюзив панели. |
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)
// ✅ РАБОТАЕТ в 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 }
// ❌ НЕ РАБОТАЕТ — тег не регистрируется, трафик молча теряется
"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.
Питфоллы (все проверены фактом)
- 🔴 SSH к TrueNAS обрывается на
docker stop xray-admin—closed by remote host. Контейнер сам поднялся (restart: unless-stopped), БД не пострадала. Останавливать и применять SQL одной короткой командой, не серией вызовов. - WAL:
docker stopу 3x-ui корректно закрывает БД и сливает WAL в основной файл. Удалять-wal/-shmможно только после остановки. Правка при живом WAL = риск порчи базы. sqlite3внутри контейнера ОТСУТСТВУЕТ (which sqlite3пусто). Использовать:docker cpБД на хост, либо хостовыйsqlite3, либоalpine-контейнер.- Дедуп подписки: 28 строк → 10 уникальных серверов. Одна подписка отдаёт несколько записей на один хост с разными
pbk/sid(перебор ключей). Ключ дедупа —host + publicKey + shortId. - REALITY в outbound: параметры кладутся в
streamSettings.realitySettings(serverName,publicKey,shortId,spiderX,fingerprint), аflow: xtls-rprx-vision— вusers[0].flow(в ссылке он приходит query-параметром). spxв ссылке URL-энкодирован (%2F…) — обязателенunquote, иначе Xray падает наspiderX.routing.balancers.selector— префиксный матч:["space-"]подхватываетspace-01…space-10.- Правки в
routing.rulesдобавлять В КОНЕЦ — существующие правила (api,geoip:private→blocked,bittorrent→blocked,kraken-user→via-kraken) должны сохранить приоритет.
Шаблон конфига — что добавляется
Существующее (outbound'ы direct/blocked/via-kraken, правила, inbound) не трогается. Добавляется:
// в 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 |
Параметры клиента (сгенерированы):
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) ← СТАРАЯ дата, правки не попали
x-ui.dbне обновлялся — правки, применённые в 15:53/16:02/16:06, в основной файл не попадали. 3x-ui держит базу и пишет в-wal; удаление-walперед правкой уничтожало актуальные данные, после правки он перезаписывал файл из своего состояния.config.jsonперегенерирован из внутренней памяти 3x-ui, а не из файла — отсюда парадокс: 13 outbound'ов применились (шаблонsettingsон читает), третий клиент исчез (список клиентов берёт из своей памяти).
🔴 ВЫВОД (исправлен 2026-09-15 вечером):
inboundsи клиентов 3x-ui через SQLite править МОЖНО — но при двух жёстких условиях. Прежний вывод «нельзя вообще» был неверен: он был основан на сборках, где инбаунд лежал внутриxrayTemplateConfig(см. урок 6) и где копию БД клали рядом со старыми-wal/-shm.Условия, при которых SQLite-путь РАБОТАЕТ (проверено заливкой
xui_v7.db):
- Инбаунд
in-10095-tcpНЕ должен присутствовать вxrayTemplateConfig(в шаблоне — толькоapi).- БД правится локально на 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), не от промежуточных копий:
# 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:
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
Проверка после старта:
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 localxray-test-client». Это и есть подтверждение, что путь был через API панели, а не через SQL.
§6 Как довести до конца — ждёт доступа к панели
🔴 Попытка 7 (2026-09-15, финал) — xui_v7.db, шаблон БЕЗ инбаунда
Исправление ошибки попытки 6: инбаунд in-10095-tcp убран из шаблона, собиралось от оригинала (xui_copy.db).
| Что | Значение |
|---|---|
xrayTemplateConfig.inbounds |
только api ✅ |
xrayTemplateConfig.outbounds |
13 ✅ |
| balancer / observatory | space-balancer (leastPing) / selector: ["space-"] ✅ |
inbounds (таблица) |
vless-ws port 10095, clients = user1, kraken-user, vless-space |
clients / client_inbounds |
vless-space id=5 (+ привязка 5→1) |
diff от xui_copy.db |
ровно 4 изменения, все по делу |
integrity_check / journal_mode |
ok / DELETE |
Статус: не залита. Файл ~/tmp-xray-space/xui_v7.db (270 336 б).
⚠️ Ожидание — низкое. В оригинале таблица
client_trafficsпуста, хотя два клиента живые. Значит панель рендерит клиентов не из дисковых таблиц. Скорее всего барьер попыток 2-6 сохранится: шаблон применится, клиент — нет.
Диагностический признак: инбаунд «дважды» в выводе jq
Если это:
in-10095-tcp port=10095 clients=user1,kraken-user,vless-space
in-10095-tcp port=10095 clients=user1,kraken-user
— то это НЕ два инбаунда, а шаблонная копия + панельная (симптом ошибки попытки 6). Проверка-опровержение:
docker exec xray-admin cat /app/bin/config.json | /usr/bin/jq -r '.inbounds[].tag'
Правильно — только api в списке инбаундов шаблона; сам in-10095-tcp приходит из таблицы inbounds.
Путь к цели
Блокер: логин панели 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 TrueNAS90.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-скрипт.