26 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-15T00:00:00.000Z | 2026-09-15T23:59:00.000Z | tech | personal |
|
high | done-egress-via-subscription-working |
|
Xray outbound из внешней подписки в 3x-ui (vless-space)
📱 КЛИЕНТСКАЯ СТОРОНА: как включить авто-обновление (вопрос Alex 2026-09-15)
Симптом вопроса: «можно поменять на xray subscription channel (auto), чтобы он автоматом обновлял сервера?» — типичная ошибка: в клиент вбит готовый
vless://…ключ вместо ссылки подписки. Ключ — статика, он не обновляется никогда.Что отдаёт сервер (проверено HTTP 200):
Клиент Ссылка подписки user1https://vpn-panel.mallexxx.duckdns.org/sub/68c5cy5n5ui138yhkraken-userhttps://vpn-panel.mallexxx.duckdns.org/sub/f43074a029dc656evless-spacehttps://vpn-panel.mallexxx.duckdns.org/sub/24df9391356b48ffОтдаётся base64-список
vless://— именно это клиент тянет как «subscription channel».Включение авто в клиенте — 3 шага:
- Удалить вручную вбитый профиль/сервер (напр.
vless-ws-user1).- Add subscription / «Подписка» → вставить полный URL с
/sub/<subId>(без пути/subне сработает).- В настройках подписки включить auto update (v2rayN / Happ / NekoBox — галочка «Auto update interval», ставить 6–12 ч; Streisand / sing-box — свой путь в меню).
⚠️ Три разных «авто» — не путать (см. таблицу в family/how-to/truenas-infrastructure §«Sub-канал»): GUI-клиент тянет
/sub/<subId>;xray-admin(сервер) сам тянет внешнююprofilegridчерезoutbound_subscriptions(600 с);vless-proxyавто не умеет.🔴 Одно неверное утверждение, которое из-за этого убрано из доки: «Xray в docker подписки не умеет → авто только в GUI» — верно только для контейнера-клиента (
vless-proxy), но НЕ для сервераxray-admin, у которого штатное авто черезoutbound_subscriptions. Причина путаницы: оба — «Xray в docker».Безопасность:
/sub/<subId>отдаётся без авторизации — кто знаетsubId, получает рабочий ключ. Возможный фикс — подписка по токену/subUpdates. Решения нет, вопрос открыт с 2026-09-15.
🏁 2026-09-15 (ЗАКРЫТО) — EGRESS РАБОТАЕТ через штатную подписку
Статус: всё собрано и проверено живым запросом.
space-вычищен, 28 серверов подпискиsub1-*работают.
Проверка (факт) Результат vless-proxy:1080egress✅ 104.28.219.140/188.239.191.18— НЕ90.189.160.148outbounds31 = 3 базовых + 28 × sub1-*balancersspace-balancer,selector: ["sub1-"],leastLoadburstObservatory.subjectSelector["sub1-"]non existing outTagв логе0 клиенты 10095 user1,kraken-user,vless-space— целы🔴 ГЛАВНЫЙ ПИТФОЛЛ СЕССИИ — имя поля: в UI правится
burstObservatory, а неobservatory. Я искалobservatory, получилhas("observatory")→falseи ошибочно объявил секцию отсутствующей — она была под другим именем. Диагностика:docker exec xray-admin cat /app/bin/config.json | jq -r 'keys[]' docker exec xray-admin cat /app/bin/config.json | jq -c '.burstObservatory // .observatory'⚠️ Обе секции валидны для Xray 26.x.
observatoryтребуетsampling: 3;burstObservatoryиспользуетpingConfig{sampling, interval, destination}. Панель при сохранении может удалитьobservatoryи создатьburstObservatory— проверять ОБА.🔴 Правило:
routing.balancers[].selectorиobservatory|burstObservatory.subjectSelector— ДВА разных поля, менять ПАРОЙ.Историческая формулировка (до фикса) — оставлена ниже для контекста.
🏁 Архитектура (собрана и подтверждена; egress — см. блок выше)
Компонент Значение outbound_subscriptionsremark=vless-space,tag_prefix=sub1-,update_interval=600, enabledoutbound'ы 28 × sub1-*(все серверы подписки, авто-обновление 600 с)статические space-01…10удалены из шаблона (были костылём) балансировщик space-balancer,selector: ["sub1-"]← матчит префикс подпискистратегия leastLoad+observatory.sampling: 3правило {"user":["vless-space"], "balancerTag":"space-balancer"}клиенты 10095 user1,kraken-user,vless-space— все целыФормула (все 4 пункта обязательны):
balancerTag(неoutboundTag) +leastLoad(неleastPing) +observatory.sampling: 3+observatory.subjectSelector= тот же префикс, что у балансировщика. ⚠️xray -testпечатаетConfiguration OKпри всех четырёх ошибках — проверять только рантайм-логомnon existing outTagи живым запросом egress.
🔴 ВТОРОЙ КРИТИЧНЫЙ ПИТФОЛЛ: подписка попадает в
config.jsonТОЛЬКО после рестартаЗапись
outbound_subscriptionsв БД (в т.ч. видимая в-wal) сама по себе конфиг не меняет. Панель рендерит outbound'ы подписки при генерацииconfig.json— на старте и по таймеруupdate_interval.Симптом:
sqlite3 … "SELECT COUNT(*) FROM outbound_subscriptions"→ 1,strings x-ui.db-wal | grep sub1-→ есть, аjq '[.outbounds[].tag|select(startswith("sub1-"))]|length'→ 0.Лечение:
docker restart xray-admin. После рестарта — 28 из 28. ⚠️docker stop+startтоже работает, но обрывает SSH (питфолл 2).Порядок проверки после добавления подписки в UI: (1) запись в БД есть → (2)
docker restart xray-admin→ (3)sub1-*вconfig.json= 28 → (4)subjectSelectorсовпадает сselector→ (5) живой запрос egress ≠90.189.160.148.
🔴 КРИТИЧНЫЙ ПИТФОЛЛ: подмена x-ui.db при живом контейнере ТЕРЯЕТ панельные данные
Что случилось 2026-09-15 (стоило потери записи подписки):
1. Панель 3x-ui работает, пишет в WAL → outbound_subscriptions содержит запись
2. Скачал x-ui.db при РАБОТАЮЩЕМ контейнере ← ЗАПИСЬ ЖИВЁТ В WAL, НЕ В ФАЙЛЕ
3. docker stop → панель сбросила WAL в файл (уже поздно — я работаю с копией)
4. cp мой_файл → x-ui.db → ЗАПИСЬ ПОДПИСКИ УНИЧТОЖЕНА
Правильный порядок (единственный безопасный):
# 1. СНАЧАЛА остановить
docker stop xray-admin
# 2. СКАЧАТЬ с checkpoint — после stop WAL уже слит, данные на месте
sqlite3 x-ui.db "PRAGMA wal_checkpoint(TRUNCATE);" # если контейнер НЕ останавливали
# 3. только потом править/подменять
Если контейнер нельзя останавливать — читать данные через strings из -wal, а не через sqlite3 на файле:
strings /mnt/RED_2TB/docker/xray-admin/x-ui.db-wal | grep -o 'sub1-'
Признак, что попался: sqlite3 x-ui.db "SELECT COUNT(*) FROM outbound_subscriptions;" → 0, хотя панель показывает запись. Значит запись в WAL, а не в файле. НЕ перезаписывать файл.
Восстановление: если запись потеряна — заново добавить в UI (Xray → Outbound Subscriptions). URL подписки в доке не хранится (REDACTED), бэкапы БД старше правки тоже пусты — достаётся из strings WAL либо у Alex.
✅ Штатный механизм подписки — как работает (2 шага)
outbound_subscriptionsсоздаёт outbound'ы с префиксом (sub1-) и обновляет их каждыеupdate_intervalсекунд. Настраивается в панели:Xray → Outbound Subscriptions → Add.- Балансировщик создаётся ОТДЕЛЬНО (
Xray → Balancers), егоselectorматчит префикс подписки (sub1-) → подхватывает все серверы, включая будущие.
Одного без другого не хватает: подписка без балансировщика = 28 outbound'ов, на которые никто не маршрутизирует. Балансировщик без подписки = селектор ни на что не матчится.
Таблица sub_balancers — НЕ про это. Это клиентские балансировщики для sub-ссылок (JSON в клиентскую подписку), к серверному роутингу отношения не имеют. Не путать.
Задача
Добавить в 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
└→ 28 × sub1-* (leastLoad) → интернет
Ключевые решения и почему
| Решение | Почему |
|---|---|
Править settings.xrayTemplateConfig в x-ui.db, а не config.json |
✅ Подтверждено фактом: 3x-ui генерирует /app/bin/config.json из БД. /app/bin/config.json править бесполезно (перезапишется). |
Статика space-01…10 → штатная outbound_subscriptions |
Статика требовала ручной пересборки шаблона при смене серверов у провайдера. Штатная подписка даёт update_interval из коробки. Выбор Alex. |
| Балансировщик переиспользован, менялся только selector | space-balancer уже был в UI и работал; править selector space- → sub1- дешевле, чем создавать новый. |
НЕ трогать user1 и kraken-user |
Их egress-цепочки не связаны с подпиской. Проверяется после каждого изменения. |
| Caddyfile не трогать | vless-space идёт через существующий путь /vless → xray-admin:10095. |
НЕ класть существующий инбаунд в xrayTemplateConfig |
🔴 Шаблонная версия того же тега перебивает панельную → клиенты исчезают. В шаблоне — только outbound'ы/правила/балансировщик, инбаунд api. |
leastLoad, не leastPing |
В Xray 26.x leastPing не работает — балансировщик не создаётся, рантайм non existing outTag. |
Правило — balancerTag, не outboundTag |
3x-ui понимает только balancerTag (issue #5638) и не теряет балансировщик при генерации конфига. |
🔴 ОБЯЗАТЕЛЬНО: leastLoad, а НЕ leastPing
// ✅ РАБОТАЕТ в Xray 26.7.28
"balancers": [ { "tag": "space-balancer", "selector": ["sub1-"],
"strategy": { "type": "leastLoad" } } ]
"observatory": { "subjectSelector": ["sub1-"],
"probeUrl": "https://www.google.com/generate_204",
"probeInterval": "30s", "enableConcurrency": true, "sampling": 3 }
// ❌ НЕ РАБОТАЕТ — тег не регистрируется, трафик молча теряется
"strategy": { "type": "leastPing" }
A/B на минимальном конфиге (docker, TrueNAS): leastPing → non existing outTag; burstObservatory + leastPing → то же; leastLoad + observatory → the outbound sub1-xx is alive:0.249, taking detour [...] ✅.
⚠️
xray -testпечатаетConfiguration OKи дляleastPing— валидация конфига ошибку НЕ ловит. Проверять только рантайм-логом + живым запросом. ⚠️sampling: 3в observatory обязателен — без него Xray 26.7.28 молча не создаёт балансировщик.
Питфоллы (все проверены фактом)
- 🔴 Подмена БД при живом контейнере теряет панельные данные — см. шапку.
docker stopДО скачивания БД. - 🔴 SSH к TrueNAS обрывается на
docker stop xray-admin—closed by remote host. Контейнер сам поднимается (restart: unless-stopped). Останавливать и применять одной короткой командой. sqlite3внутри контейнера ОТСУТСТВУЕТ. Использоватьdocker cpБД на хост, хостовыйsqlite3, либоstringsдля WAL.- Подписка даёт дубли: 28 строк → 10 уникальных хостов. Одна подписка отдаёт несколько записей на хост с разными
pbk/sid(перебор ключей). Балансировщик это учитывает сам — дедуп не нужен. - REALITY в outbound: параметры в
streamSettings.realitySettings(serverName,publicKey,shortId,spiderX,fingerprint),flow: xtls-rprx-vision— вusers[0].flow. spxв ссылке URL-энкодирован (%2F…) — обязателенunquote.routing.balancers.selector— префиксный матч:["sub1-"]подхватываетsub1-germany,sub1-latvia-yt, … и будущие.- Правки в
routing.rules— В КОНЕЦ — существующие (api,geoip:private→blocked,bittorrent→blocked,kraken-user→via-kraken) сохраняют приоритет. - Панель может создать дубль балансировщика — если при правке UI создаётся новый тег, старый остаётся. Проверять
jq '.routing.balancers'— должно быть ровно одна запись, на которую смотрит правило. (Факт 2026-09-15: в конфиге оказались ДВА —space-balancerиvless-space-balancer, обаselector:["sub1-"]. Правило смотрит наspace-balancer; второй — мусор.) - 🔴
observatory.subjectSelector— отдельное поле, не связанное сbalancers[].selector. Сменил префикс в балансировщике → обязательно смени и в observatory. Иначе: серверы в конфиге есть, ошибок в логе нет, а трафик уходит вdirect(см. шапку). Самый коварный питфолл сессии — тихий отказ без диагностических признаков. - Запись
outbound_subscriptionsне влияет наconfig.jsonдо рестарта/тика таймера — см. второй критичный питфолл в шапке. Не делать вывод «подписка не работает» по пустомуconfig.jsonсразу после добавления в UI.
Диагностика: «трафик уходит в direct»
| Признак | Причина | Проверка |
|---|---|---|
IP = 90.189.160.148 (TrueNAS), лог чистый |
observatory.subjectSelector не матчит outbound'ы |
jq -c '{bal:.routing.balancers[0].selector, obs:.observatory.subjectSelector}' → должны совпадать |
sub1-* = 0 в config.json, но есть в БД |
подписка не отрендерена | docker restart xray-admin |
non existing outTag: <балансировщик> в логе |
outboundTag вместо balancerTag ИЛИ leastPing ИЛИ нет sampling |
jq -c '.routing.rules[]|select(.balancerTag!=null)' |
| Балансировщика нет в config.json | нет sampling: 3 в observatory |
jq -c '.observatory' |
Проверка после применения (3 команды)
docker exec xray-admin cat /app/bin/config.json | jq -r '[.outbounds[].tag]|length'
docker exec xray-admin cat /app/bin/config.json | jq -r '[.outbounds[].tag|select(startswith("sub1-"))]|length' # ожидается 28
docker exec xray-admin cat /app/bin/config.json | jq -c '.routing.balancers'
docker logs --since 3m xray-admin 2>&1 | grep -c "non existing" # ожидается 0
Артефакты (Mac)
Рабочая папка: ~/tmp-xray-space/
| Файл | Назначение |
|---|---|
xui_v10_src.db |
снимок живой БД до чистки |
xui_v10.db |
БД со вычищенной статикой (3 outbound'а) |
strip_space_outbounds.sh |
удаление space-01…10 из шаблона |
write_v10.sh |
запись шаблона в БД + integrity_check |
live2.db, prev_v9.db |
снимки БД для диагностики WAL |
b_*.db |
копии бэкапов (проверка на наличие подписки) |
Параметры клиента:
vless-space |
|
| UUID | a792c483-07e2-4723-9c50-78054c0abc07 |
subId |
24df9391356b48ff |
| sub-ссылка | https://vpn-panel.mallexxx.duckdns.org/sub/24df9391356b48ff |
Бэкапы
| Путь | Что |
|---|---|
/mnt/RED_2TB/docker/backups/xray-admin-before-vless-space-20260915-012057/ |
исходная точка отката (до всех работ) |
/mnt/RED_2TB/docker/backups/xray-admin-before-cleanup-20260915-035749/ |
перед чисткой статики |
/mnt/RED_2TB/docker/backups/xray-admin-v9-20260915-102126/ |
последний рабочий (со статикой) |
/mnt/RED_2TB/docker/xray-admin/x-ui.db.prev-v9 |
БД до подмены |
Ограничение (снято)
«auto» здесь НЕ автоматическое — СНЯТО 2026-09-15. Штатная outbound_subscriptions обновляет список серверов каждые 600 с. Ручная пересборка шаблона больше не нужна.
Открытый вопрос безопасности (не решён)
/sub/<subId> отдаётся без авторизации (/sub/abc тоже 200). Варианты: подписка по токену, ротация subId. Задано Alex 2026-09-15, решения нет.
Статус: ✅ ВСЁ ЗАКРЫТО 2026-09-15
1. observatory.subjectSelector = ["sub1-"] — ✅ СДЕЛАНО. Причина «трафика в direct» устранена. Факт: bal == obs == ["sub1-"].
🔴 Поправка к формулировке плана: правило писать не
observatory, аburstObservatory(Alex правил в UI, панель создалаburstObservatoryсpingConfig). Правка по$.observatory.subjectSelectorв этом случае не находит цель. Проверять оба имени.
2. Дубль балансировщика vless-space-balancer — ✅ УДАЛЁН Alex'ом в UI. В конфиге ровно один балансировщик. (Мой прошлый отчёт о «дубле» строился на устаревшем замере — актуальный факт: дубля нет.)
3. Живой тест egress — ✅ ОК: vless-proxy:1080 → 104.28.219.140 / 188.239.191.18, ≠ 90.189.160.148.
Проверка одним запросом (все 4 условия сразу, актуальная):
docker exec xray-admin cat /app/bin/config.json | jq -c '{bal:.routing.balancers[0].selector, obs:(.burstObservatory // .observatory).subjectSelector, sub1:([.outbounds[].tag|select(startswith("sub1-"))]|length), rule:([.routing.rules[]|select(.balancerTag!=null)]|length)}'
# ожидается: bal==obs==["sub1-"], sub1==28, rule==1
⚠️ .burstObservatory // .observatory в jq — обязателен, иначе на живом конфиге получите null и ложный вывод «секции нет».
Связанные заметки
- family/how-to/truenas-infrastructure — контейнер
xray-admin, клиенты, sub-канал - personal/tech/xray-reverse-tunnel-kraken-truenas — reverse-туннель (отдельный механизм)
- personal/tech/vless-space-subscription-egress — история отладки egress
- family/how-to/vps-qentra — удалённый VPS (источник мёртвого
v.qentra.top) - personal/tech/vault-doc-pruning — метод чистки устаревших разделов в доках
🔴 Попутные находки (2026-09-15)
1. hermes-taiga ходит НЕ через xray-admin, а через vless-proxy — ✅ ПОЧИНЕНО
Env hermes-taiga |
TELEGRAM_PROXY=socks5://vless-proxy:1080, DISCORD_PROXY=socks5://vless-proxy:1080 |
| Сети | hermes_taiga_net, ha_default — с caddy_default (xray-admin) не пересекается |
vless-proxy |
teddysun/xray:latest, SOCKS :1080 + HTTP :1081 |
user1 |
ce320965-… — клиент in-10095-tcp, hermes-taiga к нему не обращается |
✅ ФИНАЛ: vless-proxy переключён с мёртвого v.qentra.top на vpn.mallexxx.duckdns.org.
Поле в /mnt/RED_2TB/docker/vless-proxy/config.json |
Стало |
|---|---|
address |
vpn.mallexxx.duckdns.org |
users[0].id |
a792c483-07e2-4723-9c50-78054c0abc07 — клиент vless-space, НЕ user1 |
tlsSettings.serverName |
vpn.mallexxx.duckdns.org |
wsSettings.headers.Host |
vpn.mallexxx.duckdns.org |
wsSettings.path |
/vless |
🔑 Решение Alex: брать
vless-space, а неuser1— иначе egress был бы IP TrueNAS90.189.160.148(петля внутри TrueNAS). Сvless-spaceтрафик уходит через подписку (нероссийский IP). ⚠️ Файл агенту недоступен:/mnt/RED_2TB/docker/vless-proxy/—root:root 755,config.jsonпримонтирован:ro. Правку делает Alex от root; агент готовит конфиг локально (~/tmp-xray-space/vless-proxy-config-new.json). ⚠️ Питфолл проверки:wgetне умеет SOCKS5 —docker exec vless-proxy wget -qO- https://api.ipify.orgвернёт IP контейнера и даст ложный вывод «direct». Проверять только curl через SOCKS (см. §Проверка в шапке).
2. Compose-файл xray-admin утрачен — разбор
rm -f /etc/x-ui/docker-compose.yml внутри контейнера 2026-09-01 05:40:08 UTC снёс файл на хосте через bind-mount. Восстановлен из docker inspect (536 б).
3. «Auto» в Xray-контейнере = только скрипт
Xray-бинарник подписки не умеет. Умеет статические outbound'ы + routing.balancers. Авто-подписка — только GUI-клиенты (Happ/v2rayN/NekoBox) или штатная outbound_subscriptions 3x-ui через панель. Для docker-контейнера (hermes-taiga, vless-proxy) «auto» = cron-скрипт.