From e19fae8b7e2836d3144302f80d23cb38ac0c4bf9 Mon Sep 17 00:00:00 2001 From: Aleksandr Borovskii Date: Tue, 6 Oct 2026 16:12:26 +0200 Subject: [PATCH 1/5] fix(install): fall back to a GNU mirror for vcpkg asset downloads The Windows source build pulls some vcpkg dependencies (notably the gsasl port -> gsasl-.tar.gz) only from the GNU FTP network (ftpmirror.gnu.org / ftp.gnu.org). That network is intermittently unreachable; when it times out the native build fails with 'Download failed, halting portfile'. Set X_VCPKG_ASSET_SOURCES to an x-script asset provider that redirects GNU-FTP asset URLs to a reachable GNU mirror (mirrors.kernel.org, overridable via NODE_LIBCURL_GNU_MIRROR) and falls back to the original URL for every other asset. The value is appended to any X_VCPKG_ASSET_SOURCES the consumer already set, so their asset cache keeps precedence. vcpkg still verifies each asset's SHA512, so the mirror swap cannot substitute a different file. The helper uses only native Node.js modules and is a no-op on non-Windows (the source build only runs on Windows). --- scripts/vcpkg-fetch-asset.js | 108 +++++++++++++++++++++++++++++++++++ scripts/vcpkg-setup.js | 13 +++++ 2 files changed, 121 insertions(+) create mode 100644 scripts/vcpkg-fetch-asset.js diff --git a/scripts/vcpkg-fetch-asset.js b/scripts/vcpkg-fetch-asset.js new file mode 100644 index 00000000..30cffc67 --- /dev/null +++ b/scripts/vcpkg-fetch-asset.js @@ -0,0 +1,108 @@ +// this should not use any third party dependencies! Only native Node.js modules! +// +// vcpkg x-script asset fetcher. +// +// Why this exists: the Windows source build pulls some dependencies (e.g. +// vcpkg's `gsasl` port -> gsasl-.tar.gz) exclusively from the GNU FTP +// network (ftpmirror.gnu.org / ftp.gnu.org). That network is intermittently +// unreachable and, when it times out, the whole native build fails. The same +// files are mirrored verbatim across the GNU mirror network, so we redirect +// GNU-FTP asset URLs to a reachable mirror and fall back to the original URL +// for every other asset. vcpkg verifies the SHA512 of whatever is produced, +// so swapping to a mirror cannot substitute a different file. +// +// Wired in from vcpkg-setup.js via: +// X_VCPKG_ASSET_SOURCES="x-script,node {url} {sha512} {dst}" +// vcpkg substitutes {url}/{sha512}/{dst} when it invokes the command. + +const fs = require('fs') +const path = require('path') +const https = require('https') + +const [, , url, , dst] = process.argv + +if (!url || !dst) { + console.error('vcpkg-fetch-asset: usage: ') + process.exit(2) +} + +// A reachable GNU mirror that serves the identical files under the same +// /gnu/ layout. Overridable so consumers behind a proxy can point elsewhere. +const GNU_MIRROR = + process.env.NODE_LIBCURL_GNU_MIRROR || 'https://mirrors.kernel.org' +const DEAD_GNU_HOSTS = ['ftpmirror.gnu.org', 'ftp.gnu.org'] + +function mirrorFor(originalUrl) { + try { + const u = new URL(originalUrl) + if (DEAD_GNU_HOSTS.includes(u.hostname) && u.pathname.startsWith('/gnu/')) { + return `${GNU_MIRROR}${u.pathname}` + } + } catch { + // fall through to the original URL + } + + return null +} + +function download(fromUrl, toPath, redirects = 0) { + return new Promise((resolve, reject) => { + if (redirects > 5) { + return reject(new Error('too many redirects')) + } + + fs.mkdirSync(path.dirname(toPath), { recursive: true }) + + https + .get(fromUrl, (res) => { + if ( + res.statusCode >= 300 && + res.statusCode < 400 && + res.headers.location + ) { + res.resume() + const next = new URL(res.headers.location, fromUrl).toString() + + return resolve(download(next, toPath, redirects + 1)) + } + + if (res.statusCode !== 200) { + res.resume() + + return reject(new Error(`HTTP ${res.statusCode} for ${fromUrl}`)) + } + + const file = fs.createWriteStream(toPath) + res.pipe(file) + file.on('finish', () => file.close(() => resolve())) + file.on('error', reject) + }) + .on('error', reject) + }) +} + +async function fetchAsset() { + const candidates = [] + const mirror = mirrorFor(url) + if (mirror) { + candidates.push(mirror) + } + candidates.push(url) + + for (const candidate of candidates) { + try { + console.log(`vcpkg-fetch-asset: downloading ${candidate}`) + await download(candidate, dst) + + // success; vcpkg verifies the SHA512 of the result afterwards + return + } catch (error) { + console.error(`vcpkg-fetch-asset: ${candidate} failed: ${error.message}`) + } + } + + console.error(`vcpkg-fetch-asset: all sources failed for ${url}`) + process.exit(1) +} + +fetchAsset() diff --git a/scripts/vcpkg-setup.js b/scripts/vcpkg-setup.js index fc0cca98..123d48a9 100644 --- a/scripts/vcpkg-setup.js +++ b/scripts/vcpkg-setup.js @@ -16,9 +16,22 @@ const { const modulePackageJson = require('../package.json') +// Route vcpkg's asset downloads through our fetcher so that dependencies only +// published on the (intermittently unreachable) GNU FTP network — e.g. gsasl — +// fall back to a reachable GNU mirror. Appended to any asset source the +// consumer already configured, so their cache still takes precedence. vcpkg +// still verifies each asset's SHA512, so this cannot swap in a different file. +const assetScript = path.join(__dirname, 'vcpkg-fetch-asset.js') +const assetSource = `x-script,node "${assetScript}" {url} {sha512} {dst}` +const existingAssetSources = process.env.X_VCPKG_ASSET_SOURCES +const X_VCPKG_ASSET_SOURCES = existingAssetSources + ? `${existingAssetSources};${assetSource}` + : assetSource + const commonEnv = { ...process.env, VCPKG_DISABLE_METRICS: '1', + X_VCPKG_ASSET_SOURCES, } async function setupVcpkg() { From b215cae35d570e1af83646f68167c26c74727088 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:17:29 +0000 Subject: [PATCH 2/5] 5.1.5-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 476aa274..d1181403 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@brightsec/node-libcurl", - "version": "5.1.4", + "version": "5.1.5-0", "description": "The fastest http(s) client (and much more) for Node.js - Node.js bindings for libcurl", "keywords": [ "node-curl", From 053f51b5a27b0c4ab72f3ce08fc7595846786874 Mon Sep 17 00:00:00 2001 From: Aleksandr Borovskii Date: Tue, 6 Oct 2026 18:28:49 +0200 Subject: [PATCH 3/5] fix(install): invoke the asset fetcher via the absolute Node path vcpkg launches the x-script asset command with CreateProcessW (no shell) in its own build environment, where PATH is reset. A bare `node` program token therefore isn't found and the download fails with 'CreateProcessW failed with 2', so vcpkg falls back to the unreachable GNU-FTP origin and times out. Use process.execPath (the absolute path to the running Node) as the program token instead of `node`, keeping both the interpreter and script paths quoted for spaces. --- scripts/vcpkg-setup.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/vcpkg-setup.js b/scripts/vcpkg-setup.js index 123d48a9..3985bf3f 100644 --- a/scripts/vcpkg-setup.js +++ b/scripts/vcpkg-setup.js @@ -21,8 +21,13 @@ const modulePackageJson = require('../package.json') // fall back to a reachable GNU mirror. Appended to any asset source the // consumer already configured, so their cache still takes precedence. vcpkg // still verifies each asset's SHA512, so this cannot swap in a different file. +// +// vcpkg launches the x-script command with CreateProcessW (no shell) and runs +// it inside its own build environment where PATH is reset, so a bare `node` +// isn't found (CreateProcessW fails with 2). Use the absolute path to the +// current Node executable instead. Both paths are quoted for spaces. const assetScript = path.join(__dirname, 'vcpkg-fetch-asset.js') -const assetSource = `x-script,node "${assetScript}" {url} {sha512} {dst}` +const assetSource = `x-script,"${process.execPath}" "${assetScript}" {url} {sha512} {dst}` const existingAssetSources = process.env.X_VCPKG_ASSET_SOURCES const X_VCPKG_ASSET_SOURCES = existingAssetSources ? `${existingAssetSources};${assetSource}` From 56f99ea27688eb9ba6d82f0646bfb5786f975bfd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 17:23:08 +0000 Subject: [PATCH 4/5] 5.1.6-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1181403..bd3abb95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@brightsec/node-libcurl", - "version": "5.1.5-0", + "version": "5.1.6-0", "description": "The fastest http(s) client (and much more) for Node.js - Node.js bindings for libcurl", "keywords": [ "node-curl", From acd6bf08603b2f07d2205cd3a2291362773ed09b Mon Sep 17 00:00:00 2001 From: Alexander Borovsky Date: Tue, 6 Oct 2026 21:37:11 +0200 Subject: [PATCH 5/5] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bd3abb95..476aa274 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@brightsec/node-libcurl", - "version": "5.1.6-0", + "version": "5.1.4", "description": "The fastest http(s) client (and much more) for Node.js - Node.js bindings for libcurl", "keywords": [ "node-curl",