Files
panel/docs/learn/xray-json-advanced.md
Artem 60852fa6d7 docs(xray-json): clarify virtual host role in inject flow (#357)
* docs(xray-json): clarify virtual host role in inject flow

- document that the virtual host acts as a wrapper for the template and metadata
- explain that its address and profile can be arbitrary and are not used for the actual user connection
- clarify that real connection parameters are taken from injected hosts' outbounds

* Update xray-json-advanced.md
2026-02-27 18:51:31 +03:00

27 KiB
Raw Permalink Blame History

sidebar_position, title
sidebar_position title
3 Xray JSON Advanced

Обзор

Для шаблонов подписки типа XRAY_JSON в Remnawave предусмотрены Remnawave-директивы — специальные инструкции, которые вы добавляете в JSON-шаблон. Панель обрабатывает их при генерации подписки и удаляет из итогового конфига — клиент их никогда не увидит.

На данный момент доступна директива injectHosts, позволяющая динамически подставлять outbound-конфигурации хостов в шаблон. Это полезно, когда вам нужно собрать сложную конфигурацию Xray с балансировщиками, кастомным роутингом или несколькими outbound'ами, при этом данные подключения (адрес, порт, ключи) подставятся автоматически из панели.

:::tip Совет Представленные ниже конфигурации являются примерами для демонстрации механизма инжекта. Адаптируйте их под свои нужды. :::

:::warning Внимание Требуется Remnawave версии 2.6.3 или новее. :::

Условия работы

  • Виртуальный хост (хост, которому назначен шаблон с инжектом) должен быть включён и не скрыт.
  • Инжектируемые хосты (выбранные через selector) должны быть включены. По умолчанию выбираются только скрытые хосты (поведение можно изменить через selectFrom).
  • Все хосты — и виртуальный, и инжектируемые — должны быть доступны конечному пользователю: инбаунд, к которому они привязаны, должен быть включён в сквад пользователя.
  • Из виртуального хоста в итоговый конфиг попадают примечание (remark) и описание сервера (Server Description, если задано).

Структура remnawave

Объект remnawave добавляется на корневой уровень JSON-шаблона. Он поддерживает следующие поля:

Поле Описание
injectHosts Массив групп инжекта. Каждая группа содержит селектор для выбора хостов и параметры формирования тегов.
addVirtualHostAsOutbound Если true — виртуальный хост будет добавлен как outbound с тегом proxy в начало массива outbounds. По умолчанию false. См. addVirtualHostAsOutbound.

Поле injectHosts — это массив групп инжекта. Каждая группа содержит селектор для выбора хостов и собственный tagPrefix:

"remnawave": {
    "injectHosts": [
        {
            "selector": { "type": "uuids", "values": ["uuid-хоста-1", "uuid-хоста-2"] },
            "tagPrefix": "proxy"
        },
        {
            "selector": { "type": "remarkRegex", "pattern": "^RU-" },
            "tagPrefix": "backup"
        }
    ]
},

Каждый элемент массива injectHosts:

Поле Описание
selector Объект, определяющий какие хосты будут выбраны. Обязательное поле.
selectFrom Из какого пула выбирать хосты: "HIDDEN" (по умолчанию), "NOT_HIDDEN" или "ALL".
tagPrefix Префикс тега для создаваемых outbound'ов. См. правила формирования тегов.
useHostRemarkAsTag Если true — тегом outbound'а будет примечание (remark) хоста.
useHostTagAsTag Если true — тегом outbound'а будет тег хоста (если тег не задан, используется remark).

:::warning Необходимо указать ровно одно из трёх полей: tagPrefix, useHostRemarkAsTag или useHostTagAsTag. :::

Групп может быть сколько угодно — каждая формирует свой независимый набор outbound'ов с собственным префиксом. Это позволяет, например, завести отдельный балансировщик для каждой группы серверов.

Типы селекторов

uuids

Выбирает хосты по списку UUID. Порядок UUID определяет порядок outbound'ов.

"selector": {
    "type": "uuids",
    "values": [
        "8478b271-95d3-4312-85ae-ecf63fb53d1d",
        "d31d6161-1315-4c1e-9a4b-141ab1c022f6"
    ]
}

remarkRegex

Выбирает хосты, у которых примечание (remark) совпадает с регулярным выражением. Синтаксис — JavaScript RegExp.

"selector": {
    "type": "remarkRegex",
    "pattern": "^Балансер"
}

Пример выше выберет все скрытые хосты, чьё примечание начинается с «Балансер» (например, «Балансер #1», «Балансер RU»).

tagRegex

Выбирает хосты, у которых тег хоста (поле tag в настройках хоста) совпадает с регулярным выражением.

"selector": {
    "type": "tagRegex",
    "pattern": "^balancer-"
}

Пример выше выберет все скрытые хосты с тегом, начинающимся на balancer-.

sameTagAsRecipient

Выбирает все скрытые хосты, у которых тег хоста совпадает с тегом виртуального хоста. Не требует дополнительных параметров.

"selector": {
    "type": "sameTagAsRecipient"
}

Удобно, когда вы хотите автоматически группировать хосты: достаточно присвоить одинаковый тег виртуальному и инжектируемым хостам.

:::tip По умолчанию все селекторы работают только со скрытыми хостами. Чтобы изменить это поведение, используйте поле selectFrom: значение "NOT_HIDDEN" выберет только видимые хосты (включенные, но не скрытые), а "ALL" — все хосты (включенные, скрытые). :::

Правила формирования тегов

Тег outbound'а определяется тем, какое из трёх полей указано в группе инжекта:

tagPrefix — первый хост получает тег, равный tagPrefix. Каждый последующий — {tagPrefix}-{N}, начиная с 2.

Пример для трёх хостов с tagPrefix: "proxy":

Порядок Тег outbound'а
1-й proxy
2-й proxy-2
3-й proxy-3

useHostRemarkAsTag — каждый outbound получает тег, равный примечанию (remark) хоста.

{
    "selector": { "type": "tagRegex", "pattern": "^ru-" },
    "useHostRemarkAsTag": true
}

Если хосты имеют примечания «Москва», «Питер», «Казань» — outbound'ы получат теги Москва, Питер, Казань.

useHostTagAsTag — каждый outbound получает тег, равный тегу хоста. Если тег хоста не задан, используется его примечание.

{
    "selector": { "type": "tagRegex", "pattern": "^ru-" },
    "useHostTagAsTag": true
}

Префиксное сопоставление в Xray

Поля selectorrouting.balancers) и subjectSelectorburstObservatory) в Xray работают как префиксные матчеры — они сопоставляются с началом тега outbound'а, а не с его точным значением.

