diff --git a/src/server/config.js b/src/server/config.js index d7438fd..88e137b 100644 --- a/src/server/config.js +++ b/src/server/config.js @@ -1,9 +1,13 @@ import path from "node:path"; const dataDir = process.env.DATA_DIR || path.resolve(".vpn-proxy"); +const rawAppMode = String(process.env.APP_MODE || "gateway").toLowerCase(); +const appMode = ["gateway", "client", "windows"].includes(rawAppMode) + ? rawAppMode + : "gateway"; export const settings = { - appMode: process.env.APP_MODE === "client" ? "client" : "gateway", + appMode, port: Number(process.env.PORT || 3456), proxyPort: Number(process.env.PROXY_PORT || 8080), clientProxyPortStart: Number(process.env.CLIENT_PROXY_PORT_START || 8080), @@ -22,10 +26,24 @@ export const settings = { devicesPath: path.join(dataDir, "devices.json"), deviceRulesPath: path.join(dataDir, "device-rules.json"), subscriptionCachePath: path.join(dataDir, "subscription-cache.json"), + windowsProfilesPath: path.join(dataDir, "windows-profiles.json"), + windowsTargetsPath: path.join(dataDir, "proxy-targets.json"), + windowsStatePath: path.join(dataDir, "windows-state.json"), + windowsActivityPath: path.join(dataDir, "windows-activity.json"), + windowsHelperPath: + process.env.WINDOWS_HELPER || path.resolve("scripts/windows/helper.ps1"), + proxifyreConfigPath: + process.env.PROXIFYRE_CONFIG || + "C:\\Tools\\ProxiFyre\\app-config.json", sharedProxyHost: process.env.SHARED_PROXY_HOST || "", hwidPath: path.join(dataDir, "hwid"), routingRuDirect: String(process.env.ROUTING_RU_DIRECT || "true") !== "false", ruleSetDownloadDetour: process.env.RULE_SET_DOWNLOAD_DETOUR || "vpn", logLevel: process.env.LOG_LEVEL || "info", - appName: "VPN Proxy Gateway", + appName: + appMode === "windows" + ? "VPN Proxy Windows" + : appMode === "client" + ? "VPN Proxy Client" + : "VPN Proxy Gateway", }; diff --git a/src/server/singbox.js b/src/server/singbox.js index 1e6958d..ab56949 100644 --- a/src/server/singbox.js +++ b/src/server/singbox.js @@ -267,6 +267,8 @@ export function buildGatewayConfig( { bypassAll = false } = {}, ) { const customRuleSets = readCustomRuleSets(); + const proxyOnlyMode = + settings.appMode === "client" || settings.appMode === "windows"; const clientMode = settings.appMode === "client"; const clientSettings = clientMode ? readClientSettings() : null; const sharedOutbound = @@ -295,7 +297,7 @@ export function buildGatewayConfig( const mixedProxyPort = clientSettings?.proxyPort || settings.proxyPort; const proxyOnlyRules = [{ inbound: [MIXED_INBOUND], outbound: clientOutbound }]; const inbounds = [ - ...(clientMode + ...(proxyOnlyMode ? [] : [ { @@ -338,13 +340,16 @@ export function buildGatewayConfig( { type: "block", tag: "block" }, ], route: { - rule_set: bypassAll || clientMode ? [] : ruleSets(customRuleSets, vpnOutbound.tag), + rule_set: + bypassAll || proxyOnlyMode + ? [] + : ruleSets(customRuleSets, vpnOutbound.tag), rules: bypassAll ? [{ ip_is_private: true, outbound: "direct" }] - : clientMode + : proxyOnlyMode ? proxyOnlyRules : routeRules(subscriptionConfig.customRules, vpnOutbound.tag, { - includeTransparent: !clientMode, + includeTransparent: !proxyOnlyMode, }), final: "direct", auto_detect_interface: true, diff --git a/test/server/singbox-windows-mode.test.js b/test/server/singbox-windows-mode.test.js new file mode 100644 index 0000000..f8194e1 --- /dev/null +++ b/test/server/singbox-windows-mode.test.js @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +process.env.APP_MODE = "windows"; +process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "vpn-proxy-windows-test-")); +process.env.SING_BOX_CACHE = path.join(process.env.DATA_DIR, "cache.db"); +process.env.PROXY_PORT = "1080"; +process.env.PROXY_BIND_IP = "127.0.0.1"; + +const { settings } = await import( + `../../src/server/config.js?windows-mode=${Date.now()}` +); +const { buildGatewayConfig } = await import( + `../../src/server/singbox.js?windows-mode=${Date.now()}` +); + +const subscriptionConfig = { + outbounds: [ + { + type: "vless", + tag: "win-vpn", + server: "vpn.example.test", + server_port: 443, + uuid: "00000000-0000-4000-8000-000000000000", + tls: { enabled: true }, + }, + ], + customRules: [], +}; + +test("settings accepts APP_MODE=windows", () => { + assert.equal(settings.appMode, "windows"); + assert.equal(settings.proxyPort, 1080); + assert.equal(settings.bindIp, "127.0.0.1"); +}); + +test("windows mode exposes only local mixed proxy inbound", () => { + const config = buildGatewayConfig(subscriptionConfig, "win-vpn"); + + assert.deepEqual(config.inbounds.map((inbound) => inbound.tag), ["mixed-in"]); + assert.equal(config.inbounds[0].type, "mixed"); + assert.equal(config.inbounds[0].listen, "127.0.0.1"); + assert.equal(config.inbounds[0].listen_port, 1080); +}); + +test("windows mode routes mixed proxy to selected VPN outbound", () => { + const config = buildGatewayConfig(subscriptionConfig, "win-vpn"); + + assert.deepEqual(config.route.rule_set, []); + assert.deepEqual(config.route.rules, [ + { inbound: ["mixed-in"], outbound: "win-vpn" }, + ]); + assert.deepEqual(config.outbounds.map((outbound) => outbound.tag), [ + "win-vpn", + "direct", + "block", + ]); +});