diff --git a/README.md b/README.md index 640add3..385429d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Claw Box 守护进程,将本地 Linux 设备通过 WebSocket 长连接接入 [ | 7 | `lib/metrics.js` | 系统指标采集(systeminformation) | | 8 | `lib/frpc.js` | 下载 frpc、写 `frpc.toml`、HTTP 子域转发 dashboard、TCP 转发 ttyd;Watchdog | | 9 | `lib/frpc.js`(ttyd) | 本机 Web 终端(`CLAWD_TTY_USER`,默认 `sts`),供 frp 反代 | -| 10 | OpenClaw 联动 | 激活后更新 `~/.openclaw/config/config.yaml` 中 gateway origin,可选重启 gateway | +| 10 | OpenClaw 联动 | 激活后更新 `~/.openclaw/openclaw.json` 中 gateway origin(字符串字段内 URL),可选重启 gateway | | 11 | `lib/provisioning.js` | AP 配网:等待 NM 重连、有线先连云端再后台开 AP、WiFi 断再开 AP | | 12 | `lib/network.js` | `nmcli`:联网检测、WiFi 扫描/连接、开/关热点 | | 13 | `lib/dns-hijack.js` | Captive Portal:NM `dnsmasq-shared.d`(install 预写) | diff --git a/install.sh b/install.sh index 254f16f..db5cae5 100644 --- a/install.sh +++ b/install.sh @@ -187,7 +187,8 @@ Wants=NetworkManager.service [Service] Type=simple EnvironmentFile=$ENV_FILE -ExecStartPre=/bin/sh -c 'echo 1 > /sys/devices/platform/openvfd/attr/b1 2>/dev/null; echo 1 > /sys/devices/platform/openvfd/attr/b2 2>/dev/null; true' +# 部分机型无 openvfd 或路径不同;不存在则跳过(3528 等新板可忽略) +ExecStartPre=/bin/sh -c 'for p in /sys/devices/platform/openvfd/attr/b1 /sys/devices/platform/openvfd/attr/b2; do [ -e "$p" ] && echo 1 >"$p" 2>/dev/null || true; done' ExecStart=$NODE_BIN $INSTALL_DIR/bin/clawd.js WorkingDirectory=$INSTALL_DIR diff --git a/lib/client.js b/lib/client.js index 2bd95e9..bc857fc 100644 --- a/lib/client.js +++ b/lib/client.js @@ -6,7 +6,7 @@ const config = require('./config'); const log = require('./logger'); const { getBoxId } = require('./fingerprint'); const { collect } = require('./metrics'); -const { getDashboardInfo, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新 +const { getDashboardInfo, resolveOpenclawConfigFile, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新 const { ProvisionManager } = require('./provisioning'); const { BtMonitor } = require('./bt-monitor'); const { hasInternet, getLocalIps } = require('./network'); @@ -407,30 +407,65 @@ class ClawClient { // ── OpenClaw 配置 ──────────────────────────────────────────────────────────── _updateOpenClawOrigin(targetId) { - const { existsSync, readFileSync, writeFileSync } = require('fs'); - const configFile = '/home/sts/.openclaw/config/config.yaml'; + const { readFileSync, writeFileSync } = require('fs'); + const configFile = resolveOpenclawConfigFile(); - if (!existsSync(configFile)) { - log.warn('clawd', `openclaw 配置文件不存在: ${configFile}`); + if (!configFile) { + log.warn('clawd', 'openclaw 配置文件不存在(~/.openclaw/openclaw.json 等候选路径均未找到)'); return; } try { - const content = readFileSync(configFile, 'utf8'); + const raw = readFileSync(configFile, 'utf8'); + const config = JSON.parse(raw); const newOrigin = `https://${targetId}.claw.cutos.ai`; + const re = /https:\/\/[^"'\s]+\.claw\.cutos\.ai/g; - // 替换 https://任意子域.claw.cutos.ai 为目标域名 - const updated = content.replace( - /https:\/\/[^"'\s]+\.claw\.cutos\.ai/g, - newOrigin - ); + /** 与原 YAML 全文替换等价:遍历 JSON 内所有字符串并替换匹配的 origin */ + const replaceOriginStrings = (node) => { + let changed = false; + if (typeof node === 'string') { + return false; + } + if (Array.isArray(node)) { + for (let i = 0; i < node.length; i++) { + const v = node[i]; + if (typeof v === 'string') { + const next = v.replace(re, newOrigin); + if (next !== v) { + node[i] = next; + changed = true; + } + } else if (v && typeof v === 'object') { + changed = replaceOriginStrings(v) || changed; + } + } + return changed; + } + if (node && typeof node === 'object') { + for (const k of Object.keys(node)) { + const v = node[k]; + if (typeof v === 'string') { + const next = v.replace(re, newOrigin); + if (next !== v) { + node[k] = next; + changed = true; + } + } else if (v && typeof v === 'object') { + changed = replaceOriginStrings(v) || changed; + } + } + return changed; + } + return false; + }; - if (updated === content) { + if (!replaceOriginStrings(config)) { log.info('clawd', `openclaw origin 已是 ${newOrigin},无需变更`); return; } - writeFileSync(configFile, updated, 'utf8'); + writeFileSync(configFile, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); log.info('clawd', `openclaw config 已更新: ${newOrigin}`); // 文件有变化,kill -9 openclaw-gateway,让它被 systemd --user 自动拉起 diff --git a/lib/frpc.js b/lib/frpc.js index 3e5264e..0c83594 100644 --- a/lib/frpc.js +++ b/lib/frpc.js @@ -18,24 +18,36 @@ const FRP_VERSION = '0.62.0'; const TTYD_VERSION = '1.7.7'; const TTYD_PORT = 7681; +/** openclaw 持久化配置(JSON),结构与原 YAML 解析结果一致。 */ +const OPENCLAW_JSON_CANDIDATES = [ + path.join(os.homedir(), '.openclaw', 'openclaw.json'), + '/home/sts/.openclaw/openclaw.json', + '/root/.openclaw/openclaw.json', +]; + +/** + * 解析到第一个存在的 openclaw.json 路径(与 dashboard / 联动更新共用)。 + */ +function resolveOpenclawConfigFile() { + for (const p of OPENCLAW_JSON_CANDIDATES) { + try { + if (fs.existsSync(p)) return p; + } catch (_) { /* ignore */ } + } + return null; +} + /** * 从 openclaw 配置文件中提取 dashboard token 和端口。 - * openclaw 将配置持久化存储在 ~/.openclaw/config/config.yaml 中, + * openclaw 将配置持久化存储在 ~/.openclaw/openclaw.json 中, * 直接读取比执行命令更可靠(不依赖 PATH、不需要进程启动等待)。 * systemd 服务的 ProtectHome=read-only 允许读取 /home 下的文件。 */ function getDashboardInfo() { - const yaml = require('js-yaml'); - const configCandidates = [ - path.join(os.homedir(), '.openclaw', 'config', 'config.yaml'), - '/home/sts/.openclaw/config/config.yaml', - '/root/.openclaw/config/config.yaml', - ]; - - for (const cfgPath of configCandidates) { + for (const cfgPath of OPENCLAW_JSON_CANDIDATES) { try { const raw = fs.readFileSync(cfgPath, 'utf8'); - const config = yaml.load(raw); + const config = JSON.parse(raw); const token = config?.gateway?.auth?.token; const port = config?.gateway?.port || 18789; if (token) { @@ -206,4 +218,4 @@ class FrpcManager { } } -module.exports = { getDashboardInfo, startTtyd, FrpcManager }; +module.exports = { getDashboardInfo, resolveOpenclawConfigFile, startTtyd, FrpcManager }; diff --git a/package-lock.json b/package-lock.json index 045dde7..3d00e13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "js-yaml": "^4.1.1", "ssh2": "^1.17.0", "systeminformation": "^5.25.0", "ws": "^8.18.0" @@ -21,12 +20,6 @@ "node": ">=18.0.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -68,18 +61,6 @@ "node": ">=10.0.0" } }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/nan": { "version": "2.26.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.1.tgz", diff --git a/package.json b/package.json index c7d394a..96cbd50 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "node": ">=18.0.0" }, "dependencies": { - "js-yaml": "^4.1.1", "ssh2": "^1.17.0", "systeminformation": "^5.25.0", "ws": "^8.18.0" diff --git a/tools/deploy-rsync.sh b/tools/deploy-rsync.sh new file mode 100644 index 0000000..d9277a6 --- /dev/null +++ b/tools/deploy-rsync.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# 将本机 clawd 源码同步到已安装过 clawd 的盒子并重启服务。 +# +# 前置:设备上已至少执行过一次 install.sh(/opt/clawd、systemd、/etc/clawd 已就绪)。 +# 通用功能可先跑通;VFD/LED 与 3566 不一致时,进程内会打 warn,不影响 WS/配网/frp。 +# +# 用法(在仓库根或 tools 下): +# bash tools/deploy-rsync.sh +# TARGET_HOST=192.168.1.105 TARGET_USER=sts bash tools/deploy-rsync.sh +# +# 依赖:本机 ssh、rsync;设备上 sts 能 sudo(首次 sudo 会提示密码)。 +# +set -euo pipefail + +TARGET_HOST="${TARGET_HOST:-192.168.1.104}" +TARGET_USER="${TARGET_USER:-sts}" +REMOTE_TMP="/home/${TARGET_USER}/clawd-rsync-staging" +REMOTE_OPT="/opt/clawd" + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +echo "[deploy] ${ROOT}/ -> ${TARGET_USER}@${TARGET_HOST}:${REMOTE_TMP} -> sudo ${REMOTE_OPT}" + +rsync -avz --delete \ + --exclude node_modules \ + --exclude .git \ + --exclude .cursor \ + --exclude '*.log' \ + "${ROOT}/" "${TARGET_USER}@${TARGET_HOST}:${REMOTE_TMP}/" + +ssh "${TARGET_USER}@${TARGET_HOST}" \ + "sudo rsync -a ${REMOTE_TMP}/ ${REMOTE_OPT}/ && \ + sudo npm install --prefix ${REMOTE_OPT} --omit=dev && \ + sudo systemctl restart clawd && \ + sudo systemctl --no-pager -l status clawd || true" + +echo "[deploy] 完成。跟踪日志: ssh ${TARGET_USER}@${TARGET_HOST} 'sudo journalctl -u clawd -f'"