Например, если в конфиге есть outbound'ы с тегами proxy, proxy-2, proxy-3, direct:

Значение selector / subjectSelector Какие outbound'ы будут выбраны
["proxy"] proxy, proxy-2, proxy-3
["proxy-"] proxy-2, proxy-3
  • "selector": ["proxy"] — подхватит все инжектированные outbound'ы, включая первый.
  • "selector": ["proxy-"] — подхватит все кроме первого (только proxy-2, proxy-3, ...).

:::note Fallback через первый хост Первый выбранный хост всегда получает тег без суффикса -{N} (просто proxy). Это позволяет использовать его как fallbackTag в балансировщике: если все outbound'ы из selector окажутся недоступны, трафик уйдёт на первый хост. Для этого задайте "selector": ["proxy-"] (только proxy-2, proxy-3, ...) и "fallbackTag": "proxy". :::

addVirtualHostAsOutbound

По умолчанию при использовании remnawave-директивы в итоговый конфиг попадают только инжектированные хосты. Сам виртуальный хост (recipient) используется только как источник remarks и serverDescription.

Если вам нужно, чтобы виртуальный хост также стал outbound'ом с тегом proxy, добавьте поле addVirtualHostAsOutbound: true на уровне объекта remnawave:

"remnawave": {
    //highlight-next-line-green
    "addVirtualHostAsOutbound": true,
    "injectHosts": [
        {
            "selector": { "type": "uuids", "values": ["uuid-хоста-1", "uuid-хоста-2"] },
            "tagPrefix": "proxy"
        },
        {
            "selector": { "type": "remarkRegex", "pattern": "^RU-" },
            "tagPrefix": "backup"
        }
    ]
}

