Add home bypass mode for the Mac client
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 12s
Build and Deploy Gateway / deploy (push) Successful in 1s

This commit is contained in:
2026-05-19 13:47:53 +03:00
parent d02dbe10de
commit c6352d781f
10 changed files with 199 additions and 8 deletions

View File

@@ -0,0 +1,43 @@
import fs from "node:fs";
import path from "node:path";
import { settings } from "./config.js";
const DEFAULT_CLIENT_SETTINGS = {
homeBypassEnabled: false,
};
function readJson(filePath, fallback) {
try {
if (!fs.existsSync(filePath)) return fallback;
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch {
return fallback;
}
}
function writeJson(filePath, value) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf8");
}
export function normalizeClientSettings(input = {}) {
return {
homeBypassEnabled: Boolean(input.homeBypassEnabled),
};
}
export function readClientSettings() {
return normalizeClientSettings({
...DEFAULT_CLIENT_SETTINGS,
...readJson(settings.clientSettingsPath, {}),
});
}
export function writeClientSettings(input) {
const normalized = normalizeClientSettings({
...readClientSettings(),
...(input && typeof input === "object" ? input : {}),
});
writeJson(settings.clientSettingsPath, normalized);
return normalized;
}

View File

@@ -16,6 +16,7 @@ export const settings = {
statePath: path.join(dataDir, "state.json"),
customRulesPath: path.join(dataDir, "custom-rules.json"),
customRuleSetsPath: path.join(dataDir, "custom-rule-sets.json"),
clientSettingsPath: path.join(dataDir, "client-settings.json"),
devicesPath: path.join(dataDir, "devices.json"),
deviceRulesPath: path.join(dataDir, "device-rules.json"),
subscriptionCachePath: path.join(dataDir, "subscription-cache.json"),

View File

@@ -17,6 +17,10 @@ import {
readDeviceProfiles,
writeDeviceProfiles,
} from "./devices.js";
import {
readClientSettings,
writeClientSettings,
} from "./clientSettings.js";
import { matchRoute, detectRuleConflicts } from "./routeMatcher.js";
import { tcpPing, resolveHost } from "./ping.js";
@@ -602,6 +606,7 @@ function publicState() {
proxyBindIp: settings.bindIp,
tproxyPort: settings.appMode === "gateway" ? settings.tproxyPort : null,
routingRuDirect: settings.routingRuDirect,
clientSettings: readClientSettings(),
configExists: fs.existsSync(settings.configPath),
singboxRunning: Boolean(singboxProcess),
singboxStartedAt,
@@ -924,6 +929,33 @@ async function handleApi(req, res) {
});
}
if (req.method === "GET" && req.url === "/api/client-settings") {
return sendJson(res, 200, {
success: true,
clientSettings: readClientSettings(),
});
}
if (req.method === "PUT" && req.url === "/api/client-settings") {
const body = await readBody(req);
const clientSettings = writeClientSettings(body.clientSettings || body);
const prevState = readJson(settings.statePath, {});
if (
settings.appMode === "client" &&
prevState.selectedTag &&
readJson(settings.subscriptionCachePath, null)?.config
) {
await applySelectedServer(prevState.selectedTag);
}
return sendJson(res, 200, {
success: true,
clientSettings,
singboxRunning: Boolean(singboxProcess),
});
}
if (req.method === "GET" && req.url === "/api/rule-sets") {
return sendJson(res, 200, {
success: true,

View File

@@ -7,6 +7,7 @@ import {
normalizeCidr,
readDeviceProfiles,
} from "./devices.js";
import { readClientSettings } from "./clientSettings.js";
const PROXY_TYPES = new Set([
"vless",
@@ -259,7 +260,11 @@ export function buildGatewayConfig(
const customRuleSets = readCustomRuleSets();
const clientMode = settings.appMode === "client";
const proxyOnlyRules = [{ inbound: [MIXED_INBOUND], outbound: vpnOutbound.tag }];
const clientSettings = clientMode ? readClientSettings() : null;
const clientOutbound = clientSettings?.homeBypassEnabled
? "direct"
: vpnOutbound.tag;
const proxyOnlyRules = [{ inbound: [MIXED_INBOUND], outbound: clientOutbound }];
const inbounds = [
...(clientMode
? []