diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index 3c7b7f972..5dcd6d30e 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -525,8 +525,9 @@ function isOpenclawReady(sandboxName) { return Boolean(fetchGatewayAuthTokenFromSandbox(sandboxName)); } -function writeSandboxConfigSyncFile(script, tmpDir = os.tmpdir(), now = Date.now()) { - const scriptFile = path.join(tmpDir, `nemoclaw-sync-${now}.sh`); +function writeSandboxConfigSyncFile(script, tmpDir = os.tmpdir()) { + const dir = fs.mkdtempSync(path.join(tmpDir, "nemoclaw-sync-")); + const scriptFile = path.join(dir, "sync.sh"); fs.writeFileSync(scriptFile, `${script}\n`, { mode: 0o600 }); return scriptFile; } @@ -665,7 +666,8 @@ function probeOpenAiLikeEndpoint(endpointUrl, model, apiKey) { const failures = []; for (const probe of probes) { - const bodyFile = path.join(os.tmpdir(), `nemoclaw-probe-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); + const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-probe-")); + const bodyFile = path.join(probeDir, "body.json"); try { const cmd = [ "curl -sS", @@ -698,7 +700,7 @@ function probeOpenAiLikeEndpoint(endpointUrl, model, apiKey) { message: summarizeProbeError(body, status || result.status || 0), }); } finally { - fs.rmSync(bodyFile, { force: true }); + fs.rmSync(probeDir, { recursive: true, force: true }); } } @@ -710,7 +712,8 @@ function probeOpenAiLikeEndpoint(endpointUrl, model, apiKey) { } function probeAnthropicEndpoint(endpointUrl, model, apiKey) { - const bodyFile = path.join(os.tmpdir(), `nemoclaw-anthropic-probe-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); + const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-anthropic-probe-")); + const bodyFile = path.join(probeDir, "body.json"); try { const cmd = [ "curl -sS", @@ -753,7 +756,7 @@ function probeAnthropicEndpoint(endpointUrl, model, apiKey) { ], }; } finally { - fs.rmSync(bodyFile, { force: true }); + fs.rmSync(probeDir, { recursive: true, force: true }); } } @@ -857,7 +860,8 @@ async function validateCustomAnthropicSelection(label, endpointUrl, model, crede } function fetchNvidiaEndpointModels(apiKey) { - const bodyFile = path.join(os.tmpdir(), `nemoclaw-nvidia-models-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); + const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-nvidia-models-")); + const bodyFile = path.join(probeDir, "body.json"); try { const cmd = [ "curl -sS", @@ -890,7 +894,7 @@ function fetchNvidiaEndpointModels(apiKey) { } catch (error) { return { ok: false, message: error.message || String(error) }; } finally { - fs.rmSync(bodyFile, { force: true }); + fs.rmSync(probeDir, { recursive: true, force: true }); } } @@ -912,7 +916,8 @@ function validateNvidiaEndpointModel(model, apiKey) { } function fetchOpenAiLikeModels(endpointUrl, apiKey) { - const bodyFile = path.join(os.tmpdir(), `nemoclaw-openai-models-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); + const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openai-models-")); + const bodyFile = path.join(probeDir, "body.json"); try { const cmd = [ "curl -sS", @@ -944,12 +949,13 @@ function fetchOpenAiLikeModels(endpointUrl, apiKey) { } catch (error) { return { ok: false, status: 0, message: error.message || String(error) }; } finally { - fs.rmSync(bodyFile, { force: true }); + fs.rmSync(probeDir, { recursive: true, force: true }); } } function fetchAnthropicModels(endpointUrl, apiKey) { - const bodyFile = path.join(os.tmpdir(), `nemoclaw-anthropic-models-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); + const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-anthropic-models-")); + const bodyFile = path.join(probeDir, "body.json"); try { const cmd = [ "curl -sS", @@ -982,7 +988,7 @@ function fetchAnthropicModels(endpointUrl, apiKey) { } catch (error) { return { ok: false, status: 0, message: error.message || String(error) }; } finally { - fs.rmSync(bodyFile, { force: true }); + fs.rmSync(probeDir, { recursive: true, force: true }); } } @@ -2417,7 +2423,7 @@ async function setupOpenclaw(sandboxName, model, provider) { { stdio: ["ignore", "ignore", "inherit"] } ); } finally { - fs.unlinkSync(scriptFile); + fs.rmSync(path.dirname(scriptFile), { recursive: true, force: true }); } } diff --git a/test/onboard.test.js b/test/onboard.test.js index 16b7e5453..aa26e5b23 100644 --- a/test/onboard.test.js +++ b/test/onboard.test.js @@ -428,9 +428,13 @@ describe("onboard helpers", () => { it("writes sandbox sync scripts to a temp file for stdin redirection", () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-onboard-test-")); try { - const scriptFile = writeSandboxConfigSyncFile("echo test", tmpDir, 1234); - expect(scriptFile).toBe(path.join(tmpDir, "nemoclaw-sync-1234.sh")); + const scriptFile = writeSandboxConfigSyncFile("echo test", tmpDir); + expect(scriptFile).toMatch(/nemoclaw-sync-.*[/\\]sync\.sh$/); expect(fs.readFileSync(scriptFile, "utf8")).toBe("echo test\n"); + if (process.platform !== "win32") { + const stat = fs.statSync(scriptFile); + expect(stat.mode & 0o777).toBe(0o600); + } } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); }