@vantaloom/runtime-win32-x64 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/bin/Packet.dll +0 -0
- package/bin/WinDivert64.sys +0 -0
- package/bin/easytier-cli.exe +0 -0
- package/bin/easytier-core.exe +0 -0
- package/bin/vantaloom-agent.exe +0 -0
- package/bin/vantaloom-api.exe +0 -0
- package/bin/vantaloom-mesh.exe +0 -0
- package/bin/vantaloom-tray.exe +0 -0
- package/bin/vantaloomctl.exe +0 -0
- package/bin/wintun.dll +0 -0
- package/cli/package.json +1 -1
- package/cli/src/cli.mjs +764 -37
- package/manifest.json +2 -2
- package/package.json +1 -1
- package/web/404.html +1 -1
- package/web/__next.__PAGE__.txt +2 -2
- package/web/__next._full.txt +3 -3
- package/web/__next._head.txt +1 -1
- package/web/__next._index.txt +2 -2
- package/web/__next._tree.txt +2 -2
- package/web/_next/static/chunks/73d2c788f1076e9a.css +2 -0
- package/web/_next/static/chunks/80b4a364458ec11d.js +52 -0
- package/web/_not-found/__next._full.txt +2 -2
- package/web/_not-found/__next._head.txt +1 -1
- package/web/_not-found/__next._index.txt +2 -2
- package/web/_not-found/__next._not-found/__PAGE__.txt +1 -1
- package/web/_not-found/__next._not-found.txt +1 -1
- package/web/_not-found/__next._tree.txt +2 -2
- package/web/_not-found.html +1 -1
- package/web/_not-found.txt +2 -2
- package/web/index.html +1 -1
- package/web/index.txt +3 -3
- package/web/_next/static/chunks/142a2ac944b17c0d.js +0 -52
- package/web/_next/static/chunks/afcc024bee6626a3.css +0 -2
- /package/web/_next/static/{NAZHZ_h1mU5v6lVYZ5tUw → 1nS86NCCueT0DPdtcRvFo}/_buildManifest.js +0 -0
- /package/web/_next/static/{NAZHZ_h1mU5v6lVYZ5tUw → 1nS86NCCueT0DPdtcRvFo}/_clientMiddlewareManifest.json +0 -0
- /package/web/_next/static/{NAZHZ_h1mU5v6lVYZ5tUw → 1nS86NCCueT0DPdtcRvFo}/_ssgManifest.js +0 -0
package/cli/src/cli.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from "node:child_process"
|
|
2
2
|
import {
|
|
3
|
+
appendFileSync,
|
|
3
4
|
chmodSync,
|
|
4
5
|
existsSync,
|
|
5
6
|
mkdirSync,
|
|
@@ -10,21 +11,61 @@ import {
|
|
|
10
11
|
writeFileSync,
|
|
11
12
|
} from "node:fs"
|
|
12
13
|
import { cp, writeFile } from "node:fs/promises"
|
|
14
|
+
import { createHash } from "node:crypto"
|
|
13
15
|
import os from "node:os"
|
|
14
16
|
import path from "node:path"
|
|
15
17
|
import { fileURLToPath } from "node:url"
|
|
16
18
|
|
|
19
|
+
// The mesh binaries the privileged service holds open while running. On Windows
|
|
20
|
+
// a file copy can't overwrite them in place, so the lock-safe `apply` path swaps
|
|
21
|
+
// them after stopping the service.
|
|
22
|
+
const WINDOWS_MESH_BINARIES = ["easytier-core.exe", "easytier-cli.exe", "wintun.dll", "Packet.dll", "WinDivert64.sys", "vantaloom-mesh.exe"]
|
|
23
|
+
|
|
17
24
|
const cliRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..")
|
|
18
25
|
const repoCandidate = path.resolve(cliRoot, "..", "..")
|
|
19
26
|
const installedConfigPath = path.join(cliRoot, "config.json")
|
|
20
27
|
const defaultReleaseTag = "runtime-latest"
|
|
21
28
|
const defaultRepo = "Timefiles404/Vantaloom-next"
|
|
22
29
|
const defaultNpmRegistry = "https://registry.npmjs.org"
|
|
30
|
+
const fallbackNpmRegistries = [
|
|
31
|
+
"https://registry.npmmirror.com",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// Inherit strict-ssl=false from npm/npx config, or respect NODE_TLS_REJECT_UNAUTHORIZED.
|
|
35
|
+
// When npm runs us via npx with strict-ssl disabled, it sets npm_config_strict_ssl="false".
|
|
36
|
+
// Also check user .npmrc for strict-ssl=false (common in China behind proxies).
|
|
37
|
+
function shouldDisableTLS() {
|
|
38
|
+
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0") return true
|
|
39
|
+
if (process.env.npm_config_strict_ssl === "false") return true
|
|
40
|
+
if (process.env.npm_config_strict_ssl === "") return true
|
|
41
|
+
try {
|
|
42
|
+
const npmrcPath = path.join(os.homedir(), ".npmrc")
|
|
43
|
+
if (existsSync(npmrcPath)) {
|
|
44
|
+
const content = readFileSync(npmrcPath, "utf8")
|
|
45
|
+
if (/^\s*strict-ssl\s*=\s*false/m.test(content)) return true
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
if (shouldDisableTLS()) {
|
|
51
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const cliVersion = readJSONIfExists(path.join(cliRoot, "package.json")).version ?? "unknown"
|
|
23
55
|
|
|
24
56
|
export async function main(argv) {
|
|
25
57
|
const command = argv[0] ?? "help"
|
|
26
58
|
const options = parseOptions(argv.slice(1))
|
|
27
59
|
|
|
60
|
+
// --no-strict-ssl flag disables TLS certificate verification for fetch calls
|
|
61
|
+
if (options.noStrictSsl) {
|
|
62
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (command === "install" || command === "update") {
|
|
66
|
+
console.log(`vantaloom-cli v${cliVersion} (${platformId()})`)
|
|
67
|
+
}
|
|
68
|
+
|
|
28
69
|
switch (command) {
|
|
29
70
|
case "install":
|
|
30
71
|
if (options.package) {
|
|
@@ -64,6 +105,9 @@ export async function main(argv) {
|
|
|
64
105
|
case "path":
|
|
65
106
|
printPaths(options)
|
|
66
107
|
return
|
|
108
|
+
case "uninstall":
|
|
109
|
+
await uninstallRuntime(options)
|
|
110
|
+
return
|
|
67
111
|
case "help":
|
|
68
112
|
case "-h":
|
|
69
113
|
case "--help":
|
|
@@ -87,10 +131,13 @@ async function installFromSource(options) {
|
|
|
87
131
|
noStart: options.noStart,
|
|
88
132
|
sourceRoot,
|
|
89
133
|
update: options.update,
|
|
134
|
+
withMesh: options.withMesh,
|
|
135
|
+
skipMesh: options.skipMesh,
|
|
90
136
|
})
|
|
91
137
|
|
|
92
138
|
console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
|
|
93
139
|
console.log(`version: ${version}`)
|
|
140
|
+
ensureInPath(prefix)
|
|
94
141
|
console.log(`run: ${displayCommand(prefix)} status`)
|
|
95
142
|
}
|
|
96
143
|
|
|
@@ -100,10 +147,13 @@ async function installFromPackage(options) {
|
|
|
100
147
|
const version = await applyPackage(packageRoot, prefix, {
|
|
101
148
|
noStart: options.noStart,
|
|
102
149
|
update: options.update,
|
|
150
|
+
withMesh: options.withMesh,
|
|
151
|
+
skipMesh: options.skipMesh,
|
|
103
152
|
})
|
|
104
153
|
|
|
105
154
|
console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
|
|
106
155
|
console.log(`version: ${version}`)
|
|
156
|
+
ensureInPath(prefix)
|
|
107
157
|
console.log(`run: ${displayCommand(prefix)} status`)
|
|
108
158
|
}
|
|
109
159
|
|
|
@@ -152,6 +202,9 @@ async function buildRuntimePackage(sourceRoot, packageRoot, options) {
|
|
|
152
202
|
buildGo(sourceRoot, buildBin, "vantaloom-api", platform)
|
|
153
203
|
buildGo(sourceRoot, buildBin, "vantaloom-agent", platform)
|
|
154
204
|
buildGo(sourceRoot, buildBin, "vantaloomctl", platform)
|
|
205
|
+
// Privileged EasyTier sidecar (runs the mesh node as a service on Windows/macOS).
|
|
206
|
+
// Pure-Go (no CGO), so it cross-compiles cleanly for every target.
|
|
207
|
+
buildGo(sourceRoot, buildBin, "vantaloom-mesh", platform)
|
|
155
208
|
// Tray app requires CGO (systray), only buildable natively on Windows.
|
|
156
209
|
if (platform.startsWith("win32") && process.platform === "win32") {
|
|
157
210
|
try {
|
|
@@ -159,6 +212,9 @@ async function buildRuntimePackage(sourceRoot, packageRoot, options) {
|
|
|
159
212
|
} catch { /* optional: skip if systray build fails */ }
|
|
160
213
|
}
|
|
161
214
|
|
|
215
|
+
// Bundle the EasyTier mesh binaries (P2P virtual network) for the target platform.
|
|
216
|
+
await copyEasyTier(sourceRoot, buildBin, platform)
|
|
217
|
+
|
|
162
218
|
if (options.buildWeb) {
|
|
163
219
|
runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
|
|
164
220
|
}
|
|
@@ -170,6 +226,45 @@ async function buildRuntimePackage(sourceRoot, packageRoot, options) {
|
|
|
170
226
|
return { platform, version }
|
|
171
227
|
}
|
|
172
228
|
|
|
229
|
+
// copyEasyTier bundles the vendored EasyTier binaries (easytier-core/easytier-cli,
|
|
230
|
+
// plus wintun.dll on Windows) for the target platform into the package bin/ dir.
|
|
231
|
+
async function copyEasyTier(sourceRoot, buildBin, platform) {
|
|
232
|
+
const vendorMap = {
|
|
233
|
+
"win32-x64": "windows-x86_64",
|
|
234
|
+
"darwin-arm64": "macos-aarch64",
|
|
235
|
+
"linux-x64": "linux-x86_64",
|
|
236
|
+
}
|
|
237
|
+
const vendorName = vendorMap[platform]
|
|
238
|
+
if (!vendorName) {
|
|
239
|
+
console.warn(` warning: no EasyTier mapping for ${platform}; mesh disabled for this platform`)
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
const srcDir = path.join(sourceRoot, "vendor", "easytier", vendorName, `easytier-${vendorName}`)
|
|
243
|
+
if (!existsSync(srcDir)) {
|
|
244
|
+
console.warn(` warning: EasyTier binaries not found at ${srcDir}; run vendor download first`)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
const isWin = platform.startsWith("win32")
|
|
248
|
+
// Packet.dll + WinDivert64.sys are REQUIRED: easytier-core.exe statically
|
|
249
|
+
// imports Packet.dll, so without it the process dies at launch with
|
|
250
|
+
// STATUS_DLL_NOT_FOUND (0xC0000135) before writing any log. wintun.dll is the
|
|
251
|
+
// TUN backend. easytier-cli.exe does not need Packet.dll (it still launches).
|
|
252
|
+
const files = isWin
|
|
253
|
+
? ["easytier-core.exe", "easytier-cli.exe", "wintun.dll", "Packet.dll", "WinDivert64.sys"]
|
|
254
|
+
: ["easytier-core", "easytier-cli"]
|
|
255
|
+
let copied = 0
|
|
256
|
+
for (const f of files) {
|
|
257
|
+
const src = path.join(srcDir, f)
|
|
258
|
+
if (!existsSync(src)) {
|
|
259
|
+
console.warn(` warning: EasyTier file missing: ${f}`)
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
await cp(src, path.join(buildBin, f), { force: true })
|
|
263
|
+
copied++
|
|
264
|
+
}
|
|
265
|
+
console.log(` bundled EasyTier ${vendorName} (${copied} files)`)
|
|
266
|
+
}
|
|
267
|
+
|
|
173
268
|
async function applyPackage(packageRoot, prefix, options) {
|
|
174
269
|
assertRuntimePackage(packageRoot)
|
|
175
270
|
|
|
@@ -186,15 +281,82 @@ async function applyPackage(packageRoot, prefix, options) {
|
|
|
186
281
|
spawnSync(existingCtl, ["stop", "--prefix", prefix], { stdio: "inherit" })
|
|
187
282
|
}
|
|
188
283
|
|
|
284
|
+
// Kill lingering tray process that may hold locks on bin/ (older versions
|
|
285
|
+
// don't write tray.pid, so vantaloomctl stop won't find them).
|
|
286
|
+
killTrayProcess(prefix)
|
|
287
|
+
|
|
288
|
+
// On Windows the privileged mesh service holds its binaries open, so we can't
|
|
289
|
+
// wipe or overwrite them with a plain copy. Detect that up front: if the
|
|
290
|
+
// service is running, skip those files in the bin/ copy (the elevated `apply`
|
|
291
|
+
// swaps them after stopping the service).
|
|
292
|
+
const meshLocked =
|
|
293
|
+
process.platform === "win32" &&
|
|
294
|
+
meshServiceRunning(path.join(packageRoot, "bin"), "win32")
|
|
295
|
+
|
|
296
|
+
// Copy package contents to install prefix
|
|
189
297
|
for (const name of ["bin", "web", "cli"]) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
298
|
+
const src = path.join(packageRoot, name)
|
|
299
|
+
const dst = path.join(prefix, name)
|
|
300
|
+
if (!existsSync(src)) {
|
|
301
|
+
console.error(` warning: package missing ${name}/ directory`)
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
const copyOpts = { recursive: true, force: true, dereference: false }
|
|
305
|
+
if (name === "bin" && meshLocked) {
|
|
306
|
+
// Overwrite in place (don't wipe — that would fail on the locked files) and
|
|
307
|
+
// skip the mesh binaries the running service holds open.
|
|
308
|
+
copyOpts.filter = (s) => !WINDOWS_MESH_BINARIES.includes(path.basename(s))
|
|
309
|
+
} else {
|
|
310
|
+
removeKnownPath(dst, prefix)
|
|
311
|
+
}
|
|
312
|
+
await cp(src, dst, copyOpts)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Ensure binaries are executable on Unix (cross-compiled from Windows they lose +x)
|
|
316
|
+
const binDir = path.join(prefix, "bin")
|
|
317
|
+
if (process.platform !== "win32" && existsSync(binDir)) {
|
|
318
|
+
for (const entry of readdirSync(binDir)) {
|
|
319
|
+
const binPath = path.join(binDir, entry)
|
|
320
|
+
try { chmodSync(binPath, 0o755) } catch {}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Linux: grant easytier-core the TUN capability so the unprivileged runtime can
|
|
325
|
+
// bring up the mesh virtual network. This is the one privileged step of the
|
|
326
|
+
// install (a single sudo); the app itself stays unprivileged.
|
|
327
|
+
ensureLinuxMeshCapabilities(binDir)
|
|
328
|
+
|
|
329
|
+
// Windows/macOS: register (or lock-safely update) the privileged mesh service
|
|
330
|
+
// that runs easytier-core. This is the one elevation of the install on those
|
|
331
|
+
// platforms. Skipped for --no-start and gated to avoid prompting on every
|
|
332
|
+
// source rebuild (see ensureMeshService).
|
|
333
|
+
if (!options.noStart) {
|
|
334
|
+
ensureMeshService(packageRoot, prefix, {
|
|
335
|
+
sourceInstall: Boolean(options.sourceRoot),
|
|
336
|
+
withMesh: Boolean(options.withMesh),
|
|
337
|
+
skipMesh: Boolean(options.skipMesh),
|
|
195
338
|
})
|
|
196
339
|
}
|
|
197
340
|
|
|
341
|
+
// Make the runtime start at login/boot (per-user, no elevation). Idempotent.
|
|
342
|
+
if (!options.noStart && !options.skipAutostart) {
|
|
343
|
+
enableRuntimeAutostart(prefix)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Verify vantaloomctl binary exists before trying to run it
|
|
347
|
+
const ctlBin = path.join(prefix, "bin", binaryName("vantaloomctl"))
|
|
348
|
+
if (!existsSync(ctlBin)) {
|
|
349
|
+
const binContents = existsSync(binDir) ? readdirSync(binDir) : []
|
|
350
|
+
const srcBinContents = existsSync(path.join(packageRoot, "bin")) ? readdirSync(path.join(packageRoot, "bin")) : []
|
|
351
|
+
throw new Error(
|
|
352
|
+
`vantaloomctl binary not found at ${ctlBin}\n` +
|
|
353
|
+
` installed bin/: [${binContents.join(", ")}]\n` +
|
|
354
|
+
` package bin/: [${srcBinContents.join(", ")}]\n` +
|
|
355
|
+
` platform: ${platformId()}\n` +
|
|
356
|
+
` This may indicate a corrupt download. Try again or install from source.`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
198
360
|
await writeLauncher(prefix)
|
|
199
361
|
await writeText(path.join(prefix, "cli", "config.json"), `${JSON.stringify(mergedConfig, null, 2)}\n`)
|
|
200
362
|
await writeText(path.join(prefix, "VERSION"), `${version}\n`)
|
|
@@ -202,7 +364,7 @@ async function applyPackage(packageRoot, prefix, options) {
|
|
|
202
364
|
force: true,
|
|
203
365
|
})
|
|
204
366
|
|
|
205
|
-
run(
|
|
367
|
+
run(ctlBin, [
|
|
206
368
|
"install",
|
|
207
369
|
"--prefix",
|
|
208
370
|
prefix,
|
|
@@ -211,7 +373,7 @@ async function applyPackage(packageRoot, prefix, options) {
|
|
|
211
373
|
])
|
|
212
374
|
|
|
213
375
|
if (!options.noStart) {
|
|
214
|
-
run(
|
|
376
|
+
run(ctlBin, [
|
|
215
377
|
"start",
|
|
216
378
|
"--prefix",
|
|
217
379
|
prefix,
|
|
@@ -221,12 +383,350 @@ async function applyPackage(packageRoot, prefix, options) {
|
|
|
221
383
|
return version
|
|
222
384
|
}
|
|
223
385
|
|
|
386
|
+
// ensureLinuxMeshCapabilities grants easytier-core the network capabilities it
|
|
387
|
+
// needs to create a TUN device, so the unprivileged Vantaloom runtime can bring
|
|
388
|
+
// up the EasyTier mesh without running as root. Re-run on every update because
|
|
389
|
+
// replacing the binary clears file capabilities. Non-fatal: if it can't elevate
|
|
390
|
+
// or setcap is missing, mesh just falls back to the Hub relay.
|
|
391
|
+
function ensureLinuxMeshCapabilities(binDir) {
|
|
392
|
+
if (process.platform !== "linux") {
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
const core = path.join(binDir, "easytier-core")
|
|
396
|
+
if (!existsSync(core)) {
|
|
397
|
+
console.warn(" mesh: easytier-core not bundled; skipping TUN capability setup")
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
const caps = "cap_net_admin,cap_net_bind_service+ep"
|
|
401
|
+
const isRoot = typeof process.getuid === "function" && process.getuid() === 0
|
|
402
|
+
console.log(` mesh: granting TUN capability to easytier-core${isRoot ? "" : " (sudo)"} ...`)
|
|
403
|
+
const result = isRoot
|
|
404
|
+
? spawnSync("setcap", [caps, core], { stdio: "inherit" })
|
|
405
|
+
: spawnSync("sudo", ["setcap", caps, core], { stdio: "inherit" })
|
|
406
|
+
if (result.error || result.status !== 0) {
|
|
407
|
+
console.warn(
|
|
408
|
+
" mesh: could not set capabilities (P2P will fall back to the Hub relay).\n" +
|
|
409
|
+
` enable it manually with: sudo setcap ${caps} ${core}\n` +
|
|
410
|
+
" (needs the 'setcap' tool, e.g. apt install libcap2-bin)"
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ensureMeshService registers (or lock-safely updates) the privileged EasyTier
|
|
416
|
+
// sidecar service on Windows/macOS — the one elevation of the install on those
|
|
417
|
+
// platforms. Linux uses setcap instead (see ensureLinuxMeshCapabilities).
|
|
418
|
+
//
|
|
419
|
+
// To keep the source/dev rebuild loop frictionless it only elevates when the
|
|
420
|
+
// work is actually needed (service missing, or the mesh binaries changed) and,
|
|
421
|
+
// for source installs, defers to a printed one-liner unless --with-mesh is set.
|
|
422
|
+
function ensureMeshService(packageRoot, prefix, opts) {
|
|
423
|
+
const platform = process.platform
|
|
424
|
+
if (platform !== "win32" && platform !== "darwin") return // Linux: setcap path
|
|
425
|
+
if (opts.skipMesh) return
|
|
426
|
+
|
|
427
|
+
const pkgBin = path.join(packageRoot, "bin")
|
|
428
|
+
const meshExe = path.join(pkgBin, binaryName("vantaloom-mesh"))
|
|
429
|
+
if (!existsSync(meshExe)) return // sidecar not bundled
|
|
430
|
+
|
|
431
|
+
if (!meshServiceNeedsApply(pkgBin, path.join(prefix, "bin"), platform)) {
|
|
432
|
+
return // installed binaries current and service already managing them
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (opts.sourceInstall && !opts.withMesh) {
|
|
436
|
+
printMeshManualHint(prefix, platform)
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (platform === "win32") {
|
|
441
|
+
elevateWindowsMeshApply(pkgBin, prefix)
|
|
442
|
+
} else {
|
|
443
|
+
elevateDarwinMeshInstall(prefix)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// meshServiceNeedsApply reports whether the privileged service has to be
|
|
448
|
+
// (re)applied: true if the installed mesh binaries are missing/outdated, or if
|
|
449
|
+
// the service isn't currently up.
|
|
450
|
+
function meshServiceNeedsApply(pkgBin, installBin, platform) {
|
|
451
|
+
// On Windows compare every lockable mesh file (incl. Packet.dll/WinDivert/wintun)
|
|
452
|
+
// so a missing or changed support DLL also triggers a (re)apply.
|
|
453
|
+
const names = platform === "win32"
|
|
454
|
+
? WINDOWS_MESH_BINARIES
|
|
455
|
+
: [binaryName("vantaloom-mesh"), binaryName("easytier-core")]
|
|
456
|
+
for (const name of names) {
|
|
457
|
+
const installed = path.join(installBin, name)
|
|
458
|
+
if (!existsSync(installed)) return true
|
|
459
|
+
const staged = path.join(pkgBin, name)
|
|
460
|
+
if (existsSync(staged) && fileSha(staged) !== fileSha(installed)) return true
|
|
461
|
+
}
|
|
462
|
+
return !meshServiceRunning(pkgBin, platform)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// meshServiceRunning queries the OS service state via the (unprivileged) mesh
|
|
466
|
+
// `status` command. binDir is where to find the vantaloom-mesh binary to run.
|
|
467
|
+
function meshServiceRunning(binDir, platform) {
|
|
468
|
+
const exe = path.join(binDir, binaryName("vantaloom-mesh"))
|
|
469
|
+
if (!existsSync(exe)) return false
|
|
470
|
+
const r = spawnSync(exe, ["status"], { encoding: "utf8" })
|
|
471
|
+
if (r.error || typeof r.stdout !== "string") return false
|
|
472
|
+
const out = r.stdout.toLowerCase()
|
|
473
|
+
if (platform === "win32") return out.includes("running")
|
|
474
|
+
return r.status === 0 && !out.includes("not loaded") && !out.includes("not installed")
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function fileSha(file) {
|
|
478
|
+
return createHash("sha256").update(readFileSync(file)).digest("hex")
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// elevateWindowsMeshApply runs the lock-safe `apply` elevated (one UAC prompt).
|
|
482
|
+
// It launches from the package copy so it can overwrite the installed binary.
|
|
483
|
+
function elevateWindowsMeshApply(pkgBin, prefix) {
|
|
484
|
+
const exe = path.join(pkgBin, "vantaloom-mesh.exe")
|
|
485
|
+
console.log(" mesh: registering privileged P2P service (UAC prompt) ...")
|
|
486
|
+
const argList = ["apply", "--pkg-bin", pkgBin, "--install-dir", prefix].map(psQuote).join(",")
|
|
487
|
+
const ps = `$ErrorActionPreference='Stop'; $p = Start-Process -FilePath ${psQuote(exe)} -ArgumentList ${argList} -Verb RunAs -Wait -PassThru; exit $p.ExitCode`
|
|
488
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "inherit" })
|
|
489
|
+
if (r.error || r.status !== 0) {
|
|
490
|
+
const installed = path.join(prefix, "bin", "vantaloom-mesh.exe")
|
|
491
|
+
console.warn(" mesh: service registration was cancelled or failed; P2P falls back to the Hub relay.")
|
|
492
|
+
console.warn(` enable it later (as Administrator): "${installed}" install --install-dir "${prefix}"`)
|
|
493
|
+
} else {
|
|
494
|
+
console.log(" mesh: privileged P2P service active")
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// elevateDarwinMeshInstall registers the LaunchDaemon via sudo (one prompt). The
|
|
499
|
+
// runtime already copied the fresh binary into place; install reloads the daemon.
|
|
500
|
+
function elevateDarwinMeshInstall(prefix) {
|
|
501
|
+
const exe = path.join(prefix, "bin", "vantaloom-mesh")
|
|
502
|
+
console.log(" mesh: registering privileged P2P service (sudo) ...")
|
|
503
|
+
const r = spawnSync("sudo", [exe, "install", "--install-dir", prefix], { stdio: "inherit" })
|
|
504
|
+
if (r.error || r.status !== 0) {
|
|
505
|
+
console.warn(" mesh: LaunchDaemon registration failed; P2P falls back to the Hub relay.")
|
|
506
|
+
console.warn(` enable it later: sudo "${exe}" install --install-dir "${prefix}"`)
|
|
507
|
+
} else {
|
|
508
|
+
console.log(" mesh: privileged P2P service active")
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function printMeshManualHint(prefix, platform) {
|
|
513
|
+
console.log(" mesh: P2P service not auto-registered for source installs.")
|
|
514
|
+
if (platform === "win32") {
|
|
515
|
+
const exe = path.join(prefix, "bin", "vantaloom-mesh.exe")
|
|
516
|
+
console.log(` enable it once (as Administrator): "${exe}" install --install-dir "${prefix}"`)
|
|
517
|
+
console.log(" or re-run install/update with --with-mesh")
|
|
518
|
+
} else {
|
|
519
|
+
const exe = path.join(prefix, "bin", "vantaloom-mesh")
|
|
520
|
+
console.log(` enable it once: sudo "${exe}" install --install-dir "${prefix}"`)
|
|
521
|
+
console.log(" or re-run install/update with --with-mesh")
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// psQuote wraps a value as a PowerShell single-quoted string literal.
|
|
526
|
+
function psQuote(value) {
|
|
527
|
+
return "'" + String(value).replace(/'/g, "''") + "'"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// uninstallRuntime tears down a local install: stop the runtime, remove the
|
|
531
|
+
// privileged mesh service (elevated), then delete the install directory.
|
|
532
|
+
async function uninstallRuntime(options) {
|
|
533
|
+
const prefix = safeDirectory(options.prefix ?? defaultPrefix())
|
|
534
|
+
if (!existsSync(prefix)) {
|
|
535
|
+
console.log(`Vantaloom is not installed at ${prefix}`)
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
console.log(`Uninstalling Vantaloom from ${prefix} ...`)
|
|
539
|
+
|
|
540
|
+
// 1. Stop the runtime (api/agent/web/tray). Best-effort.
|
|
541
|
+
const ctlBin = path.join(prefix, "bin", binaryName("vantaloomctl"))
|
|
542
|
+
if (existsSync(ctlBin)) {
|
|
543
|
+
spawnSync(ctlBin, ["stop", "--prefix", prefix], { stdio: "inherit" })
|
|
544
|
+
}
|
|
545
|
+
killTrayProcess(prefix)
|
|
546
|
+
|
|
547
|
+
// 2. Remove the privileged mesh service (releases TUN adapter + file locks).
|
|
548
|
+
removeMeshService(prefix, options)
|
|
549
|
+
|
|
550
|
+
// 2b. Remove the login-autostart entry (lives outside the install dir).
|
|
551
|
+
disableRuntimeAutostart()
|
|
552
|
+
|
|
553
|
+
// 3. Delete the install directory.
|
|
554
|
+
try {
|
|
555
|
+
const parent = path.dirname(prefix)
|
|
556
|
+
removeKnownPath(prefix, parent)
|
|
557
|
+
console.log(`removed ${prefix}`)
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.warn(` could not remove ${prefix}: ${error.message}`)
|
|
560
|
+
console.warn(" some files may still be locked; re-run after closing Vantaloom processes.")
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
console.log("Vantaloom uninstalled.")
|
|
564
|
+
console.log("note: the install dir was left out of PATH edits; remove the bin/ entry from your shell profile if you added it.")
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function removeMeshService(prefix, options) {
|
|
568
|
+
const platform = process.platform
|
|
569
|
+
if (platform !== "win32" && platform !== "darwin") return // Linux: nothing registered
|
|
570
|
+
const exe = path.join(prefix, "bin", binaryName("vantaloom-mesh"))
|
|
571
|
+
if (!existsSync(exe)) return
|
|
572
|
+
if (options.skipMesh) return
|
|
573
|
+
|
|
574
|
+
if (platform === "win32") {
|
|
575
|
+
if (!meshServiceRunningOrInstalled(path.join(prefix, "bin"))) return
|
|
576
|
+
console.log(" mesh: removing privileged P2P service (UAC prompt) ...")
|
|
577
|
+
const ps = `$ErrorActionPreference='Stop'; $p = Start-Process -FilePath ${psQuote(exe)} -ArgumentList 'uninstall' -Verb RunAs -Wait -PassThru; exit $p.ExitCode`
|
|
578
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "inherit" })
|
|
579
|
+
if (r.error || r.status !== 0) {
|
|
580
|
+
console.warn(` mesh: could not remove service; run manually (as Administrator): "${exe}" uninstall`)
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
console.log(" mesh: removing privileged P2P service (sudo) ...")
|
|
584
|
+
const r = spawnSync("sudo", [exe, "uninstall"], { stdio: "inherit" })
|
|
585
|
+
if (r.error || r.status !== 0) {
|
|
586
|
+
console.warn(` mesh: could not remove service; run manually: sudo "${exe}" uninstall`)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// meshServiceRunningOrInstalled reports whether the Windows service exists at
|
|
592
|
+
// all (running or stopped), so uninstall only prompts for elevation when there
|
|
593
|
+
// is actually something to remove.
|
|
594
|
+
function meshServiceRunningOrInstalled(binDir) {
|
|
595
|
+
const exe = path.join(binDir, "vantaloom-mesh.exe")
|
|
596
|
+
if (!existsSync(exe)) return false
|
|
597
|
+
const r = spawnSync(exe, ["status"], { encoding: "utf8" })
|
|
598
|
+
if (r.error || typeof r.stdout !== "string") return false
|
|
599
|
+
return !r.stdout.toLowerCase().includes("not installed")
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// ── Runtime login-autostart (no elevation) ──
|
|
603
|
+
// Start the local runtime (api/agent/web/tray) at login/boot using only
|
|
604
|
+
// per-user mechanisms — no UAC/sudo. This is separate from the privileged mesh
|
|
605
|
+
// service: the mesh sidecar autostarts via the OS service manager; this brings
|
|
606
|
+
// up the unprivileged runtime that joins the mesh and serves the local API.
|
|
607
|
+
|
|
608
|
+
const AUTOSTART_LABEL = "online.timefiles.vantaloom.runtime"
|
|
609
|
+
// PowerShell HKCU: drive path. Set-ItemProperty handles values with spaces and
|
|
610
|
+
// embedded quotes cleanly (reg.exe's /d escaping is brittle), and the Run key is
|
|
611
|
+
// not blocked by Controlled Folder Access the way the Startup folder is.
|
|
612
|
+
const WINDOWS_RUN_KEY = "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
|
|
613
|
+
const WINDOWS_RUN_VALUE = "Vantaloom"
|
|
614
|
+
|
|
615
|
+
function enableRuntimeAutostart(prefix) {
|
|
616
|
+
try {
|
|
617
|
+
if (process.platform === "win32") {
|
|
618
|
+
// A .vbs launches the runtime with a hidden window (no console flash); the
|
|
619
|
+
// HKCU Run key runs it at login without elevation.
|
|
620
|
+
const launcher = path.join(prefix, "vantaloom.cmd")
|
|
621
|
+
const vbsPath = path.join(prefix, "autostart.vbs")
|
|
622
|
+
const vbs = `' Vantaloom runtime autostart (hidden)\r\nCreateObject("WScript.Shell").Run """${launcher}"" start", 0, False\r\n`
|
|
623
|
+
writeFileSync(vbsPath, vbs)
|
|
624
|
+
const result = spawnSync(
|
|
625
|
+
"powershell",
|
|
626
|
+
[
|
|
627
|
+
"-NoProfile",
|
|
628
|
+
"-NonInteractive",
|
|
629
|
+
"-Command",
|
|
630
|
+
`Set-ItemProperty -Path '${WINDOWS_RUN_KEY}' -Name '${WINDOWS_RUN_VALUE}' -Value 'wscript.exe "${vbsPath}"'`,
|
|
631
|
+
],
|
|
632
|
+
{ stdio: "ignore" }
|
|
633
|
+
)
|
|
634
|
+
if (result.error || result.status !== 0) {
|
|
635
|
+
console.warn(" autostart: could not register login entry (HKCU Run)")
|
|
636
|
+
} else {
|
|
637
|
+
console.log(" autostart: enabled (login Run key)")
|
|
638
|
+
}
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
if (process.platform === "darwin") {
|
|
642
|
+
const launcher = path.join(prefix, "vantaloom")
|
|
643
|
+
const dir = path.join(os.homedir(), "Library", "LaunchAgents")
|
|
644
|
+
mkdirSync(dir, { recursive: true })
|
|
645
|
+
const plistPath = path.join(dir, `${AUTOSTART_LABEL}.plist`)
|
|
646
|
+
// RunAtLoad only (no KeepAlive): `vantaloom start` spawns detached and exits.
|
|
647
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
648
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
649
|
+
<plist version="1.0"><dict>
|
|
650
|
+
<key>Label</key><string>${AUTOSTART_LABEL}</string>
|
|
651
|
+
<key>ProgramArguments</key><array><string>${launcher}</string><string>start</string></array>
|
|
652
|
+
<key>RunAtLoad</key><true/>
|
|
653
|
+
</dict></plist>
|
|
654
|
+
`
|
|
655
|
+
writeFileSync(plistPath, plist)
|
|
656
|
+
spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" })
|
|
657
|
+
spawnSync("launchctl", ["load", "-w", plistPath], { stdio: "ignore" })
|
|
658
|
+
console.log(" autostart: enabled (LaunchAgent)")
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
// Linux: user systemd unit + linger (no root).
|
|
662
|
+
const launcher = path.join(prefix, "vantaloom")
|
|
663
|
+
const dir = path.join(os.homedir(), ".config", "systemd", "user")
|
|
664
|
+
mkdirSync(dir, { recursive: true })
|
|
665
|
+
const unit = `[Unit]
|
|
666
|
+
Description=Vantaloom local runtime
|
|
667
|
+
After=network-online.target
|
|
668
|
+
Wants=network-online.target
|
|
669
|
+
|
|
670
|
+
[Service]
|
|
671
|
+
Type=oneshot
|
|
672
|
+
RemainAfterExit=yes
|
|
673
|
+
ExecStart=${launcher} start
|
|
674
|
+
ExecStop=${launcher} stop
|
|
675
|
+
|
|
676
|
+
[Install]
|
|
677
|
+
WantedBy=default.target
|
|
678
|
+
`
|
|
679
|
+
writeFileSync(path.join(dir, "vantaloom-runtime.service"), unit)
|
|
680
|
+
spawnSync("loginctl", ["enable-linger"], { stdio: "ignore" })
|
|
681
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" })
|
|
682
|
+
spawnSync("systemctl", ["--user", "enable", "vantaloom-runtime.service"], { stdio: "ignore" })
|
|
683
|
+
console.log(" autostart: enabled (systemd user unit)")
|
|
684
|
+
} catch (error) {
|
|
685
|
+
console.warn(` autostart: could not enable (${error.message})`)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function disableRuntimeAutostart() {
|
|
690
|
+
try {
|
|
691
|
+
if (process.platform === "win32") {
|
|
692
|
+
// Remove the Run-key entry; the autostart.vbs lives in the install dir and
|
|
693
|
+
// is deleted with it.
|
|
694
|
+
spawnSync(
|
|
695
|
+
"powershell",
|
|
696
|
+
["-NoProfile", "-NonInteractive", "-Command", `Remove-ItemProperty -Path '${WINDOWS_RUN_KEY}' -Name '${WINDOWS_RUN_VALUE}' -ErrorAction SilentlyContinue`],
|
|
697
|
+
{ stdio: "ignore" }
|
|
698
|
+
)
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
if (process.platform === "darwin") {
|
|
702
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${AUTOSTART_LABEL}.plist`)
|
|
703
|
+
if (existsSync(plistPath)) {
|
|
704
|
+
spawnSync("launchctl", ["unload", "-w", plistPath], { stdio: "ignore" })
|
|
705
|
+
rmSync(plistPath, { force: true })
|
|
706
|
+
}
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
const unit = path.join(os.homedir(), ".config", "systemd", "user", "vantaloom-runtime.service")
|
|
710
|
+
if (existsSync(unit)) {
|
|
711
|
+
spawnSync("systemctl", ["--user", "disable", "vantaloom-runtime.service"], { stdio: "ignore" })
|
|
712
|
+
rmSync(unit, { force: true })
|
|
713
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" })
|
|
714
|
+
}
|
|
715
|
+
} catch (error) {
|
|
716
|
+
console.warn(` autostart: could not disable (${error.message})`)
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
224
720
|
async function syncFromNpmRegistry(options, action) {
|
|
225
721
|
const prefix = safeDirectory(options.prefix ?? defaultPrefix())
|
|
226
722
|
const installedConfig = readInstalledConfig(prefix)
|
|
227
|
-
|
|
723
|
+
// Always derive runtimePackage from current platform — never trust stale config
|
|
724
|
+
// from a different platform (e.g. win32 config baked into a darwin package).
|
|
725
|
+
// Only explicit --runtime-package flag can override.
|
|
726
|
+
const runtimePackage = options.runtimePackage || runtimePackageName(platformId())
|
|
228
727
|
const runtimeVersion = options.runtimeVersion || installedConfig.runtimeVersion || "latest"
|
|
229
|
-
const
|
|
728
|
+
const explicitRegistry = options.npmRegistry || installedConfig.npmRegistry
|
|
729
|
+
const registry = normalizeRegistry(explicitRegistry || detectNpmRegistry() || defaultNpmRegistry)
|
|
230
730
|
const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-npm-"))
|
|
231
731
|
|
|
232
732
|
try {
|
|
@@ -234,7 +734,11 @@ async function syncFromNpmRegistry(options, action) {
|
|
|
234
734
|
const archive = path.join(tempRoot, `${packageBasename(runtimePackage)}-${runtimeVersion}.tgz`)
|
|
235
735
|
mkdirSync(extractRoot, { recursive: true })
|
|
236
736
|
|
|
237
|
-
const resolved = await
|
|
737
|
+
const resolved = await resolveNpmPackageWithFallback({
|
|
738
|
+
registries: explicitRegistry ? [registry] : [registry, ...fallbackNpmRegistries.map(normalizeRegistry)],
|
|
739
|
+
name: runtimePackage,
|
|
740
|
+
version: runtimeVersion,
|
|
741
|
+
})
|
|
238
742
|
await downloadNpmTarball({
|
|
239
743
|
tarballUrl: resolved.tarball,
|
|
240
744
|
target: archive,
|
|
@@ -248,13 +752,17 @@ async function syncFromNpmRegistry(options, action) {
|
|
|
248
752
|
noStart: options.noStart,
|
|
249
753
|
runtimePackage,
|
|
250
754
|
runtimeVersion: options.runtimeVersion ? resolved.version : "latest",
|
|
251
|
-
npmRegistry: registry,
|
|
755
|
+
npmRegistry: resolved.registry,
|
|
252
756
|
update: action === "update",
|
|
757
|
+
withMesh: options.withMesh,
|
|
758
|
+
skipMesh: options.skipMesh,
|
|
253
759
|
})
|
|
254
760
|
|
|
255
761
|
console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
|
|
256
762
|
console.log(`version: ${version}`)
|
|
257
763
|
console.log(`source: ${runtimePackage}@${resolved.version}`)
|
|
764
|
+
console.log(`registry: ${resolved.registry}`)
|
|
765
|
+
ensureInPath(prefix)
|
|
258
766
|
console.log(`run: ${displayCommand(prefix)} status`)
|
|
259
767
|
} finally {
|
|
260
768
|
removeKnownPath(tempRoot, os.tmpdir())
|
|
@@ -291,11 +799,14 @@ async function syncFromRelease(options, action) {
|
|
|
291
799
|
noStart: options.noStart,
|
|
292
800
|
sourceRoot: installedConfig.sourceRoot,
|
|
293
801
|
update: action === "update",
|
|
802
|
+
withMesh: options.withMesh,
|
|
803
|
+
skipMesh: options.skipMesh,
|
|
294
804
|
})
|
|
295
805
|
|
|
296
806
|
console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
|
|
297
807
|
console.log(`version: ${version}`)
|
|
298
808
|
console.log(`source: ${repo}@${releaseTag}`)
|
|
809
|
+
ensureInPath(prefix)
|
|
299
810
|
console.log(`run: ${displayCommand(prefix)} status`)
|
|
300
811
|
} finally {
|
|
301
812
|
removeKnownPath(tempRoot, os.tmpdir())
|
|
@@ -346,14 +857,114 @@ async function downloadReleaseAsset({ repo, releaseTag, assetName, target, token
|
|
|
346
857
|
await writeFile(target, buffer)
|
|
347
858
|
}
|
|
348
859
|
|
|
860
|
+
function detectNpmRegistry() {
|
|
861
|
+
// 1. NPM_CONFIG_REGISTRY env var (highest priority, set by npm/npx when running)
|
|
862
|
+
if (process.env.NPM_CONFIG_REGISTRY) {
|
|
863
|
+
return process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY
|
|
864
|
+
}
|
|
865
|
+
// npm also sets the lowercase variant
|
|
866
|
+
if (process.env.npm_config_registry) {
|
|
867
|
+
return process.env.npm_config_registry
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// 2. Read user .npmrc
|
|
871
|
+
try {
|
|
872
|
+
const npmrcPaths = [
|
|
873
|
+
path.join(os.homedir(), ".npmrc"),
|
|
874
|
+
]
|
|
875
|
+
// Also check project-level .npmrc
|
|
876
|
+
const localNpmrc = path.resolve(".npmrc")
|
|
877
|
+
if (localNpmrc !== npmrcPaths[0]) {
|
|
878
|
+
npmrcPaths.unshift(localNpmrc)
|
|
879
|
+
}
|
|
880
|
+
for (const npmrcPath of npmrcPaths) {
|
|
881
|
+
if (existsSync(npmrcPath)) {
|
|
882
|
+
const content = readFileSync(npmrcPath, "utf8")
|
|
883
|
+
const match = content.match(/^\s*registry\s*=\s*(.+)/m)
|
|
884
|
+
if (match) {
|
|
885
|
+
return match[1].trim()
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} catch {
|
|
890
|
+
// Ignore .npmrc read errors
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return ""
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async function resolveNpmPackageWithFallback({ registries, name, version }) {
|
|
897
|
+
const errors = []
|
|
898
|
+
for (const registry of registries) {
|
|
899
|
+
try {
|
|
900
|
+
const result = await resolveNpmPackage({ registry, name, version })
|
|
901
|
+
return { ...result, registry }
|
|
902
|
+
} catch (error) {
|
|
903
|
+
const causeCode = error?.cause?.code || ""
|
|
904
|
+
const isNetworkError = causeCode === "ECONNREFUSED"
|
|
905
|
+
|| causeCode === "ENOTFOUND"
|
|
906
|
+
|| causeCode === "ETIMEDOUT"
|
|
907
|
+
|| causeCode === "ECONNRESET"
|
|
908
|
+
|| causeCode === "UND_ERR_CONNECT_TIMEOUT"
|
|
909
|
+
|| (error instanceof TypeError && error.message === "fetch failed")
|
|
910
|
+
const isSslError = causeCode === "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
|
|
911
|
+
|| causeCode === "CERT_HAS_EXPIRED"
|
|
912
|
+
|| causeCode === "DEPTH_ZERO_SELF_SIGNED_CERT"
|
|
913
|
+
|| causeCode === "SELF_SIGNED_CERT_IN_CHAIN"
|
|
914
|
+
|| causeCode === "ERR_TLS_CERT_ALTNAME_INVALID"
|
|
915
|
+
const isRetryable = isNetworkError || isSslError
|
|
916
|
+
errors.push({ registry, error, isRetryable, isSslError })
|
|
917
|
+
|
|
918
|
+
if (isRetryable && registries.length > 1) {
|
|
919
|
+
const hint = isSslError ? " (SSL certificate error)" : ""
|
|
920
|
+
console.error(`vantaloom: ${registry} unreachable${hint}, trying next registry...`)
|
|
921
|
+
continue
|
|
922
|
+
}
|
|
923
|
+
if (isSslError) {
|
|
924
|
+
throw new Error(
|
|
925
|
+
`SSL certificate error connecting to ${registry}: ${causeCode}\n` +
|
|
926
|
+
` Fix: run with --no-strict-ssl, or set npm config: npm config set strict-ssl false`
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
// Non-network error (404, parse error, etc.) — don't try fallbacks
|
|
930
|
+
throw error
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// All registries failed
|
|
935
|
+
const hasSslError = errors.some((e) => e.isSslError)
|
|
936
|
+
const tried = errors.map((e) => e.registry).join(", ")
|
|
937
|
+
const sslHint = hasSslError
|
|
938
|
+
? `\n SSL fix: run with --no-strict-ssl, or set npm config: npm config set strict-ssl false`
|
|
939
|
+
: ""
|
|
940
|
+
throw new Error(
|
|
941
|
+
`all registries unreachable (tried: ${tried}). ` +
|
|
942
|
+
`Check your network or specify --npm-registry <url>${sslHint}`
|
|
943
|
+
)
|
|
944
|
+
}
|
|
945
|
+
|
|
349
946
|
async function resolveNpmPackage({ registry, name, version }) {
|
|
350
947
|
const metadataUrl = `${registry}/${encodeURIComponent(name).replace("%2F", "%2f")}`
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
948
|
+
let response
|
|
949
|
+
try {
|
|
950
|
+
response = await fetch(metadataUrl, {
|
|
951
|
+
headers: {
|
|
952
|
+
Accept: "application/vnd.npm.install-v1+json",
|
|
953
|
+
"User-Agent": "vantaloom-cli",
|
|
954
|
+
},
|
|
955
|
+
signal: AbortSignal.timeout(15000),
|
|
956
|
+
})
|
|
957
|
+
} catch (error) {
|
|
958
|
+
if (error instanceof TypeError && error.message === "fetch failed") {
|
|
959
|
+
const causeCode = error.cause?.code || ""
|
|
960
|
+
const causeMsg = causeCode || error.cause?.message || String(error.cause || "")
|
|
961
|
+
throw Object.assign(
|
|
962
|
+
new Error(`cannot reach registry ${registry} (${causeMsg})`),
|
|
963
|
+
{ cause: error.cause }
|
|
964
|
+
)
|
|
965
|
+
}
|
|
966
|
+
throw error
|
|
967
|
+
}
|
|
357
968
|
if (!response.ok) {
|
|
358
969
|
const detail = await response.text().catch(() => "")
|
|
359
970
|
throw new Error(`failed to inspect npm package ${name}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
|
|
@@ -373,11 +984,22 @@ async function resolveNpmPackage({ registry, name, version }) {
|
|
|
373
984
|
}
|
|
374
985
|
|
|
375
986
|
async function downloadNpmTarball({ tarballUrl, target, packageName, version }) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
987
|
+
console.log(` downloading ${packageName}@${version}...`)
|
|
988
|
+
let response
|
|
989
|
+
try {
|
|
990
|
+
response = await fetch(tarballUrl, {
|
|
991
|
+
headers: {
|
|
992
|
+
"User-Agent": "vantaloom-cli",
|
|
993
|
+
},
|
|
994
|
+
signal: AbortSignal.timeout(120000),
|
|
995
|
+
})
|
|
996
|
+
} catch (error) {
|
|
997
|
+
const causeCode = error?.cause?.code || ""
|
|
998
|
+
const causeMsg = causeCode || error?.cause?.message || error.message || ""
|
|
999
|
+
const isSsl = causeCode.includes("CERT") || causeCode === "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
|
|
1000
|
+
const hint = isSsl ? `\n Fix: run with --no-strict-ssl` : ""
|
|
1001
|
+
throw new Error(`failed to download ${packageName}@${version} from ${tarballUrl}: ${causeMsg}${hint}`)
|
|
1002
|
+
}
|
|
381
1003
|
if (!response.ok) {
|
|
382
1004
|
const detail = await response.text().catch(() => "")
|
|
383
1005
|
throw new Error(`failed to download ${packageName}@${version}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
|
|
@@ -385,6 +1007,7 @@ async function downloadNpmTarball({ tarballUrl, target, packageName, version })
|
|
|
385
1007
|
|
|
386
1008
|
const buffer = Buffer.from(await response.arrayBuffer())
|
|
387
1009
|
await writeFile(target, buffer)
|
|
1010
|
+
console.log(` downloaded ${(buffer.length / 1024 / 1024).toFixed(1)} MB`)
|
|
388
1011
|
}
|
|
389
1012
|
|
|
390
1013
|
function shouldUseSourceInstall(options) {
|
|
@@ -448,9 +1071,9 @@ function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
|
|
|
448
1071
|
if (!merged.releaseTag) {
|
|
449
1072
|
merged.releaseTag = defaultReleaseTag
|
|
450
1073
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1074
|
+
// Always force runtimePackage to match the running platform — a cross-compiled
|
|
1075
|
+
// package may carry a config for a different platform (e.g. win32 inside darwin).
|
|
1076
|
+
merged.runtimePackage = runtimePackageName(platformId())
|
|
454
1077
|
if (!merged.runtimeVersion) {
|
|
455
1078
|
merged.runtimeVersion = "latest"
|
|
456
1079
|
}
|
|
@@ -470,7 +1093,7 @@ function runtimeConfigFromSource(sourceRoot) {
|
|
|
470
1093
|
releaseTag: defaultReleaseTag,
|
|
471
1094
|
runtimePackage: runtimePackageName(platformId()),
|
|
472
1095
|
runtimeVersion: "latest",
|
|
473
|
-
npmRegistry: defaultNpmRegistry,
|
|
1096
|
+
npmRegistry: detectNpmRegistry() || defaultNpmRegistry,
|
|
474
1097
|
}
|
|
475
1098
|
}
|
|
476
1099
|
|
|
@@ -776,6 +1399,18 @@ function parseOptions(args) {
|
|
|
776
1399
|
case "local":
|
|
777
1400
|
options.local = true
|
|
778
1401
|
break
|
|
1402
|
+
case "with-mesh":
|
|
1403
|
+
options.withMesh = true
|
|
1404
|
+
break
|
|
1405
|
+
case "skip-mesh":
|
|
1406
|
+
options.skipMesh = true
|
|
1407
|
+
break
|
|
1408
|
+
case "skip-autostart":
|
|
1409
|
+
options.skipAutostart = true
|
|
1410
|
+
break
|
|
1411
|
+
case "no-strict-ssl":
|
|
1412
|
+
options.noStrictSsl = true
|
|
1413
|
+
break
|
|
779
1414
|
default:
|
|
780
1415
|
throw new Error(`unknown option --${key}`)
|
|
781
1416
|
}
|
|
@@ -897,21 +1532,113 @@ async function writeText(filePath, content) {
|
|
|
897
1532
|
await writeFile(filePath, content)
|
|
898
1533
|
}
|
|
899
1534
|
|
|
1535
|
+
// ensureInPath adds the Vantaloom install directory to the user's shell PATH
|
|
1536
|
+
// on macOS and Linux, so `vantaloom` can be run directly after install.
|
|
1537
|
+
// On Windows this is not needed (vantaloom.cmd is run by full path or added via installer).
|
|
1538
|
+
function ensureInPath(prefix) {
|
|
1539
|
+
if (process.platform === "win32") return
|
|
1540
|
+
|
|
1541
|
+
// Check if already in PATH
|
|
1542
|
+
const pathDirs = (process.env.PATH || "").split(":")
|
|
1543
|
+
if (pathDirs.includes(prefix)) return
|
|
1544
|
+
|
|
1545
|
+
// Determine shell profile file
|
|
1546
|
+
const home = os.homedir()
|
|
1547
|
+
const shell = process.env.SHELL || ""
|
|
1548
|
+
let profilePath
|
|
1549
|
+
if (shell.endsWith("/zsh") || existsSync(path.join(home, ".zshrc"))) {
|
|
1550
|
+
profilePath = path.join(home, ".zshrc")
|
|
1551
|
+
} else if (shell.endsWith("/bash")) {
|
|
1552
|
+
// On macOS, bash uses .bash_profile; on Linux, .bashrc
|
|
1553
|
+
profilePath = process.platform === "darwin"
|
|
1554
|
+
? path.join(home, ".bash_profile")
|
|
1555
|
+
: path.join(home, ".bashrc")
|
|
1556
|
+
} else if (existsSync(path.join(home, ".profile"))) {
|
|
1557
|
+
profilePath = path.join(home, ".profile")
|
|
1558
|
+
} else {
|
|
1559
|
+
profilePath = path.join(home, ".profile")
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Use $HOME-relative path for portability
|
|
1563
|
+
const homeRelative = prefix.startsWith(home)
|
|
1564
|
+
? `$HOME${prefix.slice(home.length)}`
|
|
1565
|
+
: prefix
|
|
1566
|
+
const exportLine = `export PATH="${homeRelative}:$PATH"`
|
|
1567
|
+
const marker = "# vantaloom"
|
|
1568
|
+
|
|
1569
|
+
// Check if already added to profile
|
|
1570
|
+
try {
|
|
1571
|
+
if (existsSync(profilePath)) {
|
|
1572
|
+
const content = readFileSync(profilePath, "utf8")
|
|
1573
|
+
if (content.includes("vantaloom") && content.includes("PATH")) return
|
|
1574
|
+
}
|
|
1575
|
+
} catch {}
|
|
1576
|
+
|
|
1577
|
+
// Append to profile
|
|
1578
|
+
try {
|
|
1579
|
+
const entry = `\n${marker}\n${exportLine}\n`
|
|
1580
|
+
appendFileSync(profilePath, entry)
|
|
1581
|
+
console.log(`PATH: added ${prefix} to ${profilePath}`)
|
|
1582
|
+
console.log(` run: source ${profilePath} (or open a new terminal)`)
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
console.log(`PATH: could not update ${profilePath}: ${error.message}`)
|
|
1585
|
+
console.log(` add manually: ${exportLine}`)
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function killTrayProcess(prefix) {
|
|
1590
|
+
if (process.platform === "win32") {
|
|
1591
|
+
// Try PID file first (new tray versions write runtime/tray.pid).
|
|
1592
|
+
const pidFile = path.join(prefix, "runtime", "tray.pid")
|
|
1593
|
+
if (existsSync(pidFile)) {
|
|
1594
|
+
const pid = readFileSync(pidFile, "utf8").trim()
|
|
1595
|
+
if (pid) {
|
|
1596
|
+
spawnSync("taskkill", ["/PID", pid, "/F"], { stdio: "ignore" })
|
|
1597
|
+
try { rmSync(pidFile, { force: true }) } catch {}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
// Fallback: kill by image name if the binary is inside our prefix.
|
|
1601
|
+
const result = spawnSync("tasklist", ["/FI", "IMAGENAME eq vantaloom-tray.exe", "/FO", "CSV", "/NH"], {
|
|
1602
|
+
encoding: "utf8",
|
|
1603
|
+
windowsHide: true,
|
|
1604
|
+
})
|
|
1605
|
+
if (result.stdout) {
|
|
1606
|
+
for (const line of result.stdout.split("\n")) {
|
|
1607
|
+
const match = line.match(/"vantaloom-tray\.exe","(\d+)"/)
|
|
1608
|
+
if (match) {
|
|
1609
|
+
spawnSync("taskkill", ["/PID", match[1], "/F"], { stdio: "ignore" })
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
// Brief pause to let file handles release.
|
|
1614
|
+
spawnSync("timeout", ["/t", "1", "/nobreak"], { stdio: "ignore", windowsHide: true })
|
|
1615
|
+
} else {
|
|
1616
|
+
// Unix: pkill by name (best-effort).
|
|
1617
|
+
spawnSync("pkill", ["-f", "vantaloom-tray"], { stdio: "ignore" })
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
900
1621
|
function printHelp() {
|
|
901
1622
|
console.log(`Vantaloom CLI
|
|
902
1623
|
|
|
903
1624
|
Usage:
|
|
904
|
-
vantaloom install
|
|
905
|
-
vantaloom install
|
|
906
|
-
vantaloom update
|
|
907
|
-
vantaloom update
|
|
908
|
-
vantaloom
|
|
909
|
-
vantaloom
|
|
910
|
-
vantaloom
|
|
911
|
-
vantaloom
|
|
912
|
-
vantaloom
|
|
913
|
-
vantaloom
|
|
914
|
-
vantaloom
|
|
1625
|
+
vantaloom install [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--package <dir>] [--no-start] [--with-mesh|--skip-mesh]
|
|
1626
|
+
vantaloom install --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start] [--with-mesh]
|
|
1627
|
+
vantaloom update [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--no-start] [--with-mesh|--skip-mesh]
|
|
1628
|
+
vantaloom update --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start] [--with-mesh]
|
|
1629
|
+
vantaloom uninstall [--prefix <dir>] [--skip-mesh]
|
|
1630
|
+
vantaloom package [--source <repo>] [--output <dir>] [--build-web] [--archive] [--npm-package] [--target <platform>]
|
|
1631
|
+
vantaloom start [--prefix <dir>] [--component all|api|agent|web]
|
|
1632
|
+
vantaloom stop [--prefix <dir>] [--component all|api|agent|web]
|
|
1633
|
+
vantaloom restart [--prefix <dir>] [--component all|api|agent|web]
|
|
1634
|
+
vantaloom status [--prefix <dir>]
|
|
1635
|
+
vantaloom ports [--prefix <dir>]
|
|
1636
|
+
vantaloom path [--prefix <dir>] [--source <repo>]
|
|
915
1637
|
vantaloom platform
|
|
1638
|
+
|
|
1639
|
+
Mesh (P2P) service:
|
|
1640
|
+
Windows/macOS register a privileged EasyTier service at install (one UAC/sudo prompt).
|
|
1641
|
+
Linux grants TUN capability via setcap instead. Use --skip-mesh to opt out,
|
|
1642
|
+
or --with-mesh to force registration during a --local source install.
|
|
916
1643
|
`)
|
|
917
1644
|
}
|