diff --git a/README.md b/README.md index a33ec59..b75c547 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/scripts/proxy/bridge.js b/scripts/proxy/bridge.js index df7c1c6..9095805 100644 --- a/scripts/proxy/bridge.js +++ b/scripts/proxy/bridge.js @@ -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); }); diff --git a/scripts/proxy/install_proxy.sh b/scripts/proxy/install_proxy.sh index 28881f0..be37e81 100755 --- a/scripts/proxy/install_proxy.sh +++ b/scripts/proxy/install_proxy.sh @@ -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 diff --git a/scripts/proxy/proxyctl.js b/scripts/proxy/proxyctl.js index 390492e..6b3b217 100644 --- a/scripts/proxy/proxyctl.js +++ b/scripts/proxy/proxyctl.js @@ -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); }); diff --git a/scripts/proxy/setup.js b/scripts/proxy/setup.js index 5456368..e5100a3 100644 --- a/scripts/proxy/setup.js +++ b/scripts/proxy/setup.js @@ -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); diff --git a/scripts/proxy/update_apt_proxy.sh b/scripts/proxy/update_apt_proxy.sh index ce8eb7b..cde33a4 100755 --- a/scripts/proxy/update_apt_proxy.sh +++ b/scripts/proxy/update_apt_proxy.sh @@ -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" diff --git a/tools/system/copy_firefox_extension.sh b/tools/system/copy_firefox_extension.sh new file mode 100755 index 0000000..e6777d0 --- /dev/null +++ b/tools/system/copy_firefox_extension.sh @@ -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 "$@" \ No newline at end of file