diff --git a/lib/client.js b/lib/client.js index d72a36c..755eb99 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,10 +1,13 @@ 'use strict'; -const fs = require('fs'); +const fs = require('fs'); +const path = require('path'); const { getNotifySocket } = require('./systemd-env'); const WebSocket = require('ws'); const { execFileSync, exec } = require('child_process'); const config = require('./config'); + +const CLAWD_VERSION = require(path.join(__dirname, '..', 'package.json')).version; const log = require('./logger'); const { getBoxId } = require('./fingerprint'); const { collect } = require('./metrics'); @@ -371,6 +374,7 @@ class ClawClient { box_id: this._boxId, claw_id: this._cfg.claw_id ?? null, token: this._cfg.token ?? null, + version: CLAWD_VERSION, local_ip: getLocalIps(), local_networks: getLocalNetworks(), external_ip: this._externalIp ?? null, @@ -392,6 +396,9 @@ class ClawClient { case 'status_update': this._applyStatus(msg); break; + case 'upgrade': + this._handleUpgrade(msg); + break; case 'error': log.error('clawd', `服务器错误: ${msg.msg}`); if (msg.msg === 'hardware_mismatch') { @@ -598,6 +605,7 @@ class ClawClient { type: 'heartbeat', claw_id: this._cfg.claw_id, token: this._cfg.token, + version: CLAWD_VERSION, local_ip: getLocalIps(), local_networks: getLocalNetworks(), ...this._dashInfo, @@ -618,6 +626,80 @@ class ClawClient { } } + // ── 升级 ──────────────────────────────────────────────────────────────────── + + _sendUpgradeProgress(progress, step, failed = false, errorMsg = null) { + const msg = { + type: 'upgrade_progress', + claw_id: this._cfg.claw_id, + token: this._cfg.token, + progress, + step, + failed, + }; + if (errorMsg) msg.error = errorMsg; + this._send(msg); + log.info('upgrade', `进度 ${progress}% - ${step}${failed ? ` [失败: ${errorMsg}]` : ''}`); + } + + async _handleUpgrade(msg) { + const targetVersion = msg.version; + const installDir = path.dirname(__dirname); // /opt/clawd 或同等安装目录 + const scriptPath = path.join(installDir, 'tools', 'update-clawd.sh'); + + log.info('upgrade', `收到升级命令: ${CLAWD_VERSION} → ${targetVersion}`); + this._sendUpgradeProgress(5, 'starting'); + + // 检查脚本是否存在 + if (!fs.existsSync(scriptPath)) { + const err = `升级脚本不存在: ${scriptPath}`; + log.error('upgrade', err); + this._sendUpgradeProgress(0, 'failed', true, err); + return; + } + + try { + await new Promise((resolve, reject) => { + const child = exec(`bash "${scriptPath}" --no-restart`, { timeout: 300_000 }); + + child.stdout.on('data', (data) => { + const line = data.toString().trim(); + log.info('upgrade', line); + + // 根据脚本输出关键字上报进度 + if (line.includes('Fetching latest')) this._sendUpgradeProgress(20, '拉取更新中'); + else if (line.includes('Already up to date')) this._sendUpgradeProgress(100, 'already_up_to_date'); + else if (line.includes('Updating working tree')) this._sendUpgradeProgress(50, '更新文件中'); + else if (line.includes('npm install')) this._sendUpgradeProgress(70, '安装依赖中'); + else if (line.includes('No dependency')) this._sendUpgradeProgress(80, '无需安装依赖'); + else if (line.includes('Current commit')) this._sendUpgradeProgress(90, '即将重启'); + }); + + child.stderr.on('data', (data) => { + log.warn('upgrade', data.toString().trim()); + }); + + child.on('close', (code) => { + if (code === 0) resolve(); + else reject(new Error(`脚本退出码: ${code}`)); + }); + + child.on('error', reject); + }); + + // 脚本执行成功,通知服务端完成,然后退出让 systemd 重启 + this._sendUpgradeProgress(100, 'done'); + log.info('upgrade', `升级至 v${targetVersion} 完成,即将重启...`); + + // 延迟 1.5 秒确保进度消息送达,再退出 + setTimeout(() => process.exit(0), 1500); + + } catch (e) { + log.error('upgrade', `升级失败: ${e.message}`); + this._sendUpgradeProgress(0, 'failed', true, e.message); + } + } + // ── 工具 ──────────────────────────────────────────────────────────────────── _send(obj) { diff --git a/tools/update-clawd.sh b/tools/update-clawd.sh index aa79481..1377316 100644 --- a/tools/update-clawd.sh +++ b/tools/update-clawd.sh @@ -5,6 +5,13 @@ REPO_DIR="/opt/clawd" REMOTE="origin" BRANCH="main" SERVICE="clawd.service" +NO_RESTART=false + +for arg in "$@"; do + case "$arg" in + --no-restart) NO_RESTART=true ;; + esac +done echo "==> clawd update start" date @@ -41,13 +48,18 @@ else echo "==> No dependency changes, skip npm install" fi +echo "==> Current commit:" +git log --oneline -1 + +if [ "$NO_RESTART" = true ]; then + echo "==> --no-restart: skip systemctl restart (caller handles restart)" + exit 0 +fi + echo "==> Restarting service: $SERVICE" systemctl restart "$SERVICE" echo "==> Service status:" systemctl status "$SERVICE" --no-pager -l || true -echo "==> Current commit:" -git log --oneline -1 - echo "==> clawd update done" \ No newline at end of file