From 753fac9fe9474db8a41dfd549abef434ea8baf70 Mon Sep 17 00:00:00 2001 From: tke Date: Wed, 23 Sep 2026 15:15:33 +0200 Subject: [PATCH] =?UTF-8?q?fix(proxy):=20installer=20=E2=80=94=20no=20prom?= =?UTF-8?q?pts=20during=20dry-run;=20never=20pin=20service=20to=20agent-bu?= =?UTF-8?q?ndled=20node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - askConfirm short-circuits non-interactively in --dry-run (prints what would be asked) - systemd node resolution order: existing unit's pinned binary > newest nvm node > PATH lookup excluding hermes-agent runtimes > process.execPath. On the live box this keeps the unit byte-identical (true idempotency for the service section). --- scripts/proxy/install.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/proxy/install.js b/scripts/proxy/install.js index db44eb9..f740986 100644 --- a/scripts/proxy/install.js +++ b/scripts/proxy/install.js @@ -94,6 +94,7 @@ function askSecret(question) { } async function askConfirm(question, defaultYes = true) { + if (FLAGS.dryRun) { console.log(`(dry-run) would ask: ${question} -> default ${defaultYes ? 'y' : 'n'}`); return defaultYes; } if (FLAGS.nonInteractive) return defaultYes; const a = (await ask(`${question} (y/n)`, defaultYes ? 'y' : 'n')).toLowerCase(); return a === 'y'; @@ -399,7 +400,30 @@ function sectionUnit() { const S = 'systemd service'; if (FLAGS.noSystemd) { report(S, 'SKIPPED', '--no-systemd'); return true; } const unitPath = path.join(SYSTEMD_USER_DIR, 'proxy-bridge.service'); - const nodeBin = process.execPath; + // The service must not run on an agent-bundled runtime: prefer (1) the node + // already pinned in an existing unit (stable, known-good), (2) the newest + // nvm-installed node, (3) PATH lookup that does NOT resolve into the hermes + // agent dir, (4) process.execPath as a last resort. + let nodeBin = null; + try { + const existing = fs.readFileSync(unitPath, 'utf8'); + const m = existing.match(/^ExecStart=(\S+)\s+/m); + if (m && fs.existsSync(m[1])) nodeBin = m[1]; + } catch {} + if (!nodeBin) { + const nvmRoot = path.join(os.homedir(), '.nvm', 'versions', 'node'); + try { + const versions = fs.readdirSync(nvmRoot).filter(v => /^v\d+/.test(v)).sort((a, b) => + a.localeCompare(b, undefined, { numeric: true })); + if (versions.length > 0) nodeBin = path.join(nvmRoot, versions[versions.length - 1], 'bin', 'node'); + } catch {} + } + if (!nodeBin) { + const loginNode = run('bash', ['--noprofile', '--norc', '-c', 'command -v node'], { readOnly: true }); + const cand = loginNode.status === 0 ? loginNode.stdout.trim() : ''; + if (cand && !fs.realpathSync(cand).includes('/.hermes/')) nodeBin = cand; + } + if (!nodeBin) nodeBin = process.execPath; const want = `[Unit]\nDescription=Local Proxy Bridge (Keyring Auth)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${nodeBin} ${BRIDGE_DIR}/bridge.js\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`; let current = null; try { current = fs.readFileSync(unitPath, 'utf8'); } catch {}