Update proxy bridge setup

This commit is contained in:
tke
2026-05-22 13:01:54 +02:00
parent 203f2bf189
commit 7f73746427
7 changed files with 341 additions and 250 deletions
+2 -1
View File
@@ -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/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` - `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/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/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 ### Formats, System, And Text Experiments
+22 -22
View File
@@ -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 LISTEN_HOST = process.env.PROXY_BRIDGE_LISTEN_HOST || '127.0.0.1';
const BASE_PORT = Number(process.env.PROXY_BRIDGE_BASE_PORT || 8888); 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 { try {
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
INTERNET_PROXY_HOST = config.host; INTERNET_PROXY_HOST = config.host;
INTERNET_PROXY_PORT = Number(config.port); INTERNET_PROXY_PORT = Number(config.port);
UPSTREAM_ENABLED = (config.enabled !== false);
} catch (e) { } catch (e) {
INTERNET_PROXY_HOST = 'PROXY_HOST_PLACEHOLDER'; INTERNET_PROXY_HOST = 'your-proxy.example.com';
INTERNET_PROXY_PORT = 8080; INTERNET_PROXY_PORT = 8080;
} }
let requestCounter = 0; let requestCounter = 0;
let dynamicProfile = 'internet'; let dynamicProfile = UPSTREAM_ENABLED ? 'internet' : 'direct';
const pacContexts = new Map(); // profileName -> vmContext const pacContexts = new Map();
function now() { function now() { return new Date().toISOString(); }
return new Date().toISOString();
}
function log(level, id, message, fields = {}) { function log(level, id, message, fields = {}) {
const suffix = Object.entries(fields) const suffix = Object.entries(fields)
@@ -38,12 +37,10 @@ function log(level, id, message, fields = {}) {
console.log(`${now()} ${level} [${id}] ${message}${suffix ? ` ${suffix}` : ''}`); console.log(`${now()} ${level} [${id}] ${message}${suffix ? ` ${suffix}` : ''}`);
} }
function nextId(prefix) { function nextId(prefix) { requestCounter += 1; return `${prefix}-${requestCounter}`; }
requestCounter += 1;
return `${prefix}-${requestCounter}`;
}
function loadCredentials() { function loadCredentials() {
if (!UPSTREAM_ENABLED) return null;
try { try {
const user = JSON.parse(fs.readFileSync(USER_FILE, 'utf8')).username; const user = JSON.parse(fs.readFileSync(USER_FILE, 'utf8')).username;
const pass = execFileSync('secret-tool', [ const pass = execFileSync('secret-tool', [
@@ -54,9 +51,7 @@ function loadCredentials() {
user, user,
], { encoding: 'utf8' }).trim(); ], { encoding: 'utf8' }).trim();
return pass ? { user, pass } : null; return pass ? { user, pass } : null;
} catch { } catch { return null; }
return null;
}
} }
let AUTH_HEADER; let AUTH_HEADER;
@@ -111,7 +106,10 @@ function loadPacFile(profileName) {
} }
function getAvailableProfiles() { 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'); const pacDir = path.join(os.homedir(), '.mozilla');
if (fs.existsSync(pacDir)) { if (fs.existsSync(pacDir)) {
fs.readdirSync(pacDir).forEach(f => { fs.readdirSync(pacDir).forEach(f => {
@@ -122,8 +120,7 @@ function getAvailableProfiles() {
} }
function refreshPacContexts() { function refreshPacContexts() {
const all = getAvailableProfiles(); getAvailableProfiles().forEach(name => {
all.forEach(name => {
if (!['internet', 'direct', 'off'].includes(name)) { if (!['internet', 'direct', 'off'].includes(name)) {
pacContexts.set(name, loadPacFile(name)); pacContexts.set(name, loadPacFile(name));
} }
@@ -134,10 +131,14 @@ function updateDynamicProfile() {
try { try {
if (fs.existsSync(PROFILE_FILE)) { if (fs.existsSync(PROFILE_FILE)) {
const data = JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8')); const data = JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8'));
const newProfile = data.profile || 'internet'; const newProfile = data.profile;
if (newProfile !== dynamicProfile) { const valid = getAvailableProfiles();
if (newProfile && valid.includes(newProfile) && newProfile !== dynamicProfile) {
log('INFO', 'profile', 'dynamic profile switch', { old: dynamicProfile, new: newProfile }); log('INFO', 'profile', 'dynamic profile switch', { old: dynamicProfile, new: newProfile });
dynamicProfile = 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 {} } catch {}
@@ -152,7 +153,7 @@ function getUpstream(profileName, url, host) {
if (profile === 'off') return 'OFF'; if (profile === 'off') return 'OFF';
if (profile === 'direct') return 'DIRECT'; 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); const ctx = pacContexts.get(profile);
if (ctx) { if (ctx) {
@@ -289,7 +290,6 @@ function createBridge(port, profileName) {
createBridge(BASE_PORT, 'dynamic'); createBridge(BASE_PORT, 'dynamic');
// 2. Static bridges on 8889+ // 2. Static bridges on 8889+
const allProfiles = getAvailableProfiles(); getAvailableProfiles().forEach((name, i) => {
allProfiles.forEach((name, i) => {
createBridge(BASE_PORT + 1 + i, name); createBridge(BASE_PORT + 1 + i, name);
}); });
+1 -1
View File
@@ -46,7 +46,7 @@ mkdir -p "$BIN_DIR"
ln -sf "$BRIDGE_DIR/proxyctl.js" "$BIN_DIR/proxy-bridge" ln -sf "$BRIDGE_DIR/proxyctl.js" "$BIN_DIR/proxy-bridge"
# 5. CONFIGURATION & CREDENTIALS # 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..." info "Launching interactive setup..."
node "$BRIDGE_DIR/setup.js" node "$BRIDGE_DIR/setup.js"
fi fi
+86 -189
View File
@@ -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 CONFIG_FILE = process.env.PROXY_BRIDGE_CONFIG_FILE || '/opt/proxy-bridge/config.json';
const PROFILE_FILE = '/opt/proxy-bridge/profile.json'; const PROFILE_FILE = '/opt/proxy-bridge/profile.json';
const LOCAL_PROXY_URL = `http://${LISTEN_HOST}:${LISTEN_PORT}`; 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 = {}) { function run(command, args, options = {}) {
return spawnSync(command, args, { return spawnSync(command, args, {
@@ -26,23 +26,38 @@ function getUpstreamConfig() {
try { try {
if (fs.existsSync(CONFIG_FILE)) { if (fs.existsSync(CONFIG_FILE)) {
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
if (config.enabled === false) return 'DISABLED';
return `${config.host}:${config.port}`; return `${config.host}:${config.port}`;
} }
} catch {} } catch {}
return 'PROXY_HOST_PLACEHOLDER:8080 (default)'; return 'your-proxy.example.com:8080 (default)';
} }
function getProfile() { function getProfile() {
try { try {
if (fs.existsSync(PROFILE_FILE)) { 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 {} } 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() { 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'); const pacDir = path.join(os.homedir(), '.mozilla');
if (fs.existsSync(pacDir)) { if (fs.existsSync(pacDir)) {
const files = fs.readdirSync(pacDir); const files = fs.readdirSync(pacDir);
@@ -71,49 +86,28 @@ function systemctl(args, options = {}) {
} }
function printCommandFailure(result) { function printCommandFailure(result) {
if (result.error) { if (result.error) { console.error(result.error.message); return; }
console.error(result.error.message); if (result.stderr) { console.error(result.stderr.trim()); }
return;
}
if (result.stderr) {
console.error(result.stderr.trim());
}
} }
function serviceValue(property) { function serviceValue(property) {
const result = systemctl(['show', SERVICE, '--property', property, '--value']); const result = systemctl(['show', SERVICE, '--property', property, '--value']);
if (result.status !== 0) { if (result.status !== 0) return '';
return '';
}
return result.stdout.trim(); return result.stdout.trim();
} }
function serviceState(command) { function serviceState(command) {
const result = systemctl([command, SERVICE]); const result = systemctl([command, SERVICE]);
if (result.status !== 0) { if (result.status !== 0) return 'unknown';
return 'unknown';
}
return result.stdout.trim() || 'unknown'; return result.stdout.trim() || 'unknown';
} }
function checkPort() { function checkPort() {
return new Promise((resolve) => { return new Promise((resolve) => {
const socket = net.connect({ host: LISTEN_HOST, port: LISTEN_PORT }); const socket = net.connect({ host: LISTEN_HOST, port: LISTEN_PORT });
const timer = setTimeout(() => { const timer = setTimeout(() => { socket.destroy(); resolve(false); }, 1000);
socket.destroy(); socket.once('connect', () => { clearTimeout(timer); socket.end(); resolve(true); });
resolve(false); socket.once('error', () => { clearTimeout(timer); 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 enabled = serviceState('is-enabled');
const pid = serviceValue('MainPID'); const pid = serviceValue('MainPID');
const listening = await checkPort(); const listening = await checkPort();
const profile = getProfile(); const current = getProfile();
const all = getAvailableProfiles(); const all = getAvailableProfiles();
console.log(`service: ${SERVICE}`); console.log(`service: ${SERVICE}`);
console.log(`active: ${active}`); console.log(`active: ${active}`);
console.log(`enabled: ${enabled}`); console.log(`enabled: ${enabled}`);
console.log(`pid: ${pid && pid !== '0' ? pid : 'none'}`); console.log(`pid: ${pid && pid !== '0' ? pid : 'none'}`);
console.log(`profile: ${profile}`); if (isUpstreamEnabled()) console.log(`account: ${readAccount()}`);
console.log(`available: ${all.join(', ')}`);
console.log(`listen: http://${LISTEN_HOST}:${LISTEN_PORT} (${listening ? 'reachable' : 'not reachable'})`);
console.log(`upstream: ${getUpstreamConfig()}`); 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) { function control(action) {
const result = systemctl([action, SERVICE], { stdio: 'inherit' }); const result = systemctl([action, SERVICE], { stdio: 'inherit' });
if (result.status !== 0) { if (result.status !== 0) { printCommandFailure(result); process.exit(result.status || 1); }
printCommandFailure(result);
process.exit(result.status || 1);
}
} }
function logs() { function logs() {
const child = spawn('journalctl', ['--user', '-u', SERVICE, '-f'], { const child = spawn('journalctl', ['--user', '-u', SERVICE, '-f'], { stdio: 'inherit' });
stdio: 'inherit',
});
child.on('exit', (code) => process.exit(code || 0)); child.on('exit', (code) => process.exit(code || 0));
} }
function setup() { function setup() {
const child = spawn('node', ['/opt/proxy-bridge/setup.js'], { const child = spawn('node', ['/opt/proxy-bridge/setup.js'], { stdio: 'inherit' });
stdio: 'inherit',
});
child.on('exit', (code) => process.exit(code || 0)); child.on('exit', (code) => process.exit(code || 0));
} }
function config() { 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(`upstream: ${getUpstreamConfig()}`);
console.log(`profile: ${getProfile()}`); console.log(`\nEffective Mappings:`);
console.log(`available profiles: ${getAvailableProfiles().join(', ')}`); console.log(` ${LISTEN_PORT} -> dynamic (${getProfile()})`);
console.log(`user_file: ${USER_FILE}`); all.forEach((name, i) => {
console.log(`account: ${readAccount()}`); console.log(` ${(LISTEN_PORT + 1 + i).toString().padEnd(5)} -> ${name}`);
});
} }
function codeSettingsPath() { function codeSettingsPath() {
@@ -181,16 +178,9 @@ function codeSettingsPath() {
} }
function readJsonFile(file) { function readJsonFile(file) {
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) return {};
return {};
}
const content = fs.readFileSync(file, 'utf8').trim(); const content = fs.readFileSync(file, 'utf8').trim();
if (!content) { return content ? JSON.parse(content) : {};
return {};
}
return JSON.parse(content);
} }
function writeJsonFile(file, data) { function writeJsonFile(file, data) {
@@ -206,33 +196,19 @@ function desktopApplicationsDir() {
function desktopLauncherPaths() { function desktopLauncherPaths() {
const userDir = desktopApplicationsDir(); const userDir = desktopApplicationsDir();
return [ return [
{ { name: 'main', system: '/usr/share/applications/code.desktop', user: path.join(userDir, 'code.desktop') },
name: 'main', { name: 'url-handler', system: '/usr/share/applications/code-url-handler.desktop', user: path.join(userDir, 'code-url-handler.desktop') },
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) { function addCodeFlag(command) {
if (command.includes(VSCODE_CERT_FLAG)) { if (!VSCODE_CERT_FLAG) return command;
return command; return command.includes(VSCODE_CERT_FLAG) ? command : command.replace(/^(Exec=\S+)/, `$1 ${VSCODE_CERT_FLAG}`);
}
return command.replace(/^(Exec=\S+)/, `$1 ${VSCODE_CERT_FLAG}`);
} }
function launcherHasFlag(file) { function launcherHasFlag(file) {
if (!fs.existsSync(file)) { if (!VSCODE_CERT_FLAG) return false;
return false; return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').includes(VSCODE_CERT_FLAG) : false;
}
return fs.readFileSync(file, 'utf8').includes(VSCODE_CERT_FLAG);
} }
function vscodeLaunchStatus() { function vscodeLaunchStatus() {
@@ -241,60 +217,39 @@ function vscodeLaunchStatus() {
const enabled = launcherHasFlag(launcher.user); const enabled = launcherHasFlag(launcher.user);
console.log(`${launcher.name}: ${launcher.user}`); console.log(`${launcher.name}: ${launcher.user}`);
console.log(` user override: ${exists ? 'yes' : 'no'}`); 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'); const userCode = path.join(os.homedir(), '.local', 'bin', 'code');
console.log(`terminal wrapper: ${userCode}`); console.log(`terminal wrapper: ${userCode}`);
console.log(` installed: ${fs.existsSync(userCode) ? 'yes' : 'no'}`); 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() { function userCodeWrapperPath() { return path.join(os.homedir(), '.local', 'bin', 'code'); }
return path.join(os.homedir(), '.local', 'bin', 'code');
}
function vscodeTerminalSetup() { function vscodeTerminalSetup() {
const file = userCodeWrapperPath(); const file = userCodeWrapperPath();
fs.mkdirSync(path.dirname(file), { recursive: true }); 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); fs.chmodSync(file, 0o755);
console.log(`Updated ${file}`); console.log(`Updated ${file}`);
} }
function vscodeLaunchSetup() { function vscodeLaunchSetup() {
fs.mkdirSync(desktopApplicationsDir(), { recursive: true }); fs.mkdirSync(desktopApplicationsDir(), { recursive: true });
for (const launcher of desktopLauncherPaths()) { for (const launcher of desktopLauncherPaths()) {
if (!fs.existsSync(launcher.system)) { if (!fs.existsSync(launcher.system)) { console.error(`Missing system launcher: ${launcher.system}`); continue; }
console.error(`Missing system launcher: ${launcher.system}`); const content = fs.readFileSync(launcher.system, 'utf8').split('\n').map((line) => (line.startsWith('Exec=') ? addCodeFlag(line) : line)).join('\n');
process.exitCode = 1;
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); fs.writeFileSync(launcher.user, content);
console.log(`Updated ${launcher.user}`); console.log(`Updated ${launcher.user}`);
} }
vscodeTerminalSetup(); vscodeTerminalSetup();
} }
async function vscodeStatus() { async function vscodeStatus() {
const file = codeSettingsPath(); const file = codeSettingsPath();
let settings; let settings = readJsonFile(file);
try {
settings = readJsonFile(file);
} catch (error) {
console.error(`Could not parse ${file}: ${error.message}`);
process.exit(1);
}
console.log(`settings: ${file}`); console.log(`settings: ${file}`);
console.log(`http.proxy: ${settings['http.proxy'] || 'not set'}`); console.log(`http.proxy: ${settings['http.proxy'] || 'not set'}`);
console.log(`http.proxySupport: ${settings['http.proxySupport'] || 'not set'}`); console.log(`http.proxySupport: ${settings['http.proxySupport'] || 'not set'}`);
@@ -305,60 +260,29 @@ async function vscodeStatus() {
async function vscodeSetup() { async function vscodeSetup() {
const file = codeSettingsPath(); const file = codeSettingsPath();
let settings; let settings = readJsonFile(file);
try {
settings = readJsonFile(file);
} catch (error) {
console.error(`Could not parse ${file}: ${error.message}`);
process.exit(1);
}
settings['http.proxy'] = LOCAL_PROXY_URL; settings['http.proxy'] = LOCAL_PROXY_URL;
settings['http.proxySupport'] = 'override'; settings['http.proxySupport'] = 'override';
settings['http.proxyStrictSSL'] = false; settings['http.proxyStrictSSL'] = false;
settings['http.systemCertificates'] = true; settings['http.systemCertificates'] = true;
writeJsonFile(file, settings); writeJsonFile(file, settings);
console.log(`Updated ${file}\nhttp.proxy: ${LOCAL_PROXY_URL}\nhttp.proxySupport: override\nhttp.proxyStrictSSL: false\nhttp.systemCertificates: true`);
console.log(`Updated ${file}`); console.log(`local proxy reachable: ${await checkPort() ? 'yes' : 'no'}`);
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'}`);
} }
async function vscode(args) { async function vscode(args) {
const action = args[0] || 'status'; const action = args[0] || 'status';
switch (action) { switch (action) {
case 'status': case 'status': await vscodeStatus(); break;
await vscodeStatus(); case 'setup': await vscodeSetup(); break;
break;
case 'setup':
await vscodeSetup();
break;
case 'launch': case 'launch':
switch (args[1] || 'status') { switch (args[1] || 'status') {
case 'status': case 'status': vscodeLaunchStatus(); break;
vscodeLaunchStatus(); case 'setup': vscodeLaunchSetup(); break;
break; default: console.error('Usage: proxy-bridge vscode launch [status|setup]'); process.exit(1);
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);
} }
break; break;
default: default: console.error('Usage: proxy-bridge vscode [status|setup|launch]'); process.exit(1);
console.error(`Unknown vscode action: ${action}`);
console.error('Usage: proxy-bridge vscode [status|setup|launch]');
process.exit(1);
} }
} }
@@ -375,7 +299,7 @@ Commands:
enable Enable the user service at login enable Enable the user service at login
disable Disable the user service at login disable Disable the user service at login
logs Follow service logs 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 config Print effective non-secret configuration
vscode Manage VS Code proxy settings vscode Manage VS Code proxy settings
help Show this help help Show this help
@@ -390,17 +314,11 @@ VS Code:
async function main() { async function main() {
const command = process.argv[2] || 'status'; const command = process.argv[2] || 'status';
switch (command) { switch (command) {
case 'status': case 'status': await status(); break;
await status();
break;
case 'profile': case 'profile':
if (process.argv[3]) { if (process.argv[3]) setProfile(process.argv[3]);
setProfile(process.argv[3]); else console.log(getProfile());
} else {
console.log(getProfile());
}
break; break;
case 'toggle': case 'toggle':
const current = getProfile(); const current = getProfile();
@@ -412,34 +330,13 @@ async function main() {
case 'stop': case 'stop':
case 'restart': case 'restart':
case 'enable': case 'enable':
case 'disable': case 'disable': control(command); break;
control(command); case 'logs': logs(); break;
break; case 'setup': setup(); break;
case 'logs': case 'config': config(); break;
logs(); case 'vscode': await vscode(process.argv.slice(3)); break;
break; default: help(); process.exit(1);
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);
} }
} }
main().catch((error) => { main().catch((error) => { console.error(error.message); process.exit(1); });
console.error(error.message);
process.exit(1);
});
+60 -36
View File
@@ -17,7 +17,7 @@ function ask(question, defaultValue = "") {
return new Promise((resolve) => { return new Promise((resolve) => {
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `; const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
rl.question(prompt, (answer) => { rl.question(prompt, (answer) => {
resolve(answer || defaultValue); resolve(answer.trim() || defaultValue);
}); });
}); });
} }
@@ -26,50 +26,74 @@ async function run() {
try { try {
// 1. Upstream Proxy Configuration // 1. Upstream Proxy Configuration
console.log("\n--- Upstream Proxy (Corporate) ---"); console.log("\n--- Upstream Proxy (Corporate) ---");
const host = await ask("Corporate Proxy Hostname (e.g. proxy.company.com)"); const enableUpstream = (await ask("Enable Corporate Upstream Proxy? (y/n)", "y")).toLowerCase() === 'y';
const port = await ask("Corporate Proxy Port", "8080");
if (!host) { let host = "";
console.error("❌ Hostname is required."); let port = "8080";
process.exit(1); let user = "";
let pass = "";
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 // Save Config
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
if (!fs.existsSync(CONFIG_DIR)) { if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true }); fs.mkdirSync(CONFIG_DIR, { recursive: true });
} }
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ host, port }, null, 2));
// Save Username fs.writeFileSync(CONFIG_FILE, JSON.stringify({
fs.writeFileSync(USER_FILE, JSON.stringify({ username: user })); enabled: enableUpstream,
host: host,
port: port
}, null, 2));
// Store Password in Keyring if (enableUpstream) {
console.log("\n--> Storing password in system keyring..."); // Save Username
execFileSync('secret-tool', [ fs.writeFileSync(USER_FILE, JSON.stringify({ username: user }));
'store',
'--label=Proxy Bridge Credentials',
'service',
'proxy-bridge',
'account',
user,
], {
input: pass
});
console.log("\n✅ Configuration and credentials successfully stored."); // 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 successfully updated.");
} catch (error) { } catch (error) {
console.error("\n❌ Setup failed:", error.message); console.error("\n❌ Setup failed:", error.message);
process.exit(1); process.exit(1);
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
config_file='/etc/apt/apt.conf.d/80certproxy' config_file='/etc/apt/apt.conf.d/80proxy'
#remove proxy settings from docker #remove proxy settings from docker
if [[ "${1}" == "off" ]]; then if [[ "${1}" == "off" ]]; then
echo "TURNING OFF PROXY FOR APT" echo "TURNING OFF PROXY FOR APT"
+169
View File
@@ -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 "$@"