Files
harbor-net/src/server/singbox.js
Dmitriy Petrov c9223aa3a9
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 16s
Build and Deploy Gateway / deploy (push) Successful in 6s
Harden server state and config persistence
2026-07-11 21:07:11 +03:00

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 });
}