90 lines
2.7 KiB
JavaScript
90 lines
2.7 KiB
JavaScript
import fs from 'node:fs';
|
|
import { settings } from './config.js';
|
|
import { HarborError } from '../shared/errors.js';
|
|
import { atomicWriteFile, atomicWriteJson } from './services/stateStore.js';
|
|
|
|
const PROXY_TYPES = new Set(['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2']);
|
|
const MIXED_INBOUND = 'mixed-in';
|
|
const TPROXY_INBOUND = 'tproxy-in';
|
|
|
|
function findOutbound(subscriptionConfig, selectedTag) {
|
|
const outbounds = Array.isArray(subscriptionConfig?.outbounds)
|
|
? subscriptionConfig.outbounds
|
|
: [];
|
|
const tag = String(selectedTag || '').trim();
|
|
return outbounds.find((outbound) => (
|
|
String(outbound.tag || '').trim() === tag && PROXY_TYPES.has(outbound.type)
|
|
));
|
|
}
|
|
|
|
export function buildGatewayConfig(subscriptionConfig, selectedTag, { clientDirect = false } = {}) {
|
|
const clientMode = settings.appMode === 'client';
|
|
const directClient = clientMode && clientDirect;
|
|
const vpnOutbound = directClient
|
|
? null
|
|
: structuredClone(findOutbound(subscriptionConfig, selectedTag));
|
|
if (!directClient && !vpnOutbound) throw new HarborError('SERVER_NOT_FOUND');
|
|
if (vpnOutbound && !vpnOutbound.tag) vpnOutbound.tag = 'vpn-out';
|
|
if (vpnOutbound?.type === 'vless' && !vpnOutbound.packet_encoding) {
|
|
vpnOutbound.packet_encoding = 'xudp';
|
|
}
|
|
const outboundTag = directClient ? 'direct' : vpnOutbound.tag;
|
|
|
|
const inbounds = [
|
|
...(!clientMode ? [{
|
|
type: 'tproxy',
|
|
tag: TPROXY_INBOUND,
|
|
listen: '::',
|
|
listen_port: settings.tproxyPort,
|
|
sniff: true,
|
|
sniff_override_destination: true,
|
|
}] : []),
|
|
{
|
|
type: 'mixed',
|
|
tag: MIXED_INBOUND,
|
|
listen: settings.bindIp,
|
|
listen_port: settings.proxyPort,
|
|
sniff: true,
|
|
set_system_proxy: false,
|
|
},
|
|
];
|
|
const rules = clientMode
|
|
? [{ inbound: [MIXED_INBOUND], outbound: outboundTag }]
|
|
: [
|
|
{ inbound: [TPROXY_INBOUND], outbound: outboundTag },
|
|
{ inbound: [MIXED_INBOUND], outbound: outboundTag },
|
|
];
|
|
|
|
return {
|
|
log: { level: settings.logLevel, timestamp: true },
|
|
experimental: {
|
|
cache_file: { enabled: true, path: settings.cachePath },
|
|
},
|
|
dns: { independent_cache: true },
|
|
inbounds,
|
|
outbounds: [
|
|
...(vpnOutbound ? [vpnOutbound] : []),
|
|
{ type: 'direct', tag: 'direct' },
|
|
{ type: 'block', tag: 'block' },
|
|
],
|
|
route: {
|
|
rule_set: [],
|
|
rules,
|
|
final: outboundTag,
|
|
...(clientMode ? {} : { auto_detect_interface: true }),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function writeSingboxConfig(config) {
|
|
atomicWriteJson(settings.configPath, config);
|
|
}
|
|
|
|
export function restoreSingboxConfig(contents) {
|
|
atomicWriteFile(settings.configPath, contents);
|
|
}
|
|
|
|
export function removeSingboxConfig() {
|
|
fs.rmSync(settings.configPath, { force: true });
|
|
}
|