From a259e56fe997fa161a11cee70fddb6f8e8d2995f Mon Sep 17 00:00:00 2001 From: bittoby Date: Mon, 2 Feb 2026 14:36:50 +0100 Subject: [PATCH 1/5] fix: enable proxy support to resolve --- electron/main/utils/process.ts | 34 +++- resources/scripts/download.js | 279 ++++++++++++++++++++++++++++++--- 2 files changed, 286 insertions(+), 27 deletions(-) diff --git a/electron/main/utils/process.ts b/electron/main/utils/process.ts index c755ca5f8..9a37c5d06 100644 --- a/electron/main/utils/process.ts +++ b/electron/main/utils/process.ts @@ -18,6 +18,7 @@ import log from 'electron-log'; import fs from 'fs'; import os from 'os'; import path from 'path'; +import { maskProxyUrl, readGlobalEnvKey } from './envUtil'; export function getResourcePath() { return path.join(app.getAppPath(), 'resources'); @@ -33,6 +34,30 @@ export function getBackendPath() { } } +/** + * Get proxy environment variables from global ~/.eigent/.env config. + * Returns an object with HTTP_PROXY, HTTPS_PROXY, and lowercase variants + * if a proxy is configured, or an empty object if not. + */ +function getProxyEnvVars(): Record { + const proxyUrl = readGlobalEnvKey('HTTP_PROXY'); + if (!proxyUrl) { + return {}; + } + + log.info( + `[INSTALL SCRIPT] Proxy configured: ${maskProxyUrl(proxyUrl)}` + ); + + // Set all common proxy env var names for maximum compatibility + return { + HTTP_PROXY: proxyUrl, + HTTPS_PROXY: proxyUrl, + http_proxy: proxyUrl, + https_proxy: proxyUrl, + }; +} + export function runInstallScript(scriptPath: string): Promise { return new Promise((resolve, reject) => { const installScriptPath = path.join( @@ -42,8 +67,15 @@ export function runInstallScript(scriptPath: string): Promise { ); log.info(`Running script at: ${installScriptPath}`); + // Get proxy configuration from global .env file + const proxyEnv = getProxyEnvVars(); + const nodeProcess = spawn(process.execPath, [installScriptPath], { - env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, + env: { + ...process.env, + ...proxyEnv, + ELECTRON_RUN_AS_NODE: '1', + }, }); let stderrOutput = ''; diff --git a/resources/scripts/download.js b/resources/scripts/download.js index b527c6c8f..ba21c0fdc 100644 --- a/resources/scripts/download.js +++ b/resources/scripts/download.js @@ -12,13 +12,222 @@ // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= -/* global console, setTimeout, clearTimeout, require */ +/* global console, process */ // @ts-check import fs from 'fs'; +import http from 'http'; import https from 'https'; +import tls from 'tls'; +import { URL } from 'url'; /** - * Downloads a file from a URL with redirect handling + * Check if the target URL should bypass proxy based on NO_PROXY env var. + * @param {string} targetUrl - The target URL + * @returns {boolean} True if proxy should be bypassed + */ +function shouldBypassProxy(targetUrl) { + const noProxy = process.env.NO_PROXY || process.env.no_proxy; + if (!noProxy) return false; + + try { + const targetHost = new URL(targetUrl).hostname.toLowerCase(); + const noProxyList = noProxy.split(',').map((s) => s.trim().toLowerCase()); + + for (const pattern of noProxyList) { + if (!pattern) continue; + if (pattern === '*') return true; + if (targetHost === pattern) return true; + if (pattern.startsWith('.') && targetHost.endsWith(pattern)) return true; + if (targetHost.endsWith('.' + pattern)) return true; + } + } catch (error) { + console.warn(`Warning: Failed to parse NO_PROXY: ${error.message}`); + } + return false; +} + +/** + * Get proxy URL from environment variables. + * @param {string} targetUrl - The target URL to determine which proxy to use + * @returns {string | null} The proxy URL or null if not configured + */ +function getProxyUrl(targetUrl) { + // Check NO_PROXY first + if (shouldBypassProxy(targetUrl)) { + return null; + } + + const isHttps = targetUrl.startsWith('https://'); + + // Priority order for proxy env vars (check both uppercase and lowercase) + const envVars = isHttps + ? ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy'] + : ['HTTP_PROXY', 'http_proxy']; + + for (const envVar of envVars) { + const value = process.env[envVar]; + if (value && value.trim()) { + return value.trim(); + } + } + + return null; +} + +/** + * Mask credentials in a proxy URL for safe logging. + * @param {string} url - The URL to mask + * @returns {string} The masked URL + */ +function maskProxyUrl(url) { + try { + const parsed = new URL(url); + if (parsed.username || parsed.password) { + parsed.username = parsed.username ? '***' : ''; + parsed.password = parsed.password ? '***' : ''; + return parsed.toString(); + } + } catch (error) { + // Not a valid URL, return as-is + console.warn(`Warning: Failed to parse proxy URL: ${error.message}`); + } + return url; +} + +/** + * Make an HTTP GET request with optional proxy support. + * For HTTPS URLs through HTTP proxy, uses CONNECT tunnel with TLS. + * @param {string} url - The URL to request + * @param {(response: http.IncomingMessage) => void} callback - Response callback + * @param {(error: Error) => void} onError - Error callback + */ +function makeRequest(url, callback, onError) { + const proxyUrl = getProxyUrl(url); + const isHttps = url.startsWith('https://'); + const targetUrl = new URL(url); + const targetPort = parseInt(targetUrl.port, 10) || (isHttps ? 443 : 80); + + if (!proxyUrl) { + // Direct connection (no proxy) + const httpModule = isHttps ? https : http; + const req = httpModule.get(url, callback); + req.on('error', onError); + return; + } + + console.log(`Using proxy: ${maskProxyUrl(proxyUrl)}`); + + const proxy = new URL(proxyUrl); + const proxyPort = parseInt(proxy.port, 10) || 80; + + // Build proxy auth header if credentials provided + const proxyAuthHeader = + proxy.username || proxy.password + ? { + 'Proxy-Authorization': `Basic ${Buffer.from( + `${decodeURIComponent(proxy.username || '')}:${decodeURIComponent(proxy.password || '')}` + ).toString('base64')}`, + } + : {}; + + if (isHttps) { + // HTTPS through HTTP proxy: Use CONNECT tunnel + const connectReq = http.request({ + host: proxy.hostname, + port: proxyPort, + method: 'CONNECT', + path: `${targetUrl.hostname}:${targetPort}`, + headers: { + Host: `${targetUrl.hostname}:${targetPort}`, + ...proxyAuthHeader, + }, + }); + + connectReq.on('connect', (res, socket) => { + if (res.statusCode !== 200) { + socket.destroy(); + onError( + new Error( + `Proxy CONNECT failed with status ${res.statusCode}: ${res.statusMessage}` + ) + ); + return; + } + + // Upgrade socket to TLS + const tlsSocket = tls.connect( + { + host: targetUrl.hostname, + port: targetPort, + socket: socket, + servername: targetUrl.hostname, // SNI + }, + () => { + // Make HTTPS request over TLS socket + const req = https.request( + { + hostname: targetUrl.hostname, + port: targetPort, + path: targetUrl.pathname + targetUrl.search, + method: 'GET', + headers: { Host: targetUrl.host }, + agent: false, + // Use createConnection to provide the pre-established TLS socket + createConnection: () => tlsSocket, + }, + callback + ); + req.on('error', (err) => { + onError(new Error(`HTTPS request error: ${err.message}`)); + }); + req.end(); + } + ); + + tlsSocket.on('error', (err) => { + onError(new Error(`TLS connection error: ${err.message}`)); + }); + }); + + connectReq.on('error', (err) => { + onError(new Error(`Proxy connection error: ${err.message}`)); + }); + + connectReq.setTimeout(30000, () => { + connectReq.destroy(); + onError(new Error('Proxy connection timeout after 30 seconds')); + }); + + connectReq.end(); + } else { + // HTTP through HTTP proxy: Use proxy as target with full URL as path + const req = http.request( + { + host: proxy.hostname, + port: proxyPort, + path: url, // Full URL for HTTP proxy + method: 'GET', + headers: { + Host: targetUrl.host, + ...proxyAuthHeader, + }, + }, + callback + ); + req.on('error', (err) => { + onError(new Error(`HTTP proxy request error: ${err.message}`)); + }); + req.end(); + } +} + +/** + * Downloads a file from a URL with redirect handling and proxy support. + * Proxy is automatically detected from environment variables: + * - HTTPS_PROXY / https_proxy (for HTTPS URLs) + * - HTTP_PROXY / http_proxy (for HTTP URLs, or as fallback for HTTPS) + * - NO_PROXY / no_proxy (to bypass proxy for specific hosts) + * * @param {string} url The URL to download from * @param {string} destinationPath The path to save the file to * @returns {Promise} Promise that resolves when download is complete @@ -26,9 +235,7 @@ import https from 'https'; export async function downloadWithRedirects(url, destinationPath) { return new Promise((resolve, reject) => { const timeoutMs = 10 * 60 * 1000; // 10 minutes - const timeout = setTimeout(() => { - reject(new Error(`timeout(${timeoutMs / 1000} seconds)`)); - }, timeoutMs); + let timeoutId = null; // Use flag to prevent multiple resolve/reject calls let settled = false; @@ -36,7 +243,7 @@ export async function downloadWithRedirects(url, destinationPath) { const safeReject = (error) => { if (!settled) { settled = true; - clearTimeout(timeout); + if (timeoutId) clearTimeout(timeoutId); reject(error); } }; @@ -44,17 +251,19 @@ export async function downloadWithRedirects(url, destinationPath) { const safeResolve = () => { if (!settled) { settled = true; - clearTimeout(timeout); + if (timeoutId) clearTimeout(timeoutId); resolve(); } }; - const request = (url) => { - // Support both http and https - const httpModule = url.startsWith('https://') ? https : require('http'); + timeoutId = setTimeout(() => { + safeReject(new Error(`Download timeout after ${timeoutMs / 1000} seconds`)); + }, timeoutMs); - httpModule - .get(url, (response) => { + const request = (requestUrl) => { + makeRequest( + requestUrl, + (response) => { const statusCode = response.statusCode || 0; // Handle redirects (301, 302, 307, 308) @@ -63,15 +272,28 @@ export async function downloadWithRedirects(url, destinationPath) { statusCode <= 308 && response.headers.location ) { - const redirectUrl = response.headers.location; + let redirectUrl = response.headers.location; + + // Handle relative redirects + if (redirectUrl.startsWith('/')) { + try { + const originalUrl = new URL(requestUrl); + redirectUrl = `${originalUrl.protocol}//${originalUrl.host}${redirectUrl}`; + } catch (error) { + safeReject(new Error(`Failed to parse redirect URL: ${error.message}`)); + return; + } + } + console.log(`Following redirect to: ${redirectUrl}`); request(redirectUrl); return; } + if (statusCode !== 200) { safeReject( new Error( - `Download failed: ${statusCode} ${response.statusMessage || 'Unknown error'}` + `Download failed with status ${statusCode}: ${response.statusMessage || 'Unknown error'}` ) ); return; @@ -80,7 +302,8 @@ export async function downloadWithRedirects(url, destinationPath) { const file = fs.createWriteStream(destinationPath); let downloadedBytes = 0; const expectedBytes = parseInt( - response.headers['content-length'] || '0' + response.headers['content-length'] || '0', + 10 ); const startTime = Date.now(); let lastProgressTime = Date.now(); @@ -131,8 +354,10 @@ export async function downloadWithRedirects(url, destinationPath) { if (fs.existsSync(destinationPath)) { fs.unlinkSync(destinationPath); } - } catch (err) { - console.error('Failed to delete incomplete file:', err); + } catch (cleanupError) { + console.warn( + `Warning: Failed to delete incomplete file: ${cleanupError.message}` + ); } safeReject( new Error( @@ -155,9 +380,9 @@ export async function downloadWithRedirects(url, destinationPath) { } else { safeReject(new Error('Downloaded file does not exist')); } - } catch (err) { + } catch (verifyError) { safeReject( - new Error(`Failed to verify download: ${err.message}`) + new Error(`Failed to verify download: ${verifyError.message}`) ); } }); @@ -168,16 +393,18 @@ export async function downloadWithRedirects(url, destinationPath) { if (fs.existsSync(destinationPath)) { fs.unlinkSync(destinationPath); } - } catch (deleteErr) { - console.error('Failed to delete file after error:', deleteErr); + } catch (cleanupError) { + console.warn( + `Warning: Failed to delete file after error: ${cleanupError.message}` + ); } - safeReject(err); + safeReject(new Error(`File write error: ${err.message}`)); }); - }) - .on('error', (err) => { - safeReject(err); - }); + }, + safeReject + ); }; + request(url); }); } From 6bd6f437675c696a556cfea4736b36fa146da730 Mon Sep 17 00:00:00 2001 From: bittoby Date: Fri, 6 Feb 2026 12:18:39 +0100 Subject: [PATCH 2/5] fix: add connection cleanup for proxy tunnels and support separate HTTP/HTTPS proxy configurations --- electron/main/utils/process.ts | 35 +++++++++++++++++++---------- resources/scripts/download.js | 40 +++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/electron/main/utils/process.ts b/electron/main/utils/process.ts index 79c12a52a..924391410 100644 --- a/electron/main/utils/process.ts +++ b/electron/main/utils/process.ts @@ -38,24 +38,35 @@ export function getBackendPath() { * Get proxy environment variables from global ~/.eigent/.env config. * Returns an object with HTTP_PROXY, HTTPS_PROXY, and lowercase variants * if a proxy is configured, or an empty object if not. + * Supports separate HTTP and HTTPS proxy configurations. */ function getProxyEnvVars(): Record { - const proxyUrl = readGlobalEnvKey('HTTP_PROXY'); - if (!proxyUrl) { + const httpProxy = readGlobalEnvKey('HTTP_PROXY'); + const httpsProxy = readGlobalEnvKey('HTTPS_PROXY'); + + if (!httpProxy && !httpsProxy) { return {}; } - log.info( - `[INSTALL SCRIPT] Proxy configured: ${maskProxyUrl(proxyUrl)}` - ); + const result: Record = {}; - // Set all common proxy env var names for maximum compatibility - return { - HTTP_PROXY: proxyUrl, - HTTPS_PROXY: proxyUrl, - http_proxy: proxyUrl, - https_proxy: proxyUrl, - }; + if (httpProxy) { + result.HTTP_PROXY = httpProxy; + result.http_proxy = httpProxy; + log.info( + `[INSTALL SCRIPT] HTTP Proxy configured: ${maskProxyUrl(httpProxy)}` + ); + } + + if (httpsProxy) { + result.HTTPS_PROXY = httpsProxy; + result.https_proxy = httpsProxy; + log.info( + `[INSTALL SCRIPT] HTTPS Proxy configured: ${maskProxyUrl(httpsProxy)}` + ); + } + + return result; } export function runInstallScript(scriptPath: string): Promise { diff --git a/resources/scripts/download.js b/resources/scripts/download.js index ba21c0fdc..5b61b1e5c 100644 --- a/resources/scripts/download.js +++ b/resources/scripts/download.js @@ -143,9 +143,27 @@ function makeRequest(url, callback, onError) { }, }); + // Track resources for cleanup + let tlsSocket = null; + let httpsReq = null; + + // Cleanup function to destroy all connections + const cleanup = () => { + if (httpsReq && !httpsReq.destroyed) { + httpsReq.destroy(); + } + if (tlsSocket && !tlsSocket.destroyed) { + tlsSocket.destroy(); + } + if (connectReq && !connectReq.destroyed) { + connectReq.destroy(); + } + }; + connectReq.on('connect', (res, socket) => { if (res.statusCode !== 200) { socket.destroy(); + cleanup(); onError( new Error( `Proxy CONNECT failed with status ${res.statusCode}: ${res.statusMessage}` @@ -155,7 +173,7 @@ function makeRequest(url, callback, onError) { } // Upgrade socket to TLS - const tlsSocket = tls.connect( + tlsSocket = tls.connect( { host: targetUrl.hostname, port: targetPort, @@ -164,7 +182,7 @@ function makeRequest(url, callback, onError) { }, () => { // Make HTTPS request over TLS socket - const req = https.request( + httpsReq = https.request( { hostname: targetUrl.hostname, port: targetPort, @@ -175,26 +193,36 @@ function makeRequest(url, callback, onError) { // Use createConnection to provide the pre-established TLS socket createConnection: () => tlsSocket, }, - callback + (response) => { + // Cleanup connections when response ends + response.on('end', cleanup); + response.on('close', cleanup); + callback(response); + } ); - req.on('error', (err) => { + + httpsReq.on('error', (err) => { + cleanup(); onError(new Error(`HTTPS request error: ${err.message}`)); }); - req.end(); + + httpsReq.end(); } ); tlsSocket.on('error', (err) => { + cleanup(); onError(new Error(`TLS connection error: ${err.message}`)); }); }); connectReq.on('error', (err) => { + cleanup(); onError(new Error(`Proxy connection error: ${err.message}`)); }); connectReq.setTimeout(30000, () => { - connectReq.destroy(); + cleanup(); onError(new Error('Proxy connection timeout after 30 seconds')); }); From 777dbdc5d7f8ee93ecf7be44c7a48609f5d3d48a Mon Sep 17 00:00:00 2001 From: bittoby Date: Tue, 10 Feb 2026 04:05:18 +0000 Subject: [PATCH 3/5] feat: enhance proxy configuration with multi-source priority system and HTTPS_PROXY support --- electron/main/index.ts | 21 +++++++++-- electron/main/utils/envUtil.ts | 66 ++++++++++++++++++++++++++++++++++ electron/main/utils/process.ts | 35 ++++++++++++------ 3 files changed, 110 insertions(+), 12 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 3cf4facb2..8eaded39b 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -52,6 +52,7 @@ import { getEmailFolderPath, getEnvPath, maskProxyUrl, + readEnvValue, readGlobalEnvKey, removeEnvKey, updateEnvBlock, @@ -134,11 +135,27 @@ app.commandLine.appendSwitch('enable-features', 'MemoryPressureReduction'); app.commandLine.appendSwitch('renderer-process-limit', '8'); // ==================== Proxy configuration ==================== -// Read proxy from global .env file on startup -proxyUrl = readGlobalEnvKey('HTTP_PROXY'); +// Read proxy from multiple sources with priority: +// 1. Process environment (inline: SET HTTP_PROXY=... && eigent.exe) +// 2. .env.development (development mode) +// 3. Global ~/.eigent/.env file +// Check both HTTP_PROXY and HTTPS_PROXY (with lowercase variants) +const httpProxy = readEnvValue('HTTP_PROXY') || readEnvValue('http_proxy'); +const httpsProxy = readEnvValue('HTTPS_PROXY') || readEnvValue('https_proxy'); + +// Prefer HTTPS proxy if available, fallback to HTTP proxy +proxyUrl = httpsProxy || httpProxy; + if (proxyUrl) { log.info(`[PROXY] Applying proxy configuration: ${maskProxyUrl(proxyUrl)}`); app.commandLine.appendSwitch('proxy-server', proxyUrl); + + // Log which proxy type is being used + if (httpsProxy) { + log.info('[PROXY] Using HTTPS_PROXY configuration'); + } else if (httpProxy) { + log.info('[PROXY] Using HTTP_PROXY configuration'); + } } else { log.info('[PROXY] No proxy configured'); } diff --git a/electron/main/utils/envUtil.ts b/electron/main/utils/envUtil.ts index 565c8e017..198a324e3 100644 --- a/electron/main/utils/envUtil.ts +++ b/electron/main/utils/envUtil.ts @@ -116,6 +116,72 @@ export function readGlobalEnvKey(key: string): string | null { return null; } +/** + * Read environment variable value with priority system. + * + * Priority order (highest to lowest): + * 1. Process environment variables (inline/system) + * 2. .env.development file (development mode only) + * 3. Global ~/.eigent/.env file + * + * This allows flexible configuration via: + * - Command line: SET HTTP_PROXY=... && eigent.exe (Windows) + * - Command line: export HTTP_PROXY=... && ./eigent (macOS/Linux) + * - Development: .env.development file in project root + * - User config: ~/.eigent/.env file + * + * @param key - The environment variable key to read + * @returns The value if found, null otherwise + * + * @example + * // Read HTTP_PROXY from any source + * const proxy = readEnvValue('HTTP_PROXY'); + * if (proxy) { + * console.log('Proxy configured:', proxy); + * } + */ +export function readEnvValue(key: string): string | null { + // Priority 1: Process environment variables (highest priority) + // This allows inline configuration: SET HTTP_PROXY=... && eigent.exe + if (process.env[key]) { + return process.env[key]!; + } + + // Priority 2: .env.development file (development mode only) + // Only active when NODE_ENV=development + if (process.env.NODE_ENV === 'development') { + try { + const devEnvPath = path.join(process.cwd(), '.env.development'); + if (fs.existsSync(devEnvPath)) { + const content = fs.readFileSync(devEnvPath, 'utf-8'); + const prefix = key + '='; + + for (const line of content.split(/\r?\n/)) { + if (line.startsWith(prefix)) { + let value = line.slice(prefix.length).trim(); + + // Strip surrounding quotes (single or double) + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + + return value; + } + } + } + } catch (error) { + // Silently ignore read errors and fall through to next priority + } + } + + // Priority 3: Global ~/.eigent/.env file (lowest priority) + // Persistent user configuration + return readGlobalEnvKey(key); +} + /** * Mask credentials in a proxy URL for safe logging. * e.g. "http://user:pass@host:port" → "http://***:***@host:port" diff --git a/electron/main/utils/process.ts b/electron/main/utils/process.ts index 924391410..f4f1c857a 100644 --- a/electron/main/utils/process.ts +++ b/electron/main/utils/process.ts @@ -18,7 +18,7 @@ import log from 'electron-log'; import fs from 'fs'; import os from 'os'; import path from 'path'; -import { maskProxyUrl, readGlobalEnvKey } from './envUtil'; +import { maskProxyUrl, readEnvValue } from './envUtil'; export function getResourcePath() { return path.join(app.getAppPath(), 'resources'); @@ -35,35 +35,50 @@ export function getBackendPath() { } /** - * Get proxy environment variables from global ~/.eigent/.env config. + * Get proxy environment variables with priority: + * 1. Process environment variables (inline/system) + * 2. .env.development file (development mode only) + * 3. Global ~/.eigent/.env config + * * Returns an object with HTTP_PROXY, HTTPS_PROXY, and lowercase variants * if a proxy is configured, or an empty object if not. * Supports separate HTTP and HTTPS proxy configurations. */ function getProxyEnvVars(): Record { - const httpProxy = readGlobalEnvKey('HTTP_PROXY'); - const httpsProxy = readGlobalEnvKey('HTTPS_PROXY'); + // Check both uppercase and lowercase variants + const httpProxy = readEnvValue('HTTP_PROXY') || readEnvValue('http_proxy'); + const httpsProxy = readEnvValue('HTTPS_PROXY') || readEnvValue('https_proxy'); + + // Return empty object if no proxy configured if (!httpProxy && !httpsProxy) { return {}; } + // Log configured proxies + if (httpProxy) { + log.info( + `[INSTALL SCRIPT] HTTP Proxy configured: ${maskProxyUrl(httpProxy)}` + ); + } + if (httpsProxy) { + log.info( + `[INSTALL SCRIPT] HTTPS Proxy configured: ${maskProxyUrl(httpsProxy)}` + ); + } + + // Return all variants (some tools need uppercase, others lowercase) + // Filter out undefined values const result: Record = {}; if (httpProxy) { result.HTTP_PROXY = httpProxy; result.http_proxy = httpProxy; - log.info( - `[INSTALL SCRIPT] HTTP Proxy configured: ${maskProxyUrl(httpProxy)}` - ); } if (httpsProxy) { result.HTTPS_PROXY = httpsProxy; result.https_proxy = httpsProxy; - log.info( - `[INSTALL SCRIPT] HTTPS Proxy configured: ${maskProxyUrl(httpsProxy)}` - ); } return result; From 209df004c9973406ece4b466d37a7fd9d123e92d Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Tue, 10 Feb 2026 19:10:28 +0300 Subject: [PATCH 4/5] enhance: reuse readEnvValue to make readEnvValueWithPriority --- electron/main/index.ts | 36 +++++++++++++++++++++---- electron/main/init.ts | 33 ++++++++++++----------- electron/main/utils/envUtil.ts | 49 +++++----------------------------- electron/main/utils/process.ts | 21 ++++++++++++--- 4 files changed, 72 insertions(+), 67 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 8eaded39b..4581eda90 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -52,7 +52,7 @@ import { getEmailFolderPath, getEnvPath, maskProxyUrl, - readEnvValue, + readEnvValueWithPriority, readGlobalEnvKey, removeEnvKey, updateEnvBlock, @@ -140,8 +140,12 @@ app.commandLine.appendSwitch('renderer-process-limit', '8'); // 2. .env.development (development mode) // 3. Global ~/.eigent/.env file // Check both HTTP_PROXY and HTTPS_PROXY (with lowercase variants) -const httpProxy = readEnvValue('HTTP_PROXY') || readEnvValue('http_proxy'); -const httpsProxy = readEnvValue('HTTPS_PROXY') || readEnvValue('https_proxy'); +const httpProxy = + readEnvValueWithPriority('HTTP_PROXY') || + readEnvValueWithPriority('http_proxy'); +const httpsProxy = + readEnvValueWithPriority('HTTPS_PROXY') || + readEnvValueWithPriority('https_proxy'); // Prefer HTTPS proxy if available, fallback to HTTP proxy proxyUrl = httpsProxy || httpProxy; @@ -1172,17 +1176,39 @@ function registerIpcHandlers() { log.error('global env-remove error:', error); } + // Also remove from .env.development file (remove from anywhere in file, not just MCP block) + const DEV_ENV_PATH = path.join(process.cwd(), '.env.development'); + try { + if (fs.existsSync(DEV_ENV_PATH)) { + let devContent = fs.readFileSync(DEV_ENV_PATH, 'utf-8'); + let devLines = devContent.split(/\r?\n/); + // Remove key from anywhere in the file (not limited to MCP block) + devLines = devLines.filter((line) => !line.trim().startsWith(key + '=')); + fs.writeFileSync(DEV_ENV_PATH, devLines.join('\n'), 'utf-8'); + log.info(`env-remove: removed ${key} from .env.development`); + } + } catch (error) { + log.error('.env.development env-remove error:', error); + } + return { success: true }; }); // ==================== read global env handler ==================== - const ALLOWED_GLOBAL_ENV_KEYS = new Set(['HTTP_PROXY', 'HTTPS_PROXY']); + const ALLOWED_GLOBAL_ENV_KEYS = new Set([ + 'HTTP_PROXY', + 'HTTPS_PROXY', + 'NO_PROXY', + 'http_proxy', + 'https_proxy', + 'no_proxy', + ]); ipcMain.handle('read-global-env', async (_event, key: string) => { if (!ALLOWED_GLOBAL_ENV_KEYS.has(key)) { log.warn(`[ENV] Blocked read of disallowed global env key: ${key}`); return { value: null }; } - return { value: readGlobalEnvKey(key) }; + return { value: readEnvValueWithPriority(key) }; }); // ==================== new window handler ==================== diff --git a/electron/main/init.ts b/electron/main/init.ts index c7abca395..e8f209f71 100644 --- a/electron/main/init.ts +++ b/electron/main/init.ts @@ -22,7 +22,7 @@ import os from 'os'; import path from 'path'; import { promisify } from 'util'; import { PromiseReturnType } from './install-deps'; -import { maskProxyUrl, readGlobalEnvKey } from './utils/envUtil'; +import { maskProxyUrl, readEnvValueWithPriority } from './utils/envUtil'; import { getBackendPath, getBinaryPath, @@ -40,7 +40,10 @@ const execAsync = promisify(exec); const DEFAULT_SERVER_URL = 'https://dev.eigent.ai/api'; -function readEnvValue(filePath: string, key: string): string | undefined { +export function readEnvValue( + filePath: string, + key: string +): string | undefined { try { if (!fs.existsSync(filePath)) return undefined; const content = fs.readFileSync(filePath, 'utf-8'); @@ -234,22 +237,22 @@ export async function startBackend( const uvEnv = getUvEnv(currentVersion); const globalEnvPath = path.join(os.homedir(), '.eigent', '.env'); - // Load proxy configuration from global .env file - const proxyUrl = readGlobalEnvKey('HTTP_PROXY'); - // Build proxy env vars if configured - const proxyEnv = proxyUrl - ? { - HTTP_PROXY: proxyUrl, - HTTPS_PROXY: proxyUrl, - http_proxy: proxyUrl, - https_proxy: proxyUrl, - } - : {}; + const proxyEnv = { + HTTP_PROXY: readEnvValueWithPriority('HTTP_PROXY'), + HTTPS_PROXY: readEnvValueWithPriority('HTTPS_PROXY'), + http_proxy: readEnvValueWithPriority('http_proxy'), + https_proxy: readEnvValueWithPriority('https_proxy'), + // Ensure local connections bypass proxy + NO_PROXY: + readEnvValueWithPriority('NO_PROXY') || 'localhost,127.0.0.1,.local', + no_proxy: + readEnvValueWithPriority('no_proxy') || 'localhost,127.0.0.1,.local', + }; - if (proxyUrl) { + if (proxyEnv.HTTP_PROXY || proxyEnv.HTTPS_PROXY) { log.info( - `[BACKEND] Proxy configured for backend: ${maskProxyUrl(proxyUrl)}` + `[BACKEND] Proxy configured for backend: ${maskProxyUrl((proxyEnv.HTTP_PROXY || proxyEnv.HTTPS_PROXY) as string)}` ); } diff --git a/electron/main/utils/envUtil.ts b/electron/main/utils/envUtil.ts index 198a324e3..c7e89b593 100644 --- a/electron/main/utils/envUtil.ts +++ b/electron/main/utils/envUtil.ts @@ -15,6 +15,7 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; +import { readEnvValue } from '../init'; export const ENV_START = '# === MCP INTEGRATION ENV START ==='; export const ENV_END = '# === MCP INTEGRATION ENV END ==='; @@ -124,61 +125,23 @@ export function readGlobalEnvKey(key: string): string | null { * 2. .env.development file (development mode only) * 3. Global ~/.eigent/.env file * - * This allows flexible configuration via: - * - Command line: SET HTTP_PROXY=... && eigent.exe (Windows) - * - Command line: export HTTP_PROXY=... && ./eigent (macOS/Linux) - * - Development: .env.development file in project root - * - User config: ~/.eigent/.env file - * * @param key - The environment variable key to read * @returns The value if found, null otherwise - * - * @example - * // Read HTTP_PROXY from any source - * const proxy = readEnvValue('HTTP_PROXY'); - * if (proxy) { - * console.log('Proxy configured:', proxy); - * } */ -export function readEnvValue(key: string): string | null { +export function readEnvValueWithPriority(key: string): string | null { // Priority 1: Process environment variables (highest priority) - // This allows inline configuration: SET HTTP_PROXY=... && eigent.exe if (process.env[key]) { return process.env[key]!; } // Priority 2: .env.development file (development mode only) - // Only active when NODE_ENV=development if (process.env.NODE_ENV === 'development') { - try { - const devEnvPath = path.join(process.cwd(), '.env.development'); - if (fs.existsSync(devEnvPath)) { - const content = fs.readFileSync(devEnvPath, 'utf-8'); - const prefix = key + '='; - - for (const line of content.split(/\r?\n/)) { - if (line.startsWith(prefix)) { - let value = line.slice(prefix.length).trim(); - - // Strip surrounding quotes (single or double) - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } - - return value; - } - } - } - } catch (error) { - // Silently ignore read errors and fall through to next priority - } + const devEnvPath = path.join(process.cwd(), '.env.development'); + const value = readEnvValue(devEnvPath, key); + if (value) return value; } - // Priority 3: Global ~/.eigent/.env file (lowest priority) - // Persistent user configuration + // Priority 3: Global ~/.eigent/.env file return readGlobalEnvKey(key); } diff --git a/electron/main/utils/process.ts b/electron/main/utils/process.ts index f4f1c857a..c795b3636 100644 --- a/electron/main/utils/process.ts +++ b/electron/main/utils/process.ts @@ -18,7 +18,7 @@ import log from 'electron-log'; import fs from 'fs'; import os from 'os'; import path from 'path'; -import { maskProxyUrl, readEnvValue } from './envUtil'; +import { maskProxyUrl, readEnvValueWithPriority } from './envUtil'; export function getResourcePath() { return path.join(app.getAppPath(), 'resources'); @@ -40,15 +40,19 @@ export function getBackendPath() { * 2. .env.development file (development mode only) * 3. Global ~/.eigent/.env config * - * Returns an object with HTTP_PROXY, HTTPS_PROXY, and lowercase variants + * Returns an object with HTTP_PROXY, HTTPS_PROXY, NO_PROXY and lowercase variants * if a proxy is configured, or an empty object if not. * Supports separate HTTP and HTTPS proxy configurations. */ function getProxyEnvVars(): Record { // Check both uppercase and lowercase variants - const httpProxy = readEnvValue('HTTP_PROXY') || readEnvValue('http_proxy'); + const httpProxy = + readEnvValueWithPriority('HTTP_PROXY') || + readEnvValueWithPriority('http_proxy'); - const httpsProxy = readEnvValue('HTTPS_PROXY') || readEnvValue('https_proxy'); + const httpsProxy = + readEnvValueWithPriority('HTTPS_PROXY') || + readEnvValueWithPriority('https_proxy'); // Return empty object if no proxy configured if (!httpProxy && !httpsProxy) { @@ -67,6 +71,11 @@ function getProxyEnvVars(): Record { ); } + // Get NO_PROXY configuration (with default for local connections) + const noProxy = readEnvValueWithPriority('NO_PROXY') || + readEnvValueWithPriority('no_proxy') || + 'localhost,127.0.0.1,.local'; + // Return all variants (some tools need uppercase, others lowercase) // Filter out undefined values const result: Record = {}; @@ -81,6 +90,10 @@ function getProxyEnvVars(): Record { result.https_proxy = httpsProxy; } + // Always set NO_PROXY when proxy is configured to avoid issues with local connections + result.NO_PROXY = noProxy; + result.no_proxy = noProxy; + return result; } From 3281eecb737a9deefbe263ca7854d7bb14c8019c Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Tue, 10 Feb 2026 19:19:43 +0300 Subject: [PATCH 5/5] chore: update to local dev --- .env.development | 5 ++++ src/pages/Setting/General.tsx | 52 +++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/.env.development b/.env.development index 81b3be797..2614ca2cc 100644 --- a/.env.development +++ b/.env.development @@ -11,3 +11,8 @@ VITE_USE_LOCAL_PROXY=false VITE_STACK_PROJECT_ID=dummy_project_id VITE_STACK_PUBLISHABLE_CLIENT_KEY=dummy_publishable_key VITE_STACK_SECRET_SERVER_KEY=dummy_secret_server_key + +# HTTP proxy settings for local development +# HTTP_PROXY=http://127.0.0.1:9090 +# HTTPS_PROXY=https://127.0.0.1:9090 +# NO_PROXY=localhost,127.0.0.1 \ No newline at end of file diff --git a/src/pages/Setting/General.tsx b/src/pages/Setting/General.tsx index 435133d41..02cd199c5 100644 --- a/src/pages/Setting/General.tsx +++ b/src/pages/Setting/General.tsx @@ -77,6 +77,7 @@ export default function SettingGeneral() { // Proxy configuration state const [proxyUrl, setProxyUrl] = useState(''); + const [loadedProxyUrl, setLoadedProxyUrl] = useState(''); // Track the initially loaded value const [isProxySaving, setIsProxySaving] = useState(false); const [proxyNeedsRestart, setProxyNeedsRestart] = useState(false); @@ -158,16 +159,20 @@ export default function SettingGeneral() { ]; useEffect(() => { - // Load proxy configuration from global env + // Load proxy configuration from env (with priority system) const loadProxyConfig = async () => { if (window.electronAPI?.readGlobalEnv) { try { - const result = await window.electronAPI.readGlobalEnv('HTTP_PROXY'); - if (result?.value) { - setProxyUrl(result.value); - } + const result = + (await window.electronAPI.readGlobalEnv('HTTPS_PROXY')) || + (await window.electronAPI.readGlobalEnv('HTTP_PROXY')); + const value = result?.value || ''; + setProxyUrl(value); + setLoadedProxyUrl(value); // Remember the loaded value } catch (_error) { console.log('No proxy configured'); + setProxyUrl(''); + setLoadedProxyUrl(''); } } }; @@ -229,6 +234,9 @@ export default function SettingGeneral() { } }; + // Check if proxy value has changed from loaded value + const hasProxyChanged = proxyUrl.trim() !== loadedProxyUrl.trim(); + if (!chatStore) { return
Loading...
; } @@ -372,22 +380,24 @@ export default function SettingGeneral() { size="default" note={proxyNeedsRestart ? t('setting.proxy-restart-hint') : undefined} trailingButton={ - + proxyNeedsRestart ? ( + + ) : hasProxyChanged ? ( + + ) : undefined } />