Add Mac client mode and simplify local proxy UI

This commit is contained in:
2026-05-19 13:12:39 +03:00
parent 2ef1e09986
commit d02dbe10de
22 changed files with 924 additions and 70 deletions

View File

@@ -3,6 +3,7 @@ import path from "node:path";
const dataDir = process.env.DATA_DIR || path.resolve(".vpn-proxy");
export const settings = {
appMode: process.env.APP_MODE === "client" ? "client" : "gateway",
port: Number(process.env.PORT || 3456),
proxyPort: Number(process.env.PROXY_PORT || 8080),
tproxyPort: Number(process.env.TPROXY_PORT || 7895),

View File

@@ -596,11 +596,11 @@ function publicState() {
const deviceProfiles = readDeviceProfiles();
const { subscriptionUrl, ...rest } = state;
return {
mode: "gateway",
mode: settings.appMode,
port: settings.port,
proxyPort: settings.proxyPort,
proxyBindIp: settings.bindIp,
tproxyPort: settings.tproxyPort,
tproxyPort: settings.appMode === "gateway" ? settings.tproxyPort : null,
routingRuDirect: settings.routingRuDirect,
configExists: fs.existsSync(settings.configPath),
singboxRunning: Boolean(singboxProcess),

View File

@@ -202,7 +202,7 @@ function ruDirectRule() {
};
}
function routeRules(customRules, vpnTag) {
function routeRules(customRules, vpnTag, { includeTransparent = true } = {}) {
const deviceProfiles = readDeviceProfiles();
const rules = [
{
@@ -217,8 +217,10 @@ function routeRules(customRules, vpnTag) {
const ruRule = ruDirectRule();
if (ruRule) rules.push(ruRule);
// Device defaults are only transparent-gateway fallbacks after global rules.
rules.push(...deviceDefaultRouteRules(deviceProfiles.devices, vpnTag));
if (includeTransparent) {
// Device defaults are only transparent-gateway fallbacks after global rules.
rules.push(...deviceDefaultRouteRules(deviceProfiles.devices, vpnTag));
}
const proxyFallback = inboundDefaultRule(
MIXED_INBOUND,
@@ -227,12 +229,14 @@ function routeRules(customRules, vpnTag) {
);
if (proxyFallback) rules.push(proxyFallback);
const transparentFallback = inboundDefaultRule(
TPROXY_INBOUND,
deviceProfiles.defaultTransparentMode,
vpnTag,
);
if (transparentFallback) rules.push(transparentFallback);
if (includeTransparent) {
const transparentFallback = inboundDefaultRule(
TPROXY_INBOUND,
deviceProfiles.defaultTransparentMode,
vpnTag,
);
if (transparentFallback) rules.push(transparentFallback);
}
return rules;
}
@@ -254,6 +258,30 @@ export function buildGatewayConfig(
}
const customRuleSets = readCustomRuleSets();
const clientMode = settings.appMode === "client";
const proxyOnlyRules = [{ inbound: [MIXED_INBOUND], outbound: vpnOutbound.tag }];
const inbounds = [
...(clientMode
? []
: [
{
type: "tproxy",
tag: "tproxy-in",
listen: "::",
listen_port: settings.tproxyPort,
sniff: true,
sniff_override_destination: true,
},
]),
{
type: "mixed",
tag: "mixed-in",
listen: settings.bindIp,
listen_port: settings.proxyPort,
sniff: true,
set_system_proxy: false,
},
];
return {
log: {
@@ -269,34 +297,21 @@ export function buildGatewayConfig(
dns: {
independent_cache: true,
},
inbounds: [
{
type: "tproxy",
tag: "tproxy-in",
listen: "::",
listen_port: settings.tproxyPort,
sniff: true,
sniff_override_destination: true,
},
{
type: "mixed",
tag: "mixed-in",
listen: settings.bindIp,
listen_port: settings.proxyPort,
sniff: true,
set_system_proxy: false,
},
],
inbounds,
outbounds: [
vpnOutbound,
{ type: "direct", tag: "direct" },
{ type: "block", tag: "block" },
],
route: {
rule_set: bypassAll ? [] : ruleSets(customRuleSets, vpnOutbound.tag),
rule_set: bypassAll || clientMode ? [] : ruleSets(customRuleSets, vpnOutbound.tag),
rules: bypassAll
? [{ ip_is_private: true, outbound: "direct" }]
: routeRules(subscriptionConfig.customRules, vpnOutbound.tag),
: clientMode
? proxyOnlyRules
: routeRules(subscriptionConfig.customRules, vpnOutbound.tag, {
includeTransparent: !clientMode,
}),
final: "direct",
auto_detect_interface: true,
},