Files
obsidian-vault/personal/tech/vless-space-subscription-egress.md
T

14 KiB
Raw Blame History

title, created, updated, type, namespace, tags, confidence, status, related
title created updated type namespace tags confidence status related
vless-space — клиент 3x-ui с egress через external-подписку 2026-09-15 2026-09-15 tech personal
xray
3x-ui
x-ui
subscription
outbound
profilegrid
truenas
space
high in-progress
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'ы. Тогда сервер может: принять клиента → отправить его трафик через сервер из подписки. Это и есть нужный механизм.

Схема:

[клиент 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):

{
  "type": "field",
  "user": ["vless-space"],
  "outboundTag": "space-balancer",
  "ruleTag": "vless-space-via-subscription"
}

2. Балансировщик:

"balancers": [
  {
    "tag": "space-balancer",
    "selector": ["space-"],
    "strategy": { "type": "leastPing" }
  }
]

3. Observatory (нужен для leastPing):

"observatory": {
  "subjectSelector": ["space-"],
  "probeUrl": "https://www.google.com/generate_204",
  "probeInterval": "30s",
  "enableConcurrency": true
}

4. 10 outbound'ов space-01space-10, формат:

{
  "tag": "space-NN",
  "protocol": "vless",
  "settings": { "vnext": [ { "address": "<host>", "port": 443, "users": [
    { "id": "c67ce742-…", "encryption": "none", "flow": "xtls-rprx-vision" } ] } ] },
  "streamSettings": {
    "network": "tcp",
    "security": "reality",
    "realitySettings": {
      "serverName": "<host>", "fingerprint": "firefox",
      "publicKey": "<pbk>", "shortId": "<sid>", "spiderX": "<spx>"
    }
  }
}

Итого: было 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, не через копию

Команды

# 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.

Откат

# 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-<TS>/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> открыта БЕЗ авторизации — любой, кто знает subId, получит ключ. Кандидат на включение подписки-по-токену в панели (не сделано).

Что НЕ сделано / следующие шаги

  • Применить apply_space_v2.sql по исправленной процедуре
  • Проверить end-to-end: тестовый клиент с UUID vless-spacecurl → 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.

Связанные заметки