В этом случае итоговый массив outbounds будет выглядеть так:

  1. Outbound виртуального хоста с тегом proxy.
  2. Инжектированные outbound'ы (из injectHosts).
  3. Статические outbound'ы из шаблона (direct, block и т. д.).

Это полезно, когда в routing-правилах используется "outboundTag": "proxy" для направления трафика через основной хост, а инжектированные хосты обслуживают отдельные группы трафика (например, через балансировщики).

:::tip addVirtualHostAsOutbound можно использовать совместно с injectHosts или без них. Если injectHosts не указан или пуст, в конфиг будет добавлен только outbound виртуального хоста. :::

Пошаговый пример: балансировщик с тремя хостами

В этом примере мы создадим конфигурацию, в которой три outbound'а объединены в балансировщик со стратегией leastLoad и мониторятся обсерваторией.

Шаг 1. Создайте хосты

Создайте в панели хосты, которые будут участвовать в инжекте. В нашем примере это:

  • Virtual Host — виртуальный хост, которому будет назначен шаблон с инжектом. Он не скрыт и именно через него конечный пользователь получит конфиг.
  • Balancer #1, Balancer #2, Balancer #3 — хосты, outbound'ы которых будут подставлены в шаблон.

<img src={require('./images/xray-json-advanced/1.webp').default} width="100%" style={{borderRadius: '8px'}} alt="Список хостов" />

Шаг 2. Скройте инжектируемые хосты

