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/dockerpullsave.py` | goal: download Docker images as tarballs without requiring a Docker daemon | usage: `python3 tools/dockerpullsave.py image:tag`
- `scripts/proxy/install_proxy.sh` | goal: installer for the Dumb Pipe Proxy Bridge service | usage: `scripts/proxy/install_proxy.sh`
- `scripts/proxy/bridge.js` | goal: Node.js proxy bridge with keyring authentication support | usage: `node scripts/proxy/bridge.js`
- `scripts/proxy/bridge.js` | goal: Node.js proxy bridge with multi-profile routing (internet, intranet, direct, off) | usage: `node scripts/proxy/bridge.js`
- `scripts/proxy/setup.js` | goal: Interactive setup for storing proxy credentials in the system keyring | usage: `node scripts/proxy/setup.js`
- `scripts/proxy/proxyctl.js` | goal: Management CLI for the proxy bridge with dynamic PAC discovery from ~/.mozilla/ | usage: `proxy-bridge status|toggle|profile`
### Formats, System, And Text Experiments
+22 -22
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 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);
});
+1 -1
View File
@@ -46,7 +46,7 @@ mkdir -p "$BIN_DIR"
ln -sf "$BRIDGE_DIR/proxyctl.js" "$BIN_DIR/proxy-bridge"
# 5. CONFIGURATION & CREDENTIALS
if [ ! -f "$BRIDGE_DIR/user.json" ]; then
if [ ! -f "$BRIDGE_DIR/config.json" ] || [ ! -f "$BRIDGE_DIR/user.json" ]; then
info "Launching interactive setup..."
node "$BRIDGE_DIR/setup.js"
fi
+86 -189
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 PROFILE_FILE = '/opt/proxy-bridge/profile.json';
const LOCAL_PROXY_URL = `http://${LISTEN_HOST}:${LISTEN_PORT}`;
const VSCODE_CERT_FLAG = '';
const VSCODE_CERT_FLAG = ''; // e.g. '--ignore-certificate-errors'
function run(command, args, options = {}) {
return spawnSync(command, args, {
@@ -26,23 +26,38 @@ function getUpstreamConfig() {
try {
if (fs.existsSync(CONFIG_FILE)) {
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
if (config.enabled === false) return 'DISABLED';
return `${config.host}:${config.port}`;
}
} catch {}
return 'PROXY_HOST_PLACEHOLDER:8080 (default)';
return 'your-proxy.example.com:8080 (default)';
}
function getProfile() {
try {
if (fs.existsSync(PROFILE_FILE)) {
return JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8')).profile || 'internet';
const current = JSON.parse(fs.readFileSync(PROFILE_FILE, 'utf8')).profile;
const valid = getAvailableProfiles();
if (current && valid.includes(current)) return current;
}
} catch {}
return 'internet';
return isUpstreamEnabled() ? 'internet' : 'direct';
}
function isUpstreamEnabled() {
try {
if (fs.existsSync(CONFIG_FILE)) {
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')).enabled !== false;
}
} catch {}
return true;
}
function getAvailableProfiles() {
const profiles = ['internet', 'direct', 'off'];
const profiles = [];
if (isUpstreamEnabled()) profiles.push('internet');
profiles.push('direct', 'off');
const pacDir = path.join(os.homedir(), '.mozilla');
if (fs.existsSync(pacDir)) {
const files = fs.readdirSync(pacDir);
@@ -71,49 +86,28 @@ function systemctl(args, options = {}) {
}
function printCommandFailure(result) {
if (result.error) {
console.error(result.error.message);
return;
}
if (result.stderr) {
console.error(result.stderr.trim());
}
if (result.error) { console.error(result.error.message); return; }
if (result.stderr) { console.error(result.stderr.trim()); }
}
function serviceValue(property) {
const result = systemctl(['show', SERVICE, '--property', property, '--value']);
if (result.status !== 0) {
return '';
}
if (result.status !== 0) return '';
return result.stdout.trim();
}
function serviceState(command) {
const result = systemctl([command, SERVICE]);
if (result.status !== 0) {
return 'unknown';
}
if (result.status !== 0) return 'unknown';
return result.stdout.trim() || 'unknown';
}
function checkPort() {
return new Promise((resolve) => {
const socket = net.connect({ host: LISTEN_HOST, port: LISTEN_PORT });
const timer = setTimeout(() => {
socket.destroy();
resolve(false);
}, 1000);
socket.once('connect', () => {
clearTimeout(timer);
socket.end();
resolve(true);
});
socket.once('error', () => {
clearTimeout(timer);
resolve(false);
});
const timer = setTimeout(() => { socket.destroy(); resolve(false); }, 1000);
socket.once('connect', () => { clearTimeout(timer); socket.end(); resolve(true); });
socket.once('error', () => { clearTimeout(timer); resolve(false); });
});
}
@@ -130,49 +124,52 @@ async function status() {
const enabled = serviceState('is-enabled');
const pid = serviceValue('MainPID');
const listening = await checkPort();
const profile = getProfile();
const current = getProfile();
const all = getAvailableProfiles();
console.log(`service: ${SERVICE}`);
console.log(`active: ${active}`);
console.log(`enabled: ${enabled}`);
console.log(`pid: ${pid && pid !== '0' ? pid : 'none'}`);
console.log(`profile: ${profile}`);
console.log(`available: ${all.join(', ')}`);
console.log(`listen: http://${LISTEN_HOST}:${LISTEN_PORT} (${listening ? 'reachable' : 'not reachable'})`);
if (isUpstreamEnabled()) console.log(`account: ${readAccount()}`);
console.log(`upstream: ${getUpstreamConfig()}`);
console.log(`account: ${readAccount()}`);
console.log(`\nPort Mappings:`);
console.log(` ${LISTEN_PORT.toString().padEnd(5)} -> dynamic (currently: ${current})${listening ? '' : ' [NOT REACHABLE]'}`);
all.forEach((name, i) => {
console.log(` ${(LISTEN_PORT + 1 + i).toString().padEnd(5)} -> ${name}`);
});
}
function control(action) {
const result = systemctl([action, SERVICE], { stdio: 'inherit' });
if (result.status !== 0) {
printCommandFailure(result);
process.exit(result.status || 1);
}
if (result.status !== 0) { printCommandFailure(result); process.exit(result.status || 1); }
}
function logs() {
const child = spawn('journalctl', ['--user', '-u', SERVICE, '-f'], {
stdio: 'inherit',
});
const child = spawn('journalctl', ['--user', '-u', SERVICE, '-f'], { stdio: 'inherit' });
child.on('exit', (code) => process.exit(code || 0));
}
function setup() {
const child = spawn('node', ['/opt/proxy-bridge/setup.js'], {
stdio: 'inherit',
});
const child = spawn('node', ['/opt/proxy-bridge/setup.js'], { stdio: 'inherit' });
child.on('exit', (code) => process.exit(code || 0));
}
function config() {
console.log(`listen: ${LOCAL_PROXY_URL}`);
const all = getAvailableProfiles();
console.log(`listen_host: ${LISTEN_HOST}`);
console.log(`base_port: ${LISTEN_PORT}`);
if (isUpstreamEnabled()) {
console.log(`user_file: ${USER_FILE}`);
console.log(`account: ${readAccount()}`);
}
console.log(`config_file: ${CONFIG_FILE}`);
console.log(`upstream: ${getUpstreamConfig()}`);
console.log(`profile: ${getProfile()}`);
console.log(`available profiles: ${getAvailableProfiles().join(', ')}`);
console.log(`user_file: ${USER_FILE}`);
console.log(`account: ${readAccount()}`);
console.log(`\nEffective Mappings:`);
console.log(` ${LISTEN_PORT} -> dynamic (${getProfile()})`);
all.forEach((name, i) => {
console.log(` ${(LISTEN_PORT + 1 + i).toString().padEnd(5)} -> ${name}`);
});
}
function codeSettingsPath() {
@@ -181,16 +178,9 @@ function codeSettingsPath() {
}
function readJsonFile(file) {
if (!fs.existsSync(file)) {
return {};
}
if (!fs.existsSync(file)) return {};
const content = fs.readFileSync(file, 'utf8').trim();
if (!content) {
return {};
}
return JSON.parse(content);
return content ? JSON.parse(content) : {};
}
function writeJsonFile(file, data) {
@@ -206,33 +196,19 @@ function desktopApplicationsDir() {
function desktopLauncherPaths() {
const userDir = desktopApplicationsDir();
return [
{
name: 'main',
system: '/usr/share/applications/code.desktop',
user: path.join(userDir, 'code.desktop'),
},
{
name: 'url-handler',
system: '/usr/share/applications/code-url-handler.desktop',
user: path.join(userDir, 'code-url-handler.desktop'),
},
{ name: 'main', system: '/usr/share/applications/code.desktop', user: path.join(userDir, 'code.desktop') },
{ name: 'url-handler', system: '/usr/share/applications/code-url-handler.desktop', user: path.join(userDir, 'code-url-handler.desktop') },
];
}
function addCodeFlag(command) {
if (command.includes(VSCODE_CERT_FLAG)) {
return command;
}
return command.replace(/^(Exec=\S+)/, `$1 ${VSCODE_CERT_FLAG}`);
if (!VSCODE_CERT_FLAG) return command;
return command.includes(VSCODE_CERT_FLAG) ? command : command.replace(/^(Exec=\S+)/, `$1 ${VSCODE_CERT_FLAG}`);
}
function launcherHasFlag(file) {
if (!fs.existsSync(file)) {
return false;
}
return fs.readFileSync(file, 'utf8').includes(VSCODE_CERT_FLAG);
if (!VSCODE_CERT_FLAG) return false;
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').includes(VSCODE_CERT_FLAG) : false;
}
function vscodeLaunchStatus() {
@@ -241,60 +217,39 @@ function vscodeLaunchStatus() {
const enabled = launcherHasFlag(launcher.user);
console.log(`${launcher.name}: ${launcher.user}`);
console.log(` user override: ${exists ? 'yes' : 'no'}`);
console.log(` ${VSCODE_CERT_FLAG}: ${enabled ? 'yes' : 'no'}`);
console.log(` flag present: ${enabled ? 'yes' : 'no'}`);
}
const userCode = path.join(os.homedir(), '.local', 'bin', 'code');
console.log(`terminal wrapper: ${userCode}`);
console.log(` installed: ${fs.existsSync(userCode) ? 'yes' : 'no'}`);
console.log(` ${VSCODE_CERT_FLAG}: ${launcherHasFlag(userCode) ? 'yes' : 'no'}`);
console.log(` flag present: ${launcherHasFlag(userCode) ? 'yes' : 'no'}`);
}
function userCodeWrapperPath() {
return path.join(os.homedir(), '.local', 'bin', 'code');
}
function userCodeWrapperPath() { return path.join(os.homedir(), '.local', 'bin', 'code'); }
function vscodeTerminalSetup() {
const file = userCodeWrapperPath();
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `#!/bin/sh\nexec /usr/bin/code ${VSCODE_CERT_FLAG} "$@"\n`);
const flagStr = VSCODE_CERT_FLAG ? ` ${VSCODE_CERT_FLAG}` : '';
fs.writeFileSync(file, `#!/bin/sh\nexec /usr/bin/code${flagStr} "$@"\n`);
fs.chmodSync(file, 0o755);
console.log(`Updated ${file}`);
}
function vscodeLaunchSetup() {
fs.mkdirSync(desktopApplicationsDir(), { recursive: true });
for (const launcher of desktopLauncherPaths()) {
if (!fs.existsSync(launcher.system)) {
console.error(`Missing system launcher: ${launcher.system}`);
process.exitCode = 1;
continue;
}
const content = fs.readFileSync(launcher.system, 'utf8')
.split('\n')
.map((line) => (line.startsWith('Exec=') ? addCodeFlag(line) : line))
.join('\n');
if (!fs.existsSync(launcher.system)) { console.error(`Missing system launcher: ${launcher.system}`); continue; }
const content = fs.readFileSync(launcher.system, 'utf8').split('\n').map((line) => (line.startsWith('Exec=') ? addCodeFlag(line) : line)).join('\n');
fs.writeFileSync(launcher.user, content);
console.log(`Updated ${launcher.user}`);
}
vscodeTerminalSetup();
}
async function vscodeStatus() {
const file = codeSettingsPath();
let settings;
try {
settings = readJsonFile(file);
} catch (error) {
console.error(`Could not parse ${file}: ${error.message}`);
process.exit(1);
}
let settings = readJsonFile(file);
console.log(`settings: ${file}`);
console.log(`http.proxy: ${settings['http.proxy'] || 'not set'}`);
console.log(`http.proxySupport: ${settings['http.proxySupport'] || 'not set'}`);
@@ -305,60 +260,29 @@ async function vscodeStatus() {
async function vscodeSetup() {
const file = codeSettingsPath();
let settings;
try {
settings = readJsonFile(file);
} catch (error) {
console.error(`Could not parse ${file}: ${error.message}`);
process.exit(1);
}
let settings = readJsonFile(file);
settings['http.proxy'] = LOCAL_PROXY_URL;
settings['http.proxySupport'] = 'override';
settings['http.proxyStrictSSL'] = false;
settings['http.systemCertificates'] = true;
writeJsonFile(file, settings);
console.log(`Updated ${file}`);
console.log(`http.proxy: ${LOCAL_PROXY_URL}`);
console.log('http.proxySupport: override');
console.log('http.proxyStrictSSL: false');
console.log('http.systemCertificates: true');
const listening = await checkPort();
console.log(`local proxy reachable: ${listening ? 'yes' : 'no'}`);
console.log(`Updated ${file}\nhttp.proxy: ${LOCAL_PROXY_URL}\nhttp.proxySupport: override\nhttp.proxyStrictSSL: false\nhttp.systemCertificates: true`);
console.log(`local proxy reachable: ${await checkPort() ? 'yes' : 'no'}`);
}
async function vscode(args) {
const action = args[0] || 'status';
switch (action) {
case 'status':
await vscodeStatus();
break;
case 'setup':
await vscodeSetup();
break;
case 'status': await vscodeStatus(); break;
case 'setup': await vscodeSetup(); break;
case 'launch':
switch (args[1] || 'status') {
case 'status':
vscodeLaunchStatus();
break;
case 'setup':
vscodeLaunchSetup();
break;
default:
console.error(`Unknown vscode launch action: ${args[1]}`);
console.error('Usage: proxy-bridge vscode launch [status|setup]');
process.exit(1);
case 'status': vscodeLaunchStatus(); break;
case 'setup': vscodeLaunchSetup(); break;
default: console.error('Usage: proxy-bridge vscode launch [status|setup]'); process.exit(1);
}
break;
default:
console.error(`Unknown vscode action: ${action}`);
console.error('Usage: proxy-bridge vscode [status|setup|launch]');
process.exit(1);
default: console.error('Usage: proxy-bridge vscode [status|setup|launch]'); process.exit(1);
}
}
@@ -375,7 +299,7 @@ Commands:
enable Enable the user service at login
disable Disable the user service at login
logs Follow service logs
setup Store or update keyring credentials
setup Store or update keyring credentials and upstream config
config Print effective non-secret configuration
vscode Manage VS Code proxy settings
help Show this help
@@ -390,17 +314,11 @@ VS Code:
async function main() {
const command = process.argv[2] || 'status';
switch (command) {
case 'status':
await status();
break;
case 'status': await status(); break;
case 'profile':
if (process.argv[3]) {
setProfile(process.argv[3]);
} else {
console.log(getProfile());
}
if (process.argv[3]) setProfile(process.argv[3]);
else console.log(getProfile());
break;
case 'toggle':
const current = getProfile();
@@ -412,34 +330,13 @@ async function main() {
case 'stop':
case 'restart':
case 'enable':
case 'disable':
control(command);
break;
case 'logs':
logs();
break;
case 'setup':
setup();
break;
case 'config':
config();
break;
case 'vscode':
await vscode(process.argv.slice(3));
break;
case 'help':
case '--help':
case '-h':
help();
break;
default:
console.error(`Unknown command: ${command}`);
help();
process.exit(1);
case 'disable': control(command); break;
case 'logs': logs(); break;
case 'setup': setup(); break;
case 'config': config(); break;
case 'vscode': await vscode(process.argv.slice(3)); break;
default: help(); process.exit(1);
}
}
main().catch((error) => {
console.error(error.message);
process.exit(1);
});
main().catch((error) => { console.error(error.message); process.exit(1); });
+60 -36
View File
@@ -17,7 +17,7 @@ function ask(question, defaultValue = "") {
return new Promise((resolve) => {
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
rl.question(prompt, (answer) => {
resolve(answer || defaultValue);
resolve(answer.trim() || defaultValue);
});
});
}
@@ -26,50 +26,74 @@ async function run() {
try {
// 1. Upstream Proxy Configuration
console.log("\n--- Upstream Proxy (Corporate) ---");
const host = await ask("Corporate Proxy Hostname (e.g. proxy.company.com)");
const port = await ask("Corporate Proxy Port", "8080");
const enableUpstream = (await ask("Enable Corporate Upstream Proxy? (y/n)", "y")).toLowerCase() === 'y';
let host = "";
let port = "8080";
let user = "";
let pass = "";
if (!host) {
console.error("❌ Hostname is required.");
process.exit(1);
if (enableUpstream) {
host = await ask("Corporate Proxy Hostname (e.g. proxy.company.com)");
port = await ask("Corporate Proxy Port", "8080");
if (!host) {
console.error("❌ Hostname is required if upstream is enabled.");
process.exit(1);
}
// 2. Credentials
console.log("\n--- Credentials ---");
user = await ask("Corporate Username");
// Setup password mask (temporary swap)
const oldWrite = rl._writeToOutput;
rl._writeToOutput = function _writeToOutput(stringToWrite) {
// If it's a newline or specific strings, let it through
if (stringToWrite === "\n" || stringToWrite === "\r" || stringToWrite === "\r\n") {
rl.output.write(stringToWrite);
} else {
rl.output.write("*");
}
};
pass = await ask("Corporate Password");
rl._writeToOutput = oldWrite;
}
// 2. Credentials
console.log("\n--- Credentials ---");
const user = await ask("Corporate Username");
// Setup password mask
const oldWrite = rl._writeToOutput;
rl._writeToOutput = function _writeToOutput(stringToWrite) {
if (rl.line.length > 0) rl.output.write("*");
else rl.output.write(stringToWrite);
};
const pass = await ask("Corporate Password");
rl._writeToOutput = oldWrite;
// Save Proxy Config
// Save Config
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ host, port }, null, 2));
fs.writeFileSync(CONFIG_FILE, JSON.stringify({
enabled: enableUpstream,
host: host,
port: port
}, null, 2));
// Save Username
fs.writeFileSync(USER_FILE, JSON.stringify({ username: user }));
if (enableUpstream) {
// Save Username
fs.writeFileSync(USER_FILE, JSON.stringify({ username: user }));
// Store Password in Keyring
console.log("\n--> Storing password in system keyring...");
execFileSync('secret-tool', [
'store',
'--label=Proxy Bridge Credentials',
'service',
'proxy-bridge',
'account',
user,
], {
input: pass
});
// Store Password in Keyring
console.log("\n--> Storing password in system keyring...");
try {
execFileSync('secret-tool', [
'store',
'--label=Proxy Bridge Credentials',
'service',
'proxy-bridge',
'account',
user,
], {
input: pass
});
} catch (e) {
console.warn("⚠️ Warning: Failed to store password in keyring. Ensure libsecret-tools is installed.");
}
}
console.log("\n✅ Configuration and credentials successfully stored.");
console.log("\n✅ Configuration successfully updated.");
} catch (error) {
console.error("\n❌ Setup failed:", error.message);
process.exit(1);
+1 -1
View File
@@ -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"
+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 "$@"