diff --git a/scripts/proxy/get_proxy.sh b/scripts/proxy/get_proxy.sh deleted file mode 100644 index eecf0ff..0000000 --- a/scripts/proxy/get_proxy.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -proxy_ip=$(env | grep http_proxy | grep -Pio '[^@/:]+(?=:\d+/?$)') -proxy_port=$(env | grep http_proxy | grep -Pio '(?<=:)(\d+)(?=/?$)') - -if [ -z "${proxy_ip}" ]; then - echo "Enter Proxy IP or Hostname (no port): " - read proxy_ip -else - echo "Using >>${proxy_ip}<< as Proxy-Address" -fi - -if [ -z "${proxy_port}" ]; then - echo -n "Proxy-Port: " - read proxy_port -else - echo "Using >>${proxy_port}<< as Proxy-Port" -fi - -echo "Using ${proxy_ip}:${proxy_port} as Proxy!" - diff --git a/scripts/proxy/install.js b/scripts/proxy/install.js new file mode 100644 index 0000000..db44eb9 --- /dev/null +++ b/scripts/proxy/install.js @@ -0,0 +1,554 @@ +#!/usr/bin/env node + +// Proxy Bridge installer — idempotent, interactive where information is missing. +// Replaces the retired legacy scripts (install_proxy.sh, update_apt_proxy.sh, +// update_bashrc_proxy.sh, update_service_proxy.sh, get_proxy.sh, setup.js). +// +// node install.js interactive install/repair (re-runnable) +// node install.js --dry-run print the plan, change nothing +// node install.js --non-interactive use existing/defaults; fail listing missing info +// node install.js --no-systemd skip systemd user service + system service steps +// +// Hard rules honored here: +// - Passwords are NEVER printed, logged, or stored in a file. Username goes to +// user.json; the password goes to the keyring via secret-tool only AFTER a +// single successful verification against the upstream (never on failure). +// - Keyring presence is probed by `secret-tool lookup`'s exit code only. + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const readline = require('readline'); +const { spawnSync, execFileSync } = require('child_process'); + +// --------------------------------------------------------------------------- +// CLI parsing + paths +// --------------------------------------------------------------------------- + +const args = process.argv.slice(2); +const FLAGS = { + dryRun: args.includes('--dry-run'), + nonInteractive: args.includes('--non-interactive'), + noSystemd: args.includes('--no-systemd'), +}; + +const BRIDGE_DIR = process.env.PROXY_BRIDGE_DIR || '/opt/proxy-bridge'; +const BIN_DIR = process.env.PROXY_BRIDGE_BIN_DIR || path.join(os.homedir(), '.local', 'bin'); +const SYSTEMD_USER_DIR = process.env.PROXY_BRIDGE_SYSTEMD_USER_DIR || path.join(os.homedir(), '.config', 'systemd', 'user'); +const SRC_DIR = __dirname; // install.js lives next to bridge.js/proxyctl.js in the repo +const BRIDGE_URL = 'http://127.0.0.1:8888/'; +const BASHRC = process.env.PROXY_BRIDGE_BASHRC || path.join(os.homedir(), '.bashrc'); +const APT_CONF = process.env.PROXY_BRIDGE_APT_CONF || '/etc/apt/apt.conf.d/80proxy-bridge'; + +const RUNTIME_FILES = ['config.json', 'user.json', 'profile.json', 'overrides.json', 'remotes.json']; +const CODE_FILES = ['bridge.js', 'proxyctl.js']; + +const sectionResults = []; // { section, status: CHANGED|OK|SKIPPED|FAILED, detail } +function report(section, status, detail = '') { + sectionResults.push({ section, status, detail }); + const color = { CHANGED: '\x1b[33m', OK: '\x1b[32m', SKIPPED: '\x1b[36m', FAILED: '\x1b[31m' }[status] || ''; + console.log(`${color}[${status}]\x1b[0m ${section}${detail ? ` — ${detail}` : ''}`); +} + +function run(cmd, args, opts = {}) { + if (FLAGS.dryRun && !opts.readOnly) { + return { status: 0, stdout: '', stderr: '', dryRun: true }; + } + return spawnSync(cmd, args, { encoding: 'utf8', ...opts }); +} + +function sudoOk() { + const r = run('sudo', ['-n', 'true'], { readOnly: true }); + return r.status === 0; +} + +// --------------------------------------------------------------------------- +// Interactive prompting +// --------------------------------------------------------------------------- + +let rl = null; +function getRl() { + if (!rl) rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return rl; +} + +function ask(question, defaultValue = '') { + return new Promise((resolve) => { + getRl().question(question + (defaultValue ? ` [${defaultValue}]: ` : ': '), (a) => { + resolve(a.trim() || defaultValue); + }); + }); +} + +function askSecret(question) { + return new Promise((resolve) => { + const r = getRl(); + const oldWrite = r._writeToOutput; + r._writeToOutput = function (s) { + if (s === '\n' || s === '\r' || s === '\r\n') r.output.write(s); + else if (s.includes(question)) r.output.write(s); // initial prompt + else r.output.write('*'); + }; + r.question(question + ': ', (a) => { r._writeToOutput = oldWrite; r.output.write('\n'); resolve(a.trim()); }); + }); +} + +async function askConfirm(question, defaultYes = true) { + if (FLAGS.nonInteractive) return defaultYes; + const a = (await ask(`${question} (y/n)`, defaultYes ? 'y' : 'n')).toLowerCase(); + return a === 'y'; +} + +function requireInfo(name, value) { + if (value) return value; + throw new Error(`MISSING_INFO:${name}`); +} + +// --------------------------------------------------------------------------- +// Section: preflight +// --------------------------------------------------------------------------- + +function sectionPreflight() { + const S = 'Preflight'; + if (process.getuid && process.getuid() === 0) { + report(S, 'FAILED', 'run as your user, not root — sudo is invoked per-step when needed'); + return false; + } + const nodeV = run('node', ['--version'], { readOnly: true }); + if (nodeV.status !== 0) { report(S, 'FAILED', 'node not on PATH'); return false; } + const st = run('which', ['secret-tool'], { readOnly: true }); + if (st.status !== 0) { + if (FLAGS.nonInteractive) { report(S, 'FAILED', 'libsecret-tools missing (apt install -y libsecret-tools)'); return false; } + report(S, 'CHANGED', 'libsecret-tools missing — will attempt install (sudo apt-get install -y libsecret-tools)'); + run('sudo', ['apt-get', 'install', '-y', 'libsecret-tools', '-qq']); + if (run('which', ['secret-tool'], { readOnly: true }).status !== 0) { + report(S, 'FAILED', 'libsecret-tools install failed'); + return false; + } + return true; + } + report(S, 'OK', `node ${nodeV.stdout.trim()}, secret-tool present`); + return true; +} + +// --------------------------------------------------------------------------- +// Section: deploy code to BRIDGE_DIR +// --------------------------------------------------------------------------- + +function sectionDeploy() { + const S = 'Deploy code'; + let changed = false; + + if (!FLAGS.dryRun) { + fs.mkdirSync(BRIDGE_DIR, { recursive: true }); + } + for (const f of CODE_FILES) { + const src = path.join(SRC_DIR, f); + const dst = path.join(BRIDGE_DIR, f); + const srcBuf = fs.readFileSync(src); + let dstBuf = null; + try { dstBuf = fs.readFileSync(dst); } catch {} + if (!dstBuf || !srcBuf.equals(dstBuf)) { + changed = true; + if (!FLAGS.dryRun) { + fs.copyFileSync(src, dst); + if (f === 'proxyctl.js') fs.chmodSync(dst, 0o755); + } + } + } + const cliLink = path.join(BIN_DIR, 'proxy-bridge'); + const wantTarget = path.join(BRIDGE_DIR, 'proxyctl.js'); + let current = null; + try { current = fs.readlinkSync(cliLink); } catch {} + if (current !== wantTarget) { + changed = true; + if (!FLAGS.dryRun) { + fs.mkdirSync(BIN_DIR, { recursive: true }); + try { fs.unlinkSync(cliLink); } catch {} + fs.symlinkSync(wantTarget, cliLink); + } + } + report(S, changed ? 'CHANGED' : 'OK', changed ? 'code deployed to ' + BRIDGE_DIR : 'bridge.js/proxyctl.js identical, CLI symlink in place'); + return true; +} + +// --------------------------------------------------------------------------- +// Section: import / create runtime config (non-destructive) +// --------------------------------------------------------------------------- + +function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } } +function fileExists(p) { try { fs.accessSync(p); return true; } catch { return false; } } + +function keyringHasPassword(username) { + if (!username) return false; + const r = run('secret-tool', ['lookup', 'service', 'proxy-bridge', 'account', username], { readOnly: true }); + // Never inspect r.stdout — just the exit code. + return r.status === 0 && !r.error; +} + +async function sectionConfig() { + const S = 'Runtime config'; + const configPath = path.join(BRIDGE_DIR, 'config.json'); + const userPath = path.join(BRIDGE_DIR, 'user.json'); + const existingConfig = readJson(configPath); + const existingUser = readJson(userPath); + + // Import existing — show, ask before changing. + if (existingConfig && existingConfig.host) { + const enabled = existingConfig.enabled !== false; + report(S, 'OK', `imported existing: ${enabled ? 'enabled' : 'disabled'} upstream ${existingConfig.host}:${existingConfig.port} (kept)`); + if (!enabled) return { enabled: false }; + const username = existingUser && existingUser.username ? existingUser.username : null; + return { enabled: true, host: existingConfig.host, port: String(existingConfig.port), username }; + } + + // Nothing to import — ask. + if (FLAGS.nonInteractive) { + report(S, 'FAILED', 'no existing config.json to import and --non-interactive set (missing: upstream host/port or profiles mode)'); + return null; + } + console.log('\n--- Upstream proxy setup (no existing configuration found) ---'); + const enable = await askConfirm('Enable corporate upstream proxy?', true); + if (!enable) { + const cfg = { enabled: false }; + if (!FLAGS.dryRun) fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2)); + report(S, 'CHANGED', 'wrote config.json with upstream disabled'); + return { enabled: false }; + } + const host = requireInfo('upstream-host', await ask('Corporate proxy hostname')); + const port = await ask('Corporate proxy port', '8080'); + const username = requireInfo('username', await ask('Corporate username')); + const cfg = { enabled: true, host, port }; + if (!FLAGS.dryRun) { + fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2)); + fs.writeFileSync(userPath, JSON.stringify({ username })); + } + report(S, 'CHANGED', `wrote config.json (${host}:${port}) + user.json`); + return { enabled: true, host, port: String(port), username }; +} + +// --------------------------------------------------------------------------- +// Section: credentials — verify before storing (anti-lockout) +// --------------------------------------------------------------------------- + +// One single CONNECT attempt with candidate creds directly against the upstream. +// NEVER retries, NEVER prints the password, resolves: ok | authfail | unreachable +function verifyCredentials(host, port, username, password) { + return new Promise((resolve) => { + const net = require('net'); + const socket = net.connect(Number(port), host); + let buf = Buffer.alloc(0); + const finish = (r) => { try { socket.destroy(); } catch {} resolve(r); }; + socket.setTimeout(10000, () => finish('unreachable')); + socket.once('connect', () => { + const auth = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'); + socket.write(`CONNECT 127.0.0.1:443 HTTP/1.1\r\nHost: 127.0.0.1:443\r\nProxy-Authorization: ${auth}\r\n\r\n`); + }); + socket.on('data', (d) => { + buf = Buffer.concat([buf, d]); + const head = buf.toString('latin1').split('\r\n')[0] || ''; + const m = head.match(/^HTTP\/\d\.\d\s+(\d{3})/i); + if (m || buf.includes('\r\n\r\n')) { + const code = m ? Number(m[1]) : 0; + // 200/4xx non-407 (e.g. 403 blocked target) = creds accepted; 407 = rejected. + finish(code === 407 ? 'authfail' : (code > 0 ? 'ok' : 'unreachable')); + } + }); + socket.once('error', () => finish('unreachable')); + socket.once('end', () => { if (buf.length === 0) finish('unreachable'); }); + }); +} + +async function sectionCredentials(config) { + const S = 'Credentials'; + if (!config || !config.enabled) { report(S, 'SKIPPED', 'upstream disabled'); return true; } + + const username = config.username; + if (username && keyringHasPassword(username)) { + report(S, 'OK', `keyring entry present for account (not displayed)`); + return true; + } + + if (FLAGS.nonInteractive) { + report(S, 'FAILED', `keyring has no password for the configured account and --non-interactive set (missing: password)`); + return false; + } + + console.log('\n--- Credential setup (one verification round-trip, result never stored on failure) ---'); + const user = username || requireInfo('username', await ask('Corporate username')); + for (let attempt = 1; attempt <= 3; attempt++) { + const pass = await askSecret(`Corporate password for ${user} (attempt ${attempt}/3)`); + if (!pass) { console.log(' empty password, try again'); continue; } + const verdict = await verifyCredentials(config.host, config.port, user, pass); + if (verdict === 'ok') { + if (!FLAGS.dryRun) { + execFileSync('secret-tool', ['store', '--label=Proxy Bridge Credentials', 'service', 'proxy-bridge', 'account', user], { input: pass }); + } + report(S, attempt > 1 ? 'CHANGED' : 'OK', 'credentials verified against upstream and stored in keyring'); + return true; + } + if (verdict === 'authfail') console.log(' upstream rejected these credentials (407). Not stored. Check the password.'); + else console.log(' could not reach the upstream proxy to verify. Credentials NOT stored (retry when online).'); + if (verdict === 'unreachable') return false; // don't re-prompt if we can't even verify + } + report(S, 'FAILED', 'credentials could not be verified after 3 attempts — nothing stored'); + return false; +} + +// --------------------------------------------------------------------------- +// Section: legacy artifact migration +// --------------------------------------------------------------------------- + +async function sectionLegacy() { + const S = 'Legacy migration'; + const findings = []; + + // bashrc: legacy marker sourcing ~/.http_proxy (cred-bearing file from update_bashrc_proxy.sh) + let bashrc = ''; + try { bashrc = fs.readFileSync(BASHRC, 'utf8'); } catch {} + const legacyLineRe = /^.*#PROXY_A93JK2.*$/m; + const legacyMatch = bashrc.match(legacyLineRe); + const credFiles = ['.http_proxy', '.http_proxy.off', '.http_noproxy'].map(f => path.join(os.homedir(), f)).filter(fileExists); + if (legacyMatch || credFiles.length > 0) { + findings.push({ what: 'bashrc #PROXY_A93JK2 block' + (credFiles.length ? ` + ${credFiles.join(', ')} (contain PLAINTEXT credentials)` : ''), fix: 'remove legacy line' + (credFiles.length ? ' and delete cred files' : '') }); + } + + // apt legacy file with userinfo + const aptLegacy = '/etc/apt/apt.conf.d/80proxy'; + let aptLegacyHasCreds = false; + try { + const content = fs.readFileSync(aptLegacy, 'utf8'); + aptLegacyHasCreds = /:\/\/[^/\s]+:[^@\s]+@/.test(content); // presence only + } catch {} + if (aptLegacyHasCreds) findings.push({ what: `${aptLegacy} (contains embedded credentials)`, fix: 'delete (80proxy-bridge supersedes)' }); + + // snap proxy with userinfo + const snapGet = run('snap', ['get', 'system', 'proxy.http', 'proxy.https'], { readOnly: true }); + if (snapGet.status === 0 && /:\/\/[^/\s]+:[^@\s]+@/.test(snapGet.stdout)) { + findings.push({ what: 'snap system proxy.* (contain embedded credentials)', fix: 'point at bridge URL without creds (needs sudo)' }); + } + + // systemd service drop-ins with userinfo + for (const svc of ['docker', 'snapd', 'ollama']) { + const f = `/etc/systemd/system/${svc}.service.d/http-proxy.conf`; + try { + const content = fs.readFileSync(f, 'utf8'); + if (/:\/\/[^/\s]+:[^@\s]+@/.test(content)) { + findings.push({ what: `${f} (contains embedded credentials)`, fix: `rewrite to bridge URL (needs sudo)` }); + } + } catch {} + } + + if (findings.length === 0) { report(S, 'OK', 'no legacy credential-embedding artifacts found'); return true; } + + console.log('\nLegacy artifacts found:'); + findings.forEach((f, i) => console.log(` ${i + 1}. ${f.what}\n fix: ${f.fix}`)); + const proceed = await askConfirm('Migrate/remove these now?', true); + if (!proceed) { report(S, 'SKIPPED', 'user deferred legacy migration'); return true; } + + let changed = false; + if (legacyMatch) { + if (!FLAGS.dryRun) { + const cleaned = bashrc.split('\n').filter((line, idx, arr) => { + if (line.includes('#PROXY_A93JK2')) return false; + return true; + }).join('\n'); + fs.writeFileSync(BASHRC, cleaned); + } + changed = true; + } + if (credFiles.length > 0) { + const del = await askConfirm(`Delete plaintext-credential file(s) ${credFiles.join(', ')}?`, true); + if (del && !FLAGS.dryRun) credFiles.forEach(f => fs.rmSync(f, { force: true })); + changed = changed || del; + } + if (aptLegacyHasCreds) { + if (!FLAGS.dryRun) run('sudo', ['rm', '-f', aptLegacy]); + changed = true; + } + if (findings.some(f => f.what.startsWith('snap system'))) { + if (!FLAGS.dryRun) { + run('sudo', ['snap', 'set', 'system', `proxy.http=${BRIDGE_URL}`, `proxy.https=${BRIDGE_URL}`]); + } + changed = true; + } + for (const svc of ['docker', 'snapd', 'ollama']) { + const f = `/etc/systemd/system/${svc}.service.d/http-proxy.conf`; + try { + const content = fs.readFileSync(f, 'utf8'); + if (/:\/\/[^/\s]+:[^@\s]+@/.test(content)) { + if (!FLAGS.dryRun) { + const clean = `[Service]\nEnvironment="HTTP_PROXY=${BRIDGE_URL}"\nEnvironment="HTTPS_PROXY=${BRIDGE_URL}"\nEnvironment="NO_PROXY=localhost,127.0.0.1,::1"\n`; + fs.writeFileSync(`/tmp/proxy-bridge-dropin.tmp`, clean); + run('sudo', ['cp', `/tmp/proxy-bridge-dropin.tmp`, f]); + fs.rmSync(`/tmp/proxy-bridge-dropin.tmp`, { force: true }); + } + changed = true; + } + } catch {} + } + report(S, changed ? 'CHANGED' : 'OK', changed ? `migrated ${findings.length} artifact(s)` : 'no changes needed'); + return true; +} + +// --------------------------------------------------------------------------- +// Section: systemd user service +// --------------------------------------------------------------------------- + +function sectionUnit() { + const S = 'systemd service'; + if (FLAGS.noSystemd) { report(S, 'SKIPPED', '--no-systemd'); return true; } + const unitPath = path.join(SYSTEMD_USER_DIR, 'proxy-bridge.service'); + const nodeBin = process.execPath; + const want = `[Unit]\nDescription=Local Proxy Bridge (Keyring Auth)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${nodeBin} ${BRIDGE_DIR}/bridge.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`; + let current = null; + try { current = fs.readFileSync(unitPath, 'utf8'); } catch {} + if (current === want) { + report(S, 'OK', 'unit file up to date'); + return true; + } + if (!FLAGS.dryRun) { + fs.mkdirSync(SYSTEMD_USER_DIR, { recursive: true }); + fs.writeFileSync(unitPath, want); + run('systemctl', ['--user', 'daemon-reload']); + run('systemctl', ['--user', 'enable', 'proxy-bridge.service']); + } + report(S, 'CHANGED', 'unit file written + enabled (restart happens in verify section with your consent)'); + return true; +} + +// --------------------------------------------------------------------------- +// Section: system-wide proxy point-at-bridge (idempotent) +// --------------------------------------------------------------------------- + +async function sectionSystemWide() { + const S = 'System-wide proxy'; + let changed = false; + + // bashrc bridge block (marker-guarded) + let bashrc = ''; + try { bashrc = fs.readFileSync(BASHRC, 'utf8'); } catch {} + const marker = '#PROXY_BRIDGE_CONFIG'; + const block = `\n# Local Proxy Bridge Configuration ${marker}\nexport http_proxy="${BRIDGE_URL}"\nexport https_proxy="${BRIDGE_URL}"\nexport HTTP_PROXY="${BRIDGE_URL}"\nexport HTTPS_PROXY="${BRIDGE_URL}"\nexport NO_PROXY="localhost,127.0.0.1,::1"\nexport no_proxy="localhost,127.0.0.1,::1"\n`; + if (!bashrc.includes(marker)) { + changed = true; + if (!FLAGS.dryRun) fs.writeFileSync(BASHRC, bashrc.replace(/\n*$/, '\n') + block); + } + + // apt conf (needs sudo; content-compare via sudo cat is awkward — check existence + no-userinfo suffices for OK) + let aptOk = false; + try { + const c = fs.readFileSync(APT_CONF, 'utf8'); + aptOk = c.includes('127.0.0.1:8888') && !/:\/\/[^/\s]+:[^@\s]+@/.test(c); + } catch {} + if (!aptOk) { + const doIt = FLAGS.nonInteractive ? true : await askConfirm(`Write ${APT_CONF} with bridge URL (sudo)?`, true); + if (doIt) { + changed = true; + if (!FLAGS.dryRun) { + const tmpW = `/tmp/proxy-bridge-apt.tmp`; + fs.writeFileSync(tmpW, `Acquire::http::Proxy "${BRIDGE_URL}";\nAcquire::https::Proxy "${BRIDGE_URL}";\n`); + run('sudo', ['cp', tmpW, APT_CONF]); + fs.rmSync(tmpW, { force: true }); + } + } + } + + // service drop-ins (docker/snapd/ollama) — bridge URL only + for (const svc of ['docker', 'snapd', 'ollama']) { + const f = `/etc/systemd/system/${svc}.service.d/http-proxy.conf`; + let ok = false; + try { + const c = fs.readFileSync(f, 'utf8'); + ok = c.includes('127.0.0.1:8888') && !/:\/\/[^/\s]+:[^@\s]+@/.test(c); + } catch {} + if (!ok) { + const svcExists = run('systemctl', ['list-unit-files', `${svc}.service`], { readOnly: true }); + if (svcExists.status !== 0 || !svcExists.stdout.includes(svc)) continue; // service not installed + const doIt = FLAGS.nonInteractive ? true : await askConfirm(`Point ${svc} at the bridge (sudo + restart ${svc})?`, true); + if (doIt) { + changed = true; + if (!FLAGS.dryRun) { + const tmpW = `/tmp/proxy-bridge-dropin.tmp`; + fs.writeFileSync(tmpW, `[Service]\nEnvironment="HTTP_PROXY=${BRIDGE_URL}"\nEnvironment="HTTPS_PROXY=${BRIDGE_URL}"\nEnvironment="NO_PROXY=localhost,127.0.0.1,::1"\n`); + run('sudo', ['mkdir', '-p', path.dirname(f)]); + run('sudo', ['cp', tmpW, f]); + fs.rmSync(tmpW, { force: true }); + run('sudo', ['systemctl', 'daemon-reload']); + run('sudo', ['systemctl', 'restart', svc]); + } + } + } + } + report(S, changed ? 'CHANGED' : 'OK', changed ? 'system-wide proxy config updated' : 'bashrc/apt/services already point at the bridge'); + return true; +} + +// --------------------------------------------------------------------------- +// Section: verify (service restart happens here, with consent) +// --------------------------------------------------------------------------- + +async function sectionVerify() { + const S = 'Verify'; + if (FLAGS.noSystemd) { report(S, 'SKIPPED', '--no-systemd (no restart/checks)'); return true; } + + const active = run('systemctl', ['--user', 'is-active', 'proxy-bridge'], { readOnly: true }).stdout.trim(); + if (active !== 'active') { + const doIt = FLAGS.nonInteractive ? true : await askConfirm('Start/restart proxy-bridge now? (drops bridged connectivity briefly)', true); + if (doIt && !FLAGS.dryRun) { + run('systemctl', ['--user', 'restart', 'proxy-bridge']); + await new Promise(r => setTimeout(r, 1500)); + } + } + const state = run('systemctl', ['--user', 'is-active', 'proxy-bridge'], { readOnly: true }).stdout.trim(); + if (state !== 'active') { report(S, 'FAILED', `service state: ${state || 'unknown'}`); return false; } + + // ports listening + const ss = run('ss', ['-tln'], { readOnly: true }); + const ports = [8888, 8889, 8890, 8891]; + const listening = ports.filter(p => ss.stdout.includes(`127.0.0.1:${p}`)); + const allCore = listening.length === ports.length; + report(S, allCore ? 'OK' : 'FAILED', `service active; core ports listening: ${listening.join(',') || 'none'}`); + return allCore; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main() { + console.log(`=== Proxy Bridge installer ${FLAGS.dryRun ? '(DRY RUN) ' : ''}===`); + console.log(`target: ${BRIDGE_DIR}`); + const missingInfo = []; + let credsFailed = false; + try { + if (!sectionPreflight()) throw new Error('PREFLIGHT_FAILED'); + if (!sectionDeploy()) throw new Error('DEPLOY_FAILED'); + const config = await sectionConfig(); + if (config === null) throw new Error('CONFIG_FAILED'); + credsFailed = !(await sectionCredentials(config)); + // Do not abort on credential failure: remaining sections show/prep what + // else needs doing, and the summary still exits non-zero. + if (!await sectionLegacy()) throw new Error('LEGACY_FAILED'); + if (!sectionUnit()) throw new Error('UNIT_FAILED'); + if (!await sectionSystemWide()) throw new Error('SYSWIDE_FAILED'); + if (!FLAGS.dryRun && !credsFailed) await sectionVerify(); + else report('Verify', 'SKIPPED', FLAGS.dryRun ? 'dry run' : 'credentials not verified; skipping restart'); + } catch (e) { + if (String(e.message).startsWith('MISSING_INFO:')) { + missingInfo.push(e.message.split(':')[1]); + } else if (!['PREFLIGHT_FAILED', 'DEPLOY_FAILED', 'CONFIG_FAILED', 'CREDS_FAILED', 'LEGACY_FAILED', 'UNIT_FAILED', 'SYSWIDE_FAILED'].includes(e.message)) { + throw e; + } + } finally { + if (rl) rl.close(); + } + const failed = sectionResults.filter(r => r.status === 'FAILED'); + console.log('\n=== Summary ==='); + sectionResults.forEach(r => console.log(` ${r.status.padEnd(8)} ${r.section}`)); + if (missingInfo.length) console.log(`\nMissing information for --non-interactive: ${missingInfo.join(', ')}`); + if (FLAGS.dryRun) console.log('\nDry run: no changes were made.'); + process.exit(failed.length > 0 || missingInfo.length > 0 ? 1 : 0); +} + +main().catch(e => { console.error('installer error:', e.message); process.exit(1); }); diff --git a/scripts/proxy/install.test.js b/scripts/proxy/install.test.js new file mode 100644 index 0000000..1b5d922 --- /dev/null +++ b/scripts/proxy/install.test.js @@ -0,0 +1,157 @@ +const assert = require('node:assert/strict'); +const { once } = require('node:events'); +const { spawnSync, spawn } = require('node:child_process'); +const fs = require('node:fs'); +const net = require('node:net'); +const os = require('node:os'); +const path = require('node:path'); +const { test } = require('node:test'); + +const INSTALLER = path.join(__dirname, 'install.js'); +const PROXY_DIR = __dirname; + +function mkTmp(t) { + const dirs = { + bridge: fs.mkdtempSync(path.join(os.tmpdir(), 'pi-bridge-')), + bin: fs.mkdtempSync(path.join(os.tmpdir(), 'pi-bin-')), + systemd: fs.mkdtempSync(path.join(os.tmpdir(), 'pi-systemd-')), + home: fs.mkdtempSync(path.join(os.tmpdir(), 'pi-home-')), + apt: fs.mkdtempSync(path.join(os.tmpdir(), 'pi-apt-')), + }; + t.after(() => Object.values(dirs).forEach(d => fs.rmSync(d, { recursive: true, force: true }))); + return dirs; +} + +function envFor(dirs) { + return { + ...process.env, + PROXY_BRIDGE_DIR: dirs.bridge, + PROXY_BRIDGE_BIN_DIR: dirs.bin, + PROXY_BRIDGE_SYSTEMD_USER_DIR: dirs.systemd, + PROXY_BRIDGE_BASHRC: path.join(dirs.home, '.bashrc'), + PROXY_BRIDGE_APT_CONF: path.join(dirs.apt, '80proxy-bridge'), + }; +} + +function runInstaller(t, dirs, extraArgs = []) { + const r = spawnSync('node', [INSTALLER, '--no-systemd', ...extraArgs], { + env: envFor(dirs), + encoding: 'utf8', + timeout: 60000, + input: '', // no stdin: any interactive prompt must not be reached in these non-interactive paths + }); + r.stdout = String(r.stdout).replace(/\x1b\[[0-9;]*m/g, ''); + r.stderr = String(r.stderr || '').replace(/\x1b\[[0-9;]*m/g, ''); + return r; +} + +// Async variant: use whenever the test process itself hosts a TCP stub server — +// spawnSync would block the event loop and the stub would never answer. +function runInstallerAsync(t, dirs, { args = [], stdinLines = [] } = {}) { + return new Promise((resolve, reject) => { + const child = spawn('node', [INSTALLER, '--no-systemd', ...args], { env: envFor(dirs) }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', d => { stdout += d; }); + child.stderr.on('data', d => { stderr += d; }); + const timer = setTimeout(() => { child.kill(); reject(new Error('installer hung')); }, 55000); + child.on('exit', code => { + clearTimeout(timer); + resolve({ + status: code, + stdout: stdout.replace(/\x1b\[[0-9;]*m/g, ''), + stderr: stderr.replace(/\x1b\[[0-9;]*m/g, ''), + }); + }); + // Feed stdin with a small delay so readline has attached before lines arrive. + let i = 0; + const feed = () => { + if (i >= stdinLines.length) { try { child.stdin.end(); } catch {} return; } + child.stdin.write(stdinLines[i++] + '\n'); + setTimeout(feed, 150); + }; + setTimeout(feed, 300); + }); +} + +test('dry-run changes nothing on disk', { timeout: 30000 }, async t => { + const dirs = mkTmp(t); + const r = runInstaller(t, dirs, ['--dry-run', '--non-interactive']); + // non-interactive with no imported config -> CONFIG section reports FAILED by design; fine for this test + assert.equal(fs.readdirSync(dirs.bridge).length, 0, 'bridge dir must stay empty in dry-run'); + assert.equal(fs.readdirSync(dirs.bin).length, 0, 'bin dir must stay empty in dry-run'); + assert.equal(fs.readdirSync(dirs.systemd).length, 0, 'systemd dir must stay empty in dry-run'); + assert.ok(!fs.existsSync(path.join(dirs.home, '.bashrc')), 'bashrc must not be created in dry-run'); + assert.match(r.stdout, /Dry run: no changes were made\./); +}); + +test('import-only run is idempotent: second run reports zero CHANGED', { timeout: 60000 }, async t => { + const dirs = mkTmp(t); + // Seed an 'imported' config the way the live /opt has it + fs.writeFileSync(path.join(dirs.bridge, 'config.json'), JSON.stringify({ enabled: true, host: '127.0.0.1', port: '1' })); + fs.writeFileSync(path.join(dirs.bridge, 'user.json'), JSON.stringify({ username: 'seeded-user' })); + // Seed bashrc with the EXACT bridge block the installer would write (idempotency target) + fs.writeFileSync(path.join(dirs.home, '.bashrc'), + `\n# Local Proxy Bridge Configuration #PROXY_BRIDGE_CONFIG\nexport http_proxy="http://127.0.0.1:8888/"\nexport https_proxy="http://127.0.0.1:8888/"\nexport HTTP_PROXY="http://127.0.0.1:8888/"\nexport HTTPS_PROXY="http://127.0.0.1:8888/"\nexport NO_PROXY="localhost,127.0.0.1,::1"\nexport no_proxy="localhost,127.0.0.1,::1"\n`); + // Seed the apt conf the installer would want (no sudo needed: our APT_CONF is a tmp path) + fs.writeFileSync(path.join(dirs.apt, '80proxy-bridge'), + `Acquire::http::Proxy "http://127.0.0.1:8888/";\nAcquire::https::Proxy "http://127.0.0.1:8888/";\n`); + + const r1 = runInstaller(t, dirs, ['--non-interactive']); + assert.equal(r1.status, 1, JSON.stringify(r1.stdout + r1.stderr)); // creds missing -> expected FAILED in this seed + assert.match(r1.stdout, /\[OK\] Runtime config/); + assert.match(r1.stdout, /\[FAILED\] Credentials/); // no keyring entry for seeded-user; correct + + // Second run must show zero CHANGED in the sections that did run + const r2 = runInstaller(t, dirs, ['--non-interactive']); + const changedLines = r2.stdout.split('\n').filter(l => l.includes('[CHANGED]')); + assert.deepEqual(changedLines, [], `second run must be a no-op, got CHANGED: ${changedLines.join(' | ')}`); +}); + +test('credential verification: 200 from upstream stores the password (junk test account)', { timeout: 60000 }, async t => { + const dirs = mkTmp(t); + const account = `bridge-test-${process.pid}`; + // Stub "upstream" that accepts any creds once + const upstream = net.createServer(socket => { + socket.once('data', () => { + socket.end('HTTP/1.1 200 Connection Established\r\n\r\n'); + }); + }); + upstream.listen(0, '127.0.0.1'); + await once(upstream, 'listening'); + t.after(() => upstream.close()); + const port = upstream.address().port; + + fs.writeFileSync(path.join(dirs.bridge, 'config.json'), JSON.stringify({ enabled: true, host: '127.0.0.1', port: String(port) })); + fs.writeFileSync(path.join(dirs.bridge, 'user.json'), JSON.stringify({ username: account })); + fs.writeFileSync(path.join(dirs.home, '.bashrc'), ''); + + const r = await runInstallerAsync(t, dirs, { stdinLines: ['junk-test-password-not-real', 'n'] }); + // creds stored via real secret-tool; clean up regardless of outcome + spawnSync('secret-tool', ['clear', 'service', 'proxy-bridge', 'account', account], { encoding: 'utf8' }); + assert.match(r.stdout, /\[OK\] Credentials|\[CHANGED\] Credentials/); + assert.match(r.stdout, /credentials verified against upstream and stored/); +}); + +test('credential verification: 407 from upstream does NOT store and reports FAILED', { timeout: 60000 }, async t => { + const dirs = mkTmp(t); + const account = `bridge-test407-${process.pid}`; + const upstream = net.createServer(socket => { + socket.once('data', () => socket.end('HTTP/1.1 407 Proxy Authentication Required\r\n\r\n')); + }); + upstream.listen(0, '127.0.0.1'); + await once(upstream, 'listening'); + t.after(() => upstream.close()); + const port = upstream.address().port; + + fs.writeFileSync(path.join(dirs.bridge, 'config.json'), JSON.stringify({ enabled: true, host: '127.0.0.1', port: String(port) })); + fs.writeFileSync(path.join(dirs.bridge, 'user.json'), JSON.stringify({ username: account })); + fs.writeFileSync(path.join(dirs.home, '.bashrc'), ''); + + const r = await runInstallerAsync(t, dirs, { stdinLines: ['wrong1', 'wrong2', 'wrong3', 'n'] }); + // make sure nothing was stored for this account + const check = spawnSync('secret-tool', ['lookup', 'service', 'proxy-bridge', 'account', account], { encoding: 'utf8' }); + assert.notEqual(check.status, 0, 'no keyring entry must exist for rejected creds'); + assert.match(r.stdout, /\[FAILED\] Credentials/); + assert.match(r.stdout, /nothing stored/i); +}); diff --git a/scripts/proxy/install_proxy.sh b/scripts/proxy/install_proxy.sh deleted file mode 100755 index be37e81..0000000 --- a/scripts/proxy/install_proxy.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -# --- HELPER FUNCTIONS --- -function info() { echo -e "\e[34mINFO:\e[0m $1"; } -function warn() { echo -e "\e[33mWARN:\e[0m $1"; } -function error() { echo -e "\e[31mERROR:\e[0m $1"; exit 1; } -function success() { echo -e "\e[32mSUCCESS:\e[0m $1"; } - -# 1. PREREQUISITE CHECKS -info "Verifying prerequisites..." - -if [ "$EUID" -eq 0 ]; then - error "Please run this script as your standard user, not as root.\nThe script will prompt for sudo access automatically when needed." -fi - -if ! command -v node >/dev/null 2>&1; then - error "Node.js is not installed. Please install Node.js first." -fi - -# Check for files -[ -f "bridge.js" ] || error "bridge.js not found." -[ -f "setup.js" ] || error "setup.js not found." -[ -f "proxyctl.js" ] || error "proxyctl.js not found." - -# 2. SYSTEM PACKAGES -info "Installing required system packages (libsecret-tools)..." -sudo apt-get update -qq -sudo apt-get install -y libsecret-tools -qq - -# 3. DIRECTORY SETUP -BRIDGE_DIR="/opt/proxy-bridge" -BIN_DIR="${HOME}/.local/bin" -info "Setting up $BRIDGE_DIR..." -sudo mkdir -p "$BRIDGE_DIR" -sudo chown -R $USER:$USER "$BRIDGE_DIR" - -# 4. DEPLOY FILES -info "Deploying bridge scripts..." -cp bridge.js "$BRIDGE_DIR/" -cp setup.js "$BRIDGE_DIR/" -cp proxyctl.js "$BRIDGE_DIR/" -chmod +x "$BRIDGE_DIR/proxyctl.js" - -info "Installing management CLI to $BIN_DIR/proxy-bridge..." -mkdir -p "$BIN_DIR" -ln -sf "$BRIDGE_DIR/proxyctl.js" "$BIN_DIR/proxy-bridge" - -# 5. CONFIGURATION & CREDENTIALS -if [ ! -f "$BRIDGE_DIR/config.json" ] || [ ! -f "$BRIDGE_DIR/user.json" ]; then - info "Launching interactive setup..." - node "$BRIDGE_DIR/setup.js" -fi - -# 6. SYSTEMD SERVICE SETUP -info "Configuring user-level systemd service..." -mkdir -p ~/.config/systemd/user/ - -cat < ~/.config/systemd/user/proxy-bridge.service -[Unit] -Description=Local Proxy Bridge (Keyring Auth) -After=network.target - -[Service] -Type=simple -ExecStart=$(command -v node) $BRIDGE_DIR/bridge.js -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=default.target -EOF - -systemctl --user daemon-reload -systemctl --user enable proxy-bridge.service -systemctl --user restart proxy-bridge.service - -# 7. SYSTEM-WIDE PROXY CONFIGURATION (Point to local bridge) -BRIDGE_URL="http://127.0.0.1:8888/" - -# 7a. APT Configuration -info "Updating APT proxy configuration..." -APT_CONF="/etc/apt/apt.conf.d/80proxy-bridge" -echo "Acquire::http::Proxy \"$BRIDGE_URL\";" | sudo tee "$APT_CONF" > /dev/null -echo "Acquire::https::Proxy \"$BRIDGE_URL\";" | sudo tee -a "$APT_CONF" > /dev/null - -# 7b. Bashrc Configuration -info "Updating .bashrc proxy settings..." -BASHRC="$HOME/.bashrc" -MARKER="#PROXY_BRIDGE_CONFIG" - -if ! grep -q "$MARKER" "$BASHRC"; then - cat <> "$BASHRC" - -# Local Proxy Bridge Configuration $MARKER -export http_proxy="$BRIDGE_URL" -export https_proxy="$BRIDGE_URL" -export HTTP_PROXY="$BRIDGE_URL" -export HTTPS_PROXY="$BRIDGE_URL" -export NO_PROXY="localhost,127.0.0.1,::1" -export no_proxy="localhost,127.0.0.1,::1" -EOF -fi - -# 7c. System Services (Docker, Snap, etc.) -SERVICES=(docker snapd ollama) -info "Updating system services proxy configuration..." - -for service in "${SERVICES[@]}"; do - if systemctl list-unit-files | grep -q "^${service}.service"; then - info "Configuring $service..." - SERVICE_DIR="/etc/systemd/system/${service}.service.d" - sudo mkdir -p "$SERVICE_DIR" - cat < /dev/null -[Service] -Environment="HTTP_PROXY=$BRIDGE_URL" -Environment="HTTPS_PROXY=$BRIDGE_URL" -Environment="NO_PROXY=localhost,127.0.0.1,::1" -EOF - sudo systemctl daemon-reload - sudo systemctl restart "$service" 2>/dev/null || warn "Failed to restart $service (maybe not running?)" - fi -done - -success "Installation and system-wide configuration complete!" -info "Management command: proxy-bridge status|toggle|profile" -info "The Proxy Bridge is running and configured for APT, Bash, and core services." -info "Logs: journalctl --user -u proxy-bridge.service -f" diff --git a/scripts/proxy/setup.js b/scripts/proxy/setup.js deleted file mode 100644 index e5100a3..0000000 --- a/scripts/proxy/setup.js +++ /dev/null @@ -1,105 +0,0 @@ -const readline = require('readline'); -const { execFileSync } = require('child_process'); -const fs = require('fs'); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -const CONFIG_DIR = '/opt/proxy-bridge'; -const CONFIG_FILE = `${CONFIG_DIR}/config.json`; -const USER_FILE = `${CONFIG_DIR}/user.json`; - -console.log("=== Proxy Bridge Configuration Setup ==="); - -function ask(question, defaultValue = "") { - return new Promise((resolve) => { - const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `; - rl.question(prompt, (answer) => { - resolve(answer.trim() || defaultValue); - }); - }); -} - -async function run() { - try { - // 1. Upstream Proxy Configuration - console.log("\n--- Upstream Proxy (Corporate) ---"); - const enableUpstream = (await ask("Enable Corporate Upstream Proxy? (y/n)", "y")).toLowerCase() === 'y'; - - let host = ""; - let port = "8080"; - 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; - } - - // Save Config - if (!fs.existsSync(CONFIG_DIR)) { - fs.mkdirSync(CONFIG_DIR, { recursive: true }); - } - - fs.writeFileSync(CONFIG_FILE, JSON.stringify({ - enabled: enableUpstream, - host: host, - port: port - }, null, 2)); - - if (enableUpstream) { - // Save Username - fs.writeFileSync(USER_FILE, JSON.stringify({ username: user })); - - // 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) { - console.error("\n❌ Setup failed:", error.message); - process.exit(1); - } finally { - rl.close(); - } -} - -run(); diff --git a/scripts/proxy/update_apt_proxy.sh b/scripts/proxy/update_apt_proxy.sh deleted file mode 100755 index cde33a4..0000000 --- a/scripts/proxy/update_apt_proxy.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -config_file='/etc/apt/apt.conf.d/80proxy' -#remove proxy settings from docker -if [[ "${1}" == "off" ]]; then - echo "TURNING OFF PROXY FOR APT" - sudo rm -rf "${config_file}" - sudo snap unset system proxy.http - sudo snap unset system proxy.https - exit 0 -fi - -#populate proxy_ip and proxy_port variables -path=$(dirname $(readlink -f "${0}")) -. "${path}/get_proxy.sh" - - -echo -n "Username: " -read username -echo -n "Password: " -read -s password -encpassword=$(echo -n ${password} | xxd -p | sed -e 's/\(..\)/%\1/g' ) -echo 'Acquire::http::Proxy "http://'"${username}:${encpassword}@${proxy_ip}:${proxy_port}/"'";' | sudo tee "${config_file}" -echo 'Acquire::http::Timeout "360";' | sudo tee -a "${config_file}" - -echo "SETTING PROXY FOR SNAP ASWELL" -sudo snap set system proxy.http=http://${username}:${encpassword}@${proxy_ip}:${proxy_port}/ -sudo snap set system proxy.https=http://${username}:${encpassword}@${proxy_ip}:${proxy_port}/ diff --git a/scripts/proxy/update_bashrc_proxy.sh b/scripts/proxy/update_bashrc_proxy.sh deleted file mode 100755 index 2d72cc0..0000000 --- a/scripts/proxy/update_bashrc_proxy.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -user=$(logname) -bashrc_file=/home/${user}/.bashrc -proxy_file=/home/${user}/.http_proxy -noproxy_file=/home/${user}/.http_noproxy - -#turn off bashrc proxy settings -if [[ "${1}" == "off" ]]; then - echo "TURNING OFF PROXY IN BASHRC" - mv -v "${proxy_file}" "${proxy_file}.off" - exit 0 -fi - -#turn on bashrc proxy settings -if [[ "${1}" == "on" ]]; then - if [[ -f "${proxy_file}.off" ]] ; then - echo "TURNING ON PROXY IN BASHRC" - mv -v "${proxy_file}.off" "${proxy_file}" - exit 0 - else - echo "No disabled Proxy-Config found. Creating a new one!" - fi -fi - - -marker="#PROXY_A93JK2" -path=$(dirname $(readlink -f "${0}")) -. "${path}/get_proxy.sh" -echo -n "Username: " -read username -echo -n "Password: " -read -s password - - -encpassword=$(echo -n ${password} | xxd -p | sed -e 's/\(..\)/%\1/g' ) - -if ! grep -qF -e "PROXY_A93JK2" "${bashrc_file}" ; then - echo "[ -f ${proxy_file} ] && . ${proxy_file} #PROXY_A93JK2" >> "${bashrc_file}" -fi - -echo 'export "HTTP_PROXY=http://'"${username}"':"'"${encpassword}@${proxy_ip}:${proxy_port}/" > "${proxy_file}" -echo 'export "HTTPS_PROXY=http://'"${username}"':"'"${encpassword}@${proxy_ip}:${proxy_port}/" >> "${proxy_file}" -echo 'export "http_proxy=http://'"${username}"':"'"${encpassword}@${proxy_ip}:${proxy_port}/" >> "${proxy_file}" -echo 'export "https_proxy=http://'"${username}"':"'"${encpassword}@${proxy_ip}:${proxy_port}/" >> "${proxy_file}" -echo 'export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt' >> "${proxy_file}" -echo "[ -f ${noproxy_file} ] && . ${noproxy_file}" >> "${proxy_file}" - -[ -f "${noproxy_file}" ] || echo 'export "NO_PROXY=localhost,127.0.0.1"' > "${noproxy_file}" - diff --git a/scripts/proxy/update_service_proxy.sh b/scripts/proxy/update_service_proxy.sh deleted file mode 100755 index d09bc0b..0000000 --- a/scripts/proxy/update_service_proxy.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -services=(docker snapd ollama) - -function restart_services(){ - echo "Reloading Systemd - Configfiles" - sudo systemctl daemon-reload - for service in ${services[*]} ; do - echo "Restarting Service $service" - sudo systemctl restart $service - done -} - -#remove proxy settings from docker -if [[ "${1}" == "off" ]]; then - for service in ${services[*]} ; do - echo "TURNING OFF PROXY FOR ${service^^}" - sudo rm -v "/etc/systemd/system/${service}.service.d/http-proxy.conf" - done - restart_services - exit 0 -fi - -#populate proxy_ip and proxy_port variables -path=$(dirname $(readlink -f "${0}")) -. "${path}/get_proxy.sh" - - -echo -n "Username: " -read username -echo -n "Password: " -read -s password -#encpassword=$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "${password}") -#encpassword=$(echo -n ${password} | sed -e 's/\@/\\x40/g' -e 's/\!/\\x21/g' -e 's/\$/\\x24/g' -e 's/\*/\\x2a/g' -e 's/\%/\\x25/g' -e 's/\&/\\x26/g' -e 's/\#/\\x30/g') -encpassword=$(echo -n ${password} | xxd -p | sed -e 's/\(..\)/%%\1/g' ) - -for service in ${services[*]} ; do - if ! [[ -d "/etc/systemd/system/${service}.service.d" ]] ; then - sudo mkdir -p "/etc/systemd/system/${service}.service.d/" - fi - echo "[Service]" | sudo tee "/etc/systemd/system/${service}.service.d/http-proxy.conf" >/dev/null - echo 'Environment="HTTP_PROXY=http://'"${username}"':"'"${encpassword}@${proxy_ip}:${proxy_port}/" | sudo tee -a "/etc/systemd/system/${service}.service.d/http-proxy.conf" >/dev/null - echo 'Environment="HTTPS_PROXY=http://'"${username}"':"'"${encpassword}@${proxy_ip}:${proxy_port}/" | sudo tee -a "/etc/systemd/system/${service}.service.d/http-proxy.conf" >/dev/null - if [[ -z "$NO_PROXY" ]] ; then - echo -n 'Environment="NO_PROXY=localhost,127.0.0.1"' | sudo tee -a "/etc/systemd/system/${service}.service.d/http-proxy.conf" >/dev/null - else - echo 'Environment="'$(env | grep NO_PROXY)'"' | sudo tee -a "/etc/systemd/system/${service}.service.d/http-proxy.conf" >/dev/null - fi -done - -restart_services