Skip to content

Commit 0fa3488

Browse files
committed
Merge dev: virtual node UX polish + critical fixes
2 parents cca7005 + aebccc5 commit 0fa3488

25 files changed

Lines changed: 1416 additions & 255 deletions

‎index.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,23 @@ async function startServer() {
475475
// Index already dropped or never existed — safe to ignore
476476
}
477477

478+
// Migration: rebuild compound {ip, type} unique index when it exists
479+
// without a partial filter expression. Virtual nodes have ip=null, so the
480+
// unique constraint must skip non-string ip values; Mongoose will not
481+
// rewrite an existing index with new options on its own.
482+
try {
483+
const indexes = await mongoose.connection.collection('hynodes').indexes();
484+
const ipType = indexes.find(idx => idx.name === 'ip_1_type_1');
485+
if (ipType && !ipType.partialFilterExpression) {
486+
await mongoose.connection.collection('hynodes').dropIndex('ip_1_type_1');
487+
logger.info('[Migration] Dropped legacy ip_1_type_1 (non-partial) index');
488+
await HyNode.syncIndexes();
489+
logger.info('[Migration] Recreated hynodes indexes with partialFilterExpression');
490+
}
491+
} catch (e) {
492+
logger.warn(`[Migration] hynodes index sync skipped: ${e.message}`);
493+
}
494+
478495
// Migration: ensure all users have xrayUuid (for Xray VLESS support)
479496
const usersWithoutUuid = await HyUser.find({
480497
$or: [{ xrayUuid: { $exists: false } }, { xrayUuid: null }, { xrayUuid: '' }]

‎src/docs/i18n.js‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ module.exports = {
1717
3. Получить общую статистику через \`GET /stats\` и статус ноды через \`GET /nodes/{id}/status\`.
1818
4. Отключить истёкшего пользователя через \`POST /users/{userId}/disable\` и очистить устройства через \`DELETE /users/{userId}/devices\`.
1919
5. Собрать multi-hop маршрут через \`POST /cascade/links\`, затем развернуть цепочку через \`POST /cascade/chain/deploy\`.
20-
6. Автоматизировать действия панели через MCP: \`POST /mcp\` и \`tools/list\`.
20+
6. Объединить несколько реальных нод в один авто-балансировщик через \`POST /nodes\` (\`type=virtual\`); HAPP/Xray-core клиенты получат Xray balancer + observatory профиль, sing-box/Clash — \`urltest\`/\`url-test\` группу.
21+
7. Автоматизировать действия панели через MCP: \`POST /mcp\` и \`tools/list\`.
2122
2223
## Аутентификация
2324
@@ -181,23 +182,28 @@ JSON-эндпоинты возвращают ошибки в таком форм
181182
},
182183
'POST /nodes': {
183184
summary: 'Создать ноду',
184-
description: 'Создаёт Hysteria или Xray ноду; `statsSecret` генерируется сервером. 409 — если на том же IP уже есть нода того же `type`. Требуется скоуп `nodes:write`.',
185+
description: `Создаёт **Hysteria**, **Xray** или **Virtual** (балансировщик) ноду. \`statsSecret\` генерируется сервером.
186+
187+
- \`type=hysteria\` (по умолчанию) / \`type=xray\` — обязателен \`ip\`. 409, если на том же IP уже есть нода того же \`type\`.
188+
- \`type=virtual\` — логический балансировщик поверх реальных соседних нод. **Не передавайте** \`ip\` или \`ssh\`; передайте объект \`virtual\` с \`sources\` (или \`sourceGroup\`) и \`strategy\`. Виртуальные ноды не запускают setup, health-check, traffic collection и restart — они появляются только в подписках.
189+
190+
Требуется скоуп \`nodes:write\`.`,
185191
},
186192
'GET /nodes/{id}': {
187193
summary: 'Получить ноду по ID',
188194
description: 'Возвращает ноду с группами и количеством активных пользователей. Требуется скоуп `nodes:read`.',
189195
},
190196
'PUT /nodes/{id}': {
191197
summary: 'Обновить ноду',
192-
description: 'Частично обновляет настройки ноды и планирует отправку конфига при изменениях. Требуется скоуп `nodes:write`.',
198+
description: 'Частично обновляет настройки ноды и планирует отправку конфига при изменениях. При смене `type` на `virtual` нужно передать корректный объект `virtual`; `ip` будет автоматически очищен. При результирующем `type ≠ virtual` поле `ip` (унаследованное или переданное) должно быть непустым. Требуется скоуп `nodes:write`.',
193199
},
194200
'DELETE /nodes/{id}': {
195201
summary: 'Удалить ноду',
196202
description: 'Удаляет ноду и убирает её из назначений пользователей. Требуется скоуп `nodes:write`.',
197203
},
198204
'GET /nodes/check-ip': {
199205
summary: 'Проверить соседние ноды по IP',
200-
description: 'Возвращает Hysteria/Xray ноды на том же IP. Используется интерфейсом при добавлении ноды.',
206+
description: 'Возвращает Hysteria/Xray ноды на том же IP. Используется интерфейсом при добавлении ноды. Виртуальные ноды не имеют IP и в выдаче не появляются.',
201207
},
202208
'GET /nodes/{id}/status': {
203209
summary: 'Получить сохранённый статус ноды',
@@ -213,11 +219,11 @@ JSON-эндпоинты возвращают ошибки в таком форм
213219
},
214220
'POST /nodes/{id}/sync': {
215221
summary: 'Синхронизировать конкретную ноду',
216-
description: 'Отправляет актуальный конфиг на ноду через SSH.',
222+
description: 'Отправляет актуальный конфиг на ноду через SSH. Виртуальные ноды возвращают **400** — у них нет удалённого сервиса.',
217223
},
218224
'POST /nodes/{id}/setup': {
219225
summary: 'Автонастройка ноды через SSH',
220-
description: 'Настройка через SSH: для Hysteria — установка бинарника, конфиг, port hopping, перезапуск `hysteria-server`; для Xray — setup агента (из body используется только `restartService`). Обычно 30 секунд — 2 минуты; таймаут клиента ≥ 3 минуты. Нужны SSH-данные на ноде. Успех: `{ success, logs }`, ошибка: `500` с `{ success: false, error, logs }`.',
226+
description: 'Настройка через SSH: для Hysteria — установка бинарника, конфиг, port hopping, перезапуск `hysteria-server`; для Xray — setup агента (из body используется только `restartService`). Виртуальные ноды возвращают **400** — у них нет удалённого сервиса для настройки. Обычно 30 секунд — 2 минуты; таймаут клиента ≥ 3 минуты. Нужны SSH-данные на ноде. Успех: `{ success, logs }`, ошибка: `500` с `{ success: false, error, logs }`.',
221227
},
222228
'GET /nodes/{id}/config': {
223229
summary: 'Получить сгенерированный конфиг ноды',

0 commit comments

Comments
 (0)