diff --git a/server/compute-node.js b/server/compute-node.js index f62ba65e..9fecc8c1 100644 --- a/server/compute-node.js +++ b/server/compute-node.js @@ -5,8 +5,9 @@ import os from 'os'; import pty from 'node-pty'; import crypto from 'crypto'; -const CONFIG_DIR = path.join(os.homedir(), '.openclaw'); +const CONFIG_DIR = path.join(os.homedir(), '.dr-claw'); const CONFIG_FILE = path.join(CONFIG_DIR, 'compute-node.json'); +const LEGACY_CONFIG_DIR = path.join(os.homedir(), '.openclaw'); // ─── ID generation ─── @@ -23,7 +24,19 @@ async function loadRawConfig() { const data = await fs.readFile(CONFIG_FILE, 'utf8'); return JSON.parse(data); } catch (e) { - return { nodes: [], activeNodeId: null }; + // Migrate from legacy ~/.openclaw/ if it exists + const legacyFile = path.join(LEGACY_CONFIG_DIR, 'compute-node.json'); + try { + const legacyData = await fs.readFile(legacyFile, 'utf8'); + const parsed = JSON.parse(legacyData); + // Copy to new location + await fs.mkdir(CONFIG_DIR, { recursive: true }); + await fs.writeFile(CONFIG_FILE, legacyData, { mode: 0o600 }); + console.log('[compute-node] Migrated config from ~/.openclaw/ to ~/.dr-claw/'); + return parsed; + } catch { + return { nodes: [], activeNodeId: null }; + } } } @@ -213,7 +226,9 @@ async function execSsh(nodeConfig, remoteCmd) { const cmd = `${sshBase} ${nodeConfig.user}@${nodeConfig.host} ${JSON.stringify(remoteCmd)}`; return await execWithPassword(cmd, nodeConfig.password); } else { - throw new Error('No authentication method configured (need SSH key or password)'); + // Fall back to SSH agent (e.g. 1Password, ssh-agent) — no explicit key or password + const cmd = `${sshBase} ${nodeConfig.user}@${nodeConfig.host} ${JSON.stringify(remoteCmd)}`; + return await execLocal(cmd); } } @@ -226,12 +241,11 @@ async function execRsync(nodeConfig, src, dst, excludes = '') { const cmd = `rsync -avz ${excludes} -e "${sshCmd}" ${src} ${dst}`; - if (nodeConfig.keyPath) { - return await execLocal(cmd); - } else if (nodeConfig.password) { + if (nodeConfig.password && !nodeConfig.keyPath) { return await execWithPassword(cmd, nodeConfig.password, 120000); } else { - throw new Error('No authentication method configured'); + // keyPath or SSH agent — both work via execLocal + return await execLocal(cmd); } } diff --git a/server/routes/community-tools.js b/server/routes/community-tools.js index 99a711c0..e30b282b 100644 --- a/server/routes/community-tools.js +++ b/server/routes/community-tools.js @@ -77,11 +77,11 @@ router.post('/configure', async (req, res) => { const results = { steps: [], errors: [] }; - // ── Step 1: Write API keys to ~/.openclaw/community-tools.json ── + // ── Step 1: Write API keys to ~/.dr-claw/community-tools.json ── // (NOT project .env — writing .env triggers Vite restart and kills the fetch) if (apiKeys && Object.keys(apiKeys).length > 0) { try { - const configDir = path.join(os.homedir(), '.openclaw'); + const configDir = path.join(os.homedir(), '.dr-claw'); const configPath = path.join(configDir, 'community-tools.json'); await fs.mkdir(configDir, { recursive: true }); @@ -89,7 +89,16 @@ router.post('/configure', async (req, res) => { try { existing = JSON.parse(await fs.readFile(configPath, 'utf8')); } catch { - // file doesn't exist yet + // Migrate from legacy ~/.openclaw/ if it exists + const legacyPath = path.join(os.homedir(), '.openclaw', 'community-tools.json'); + try { + const legacyData = await fs.readFile(legacyPath, 'utf8'); + existing = JSON.parse(legacyData); + await fs.writeFile(configPath, legacyData, { mode: 0o600 }); + console.log('[community-tools] Migrated config from ~/.openclaw/ to ~/.dr-claw/'); + } catch { + // Neither file exists — start fresh + } } // Merge keys into config @@ -112,7 +121,7 @@ router.post('/configure', async (req, res) => { .join('\n') + '\n'; await fs.writeFile(rcPath, rcContent, { mode: 0o600 }); - results.steps.push({ step: 'env', status: 'ok', message: `Saved ${Object.keys(apiKeys).length} key(s) to ~/.openclaw/community-tools.json` }); + results.steps.push({ step: 'env', status: 'ok', message: `Saved ${Object.keys(apiKeys).length} key(s) to ~/.dr-claw/community-tools.json` }); } catch (err) { results.errors.push({ step: 'env', error: err.message }); } diff --git a/server/routes/compute.js b/server/routes/compute.js index fc252c9a..ea9ecaaf 100644 --- a/server/routes/compute.js +++ b/server/routes/compute.js @@ -93,6 +93,9 @@ router.put('/nodes/:id', async (req, res) => { updated.password = password; } // If no new password provided, keep existing + } else if (authType === 'agent') { + delete updated.keyPath; + delete updated.password; } // Slurm config @@ -340,11 +343,11 @@ router.get('/config', async (req, res) => { return res.json({ configured: false, host: '', user: '', workDir: '~', authType: 'key', keyPath: '', hasPassword: false }); } res.json({ - configured: !!(node.host && node.user && (node.keyPath || node.password)), + configured: !!(node.host && node.user), host: node.host || '', user: node.user || '', workDir: node.workDir || '~', - authType: node.keyPath ? 'key' : (node.password ? 'password' : 'key'), + authType: node.keyPath ? 'key' : (node.password ? 'password' : 'agent'), keyPath: node.keyPath || '', hasPassword: !!node.password, type: node.type || 'direct', @@ -498,7 +501,7 @@ router.get('/local/monitor', async (_req, res) => { router.get('/status', async (req, res) => { try { const node = await getActiveNode(); - const configured = !!(node && node.host && node.user && (node.keyPath || node.password)); + const configured = !!(node && node.host && node.user); res.json({ configured, host: node?.host || '', diff --git a/src/components/compute-dashboard/NodeForm.tsx b/src/components/compute-dashboard/NodeForm.tsx index d0220b6d..6815d200 100644 --- a/src/components/compute-dashboard/NodeForm.tsx +++ b/src/components/compute-dashboard/NodeForm.tsx @@ -12,7 +12,7 @@ function formFromNode(node: ComputeNode): NodeFormData { host: node.host || '', user: node.user || '', port: String(node.port || 22), - authType: node.keyPath ? 'key' : node.hasPassword ? 'password' : 'key', + authType: node.keyPath ? 'key' : node.hasPassword ? 'password' : 'agent', key: '', password: '', workDir: node.workDir || '~', @@ -168,6 +168,15 @@ export default function NodeForm({
Uses system SSH agent (1Password, ssh-agent, etc.) — no key or password needed.
+ ) : form.authType === 'key' ? (