feat: SSH STCP key generation and frp tunnel registration
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -375,6 +375,7 @@ class ClawClient {
|
|||||||
claw_id: this._cfg.claw_id ?? null,
|
claw_id: this._cfg.claw_id ?? null,
|
||||||
token: this._cfg.token ?? null,
|
token: this._cfg.token ?? null,
|
||||||
version: CLAWD_VERSION,
|
version: CLAWD_VERSION,
|
||||||
|
ssh_secret_key: this._cfg.ssh_secret_key ?? null,
|
||||||
local_ip: getLocalIps(),
|
local_ip: getLocalIps(),
|
||||||
local_networks: getLocalNetworks(),
|
local_networks: getLocalNetworks(),
|
||||||
external_ip: this._externalIp ?? null,
|
external_ip: this._externalIp ?? null,
|
||||||
@@ -437,7 +438,7 @@ class ClawClient {
|
|||||||
this._applyStatus(msg);
|
this._applyStatus(msg);
|
||||||
|
|
||||||
if (msg.frp && msg.frp.server && msg.frp.auth_token) {
|
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);
|
log.error('frpc', '启动失败:', e.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,33 @@ const DEFAULTS = {
|
|||||||
heartbeat_interval: 30, // 秒
|
heartbeat_interval: 30, // 秒
|
||||||
/** 云端已激活:用于启动/重连时立即点亮 alarm(pwr),不等首包 connected */
|
/** 云端已激活:用于启动/重连时立即点亮 alarm(pwr),不等首包 connected */
|
||||||
activated: false,
|
activated: false,
|
||||||
|
ssh_secret_key: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function _generateSshSecretKey() {
|
||||||
|
const bytes = require('crypto').randomBytes(16);
|
||||||
|
return 'sk-' + bytes.toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
|
let cfg;
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(CONFIG_FILE)) {
|
if (fs.existsSync(CONFIG_FILE)) {
|
||||||
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
||||||
return Object.assign({}, DEFAULTS, JSON.parse(raw));
|
cfg = Object.assign({}, DEFAULTS, JSON.parse(raw));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
log.error('config', '读取配置失败,使用默认值:', e.message);
|
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) {
|
function save(data) {
|
||||||
|
|||||||
17
lib/frpc.js
17
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 { auth_token, dashboard_local_port = 18789 } = frpConfig;
|
||||||
const ttyRemotePort = 10000 + Number(clawId);
|
const ttyRemotePort = 10000 + Number(clawId);
|
||||||
|
const stcpBlock = sshSecretKey ? `
|
||||||
|
[[proxies]]
|
||||||
|
name = "ssh-${clawId}-secret"
|
||||||
|
type = "stcp"
|
||||||
|
secretKey = "${sshSecretKey}"
|
||||||
|
localPort = 22
|
||||||
|
` : '';
|
||||||
const toml = `# 由 clawd 自动生成,请勿手动修改
|
const toml = `# 由 clawd 自动生成,请勿手动修改
|
||||||
serverAddr = "frp.claw.cutos.ai"
|
serverAddr = "frp.claw.cutos.ai"
|
||||||
serverPort = 443
|
serverPort = 443
|
||||||
@@ -168,10 +175,10 @@ name = "tty-${clawId}"
|
|||||||
type = "tcp"
|
type = "tcp"
|
||||||
localPort = ${TTYD_PORT}
|
localPort = ${TTYD_PORT}
|
||||||
remotePort = ${ttyRemotePort}
|
remotePort = ${ttyRemotePort}
|
||||||
`;
|
${stcpBlock}`;
|
||||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||||
fs.writeFileSync(FRPC_CONFIG, toml, 'utf8');
|
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;
|
this._watchdog = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(clawId, frpConfig) {
|
async start(clawId, frpConfig, sshSecretKey) {
|
||||||
this.stop();
|
this.stop();
|
||||||
|
|
||||||
if (!fs.existsSync(FRPC_BIN)) {
|
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], {
|
this._watchdog = new Watchdog('frpc', FRPC_BIN, ['-c', FRPC_CONFIG], {
|
||||||
maxRestarts: 10,
|
maxRestarts: 10,
|
||||||
|
|||||||
Reference in New Issue
Block a user