feat(proxy): add destination overrides and fix PAC helpers
Add a hot-reloaded override table (overrides.json) that forces specific hosts/IPs to a chosen profile regardless of the active profile, applied across all bridge ports. Matching supports exact host, wildcard, domain suffix, single IP, and CIDR. Managed via `proxy-bridge override list|add|remove|clear` and surfaced in status/config. Also fix two PAC sandbox helpers: localHostOrDomainIs (unqualified-host case) and isInNet (was a no-op stub, now does real IPv4 subnet matching). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ const LISTEN_PORT = Number(process.env.PROXY_BRIDGE_LISTEN_PORT || 8888);
|
||||
const USER_FILE = process.env.PROXY_BRIDGE_USER_FILE || '/opt/proxy-bridge/user.json';
|
||||
const CONFIG_FILE = process.env.PROXY_BRIDGE_CONFIG_FILE || '/opt/proxy-bridge/config.json';
|
||||
const PROFILE_FILE = '/opt/proxy-bridge/profile.json';
|
||||
const OVERRIDES_FILE = process.env.PROXY_BRIDGE_OVERRIDES_FILE || '/opt/proxy-bridge/overrides.json';
|
||||
const LOCAL_PROXY_URL = `http://${LISTEN_HOST}:${LISTEN_PORT}`;
|
||||
const VSCODE_CERT_FLAG = ''; // e.g. '--ignore-certificate-errors'
|
||||
|
||||
@@ -81,6 +82,60 @@ function setProfile(profile) {
|
||||
console.log(`Profile set to: ${profile}`);
|
||||
}
|
||||
|
||||
function readOverrides() {
|
||||
try {
|
||||
const data = readJsonFile(OVERRIDES_FILE);
|
||||
if (Array.isArray(data)) return data;
|
||||
if (Array.isArray(data.rules)) return data.rules;
|
||||
} catch {}
|
||||
return [];
|
||||
}
|
||||
|
||||
function writeOverrides(rules) {
|
||||
writeJsonFile(OVERRIDES_FILE, { rules });
|
||||
}
|
||||
|
||||
function overrideCmd(args) {
|
||||
const action = args[0] || 'list';
|
||||
switch (action) {
|
||||
case 'list': {
|
||||
const rules = readOverrides();
|
||||
if (!rules.length) { console.log('No overrides configured.'); return; }
|
||||
console.log('Overrides (first match wins):');
|
||||
rules.forEach((r, i) => console.log(` [${i}] ${r.match} -> ${r.profile}`));
|
||||
return;
|
||||
}
|
||||
case 'add': {
|
||||
const [match, profile] = [args[1], args[2]];
|
||||
if (!match || !profile) { console.error('Usage: proxy-bridge override add <host|ip|cidr|glob> <profile>'); process.exit(1); }
|
||||
const valid = getAvailableProfiles();
|
||||
if (!valid.includes(profile)) { console.error(`Invalid profile: ${profile}. Valid options: ${valid.join(', ')}`); process.exit(1); }
|
||||
const rules = readOverrides().filter(r => r.match !== match);
|
||||
rules.push({ match, profile });
|
||||
writeOverrides(rules);
|
||||
console.log(`Override set: ${match} -> ${profile}`);
|
||||
return;
|
||||
}
|
||||
case 'remove': {
|
||||
const match = args[1];
|
||||
if (!match) { console.error('Usage: proxy-bridge override remove <match>'); process.exit(1); }
|
||||
const before = readOverrides();
|
||||
const after = before.filter(r => r.match !== match);
|
||||
if (after.length === before.length) { console.log(`No override found for: ${match}`); return; }
|
||||
writeOverrides(after);
|
||||
console.log(`Removed override: ${match}`);
|
||||
return;
|
||||
}
|
||||
case 'clear':
|
||||
writeOverrides([]);
|
||||
console.log('All overrides cleared.');
|
||||
return;
|
||||
default:
|
||||
console.error('Usage: proxy-bridge override [list|add <match> <profile>|remove <match>|clear]');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function systemctl(args, options = {}) {
|
||||
return run('systemctl', ['--user', ...args], options);
|
||||
}
|
||||
@@ -133,6 +188,7 @@ async function status() {
|
||||
console.log(`pid: ${pid && pid !== '0' ? pid : 'none'}`);
|
||||
if (isUpstreamEnabled()) console.log(`account: ${readAccount()}`);
|
||||
console.log(`upstream: ${getUpstreamConfig()}`);
|
||||
console.log(`overrides: ${readOverrides().length}`);
|
||||
console.log(`\nPort Mappings:`);
|
||||
console.log(` ${LISTEN_PORT.toString().padEnd(5)} -> dynamic (currently: ${current})${listening ? '' : ' [NOT REACHABLE]'}`);
|
||||
all.forEach((name, i) => {
|
||||
@@ -170,6 +226,10 @@ function config() {
|
||||
all.forEach((name, i) => {
|
||||
console.log(` ${(LISTEN_PORT + 1 + i).toString().padEnd(5)} -> ${name}`);
|
||||
});
|
||||
const overrides = readOverrides();
|
||||
console.log(`\nOverrides (first match wins, apply to all ports):`);
|
||||
if (!overrides.length) console.log(' (none)');
|
||||
else overrides.forEach((r, i) => console.log(` [${i}] ${r.match} -> ${r.profile}`));
|
||||
}
|
||||
|
||||
function codeSettingsPath() {
|
||||
@@ -293,6 +353,7 @@ Commands:
|
||||
status Show service, listener, upstream, profile, and account
|
||||
profile [name] Show or set the active routing profile
|
||||
toggle Cycle through profiles (internet -> direct -> off -> [pacs...])
|
||||
override [...] Manage destination overrides (see below)
|
||||
start Start the local proxy bridge
|
||||
stop Stop the local proxy bridge
|
||||
restart Restart the local proxy bridge
|
||||
@@ -304,6 +365,14 @@ Commands:
|
||||
vscode Manage VS Code proxy settings
|
||||
help Show this help
|
||||
|
||||
Overrides (force a destination to a profile, regardless of active profile):
|
||||
proxy-bridge override list
|
||||
proxy-bridge override add <match> <profile>
|
||||
proxy-bridge override remove <match>
|
||||
proxy-bridge override clear
|
||||
<match> may be an exact host, a wildcard (*.example.com), a domain
|
||||
suffix (.example.com), an IP (192.168.1.1), or a CIDR (10.0.0.0/8).
|
||||
|
||||
VS Code:
|
||||
proxy-bridge vscode status
|
||||
proxy-bridge vscode setup
|
||||
@@ -326,6 +395,7 @@ async function main() {
|
||||
const next = profiles[(profiles.indexOf(current) + 1) % profiles.length];
|
||||
setProfile(next);
|
||||
break;
|
||||
case 'override': overrideCmd(process.argv.slice(3)); break;
|
||||
case 'start':
|
||||
case 'stop':
|
||||
case 'restart':
|
||||
|
||||
Reference in New Issue
Block a user