// --------------------------------------------------------------------------- // Outbound proxy support for the container backend scripts. // // Node's core http/https modules ignore proxy env vars, so requests that // should go through a forward proxy are tunneled manually via HTTP CONNECT. // Currently used for GitLab access (see GITLAB_PROXY below). // // Configure in .env / docker-compose.yml (no rebuild needed, container // restart is enough): // GITLAB_PROXY=http://127.0.0.1:8118 (updater run on the host) // GITLAB_PROXY=http://host.docker.internal:8118 (proxy on the docker host) // GITLAB_PROXY=http://10.0.0.5:8118 (proxy moved elsewhere) // Empty/unset disables proxying (direct connection). // --------------------------------------------------------------------------- const http = require("http"); const tls = require("tls"); const { URL } = require("url"); const GITLAB_PROXY = process.env.GITLAB_PROXY || ""; const GITLAB_HOST = (() => { try { return new URL(process.env.GITLAB_URL || "https://gitlab.ixsol.wien").hostname; } catch { return "gitlab.ixsol.wien"; } })(); // Returns the proxy URL to use for a given target URL, or null for direct. function proxyFor(url) { if (!GITLAB_PROXY) return null; try { if (new URL(url).hostname === GITLAB_HOST) return GITLAB_PROXY; } catch { // unparseable URL — no proxy } return null; } // Opens a CONNECT tunnel through the proxy and resolves to a socket usable // as the connection for a request to targetUrl: a TLS socket for https // targets, the raw tunnel socket otherwise. function connectThroughProxy(proxyUrl, targetUrl, timeoutMs, tlsOptions = {}) { return new Promise((resolve, reject) => { const target = new URL(targetUrl); const proxy = new URL(proxyUrl); const port = target.port || (target.protocol === "https:" ? "443" : "80"); const req = http.request({ host: proxy.hostname, port: proxy.port || 80, method: "CONNECT", path: `${target.hostname}:${port}`, headers: { Host: `${target.hostname}:${port}` }, timeout: timeoutMs || 10000, }); req.on("connect", (res, socket) => { if (res.statusCode !== 200) { socket.destroy(); reject(new Error(`Proxy CONNECT to ${target.hostname} failed: HTTP ${res.statusCode}`)); return; } resolve( target.protocol === "https:" ? tls.connect({ socket, servername: target.hostname, ...tlsOptions }) : socket ); }); req.on("timeout", () => { req.destroy(); reject(new Error(`Proxy CONNECT to ${target.hostname} timed out`)); }); req.on("error", reject); req.end(); }); } // Request options for client.request() that route the request through the // proxy: https via the CONNECT tunnel (caller passes the socket returned by // connectThroughProxy), plain http via an absolute-form request URI. function proxiedRequestOptions(proxyUrl, targetUrl, socket) { const u = new URL(targetUrl); if (u.protocol === "https:") { return { agent: false, createConnection: () => socket }; } const p = new URL(proxyUrl); return { host: p.hostname, port: p.port || 80, path: u.href, headers: { Host: u.host }, }; } module.exports = { proxyFor, connectThroughProxy, proxiedRequestOptions };