feat(openclaw): persist config as ~/.openclaw/openclaw.json
- Read gateway token/port via JSON.parse (same structure as former YAML) - Update claw.cutos.ai origin by replacing URLs in JSON string fields - Export resolveOpenclawConfigFile() for frpc + client - Drop js-yaml dependency - install: ExecStartPre touches openvfd only if nodes exist (3528-friendly) - Add tools/deploy-rsync.sh for syncing /opt/clawd over SSH Made-with: Cursor
This commit is contained in:
@@ -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 预写) |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 自动拉起
|
||||
|
||||
34
lib/frpc.js
34
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 };
|
||||
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
37
tools/deploy-rsync.sh
Normal file
37
tools/deploy-rsync.sh
Normal file
@@ -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'"
|
||||
Reference in New Issue
Block a user