5 Commits

Author SHA1 Message Date
Den Piligrim 1b5ad11185 Merge branch 'main' into test 2026-06-11 11:16:07 +03:00
DenPiligrim 2e1cd86e99 Merge pull request #53 from zangetsuka/feature/happ-routing
Feature/happ routing
2026-06-04 10:48:09 +03:00
root 97a8c41fc0 chore: update install.sh to use zangetsuka images with happ-routing tag 2026-06-01 12:57:42 +03:00
root b2cc357dca 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
2026-06-01 12:53:46 +03:00
root fc8fbc976f 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
2026-06-01 12:40:51 +03:00
5 changed files with 79 additions and 37 deletions
+69 -34
View File
@@ -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<Record<string, string>>((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() {
</FormControl>
{inbound.type === 'custom' ? (
<TextField size="small" label="Ссылка на подключение" placeholder="vless://..." value={inbound.link || ''} onChange={(e) => handleInboundChange(inbound.id, 'link', e.target.value)} sx={{ width: 460, flexShrink: 0 }} />
) : inbound.type === 'happ-routing' ? (
<TextField
size="small"
label="Routing профиль (JSON или base64)"
placeholder='{"Name":"MyProfile","GlobalProxy":"true","DirectSites":["geosite:cn"]}'
value={inbound.routingProfile || ''}
onChange={(e) => handleInboundChange(inbound.id, 'routingProfile', e.target.value)}
multiline
rows={4}
sx={{ width: 460, flexShrink: 0 }}
helperText="Вставьте JSON профиль или готовую ссылку happ://routing/..."
/>
) : (
<>
<FormControl size="small" sx={{ width: 170, flexShrink: 0 }}>
+2 -2
View File
@@ -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}"
+1
View File
@@ -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;
@@ -82,6 +82,11 @@ export class InboundConfigDto {
@IsString()
@IsOptional()
keyFile?: string;
// ⬇️ НОВОЕ ПОЛЕ ДЛЯ HAPP ROUTING ⬇️
@IsString()
@IsOptional()
routingProfile?: string;
}
export class CreateSubscriptionDto {
@@ -40,6 +40,7 @@ export class Subscription {
name?: string;
certificateFile?: string;
keyFile?: string;
routingProfile?: string;
}>;
@Column({ nullable: true })