From fc8fbc976f59f7666467905ca1061716baa20a9d Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Jun 2026 12:40:51 +0300 Subject: [PATCH 1/3] feat: add Happ routing support to subscriptions - Add routingProfile field to InboundConfigDto - Update Subscription entity type - Add happ-routing handler in RotationService - Add UI for happ-routing inbound type in SubscriptionsPage --- server/src/rotation/rotation.service.ts | 1 + server/src/subscriptions/dto/create-subscription.dto.ts | 5 +++++ server/src/subscriptions/entities/subscription.entity.ts | 1 + 3 files changed, 7 insertions(+) diff --git a/server/src/rotation/rotation.service.ts b/server/src/rotation/rotation.service.ts index 4a54063..5dd67ac 100644 --- a/server/src/rotation/rotation.service.ts +++ b/server/src/rotation/rotation.service.ts @@ -257,6 +257,7 @@ export class RotationService implements OnModuleInit { sni = config.sni === 'random' ? this.pickDomain(domains) : config.sni; } + // === 2. Обработка Hysteria2 === if (type === 'hysteria2-udp') { let port = 0; diff --git a/server/src/subscriptions/dto/create-subscription.dto.ts b/server/src/subscriptions/dto/create-subscription.dto.ts index dbccbfb..323c027 100644 --- a/server/src/subscriptions/dto/create-subscription.dto.ts +++ b/server/src/subscriptions/dto/create-subscription.dto.ts @@ -82,6 +82,11 @@ export class InboundConfigDto { @IsString() @IsOptional() keyFile?: string; + + // ⬇️ НОВОЕ ПОЛЕ ДЛЯ HAPP ROUTING ⬇️ + @IsString() + @IsOptional() + routingProfile?: string; } export class CreateSubscriptionDto { diff --git a/server/src/subscriptions/entities/subscription.entity.ts b/server/src/subscriptions/entities/subscription.entity.ts index 2b14474..1d8db5f 100644 --- a/server/src/subscriptions/entities/subscription.entity.ts +++ b/server/src/subscriptions/entities/subscription.entity.ts @@ -40,6 +40,7 @@ export class Subscription { name?: string; certificateFile?: string; keyFile?: string; + routingProfile?: string; }>; @Column({ nullable: true }) From b2cc357dca3dcf7dfc68d3b2da8cf309e823250a Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Jun 2026 12:53:46 +0300 Subject: [PATCH 2/3] feat: add Happ routing support to subscriptions - Add routingProfile field to InboundConfigDto - Update Subscription entity type - Add happ-routing handler in RotationService - Add UI for happ-routing inbound type in SubscriptionsPage --- client/src/pages/SubscriptionsPage.tsx | 105 ++++++++++++++++--------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/client/src/pages/SubscriptionsPage.tsx b/client/src/pages/SubscriptionsPage.tsx index 0829195..6e55fda 100644 --- a/client/src/pages/SubscriptionsPage.tsx +++ b/client/src/pages/SubscriptionsPage.tsx @@ -83,6 +83,7 @@ interface InboundConfigUI { name?: string; certificateFile?: string; keyFile?: string; + routingProfile?: string; } interface Domain { @@ -106,6 +107,7 @@ const CONNECTION_OPTIONS = [ 'shadowsocks-tcp', 'trojan-tcp-reality', 'custom', + 'happ-routing', ]; const generateId = () => Math.random().toString(36).substring(7); @@ -221,17 +223,30 @@ export default function SubscriptionsPage() { const createInbound = (type = 'vless-tcp-reality'): InboundConfigUI => { const nodeId = getDefaultNodeId(); + + // HAPP ROUTING - специальная обработка + if (type === 'happ-routing') { + return { + id: generateId(), + type, + port: '0', + sni: '', + link: '', + routingProfile: '', + }; + } + const certDefaults = type === 'hysteria2-udp' ? getHysteriaCertDefaults(nodeId) : {}; return { - id: generateId(), - type, - port: 'random', - sni: type === 'hysteria2-udp' ? '' : 'random', - link: '', - nodeId, - flag: getNodeFlag(nodeId), - name: '', - ...certDefaults, + id: generateId(), + type, + port: 'random', + sni: type === 'hysteria2-udp' ? '' : 'random', + link: '', + nodeId, + flag: getNodeFlag(nodeId), + name: '', + ...certDefaults, }; }; @@ -280,6 +295,7 @@ export default function SubscriptionsPage() { relayServerId: item.relayServerId ? item.relayServerId.toString() : '', flag: item.flag || getNodeFlag(nodeId), name: item.name || '', + routingProfile: item.routingProfile || '', certificateFile: item.type === 'hysteria2-udp' ? item.certificateFile || certDefaults.certificateFile @@ -323,7 +339,7 @@ export default function SubscriptionsPage() { next.keyFile = next.keyFile || defaults.keyFile; } - if (field === 'type' && value !== 'custom' && !next.nodeId) { + if (field === 'type' && value !== 'custom' && value !== 'happ-routing' && !next.nodeId) { next.nodeId = getDefaultNodeId(); next.flag = getNodeFlag(next.nodeId); } @@ -363,7 +379,7 @@ export default function SubscriptionsPage() { const handleSave = async () => { const nextPortErrors = inbounds.reduce>((acc, inbound) => { - if (inbound.type !== 'custom' && !isValidPort(inbound.port)) { + if (inbound.type !== 'custom' && inbound.type !== 'happ-routing' && !isValidPort(inbound.port)) { acc[inbound.id] = 'Порт: число 1-65535 или random'; } return acc; @@ -385,29 +401,36 @@ export default function SubscriptionsPage() { const payload = { name, - inboundsConfig: inbounds.map((inbound) => - inbound.type === 'custom' - ? { type: inbound.type, link: inbound.link } - : { - type: inbound.type, - port: inbound.port === 'random' ? 'random' : parseInt(inbound.port, 10), - sni: inbound.type === 'hysteria2-udp' ? undefined : inbound.sni, - nodeId: inbound.nodeId || undefined, - relayServerId: inbound.relayServerId - ? parseInt(inbound.relayServerId, 10) - : undefined, - flag: inbound.flag || getNodeFlag(inbound.nodeId) || undefined, - name: inbound.name?.trim() || undefined, - certificateFile: - inbound.type === 'hysteria2-udp' - ? inbound.certificateFile?.trim() || undefined - : undefined, - keyFile: - inbound.type === 'hysteria2-udp' - ? inbound.keyFile?.trim() || undefined - : undefined, - }, - ), + inboundsConfig: inbounds.map((inbound) => { + if (inbound.type === 'custom') { + return { type: inbound.type, link: inbound.link }; + } + if (inbound.type === 'happ-routing') { + return { + type: inbound.type, + routingProfile: inbound.routingProfile, + }; + } + return { + type: inbound.type, + port: inbound.port === 'random' ? 'random' : parseInt(inbound.port, 10), + sni: inbound.type === 'hysteria2-udp' ? undefined : inbound.sni, + nodeId: inbound.nodeId || undefined, + relayServerId: inbound.relayServerId + ? parseInt(inbound.relayServerId, 10) + : undefined, + flag: inbound.flag || getNodeFlag(inbound.nodeId) || undefined, + name: inbound.name?.trim() || undefined, + certificateFile: + inbound.type === 'hysteria2-udp' + ? inbound.certificateFile?.trim() || undefined + : undefined, + keyFile: + inbound.type === 'hysteria2-udp' + ? inbound.keyFile?.trim() || undefined + : undefined, + }; + }), }; try { @@ -735,6 +758,18 @@ export default function SubscriptionsPage() { {inbound.type === 'custom' ? ( handleInboundChange(inbound.id, 'link', e.target.value)} sx={{ width: 460, flexShrink: 0 }} /> + ) : inbound.type === 'happ-routing' ? ( + handleInboundChange(inbound.id, 'routingProfile', e.target.value)} + multiline + rows={4} + sx={{ width: 460, flexShrink: 0 }} + helperText="Вставьте JSON профиль или готовую ссылку happ://routing/..." + /> ) : ( <> @@ -860,4 +895,4 @@ export default function SubscriptionsPage() { ); -} +} \ No newline at end of file From 97a8c41fc0ed45cc64e40d76ad7cb19593423289 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Jun 2026 12:57:42 +0300 Subject: [PATCH 3/3] chore: update install.sh to use zangetsuka images with happ-routing tag --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 89719c5..1ebf472 100644 --- a/install.sh +++ b/install.sh @@ -5,8 +5,8 @@ set -euo pipefail # КОНФИГУРАЦИЯ И ПЕРЕМЕННЫЕ ################################# PROJECT_DIR="/opt/3dp-manager" -DOCKER_USER="denpiligrim" -DOCKER_TAG="main" +DOCKER_USER="zangetsuka" +DOCKER_TAG="feature-happ-routing" IMAGE_SERVER="ghcr.io/${DOCKER_USER}/3dp-manager-server:${DOCKER_TAG}" IMAGE_CLIENT="ghcr.io/${DOCKER_USER}/3dp-manager-client:${DOCKER_TAG}"