From 1a7e0a9738181378b97d2fe3ecd9dad2035ad9a8 Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Sat, 2 May 2026 18:07:33 +0800 Subject: [PATCH] feat: SSH STCP key generation and frp tunnel registration Co-authored-by: Cursor --- lib/client.js | 15 ++++++++------- lib/config.js | 18 ++++++++++++++++-- lib/frpc.js | 17 ++++++++++++----- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/lib/client.js b/lib/client.js index 755eb99..2472a04 100644 --- a/lib/client.js +++ b/lib/client.js @@ -370,15 +370,16 @@ class ClawClient { _sendConnect() { const msg = { - type: 'connect', - box_id: this._boxId, - claw_id: this._cfg.claw_id ?? null, - token: this._cfg.token ?? null, - version: CLAWD_VERSION, + type: 'connect', + box_id: this._boxId, + claw_id: this._cfg.claw_id ?? null, + token: this._cfg.token ?? null, + version: CLAWD_VERSION, + ssh_secret_key: this._cfg.ssh_secret_key ?? null, local_ip: getLocalIps(), local_networks: getLocalNetworks(), external_ip: this._externalIp ?? null, - location: this._location ?? null, + location: this._location ?? null, ...this._dashInfo, }; this._send(msg); @@ -437,7 +438,7 @@ class ClawClient { this._applyStatus(msg); if (msg.frp && msg.frp.server && msg.frp.auth_token) { - this._frpc.start(msg.claw_id, msg.frp).catch(e => { + this._frpc.start(msg.claw_id, msg.frp, this._cfg.ssh_secret_key ?? null).catch(e => { log.error('frpc', '启动失败:', e.message); }); } diff --git a/lib/config.js b/lib/config.js index 536e6ad..02971f8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -19,19 +19,33 @@ const DEFAULTS = { heartbeat_interval: 30, // 秒 /** 云端已激活:用于启动/重连时立即点亮 alarm(pwr),不等首包 connected */ activated: false, + ssh_secret_key: null, }; +function _generateSshSecretKey() { + const bytes = require('crypto').randomBytes(16); + return 'sk-' + bytes.toString('hex'); +} + function load() { + let cfg; try { if (fs.existsSync(CONFIG_FILE)) { const raw = fs.readFileSync(CONFIG_FILE, 'utf8'); - return Object.assign({}, DEFAULTS, JSON.parse(raw)); + cfg = Object.assign({}, DEFAULTS, JSON.parse(raw)); } } catch (e) { const log = require('./logger'); log.error('config', '读取配置失败,使用默认值:', e.message); } - return Object.assign({}, DEFAULTS); + if (!cfg) cfg = Object.assign({}, DEFAULTS); + + if (!cfg.ssh_secret_key) { + cfg.ssh_secret_key = _generateSshSecretKey(); + save(cfg); + } + + return cfg; } function save(data) { diff --git a/lib/frpc.js b/lib/frpc.js index cb24d96..cda6f89 100644 --- a/lib/frpc.js +++ b/lib/frpc.js @@ -143,9 +143,16 @@ function downloadFile(url, dest) { }); } -function writeFrpcConfig(clawId, frpConfig) { +function writeFrpcConfig(clawId, frpConfig, sshSecretKey) { const { auth_token, dashboard_local_port = 18789 } = frpConfig; const ttyRemotePort = 10000 + Number(clawId); + const stcpBlock = sshSecretKey ? ` +[[proxies]] +name = "ssh-${clawId}-secret" +type = "stcp" +secretKey = "${sshSecretKey}" +localPort = 22 +` : ''; const toml = `# 由 clawd 自动生成,请勿手动修改 serverAddr = "frp.claw.cutos.ai" serverPort = 443 @@ -168,10 +175,10 @@ name = "tty-${clawId}" type = "tcp" localPort = ${TTYD_PORT} remotePort = ${ttyRemotePort} -`; +${stcpBlock}`; fs.mkdirSync(CONFIG_DIR, { recursive: true }); fs.writeFileSync(FRPC_CONFIG, toml, 'utf8'); - log.info('frpc', `frpc.toml 已写入: dashboard subdomain=${clawId}, tty tcp-port=${ttyRemotePort}`); + log.info('frpc', `frpc.toml 已写入: dashboard subdomain=${clawId}, tty tcp-port=${ttyRemotePort}${sshSecretKey ? ', ssh stcp=enabled' : ''}`); } /** @@ -183,7 +190,7 @@ class FrpcManager { this._watchdog = null; } - async start(clawId, frpConfig) { + async start(clawId, frpConfig, sshSecretKey) { this.stop(); if (!fs.existsSync(FRPC_BIN)) { @@ -195,7 +202,7 @@ class FrpcManager { } } - writeFrpcConfig(clawId, frpConfig); + writeFrpcConfig(clawId, frpConfig, sshSecretKey); this._watchdog = new Watchdog('frpc', FRPC_BIN, ['-c', FRPC_CONFIG], { maxRestarts: 10,