Откройте карточку каждого хоста-балансировщика (Balancer #1, #2, #3), перейдите в раздел Расширенные и включите переключатель Скрыть хост.

Скрытые хосты не попадают в обычную подписку — они доступны только через механизм инжекта.

<img src={require('./images/xray-json-advanced/3.webp').default} width="100%" style={{borderRadius: '8px'}} alt="Скрытие хостов-балансировщиков" />

Шаг 3. Создайте шаблон подписки

Создайте шаблон подписки типа XRAY_JSON. В нём опишите полную конфигурацию: dns, routing, inbounds, outbounds, burstObservatory и другие нужные секции.

В массив outbounds поместите только статические outbound'ы (direct, block) — outbound'ы инжектируемых хостов будут добавлены автоматически.

На корневом уровне JSON добавьте объект remnawave с селектором скрытых хостов.

Пример шаблона

{
    //highlight-next-line-green
    "remnawave": {
        //highlight-next-line-green
        "injectHosts": [
            //highlight-next-line-green
            {
                //highlight-next-line-green
                "selector": {
                    //highlight-next-line-green
                    "type": "uuids",
                    //highlight-next-line-green
                    "values": [
                        //highlight-next-line-green
                        "8478b271-95d3-4312-85ae-ecf63fb53d1d",
                        //highlight-next-line-green
                        "d31d6161-1315-4c1e-9a4b-141ab1c022f6",
                        //highlight-next-line-green
                        "5749f69e-cd1b-4012-9407-450434085196"
                        //highlight-next-line-green
                    ]
                    //highlight-next-line-green
                },
                //highlight-next-line-green
                "tagPrefix": "proxy"
                //highlight-next-line-green
            }
            //highlight-next-line-green
        ]
    },
    "burstObservatory": {
        "pingConfig": {
            "timeout": "3s",
            "interval": "1m",
            "sampling": 1,
            "destination": "http://www.gstatic.com/generate_204",
            "connectivity": ""
        },
        "subjectSelector": ["proxy"]
    },
    "dns": {
        "servers": ["1.1.1.1", "1.0.0.1"],
        "queryStrategy": "UseIP"
    },
    "routing": {
        "balancers": [
            {
                "tag": "Super_Balancer",
                "selector": ["proxy"],
                "strategy": {
                    "type": "leastLoad",
                    "settings": {
                        "maxRTT": "1s",
                        "expected": 2,
                        "baselines": ["1s"],
                        "tolerance": 0.01
                    }
                },
                "fallbackTag": "direct"
            }
        ],
        "rules": [
            {
                "type": "field",
                "protocol": ["bittorrent"],
                "outboundTag": "direct"
            },
            {
                "type": "field",
                "network": "tcp,udp",
                "balancerTag": "Super_Balancer"
            }
        ],
        "domainMatcher": "hybrid",
        "domainStrategy": "IPIfNonMatch"
    },
    "inbounds": [
        {
            "tag": "socks",
            "port": 10808,
            "listen": "127.0.0.1",
            "protocol": "socks",
            "settings": {
                "udp": true,
                "auth": "noauth"
            },
            "sniffing": {
                "enabled": true,
                "routeOnly": false,
                "destOverride": ["http", "tls", "quic"]
            }
        },
        {
            "tag": "http",
            "port": 10809,
            "listen": "127.0.0.1",
            "protocol": "http",
            "settings": {
                "allowTransparent": false
            },
            "sniffing": {
                "enabled": true,
                "routeOnly": false,
                "destOverride": ["http", "tls", "quic"]
            }
        }
    ],
    "outbounds": [
        {
            "tag": "direct",
            "protocol": "freedom"
        },
        {
            "tag": "block",
            "protocol": "blackhole"
        }
    ]
}

Обратите внимание:

  • "subjectSelector": ["proxy"] — обсерватория будет мониторить все outbound'ы, тег которых начинается с proxy (т. е. proxy, proxy-2, proxy-3).
  • "selector": ["proxy"] — балансировщик Super_Balancer будет распределять трафик между теми же outbound'ами.
  • В outbounds шаблона указаны только direct и block — outbound'ы хостов добавятся автоматически перед ними.

Шаг 4. Назначьте шаблон виртуальному хосту

Откройте карточку виртуального хоста (Virtual Host), перейдите в раздел Расширенные и в поле Шаблон Xray JSON выберите созданный шаблон.

Убедитесь, что переключатель Скрыть хост для виртуального хоста выключен — он должен быть видим в подписке.

<img src={require('./images/xray-json-advanced/2.webp').default} width="100%" style={{borderRadius: '8px'}} alt="Назначение шаблона виртуальному хосту" />

Шаг 5. Результат

При запросе подписки панель автоматически:

  1. Возьмёт шаблон, назначенный виртуальному хосту.
  2. Удалит из него объект remnawave.
  3. Для каждой группы в injectHosts выберет скрытые хосты по selector и соберёт их outbound'ы.
  4. Подставит outbound'ы в начало массива outbounds.
  5. Установит remarks из примечания виртуального хоста.

Итоговый конфиг, который получит клиент

[
    {
        "dns": {
            "servers": ["1.1.1.1", "1.0.0.1"],
            "queryStrategy": "UseIP"
        },
        "routing": {
            "rules": [
                {
                    "type": "field",
                    "protocol": ["bittorrent"],
                    "outboundTag": "direct"
                },
                {
                    "type": "field",
                    "network": "tcp,udp",
                    "balancerTag": "Super_Balancer"
                }
            ],
            "balancers": [
                {
                    "tag": "Super_Balancer",
                    "selector": ["proxy"],
                    "strategy": {
                        "type": "leastLoad",
                        "settings": {
                            "maxRTT": "1s",
                            "expected": 2,
                            "baselines": ["1s"],
                            "tolerance": 0.01
                        }
                    },
                    "fallbackTag": "direct"
                }
            ],
            "domainMatcher": "hybrid",
            "domainStrategy": "IPIfNonMatch"
        },
        "inbounds": [
            {
                "tag": "socks",
                ...omitted...
            },
            {
                "tag": "http",
                ...omitted...
            }
        ],
        "outbounds": [
            {
                //highlight-next-line-green
                "tag": "proxy",
                "protocol": "vless",
                "settings": {...omitted...},
                "streamSettings": {...omitted...}
            },
            {
                //highlight-next-line-green
                "tag": "proxy-2",
                "protocol": "vless",
                "settings": {...omitted...},
                "streamSettings": {...omitted...}
            },
            {
                //highlight-next-line-green
                "tag": "proxy-3",
                "protocol": "vless",
                "settings": {...omitted...},
                "streamSettings": {...omitted...}
            },
            {
                "tag": "direct",
                "protocol": "freedom"
            },
            {
                "tag": "block",
                "protocol": "blackhole"
            }
        ],
        "burstObservatory": {
            "pingConfig": {
                "timeout": "3s",
                "interval": "1m",
                "sampling": 1,
                "destination": "http://www.gstatic.com/generate_204",
                "connectivity": ""
            },
            "subjectSelector": ["proxy"]
        },
        "remarks": "Virtual Host"
    }
]

Что произошло:

  • Объект remnawave удалён из итогового конфига.
  • Три outbound'а (proxy, proxy-2, proxy-3) подставлены в начало массива outbounds, перед direct и block.
  • "selector": ["proxy"] в балансировщике автоматически захватил все три outbound'а, поскольку их теги начинаются с proxy (префиксное сопоставление).
  • "subjectSelector": ["proxy"] в обсерватории аналогично подхватил все три outbound'а для мониторинга.
  • "remarks": "Virtual Host" — взято из примечания виртуального хоста.

::::note Адрес виртуального хоста и реальный инбаунд

Виртуальный хост в этом сценарии служит «обёрткой» для шаблона и метаданных (примечание, описание сервера), а не реальной точкой подключения.
В его настройках можно указать любой адрес (например, balancer.host.com) - он не участвует в реальном подключении пользователя.
Фактическая точка входа - это конкретный инбаунд инжектируемых хостов. Важно, чтобы у пользователя, запрашивающего подписку, был доступ к этому инбаунду через сквады, иначе виртуальный хост в подписке у него вообще не появится.
Фактические параметры подключения (адреса, порты, ключи и т. д.) берутся из инжектируемых хостов, чьи outbound-конфигурации подставляются в итоговый конфиг на стороне клиента. ::::

Важные замечания

  • Виртуальный хост должен быть включён и не скрыт. Именно он определяет, какой шаблон будет использован, и от него берутся remarks и description.
  • Инжектируемые хосты должны быть включены. По умолчанию выбираются только скрытые хосты (selectFrom: "HIDDEN"). Это поведение можно изменить на "NOT_HIDDEN" или "ALL". Если хост выключен или не найден по селектору — он будет пропущен.
  • Все участвующие хосты должны быть доступны конечному пользователю — инбаунд, к которому они привязаны, должен быть включён в сквад пользователя.
  • Объект remnawave удаляется из итогового конфига — клиент его не увидит.
  • Outbound'ы добавляются в начало массива outbounds. Если включён addVirtualHostAsOutbound, outbound виртуального хоста с тегом proxy идёт первым, затем — инжектированные, затем — статические outbound'ы из шаблона (direct, block).
  • Порядок хостов определяет порядок outbound'ов и присваиваемые им теги. Для селектора uuids — порядок UUID в массиве values. Вместо tagPrefix можно использовать useHostRemarkAsTag или useHostTagAsTag, чтобы теги формировались из свойств хостов.
  • Выбор шаблона и скрытие хоста находятся в разделе Расширенные в карточке хоста.