Update proxy bridge setup
This commit is contained in:
@@ -122,8 +122,9 @@ Format: `path | goal | usage`. This section is intentionally compact so `what` c
|
||||
- `tools/cloud/youtube_resolve.sh` | goal: resolve direct media URLs from YouTube-like inputs | usage: `tools/cloud/youtube_resolve.sh URL`
|
||||
- `tools/dockerpullsave.py` | goal: download Docker images as tarballs without requiring a Docker daemon | usage: `python3 tools/dockerpullsave.py image:tag`
|
||||
- `scripts/proxy/install_proxy.sh` | goal: installer for the Dumb Pipe Proxy Bridge service | usage: `scripts/proxy/install_proxy.sh`
|
||||
- `scripts/proxy/bridge.js` | goal: Node.js proxy bridge with keyring authentication support | usage: `node scripts/proxy/bridge.js`
|
||||
- `scripts/proxy/bridge.js` | goal: Node.js proxy bridge with multi-profile routing (internet, intranet, direct, off) | usage: `node scripts/proxy/bridge.js`
|
||||
- `scripts/proxy/setup.js` | goal: Interactive setup for storing proxy credentials in the system keyring | usage: `node scripts/proxy/setup.js`
|
||||
- `scripts/proxy/proxyctl.js` | goal: Management CLI for the proxy bridge with dynamic PAC discovery from ~/.mozilla/ | usage: `proxy-bridge status|toggle|profile`
|
||||
|
||||
### Formats, System, And Text Experiments
|
||||
|
||||
|
||||
+22
-22
@@ -12,23 +12,22 @@ const PROFILE_FILE = '/opt/proxy-bridge/profile.json';
|
||||
const LISTEN_HOST = process.env.PROXY_BRIDGE_LISTEN_HOST || '127.0.0.1';
|
||||
const BASE_PORT = Number(process.env.PROXY_BRIDGE_BASE_PORT || 8888);
|
||||
|
||||
let INTERNET_PROXY_HOST, INTERNET_PROXY_PORT;
|
||||
let INTERNET_PROXY_HOST, INTERNET_PROXY_PORT, UPSTREAM_ENABLED = true;
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||
INTERNET_PROXY_HOST = config.host;
|
||||
INTERNET_PROXY_PORT = Number(config.port);
|
||||
UPSTREAM_ENABLED = (config.enabled !== false);
|
||||
} catch (e) {
|
||||
INTERNET_PROXY_HOST = 'PROXY_HOST_PLACEHOLDER';
|
||||
INTERNET_PROXY_HOST = 'your-proxy.example.com';
|
||||
INTERNET_PROXY_PORT = 8080;
|
||||
}
|
||||
|
||||
let requestCounter = 0;
|
||||
let dynamicProfile = 'internet';
|
||||
const pacContexts = new Map(); // profileName -> vmContext
|
||||
let dynamicProfile = UPSTREAM_ENABLED ? 'internet' : 'direct';
|
||||
const pacContexts = new Map();
|
||||
|
||||
function now() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
function now() { return new Date().toISOString(); }
|
||||
|
||||
function log(level, id, message, fields = {}) {
|
||||
const suffix = Object.entries(fields)
|
||||
@@ -38,12 +37,10 @@ function log(level, id, message, fields = {}) {
|
||||
console.log(`${now()} ${level} [${id}] ${message}${suffix ? ` ${suffix}` : ''}`);
|
||||
}
|
||||
|
||||
function nextId(prefix) {
|
||||
requestCounter += 1;
|
||||
return `${prefix}-${requestCounter}`;
|
||||
}
|
||||
function nextId(prefix) { requestCounter += 1; return `${prefix}-${requestCounter}`; }
|
||||
|
||||
function loadCredentials() {
|
||||
if (!UPSTREAM_ENABLED) return null;
|
||||
try {
|
||||
const user = JSON.parse(fs.readFileSync(USER_FILE, 'utf8')).username;
|
||||
const pass = execFileSync('secret-tool', [
|
||||
@@ -54,9 +51,7 @@ function loadCredentials() {
|
||||
user,
|
||||
], { encoding: 'utf8' }).trim();
|
||||
return pass ? { user, pass } : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
let AUTH_HEADER;
|
||||
@@ -111,7 +106,10 @@ function loadPacFile(profileName) {
|
||||
}
|
||||
|
||||
function getAvailableProfiles() {
|
||||
const profiles = ['internet', 'direct', 'off'];
|
||||
const profiles = [];
|
||||
if (UPSTREAM_ENABLED) profiles.push('internet');
|
||||
profiles.push('direct', 'off');
|
||||
|
||||
const pacDir = path.join(os.homedir(), '.mozilla');
|
||||
if (fs.existsSync(pacDir)) {
|
||||
fs.readdirSync(pacDir).forEach(f => {
|
||||
@@ -122,8 +120,7 @@ function getAvailableProfiles() {
|
||||
}
|
||||
|
||||
function refreshPacContexts() {
|
||||
const all = getAvailableProfiles();
|
||||
all.forEach(name => {
|
||||
getAvailableProfiles().forEach(name => {
|
||||
if (!['internet', 'direct', 'off'].includes(name)) {
|
||||
pacContexts.set(name, loadPacFile(name));
|
||||
}
|
||||
@@ -134,10 +131,14 @@ function updateDynamicProfile() {
|
||||
try {
|
||||
if (fs.existsSync(PROFILE_FILE)) {
|
||||
const data = JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8'));
|
||||
const newProfile = data.profile || 'internet';
|
||||
if (newProfile !== dynamicProfile) {
|
||||
const newProfile = data.profile;
|
||||
const valid = getAvailableProfiles();
|
||||
if (newProfile && valid.includes(newProfile) && newProfile !== dynamicProfile) {
|
||||
log('INFO', 'profile', 'dynamic profile switch', { old: dynamicProfile, new: newProfile });
|
||||
dynamicProfile = newProfile;
|
||||
} else if (newProfile && !valid.includes(newProfile)) {
|
||||
log('WARN', 'profile', 'saved profile is invalid/disabled', { profile: newProfile });
|
||||
dynamicProfile = UPSTREAM_ENABLED ? 'internet' : 'direct';
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
@@ -152,7 +153,7 @@ function getUpstream(profileName, url, host) {
|
||||
|
||||
if (profile === 'off') return 'OFF';
|
||||
if (profile === 'direct') return 'DIRECT';
|
||||
if (profile === 'internet') return `PROXY ${INTERNET_PROXY_HOST}:${INTERNET_PROXY_PORT}`;
|
||||
if (profile === 'internet' && UPSTREAM_ENABLED) return `PROXY ${INTERNET_PROXY_HOST}:${INTERNET_PROXY_PORT}`;
|
||||
|
||||
const ctx = pacContexts.get(profile);
|
||||
if (ctx) {
|
||||
@@ -289,7 +290,6 @@ function createBridge(port, profileName) {
|
||||
createBridge(BASE_PORT, 'dynamic');
|
||||
|
||||
// 2. Static bridges on 8889+
|
||||
const allProfiles = getAvailableProfiles();
|
||||
allProfiles.forEach((name, i) => {
|
||||
getAvailableProfiles().forEach((name, i) => {
|
||||
createBridge(BASE_PORT + 1 + i, name);
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ mkdir -p "$BIN_DIR"
|
||||
ln -sf "$BRIDGE_DIR/proxyctl.js" "$BIN_DIR/proxy-bridge"
|
||||
|
||||
# 5. CONFIGURATION & CREDENTIALS
|
||||
if [ ! -f "$BRIDGE_DIR/user.json" ]; then
|
||||
if [ ! -f "$BRIDGE_DIR/config.json" ] || [ ! -f "$BRIDGE_DIR/user.json" ]; then
|
||||
info "Launching interactive setup..."
|
||||
node "$BRIDGE_DIR/setup.js"
|
||||
fi
|
||||
|
||||
+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); });
|
||||
|
||||
+60
-36
@@ -17,7 +17,7 @@ function ask(question, defaultValue = "") {
|
||||
return new Promise((resolve) => {
|
||||
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
||||
rl.question(prompt, (answer) => {
|
||||
resolve(answer || defaultValue);
|
||||
resolve(answer.trim() || defaultValue);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -26,50 +26,74 @@ async function run() {
|
||||
try {
|
||||
// 1. Upstream Proxy Configuration
|
||||
console.log("\n--- Upstream Proxy (Corporate) ---");
|
||||
const host = await ask("Corporate Proxy Hostname (e.g. proxy.company.com)");
|
||||
const port = await ask("Corporate Proxy Port", "8080");
|
||||
const enableUpstream = (await ask("Enable Corporate Upstream Proxy? (y/n)", "y")).toLowerCase() === 'y';
|
||||
|
||||
let host = "";
|
||||
let port = "8080";
|
||||
let user = "";
|
||||
let pass = "";
|
||||
|
||||
if (!host) {
|
||||
console.error("❌ Hostname is required.");
|
||||
process.exit(1);
|
||||
if (enableUpstream) {
|
||||
host = await ask("Corporate Proxy Hostname (e.g. proxy.company.com)");
|
||||
port = await ask("Corporate Proxy Port", "8080");
|
||||
|
||||
if (!host) {
|
||||
console.error("❌ Hostname is required if upstream is enabled.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. Credentials
|
||||
console.log("\n--- Credentials ---");
|
||||
user = await ask("Corporate Username");
|
||||
|
||||
// Setup password mask (temporary swap)
|
||||
const oldWrite = rl._writeToOutput;
|
||||
rl._writeToOutput = function _writeToOutput(stringToWrite) {
|
||||
// If it's a newline or specific strings, let it through
|
||||
if (stringToWrite === "\n" || stringToWrite === "\r" || stringToWrite === "\r\n") {
|
||||
rl.output.write(stringToWrite);
|
||||
} else {
|
||||
rl.output.write("*");
|
||||
}
|
||||
};
|
||||
pass = await ask("Corporate Password");
|
||||
rl._writeToOutput = oldWrite;
|
||||
}
|
||||
|
||||
// 2. Credentials
|
||||
console.log("\n--- Credentials ---");
|
||||
const user = await ask("Corporate Username");
|
||||
|
||||
// Setup password mask
|
||||
const oldWrite = rl._writeToOutput;
|
||||
rl._writeToOutput = function _writeToOutput(stringToWrite) {
|
||||
if (rl.line.length > 0) rl.output.write("*");
|
||||
else rl.output.write(stringToWrite);
|
||||
};
|
||||
const pass = await ask("Corporate Password");
|
||||
rl._writeToOutput = oldWrite;
|
||||
|
||||
// Save Proxy Config
|
||||
// Save Config
|
||||
if (!fs.existsSync(CONFIG_DIR)) {
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ host, port }, null, 2));
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify({
|
||||
enabled: enableUpstream,
|
||||
host: host,
|
||||
port: port
|
||||
}, null, 2));
|
||||
|
||||
// Save Username
|
||||
fs.writeFileSync(USER_FILE, JSON.stringify({ username: user }));
|
||||
if (enableUpstream) {
|
||||
// Save Username
|
||||
fs.writeFileSync(USER_FILE, JSON.stringify({ username: user }));
|
||||
|
||||
// Store Password in Keyring
|
||||
console.log("\n--> Storing password in system keyring...");
|
||||
execFileSync('secret-tool', [
|
||||
'store',
|
||||
'--label=Proxy Bridge Credentials',
|
||||
'service',
|
||||
'proxy-bridge',
|
||||
'account',
|
||||
user,
|
||||
], {
|
||||
input: pass
|
||||
});
|
||||
// Store Password in Keyring
|
||||
console.log("\n--> Storing password in system keyring...");
|
||||
try {
|
||||
execFileSync('secret-tool', [
|
||||
'store',
|
||||
'--label=Proxy Bridge Credentials',
|
||||
'service',
|
||||
'proxy-bridge',
|
||||
'account',
|
||||
user,
|
||||
], {
|
||||
input: pass
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("⚠️ Warning: Failed to store password in keyring. Ensure libsecret-tools is installed.");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n✅ Configuration and credentials successfully stored.");
|
||||
console.log("\n✅ Configuration successfully updated.");
|
||||
} catch (error) {
|
||||
console.error("\n❌ Setup failed:", error.message);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
config_file='/etc/apt/apt.conf.d/80certproxy'
|
||||
config_file='/etc/apt/apt.conf.d/80proxy'
|
||||
#remove proxy settings from docker
|
||||
if [[ "${1}" == "off" ]]; then
|
||||
echo "TURNING OFF PROXY FOR APT"
|
||||
|
||||
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
EXTENSION_NAME="${1:-bitwarden}"
|
||||
DIRECTION="${2:-inet-to-intra}"
|
||||
|
||||
if [[ "$DIRECTION" != "inet-to-intra" && "$DIRECTION" != "intra-to-inet" ]]; then
|
||||
echo "Usage: $0 [extension_name] [inet-to-intra|intra-to-inet]"
|
||||
echo " extension_name: Name to search for (default: bitwarden)"
|
||||
echo " direction: Which way to copy (default: inet-to-intra)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MOZILLA_BASE="${HOME}/.mozilla/firefox"
|
||||
SNAP_BASE="${HOME}/snap/firefox/common/.mozilla/firefox"
|
||||
|
||||
find_base_path() {
|
||||
if [[ -d "$MOZILLA_BASE" ]] && [[ -f "$MOZILLA_BASE/profiles.ini" ]]; then
|
||||
echo "$MOZILLA_BASE"
|
||||
return 0
|
||||
elif [[ -d "$SNAP_BASE" ]] && [[ -f "$SNAP_BASE/profiles.ini" ]]; then
|
||||
echo "$SNAP_BASE"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
find_profile() {
|
||||
local base="$1"
|
||||
local name="$2"
|
||||
|
||||
local pattern
|
||||
case "$name" in
|
||||
internet)
|
||||
pattern="*internet*" ;;
|
||||
intranet)
|
||||
pattern="*intranet*" ;;
|
||||
noproxy)
|
||||
pattern="*noProxy*" ;;
|
||||
*)
|
||||
pattern="*${name}*" ;;
|
||||
esac
|
||||
|
||||
for prof in "$base"/$pattern; do
|
||||
if [[ -d "$prof" ]]; then
|
||||
echo "$prof"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
list_profiles() {
|
||||
local base="$1"
|
||||
|
||||
if [[ ! -d "$base" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
for prof in "$base"/*.default*; do
|
||||
if [[ -d "$prof" ]]; then
|
||||
echo "$prof"
|
||||
fi
|
||||
done
|
||||
for prof in "$base"/*.*; do
|
||||
if [[ -d "$prof" ]]; then
|
||||
echo "$prof"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
find_extension() {
|
||||
local ext_dir="$1"
|
||||
local name="$2"
|
||||
|
||||
if [[ ! -d "$ext_dir" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
for ext in "$ext_dir"/*; do
|
||||
if [[ -d "$ext" ]]; then
|
||||
if [[ -f "$ext/manifest.json" ]]; then
|
||||
if grep -qi "$name" "$ext/manifest.json" 2>/dev/null; then
|
||||
echo "$ext"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
elif [[ -f "$ext" ]] && [[ "$ext" == *.xpi ]]; then
|
||||
if unzip -p "$ext" manifest.json 2>/dev/null | grep -qi "$name"; then
|
||||
echo "$ext"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=== Firefox Extension Sync ($DIRECTION) ==="
|
||||
echo ""
|
||||
|
||||
local base_path
|
||||
base_path=$(find_base_path) || {
|
||||
echo "Error: Could not find Firefox profile directory"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Profile base path: $base_path"
|
||||
echo "Available profiles:"
|
||||
list_profiles "$base_path" | while read -r p; do
|
||||
echo " - $(basename "$p")"
|
||||
done
|
||||
echo ""
|
||||
|
||||
local source_profile target_profile
|
||||
case "$DIRECTION" in
|
||||
inet-to-intra)
|
||||
source_profile=$(find_profile "$base_path" "internet")
|
||||
target_profile=$(find_profile "$base_path" "intranet")
|
||||
;;
|
||||
intra-to-inet)
|
||||
source_profile=$(find_profile "$base_path" "intranet")
|
||||
target_profile=$(find_profile "$base_path" "internet")
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -z "$source_profile" ]]; then
|
||||
echo "Error: Could not find source profile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$target_profile" ]]; then
|
||||
echo "Error: Could not find target profile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Source: $(basename "$source_profile")"
|
||||
echo "Target: $(basename "$target_profile")"
|
||||
echo ""
|
||||
|
||||
local source_ext
|
||||
source_ext=$(find_extension "$source_profile/extensions" "$EXTENSION_NAME") || {
|
||||
echo "Error: $EXTENSION_NAME extension not found in source profile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
local ext_name
|
||||
ext_name=$(basename "$source_ext")
|
||||
|
||||
echo "Found extension: $ext_name"
|
||||
echo ""
|
||||
|
||||
local target_ext="$target_profile/extensions/$ext_name"
|
||||
|
||||
if [[ -d "$target_ext" ]]; then
|
||||
echo "Removing existing extension from target..."
|
||||
rm -rf "$target_ext"
|
||||
fi
|
||||
|
||||
cp -r "$source_ext" "$target_ext"
|
||||
|
||||
echo "Copied successfully!"
|
||||
echo ""
|
||||
echo "Done! Restart Firefox to load the extension."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user