fix: read dashboard token from ~/.openclaw/openclaw.json instead of running openclaw dashboard command
Made-with: Cursor
This commit is contained in:
@@ -6,7 +6,7 @@ const config = require('./config');
|
|||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
const { getBoxId } = require('./fingerprint');
|
const { getBoxId } = require('./fingerprint');
|
||||||
const { collect } = require('./metrics');
|
const { collect } = require('./metrics');
|
||||||
const { getDashboardInfo, startTtyd, FrpcManager } = require('./frpc');
|
const { getDashboardInfo, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新
|
||||||
const { ProvisionManager } = require('./provisioning');
|
const { ProvisionManager } = require('./provisioning');
|
||||||
const { hasInternet } = require('./network');
|
const { hasInternet } = require('./network');
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ class ClawClient {
|
|||||||
this._stopped = false;
|
this._stopped = false;
|
||||||
this._frpc = new FrpcManager();
|
this._frpc = new FrpcManager();
|
||||||
this._dashInfo = {};
|
this._dashInfo = {};
|
||||||
|
this._hbCount = 0; // 心跳计数器,用于定期刷新 dashboard 信息
|
||||||
|
|
||||||
// WS 层活性检测
|
// WS 层活性检测
|
||||||
this._pingTimer = null;
|
this._pingTimer = null;
|
||||||
@@ -268,12 +269,24 @@ class ClawClient {
|
|||||||
async _sendHeartbeat() {
|
async _sendHeartbeat() {
|
||||||
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
|
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
|
||||||
try {
|
try {
|
||||||
|
this._hbCount++;
|
||||||
|
|
||||||
|
// 每 10 次心跳(约 5 分钟)刷新一次 dashboard 信息,
|
||||||
|
// 确保初次提取失败时能自动补偿,或在 token 变化后自动同步
|
||||||
|
if (this._hbCount % 10 === 0) {
|
||||||
|
const freshInfo = await getDashboardInfo().catch(() => null);
|
||||||
|
if (freshInfo && Object.keys(freshInfo).length > 0) {
|
||||||
|
this._dashInfo = freshInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const metrics = await collect();
|
const metrics = await collect();
|
||||||
this._send({
|
this._send({
|
||||||
type: 'heartbeat',
|
type: 'heartbeat',
|
||||||
claw_id: this._cfg.claw_id,
|
claw_id: this._cfg.claw_id,
|
||||||
token: this._cfg.token,
|
token: this._cfg.token,
|
||||||
metrics,
|
metrics,
|
||||||
|
...this._dashInfo, // 携带 dashboard_token / dashboard_port,供 VPS 幂等更新
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('clawd', '心跳发送失败:', e.message);
|
log.error('clawd', '心跳发送失败:', e.message);
|
||||||
|
|||||||
52
lib/frpc.js
52
lib/frpc.js
@@ -19,43 +19,33 @@ const TTYD_VERSION = '1.7.7';
|
|||||||
const TTYD_PORT = 7681;
|
const TTYD_PORT = 7681;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动 openclaw dashboard(后台运行),轮询日志文件等待 Dashboard URL 出现,
|
* 从 openclaw 配置文件中提取 dashboard token 和端口。
|
||||||
* 解析并返回 { dashboard_token, dashboard_port }。
|
* openclaw 将 gateway token 持久化存储在 ~/.openclaw/openclaw.json 中,
|
||||||
* 超时(10s)或命令不存在时返回 {}。
|
* 直接读取比执行命令更可靠(不依赖 PATH、不需要进程启动等待)。
|
||||||
|
* systemd 服务的 ProtectHome=read-only 允许读取 /home 下的文件。
|
||||||
*/
|
*/
|
||||||
function getDashboardInfo() {
|
function getDashboardInfo() {
|
||||||
return new Promise((resolve) => {
|
const configCandidates = [
|
||||||
const tmpLog = '/tmp/clawd-dashboard.log';
|
path.join(os.homedir(), '.openclaw', 'openclaw.json'),
|
||||||
|
'/home/sts/.openclaw/openclaw.json',
|
||||||
|
'/root/.openclaw/openclaw.json',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const cfgPath of configCandidates) {
|
||||||
try {
|
try {
|
||||||
execSync(`openclaw dashboard > ${tmpLog} 2>&1 &`, { shell: true, timeout: 3000 });
|
const raw = fs.readFileSync(cfgPath, 'utf8');
|
||||||
} catch (e) {
|
const config = JSON.parse(raw);
|
||||||
// 已在运行或命令不存在,继续轮询
|
const token = config?.gateway?.auth?.token;
|
||||||
|
const port = config?.gateway?.port || 18789;
|
||||||
|
if (token) {
|
||||||
|
log.info('dashboard', `从配置文件读取: port=${port}, token=${token.substring(0, 8)}...`);
|
||||||
|
return Promise.resolve({ dashboard_port: port, dashboard_token: token });
|
||||||
|
}
|
||||||
|
} catch (_) { /* 文件不存在或格式错误,尝试下一个路径 */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
let attempts = 0;
|
log.debug('dashboard', 'openclaw 配置文件未找到或无 token,跳过 dashboard 信息获取');
|
||||||
const interval = setInterval(() => {
|
return Promise.resolve({});
|
||||||
attempts++;
|
|
||||||
try {
|
|
||||||
const content = fs.readFileSync(tmpLog, 'utf8');
|
|
||||||
const match = content.match(/Dashboard URL:.*:(\d+)\/#token=([a-f0-9]+)/);
|
|
||||||
if (match) {
|
|
||||||
clearInterval(interval);
|
|
||||||
const port = parseInt(match[1], 10);
|
|
||||||
const token = match[2];
|
|
||||||
log.info('dashboard', `openclaw dashboard: port=${port}, token=${token.substring(0, 8)}...`);
|
|
||||||
resolve({ dashboard_port: port, dashboard_token: token });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) { /* 文件暂时不存在 */ }
|
|
||||||
|
|
||||||
if (attempts >= 10) {
|
|
||||||
clearInterval(interval);
|
|
||||||
log.debug('dashboard', 'openclaw dashboard 未检测到,跳过');
|
|
||||||
resolve({});
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadFrpc() {
|
async function downloadFrpc() {
|
||||||
|
|||||||
Reference in New Issue
Block a user