Update proxy bridge setup
This commit is contained in:
+86
-189
@@ -13,7 +13,7 @@ const USER_FILE = process.env.PROXY_BRIDGE_USER_FILE || '/opt/proxy-bridge/user.
|
||||
const CONFIG_FILE = process.env.PROXY_BRIDGE_CONFIG_FILE || '/opt/proxy-bridge/config.json';
|
||||
const PROFILE_FILE = '/opt/proxy-bridge/profile.json';
|
||||
const LOCAL_PROXY_URL = `http://${LISTEN_HOST}:${LISTEN_PORT}`;
|
||||
const VSCODE_CERT_FLAG = '';
|
||||
const VSCODE_CERT_FLAG = ''; // e.g. '--ignore-certificate-errors'
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
return spawnSync(command, args, {
|
||||
@@ -26,23 +26,38 @@ function getUpstreamConfig() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||
if (config.enabled === false) return 'DISABLED';
|
||||
return `${config.host}:${config.port}`;
|
||||
}
|
||||
} catch {}
|
||||
return 'PROXY_HOST_PLACEHOLDER:8080 (default)';
|
||||
return 'your-proxy.example.com:8080 (default)';
|
||||
}
|
||||
|
||||
function getProfile() {
|
||||
try {
|
||||
if (fs.existsSync(PROFILE_FILE)) {
|
||||
return JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8')).profile || 'internet';
|
||||
const current = JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8')).profile;
|
||||
const valid = getAvailableProfiles();
|
||||
if (current && valid.includes(current)) return current;
|
||||
}
|
||||
} catch {}
|
||||
return 'internet';
|
||||
return isUpstreamEnabled() ? 'internet' : 'direct';
|
||||
}
|
||||
|
||||
function isUpstreamEnabled() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')).enabled !== false;
|
||||
}
|
||||
} catch {}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getAvailableProfiles() {
|
||||
const profiles = ['internet', 'direct', 'off'];
|
||||
const profiles = [];
|
||||
if (isUpstreamEnabled()) profiles.push('internet');
|
||||
profiles.push('direct', 'off');
|
||||
|
||||
const pacDir = path.join(os.homedir(), '.mozilla');
|
||||
if (fs.existsSync(pacDir)) {
|
||||
const files = fs.readdirSync(pacDir);
|
||||
@@ -71,49 +86,28 @@ function systemctl(args, options = {}) {
|
||||
}
|
||||
|
||||
function printCommandFailure(result) {
|
||||
if (result.error) {
|
||||
console.error(result.error.message);
|
||||
return;
|
||||
}
|
||||
if (result.stderr) {
|
||||
console.error(result.stderr.trim());
|
||||
}
|
||||
if (result.error) { console.error(result.error.message); return; }
|
||||
if (result.stderr) { console.error(result.stderr.trim()); }
|
||||
}
|
||||
|
||||
function serviceValue(property) {
|
||||
const result = systemctl(['show', SERVICE, '--property', property, '--value']);
|
||||
if (result.status !== 0) {
|
||||
return '';
|
||||
}
|
||||
if (result.status !== 0) return '';
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
function serviceState(command) {
|
||||
const result = systemctl([command, SERVICE]);
|
||||
if (result.status !== 0) {
|
||||
return 'unknown';
|
||||
}
|
||||
if (result.status !== 0) return 'unknown';
|
||||
return result.stdout.trim() || 'unknown';
|
||||
}
|
||||
|
||||
function checkPort() {
|
||||
return new Promise((resolve) => {
|
||||
const socket = net.connect({ host: LISTEN_HOST, port: LISTEN_PORT });
|
||||
const timer = setTimeout(() => {
|
||||
socket.destroy();
|
||||
resolve(false);
|
||||
}, 1000);
|
||||
|
||||
socket.once('connect', () => {
|
||||
clearTimeout(timer);
|
||||
socket.end();
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
socket.once('error', () => {
|
||||
clearTimeout(timer);
|
||||
resolve(false);
|
||||
});
|
||||
const timer = setTimeout(() => { socket.destroy(); resolve(false); }, 1000);
|
||||
socket.once('connect', () => { clearTimeout(timer); socket.end(); resolve(true); });
|
||||
socket.once('error', () => { clearTimeout(timer); resolve(false); });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -130,49 +124,52 @@ async function status() {
|
||||
const enabled = serviceState('is-enabled');
|
||||
const pid = serviceValue('MainPID');
|
||||
const listening = await checkPort();
|
||||
const profile = getProfile();
|
||||
const current = getProfile();
|
||||
const all = getAvailableProfiles();
|
||||
|
||||
console.log(`service: ${SERVICE}`);
|
||||
console.log(`active: ${active}`);
|
||||
console.log(`enabled: ${enabled}`);
|
||||
console.log(`pid: ${pid && pid !== '0' ? pid : 'none'}`);
|
||||
console.log(`profile: ${profile}`);
|
||||
console.log(`available: ${all.join(', ')}`);
|
||||
console.log(`listen: http://${LISTEN_HOST}:${LISTEN_PORT} (${listening ? 'reachable' : 'not reachable'})`);
|
||||
if (isUpstreamEnabled()) console.log(`account: ${readAccount()}`);
|
||||
console.log(`upstream: ${getUpstreamConfig()}`);
|
||||
console.log(`account: ${readAccount()}`);
|
||||
console.log(`\nPort Mappings:`);
|
||||
console.log(` ${LISTEN_PORT.toString().padEnd(5)} -> dynamic (currently: ${current})${listening ? '' : ' [NOT REACHABLE]'}`);
|
||||
all.forEach((name, i) => {
|
||||
console.log(` ${(LISTEN_PORT + 1 + i).toString().padEnd(5)} -> ${name}`);
|
||||
});
|
||||
}
|
||||
|
||||
function control(action) {
|
||||
const result = systemctl([action, SERVICE], { stdio: 'inherit' });
|
||||
if (result.status !== 0) {
|
||||
printCommandFailure(result);
|
||||
process.exit(result.status || 1);
|
||||
}
|
||||
if (result.status !== 0) { printCommandFailure(result); process.exit(result.status || 1); }
|
||||
}
|
||||
|
||||
function logs() {
|
||||
const child = spawn('journalctl', ['--user', '-u', SERVICE, '-f'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
const child = spawn('journalctl', ['--user', '-u', SERVICE, '-f'], { stdio: 'inherit' });
|
||||
child.on('exit', (code) => process.exit(code || 0));
|
||||
}
|
||||
|
||||
function setup() {
|
||||
const child = spawn('node', ['/opt/proxy-bridge/setup.js'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
const child = spawn('node', ['/opt/proxy-bridge/setup.js'], { stdio: 'inherit' });
|
||||
child.on('exit', (code) => process.exit(code || 0));
|
||||
}
|
||||
|
||||
function config() {
|
||||
console.log(`listen: ${LOCAL_PROXY_URL}`);
|
||||
const all = getAvailableProfiles();
|
||||
console.log(`listen_host: ${LISTEN_HOST}`);
|
||||
console.log(`base_port: ${LISTEN_PORT}`);
|
||||
if (isUpstreamEnabled()) {
|
||||
console.log(`user_file: ${USER_FILE}`);
|
||||
console.log(`account: ${readAccount()}`);
|
||||
}
|
||||
console.log(`config_file: ${CONFIG_FILE}`);
|
||||
console.log(`upstream: ${getUpstreamConfig()}`);
|
||||
console.log(`profile: ${getProfile()}`);
|
||||
console.log(`available profiles: ${getAvailableProfiles().join(', ')}`);
|
||||
console.log(`user_file: ${USER_FILE}`);
|
||||
console.log(`account: ${readAccount()}`);
|
||||
console.log(`\nEffective Mappings:`);
|
||||
console.log(` ${LISTEN_PORT} -> dynamic (${getProfile()})`);
|
||||
all.forEach((name, i) => {
|
||||
console.log(` ${(LISTEN_PORT + 1 + i).toString().padEnd(5)} -> ${name}`);
|
||||
});
|
||||
}
|
||||
|
||||
function codeSettingsPath() {
|
||||
@@ -181,16 +178,9 @@ function codeSettingsPath() {
|
||||
}
|
||||
|
||||
function readJsonFile(file) {
|
||||
if (!fs.existsSync(file)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!fs.existsSync(file)) return {};
|
||||
const content = fs.readFileSync(file, 'utf8').trim();
|
||||
if (!content) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
return content ? JSON.parse(content) : {};
|
||||
}
|
||||
|
||||
function writeJsonFile(file, data) {
|
||||
@@ -206,33 +196,19 @@ function desktopApplicationsDir() {
|
||||
function desktopLauncherPaths() {
|
||||
const userDir = desktopApplicationsDir();
|
||||
return [
|
||||
{
|
||||
name: 'main',
|
||||
system: '/usr/share/applications/code.desktop',
|
||||
user: path.join(userDir, 'code.desktop'),
|
||||
},
|
||||
{
|
||||
name: 'url-handler',
|
||||
system: '/usr/share/applications/code-url-handler.desktop',
|
||||
user: path.join(userDir, 'code-url-handler.desktop'),
|
||||
},
|
||||
{ name: 'main', system: '/usr/share/applications/code.desktop', user: path.join(userDir, 'code.desktop') },
|
||||
{ name: 'url-handler', system: '/usr/share/applications/code-url-handler.desktop', user: path.join(userDir, 'code-url-handler.desktop') },
|
||||
];
|
||||
}
|
||||
|
||||
function addCodeFlag(command) {
|
||||
if (command.includes(VSCODE_CERT_FLAG)) {
|
||||
return command;
|
||||
}
|
||||
|
||||
return command.replace(/^(Exec=\S+)/, `$1 ${VSCODE_CERT_FLAG}`);
|
||||
if (!VSCODE_CERT_FLAG) return command;
|
||||
return command.includes(VSCODE_CERT_FLAG) ? command : command.replace(/^(Exec=\S+)/, `$1 ${VSCODE_CERT_FLAG}`);
|
||||
}
|
||||
|
||||
function launcherHasFlag(file) {
|
||||
if (!fs.existsSync(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fs.readFileSync(file, 'utf8').includes(VSCODE_CERT_FLAG);
|
||||
if (!VSCODE_CERT_FLAG) return false;
|
||||
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').includes(VSCODE_CERT_FLAG) : false;
|
||||
}
|
||||
|
||||
function vscodeLaunchStatus() {
|
||||
@@ -241,60 +217,39 @@ function vscodeLaunchStatus() {
|
||||
const enabled = launcherHasFlag(launcher.user);
|
||||
console.log(`${launcher.name}: ${launcher.user}`);
|
||||
console.log(` user override: ${exists ? 'yes' : 'no'}`);
|
||||
console.log(` ${VSCODE_CERT_FLAG}: ${enabled ? 'yes' : 'no'}`);
|
||||
console.log(` flag present: ${enabled ? 'yes' : 'no'}`);
|
||||
}
|
||||
|
||||
const userCode = path.join(os.homedir(), '.local', 'bin', 'code');
|
||||
console.log(`terminal wrapper: ${userCode}`);
|
||||
console.log(` installed: ${fs.existsSync(userCode) ? 'yes' : 'no'}`);
|
||||
console.log(` ${VSCODE_CERT_FLAG}: ${launcherHasFlag(userCode) ? 'yes' : 'no'}`);
|
||||
console.log(` flag present: ${launcherHasFlag(userCode) ? 'yes' : 'no'}`);
|
||||
}
|
||||
|
||||
function userCodeWrapperPath() {
|
||||
return path.join(os.homedir(), '.local', 'bin', 'code');
|
||||
}
|
||||
function userCodeWrapperPath() { return path.join(os.homedir(), '.local', 'bin', 'code'); }
|
||||
|
||||
function vscodeTerminalSetup() {
|
||||
const file = userCodeWrapperPath();
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
fs.writeFileSync(file, `#!/bin/sh\nexec /usr/bin/code ${VSCODE_CERT_FLAG} "$@"\n`);
|
||||
const flagStr = VSCODE_CERT_FLAG ? ` ${VSCODE_CERT_FLAG}` : '';
|
||||
fs.writeFileSync(file, `#!/bin/sh\nexec /usr/bin/code${flagStr} "$@"\n`);
|
||||
fs.chmodSync(file, 0o755);
|
||||
console.log(`Updated ${file}`);
|
||||
}
|
||||
|
||||
function vscodeLaunchSetup() {
|
||||
fs.mkdirSync(desktopApplicationsDir(), { recursive: true });
|
||||
|
||||
for (const launcher of desktopLauncherPaths()) {
|
||||
if (!fs.existsSync(launcher.system)) {
|
||||
console.error(`Missing system launcher: ${launcher.system}`);
|
||||
process.exitCode = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(launcher.system, 'utf8')
|
||||
.split('\n')
|
||||
.map((line) => (line.startsWith('Exec=') ? addCodeFlag(line) : line))
|
||||
.join('\n');
|
||||
|
||||
if (!fs.existsSync(launcher.system)) { console.error(`Missing system launcher: ${launcher.system}`); continue; }
|
||||
const content = fs.readFileSync(launcher.system, 'utf8').split('\n').map((line) => (line.startsWith('Exec=') ? addCodeFlag(line) : line)).join('\n');
|
||||
fs.writeFileSync(launcher.user, content);
|
||||
console.log(`Updated ${launcher.user}`);
|
||||
}
|
||||
|
||||
vscodeTerminalSetup();
|
||||
}
|
||||
|
||||
async function vscodeStatus() {
|
||||
const file = codeSettingsPath();
|
||||
let settings;
|
||||
|
||||
try {
|
||||
settings = readJsonFile(file);
|
||||
} catch (error) {
|
||||
console.error(`Could not parse ${file}: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let settings = readJsonFile(file);
|
||||
console.log(`settings: ${file}`);
|
||||
console.log(`http.proxy: ${settings['http.proxy'] || 'not set'}`);
|
||||
console.log(`http.proxySupport: ${settings['http.proxySupport'] || 'not set'}`);
|
||||
@@ -305,60 +260,29 @@ async function vscodeStatus() {
|
||||
|
||||
async function vscodeSetup() {
|
||||
const file = codeSettingsPath();
|
||||
let settings;
|
||||
|
||||
try {
|
||||
settings = readJsonFile(file);
|
||||
} catch (error) {
|
||||
console.error(`Could not parse ${file}: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let settings = readJsonFile(file);
|
||||
settings['http.proxy'] = LOCAL_PROXY_URL;
|
||||
settings['http.proxySupport'] = 'override';
|
||||
settings['http.proxyStrictSSL'] = false;
|
||||
settings['http.systemCertificates'] = true;
|
||||
|
||||
writeJsonFile(file, settings);
|
||||
|
||||
console.log(`Updated ${file}`);
|
||||
console.log(`http.proxy: ${LOCAL_PROXY_URL}`);
|
||||
console.log('http.proxySupport: override');
|
||||
console.log('http.proxyStrictSSL: false');
|
||||
console.log('http.systemCertificates: true');
|
||||
|
||||
const listening = await checkPort();
|
||||
console.log(`local proxy reachable: ${listening ? 'yes' : 'no'}`);
|
||||
console.log(`Updated ${file}\nhttp.proxy: ${LOCAL_PROXY_URL}\nhttp.proxySupport: override\nhttp.proxyStrictSSL: false\nhttp.systemCertificates: true`);
|
||||
console.log(`local proxy reachable: ${await checkPort() ? 'yes' : 'no'}`);
|
||||
}
|
||||
|
||||
async function vscode(args) {
|
||||
const action = args[0] || 'status';
|
||||
|
||||
switch (action) {
|
||||
case 'status':
|
||||
await vscodeStatus();
|
||||
break;
|
||||
case 'setup':
|
||||
await vscodeSetup();
|
||||
break;
|
||||
case 'status': await vscodeStatus(); break;
|
||||
case 'setup': await vscodeSetup(); break;
|
||||
case 'launch':
|
||||
switch (args[1] || 'status') {
|
||||
case 'status':
|
||||
vscodeLaunchStatus();
|
||||
break;
|
||||
case 'setup':
|
||||
vscodeLaunchSetup();
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown vscode launch action: ${args[1]}`);
|
||||
console.error('Usage: proxy-bridge vscode launch [status|setup]');
|
||||
process.exit(1);
|
||||
case 'status': vscodeLaunchStatus(); break;
|
||||
case 'setup': vscodeLaunchSetup(); break;
|
||||
default: console.error('Usage: proxy-bridge vscode launch [status|setup]'); process.exit(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown vscode action: ${action}`);
|
||||
console.error('Usage: proxy-bridge vscode [status|setup|launch]');
|
||||
process.exit(1);
|
||||
default: console.error('Usage: proxy-bridge vscode [status|setup|launch]'); process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +299,7 @@ Commands:
|
||||
enable Enable the user service at login
|
||||
disable Disable the user service at login
|
||||
logs Follow service logs
|
||||
setup Store or update keyring credentials
|
||||
setup Store or update keyring credentials and upstream config
|
||||
config Print effective non-secret configuration
|
||||
vscode Manage VS Code proxy settings
|
||||
help Show this help
|
||||
@@ -390,17 +314,11 @@ VS Code:
|
||||
|
||||
async function main() {
|
||||
const command = process.argv[2] || 'status';
|
||||
|
||||
switch (command) {
|
||||
case 'status':
|
||||
await status();
|
||||
break;
|
||||
case 'status': await status(); break;
|
||||
case 'profile':
|
||||
if (process.argv[3]) {
|
||||
setProfile(process.argv[3]);
|
||||
} else {
|
||||
console.log(getProfile());
|
||||
}
|
||||
if (process.argv[3]) setProfile(process.argv[3]);
|
||||
else console.log(getProfile());
|
||||
break;
|
||||
case 'toggle':
|
||||
const current = getProfile();
|
||||
@@ -412,34 +330,13 @@ async function main() {
|
||||
case 'stop':
|
||||
case 'restart':
|
||||
case 'enable':
|
||||
case 'disable':
|
||||
control(command);
|
||||
break;
|
||||
case 'logs':
|
||||
logs();
|
||||
break;
|
||||
case 'setup':
|
||||
setup();
|
||||
break;
|
||||
case 'config':
|
||||
config();
|
||||
break;
|
||||
case 'vscode':
|
||||
await vscode(process.argv.slice(3));
|
||||
break;
|
||||
case 'help':
|
||||
case '--help':
|
||||
case '-h':
|
||||
help();
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown command: ${command}`);
|
||||
help();
|
||||
process.exit(1);
|
||||
case 'disable': control(command); break;
|
||||
case 'logs': logs(); break;
|
||||
case 'setup': setup(); break;
|
||||
case 'config': config(); break;
|
||||
case 'vscode': await vscode(process.argv.slice(3)); break;
|
||||
default: help(); process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
main().catch((error) => { console.error(error.message); process.exit(1); });
|
||||
|
||||
Reference in New Issue
Block a user