diff --git a/.github/workflows/build-node-musl.yml b/.github/workflows/build-node-musl.yml
index 9b271b6..b549cec 100644
--- a/.github/workflows/build-node-musl.yml
+++ b/.github/workflows/build-node-musl.yml
@@ -2,41 +2,57 @@ name: Build Node.js ARM64 musl
on:
workflow_dispatch:
+ inputs:
+ build_v1:
+ description: 'Build V1 (22.15.1) - Legacy compatibility'
+ required: false
+ default: true
+ type: boolean
+ build_v2:
+ description: 'Build V2 (22.16.0) - Current version'
+ required: false
+ default: true
+ type: boolean
jobs:
- build:
- name: Build Node.js ARM64 musl
+ # ── V1 构建: 使用 Alpine apk 模式 (22.15.1) ──
+ build-v1:
+ name: Build Node.js V1 (Legacy)
+ if: ${{ inputs.build_v1 }}
runs-on: ubuntu-latest
permissions:
contents: write
+ outputs:
+ artifact_name: ${{ steps.build_info.outputs.artifact_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- - name: Extract Node.js version from source
- id: node_ver
- run: |
- NODE_VER=$(grep -oP 'NODE_VERSION="\$\{NODE_VERSION:-\K[0-9.]+' root/usr/bin/openclaw-env)
- echo "version=${NODE_VER}" >> "$GITHUB_OUTPUT"
- echo "Node.js version from openclaw-env: v${NODE_VER}"
-
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/arm64
- - name: Build Node.js ARM64 musl portable
+ - name: Build Node.js V1 ARM64 musl (apk mode)
run: |
- NODE_VER="${{ steps.node_ver.outputs.version }}"
+ NODE_VER="22.15.1"
mkdir -p dist
- echo "=== Building Node.js v${NODE_VER} ARM64 musl portable tarball ==="
+ echo "=== Building Node.js v${NODE_VER} ARM64 musl (apk mode) ==="
docker run --rm --platform linux/arm64 \
-v "$PWD/dist:/output" \
-v "$PWD/scripts/build-node-musl.sh:/build-node-musl.sh:ro" \
-e "NODE_VER=${NODE_VER}" \
+ -e "BUILD_MODE=apk" \
alpine:3.21 sh /build-node-musl.sh
+ - name: Build info
+ id: build_info
+ run: |
+ ARTIFACT_NAME=$(ls dist/*.tar.xz | head -1 | xargs basename)
+ echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
+ echo "Artifact: ${ARTIFACT_NAME}"
+
- name: Verify tarball
run: |
echo "=== Output files ==="
@@ -48,9 +64,90 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
- name: node-arm64-musl
+ name: node-arm64-musl-v1
path: dist/*.tar.xz
+ # ── V2 构建: 使用交叉编译模式 (22.16.0) ──
+ build-v2:
+ name: Build Node.js V2 (Current)
+ if: ${{ inputs.build_v2 }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ outputs:
+ artifact_name: ${{ steps.build_info.outputs.artifact_name }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: linux/arm64
+
+ - name: Build Node.js V2 ARM64 musl (cross mode)
+ run: |
+ NODE_VER="22.16.0"
+ mkdir -p dist
+ echo "=== Building Node.js v${NODE_VER} ARM64 musl (cross mode) ==="
+ docker run --rm --platform linux/arm64 \
+ -v "$PWD/dist:/output" \
+ -v "$PWD/scripts/build-node-musl.sh:/build-node-musl.sh:ro" \
+ -e "NODE_VER=${NODE_VER}" \
+ -e "BUILD_MODE=cross" \
+ alpine:3.21 sh /build-node-musl.sh
+
+ - name: Build info
+ id: build_info
+ run: |
+ ARTIFACT_NAME=$(ls dist/*.tar.xz | head -1 | xargs basename)
+ echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
+ echo "Artifact: ${ARTIFACT_NAME}"
+
+ - name: Verify tarball
+ run: |
+ echo "=== Output files ==="
+ ls -lh dist/
+ echo ""
+ echo "=== Tarball contents (first 20 entries) ==="
+ tar tJf dist/*.tar.xz | head -20
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: node-arm64-musl-v2
+ path: dist/*.tar.xz
+
+ # ── 发布到 GitHub Release ──
+ release:
+ name: Create/Update Release
+ needs: [build-v1, build-v2]
+ if: always() && (needs.build-v1.result == 'success' || needs.build-v2.result == 'success')
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Download V1 artifact
+ if: ${{ needs.build-v1.result == 'success' }}
+ uses: actions/download-artifact@v4
+ with:
+ name: node-arm64-musl-v1
+ path: dist/
+
+ - name: Download V2 artifact
+ if: ${{ needs.build-v2.result == 'success' }}
+ uses: actions/download-artifact@v4
+ with:
+ name: node-arm64-musl-v2
+ path: dist/
+
+ - name: List all artifacts
+ run: |
+ echo "=== All artifacts ==="
+ ls -lh dist/
+
- name: Create/Update Release
uses: softprops/action-gh-release@v2
with:
@@ -66,7 +163,16 @@ jobs:
Node.js 官方 [unofficial-builds](https://unofficial-builds.nodejs.org/) 仅提供 x64 musl 构建,不提供 ARM64 musl。
此 Release 使用 Alpine Linux ARM64 (musl libc) 环境打包。
- **注意**: 文件名中的版本号为 Alpine `apk add nodejs` 实际安装的版本,
- 构建时自动从 `openclaw-env` 读取。
+ ## 版本说明
- `openclaw-env setup` 会在 ARM64 musl 设备上自动从此处下载。
+ | 文件 | 版本 | 用途 |
+ |------|------|------|
+ | `node-v22.16.0-linux-arm64-musl.tar.xz` | V2 (当前) | OpenClaw v2026.3.11+ (要求 >= 22.16.0) |
+ | `node-v22.15.1-linux-arm64-musl.tar.xz` | V1 (旧版) | OpenClaw v2026.3.8 及更早版本 |
+
+ ## 构建模式
+
+ - **V2 (22.16.0)**: 交叉编译模式,从 Node.js 官方 glibc 版本转换为 musl
+ - **V1 (22.15.1)**: Alpine apk 模式,使用 Alpine 仓库的 Node.js
+
+ `openclaw-env setup` 会在 ARM64 musl 设备上自动从此处下载合适的版本。
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff38197..ceaf299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,83 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
+## [2.0.0] - 2026-03-16
+
+### 重大变更
+- **配置管理菜单重构**: 主菜单采用分组样式,更清晰的导航结构
+ - AI 模型配置:配置 AI 模型和提供商、设置活动模型
+ - 消息渠道:配置消息渠道 (电报/QQ/飞书)
+ - 系统管理:健康检查与状态、查看日志、重启 Gateway
+ - 高级选项:高级配置、重置配置、显示当前配置概览
+- **新增高级配置菜单**: 独立的高级配置入口,包含:
+ - Gateway 端口/绑定地址/运行模式配置
+ - 日志级别设置
+ - ACP Dispatch 开关
+ - 官方完整配置向导入口
+ - 原始 JSON 查看/编辑
+ - 配置备份导出/导入
+
+### 修复
+- **QQ 机器人插件配置名称不匹配** (#XX): OpenClaw v2026.3.13 加强了配置验证,`plugins.allow` 中的插件名称必须与实际安装的插件名完全匹配
+ - 问题:旧版本写入的是 `openclaw-qqbot`,但实际插件名是 `@tencent-connect/openclaw-qqbot`
+ - 影响:配置验证失败导致 Gateway 启动后立即退出,procd 进入 crash loop 保护
+ - 修复:新增 `fix_plugin_config` 函数自动检测并修正不匹配的插件名称
+ - 修复:`configure_qq` 安装插件后调用 `ensure_qqbot_plugin_allowed` 确保正确的插件名写入配置
+ - 修复:`init.d` 服务启动前自动修复配置中的插件名称
+- **安装运行环境报错** (#28): 部分系统缺少 `libstdcpp6` 导致 Node.js 无法运行,安装 pnpm 时卡住
+ - 依赖声明新增 `libstdcpp6`,安装时自动拉取 C++ 标准库
+ - Node.js 验证逻辑改进:检测到运行失败时提示缺失的库并给出修复命令
+- **环境变量路径混乱** (#42): 用户通过 SSH 直接运行 `openclaw` 命令时,CLI 使用默认 `HOME=/root` 导致配置文件和 skills 散落在 `/root/.openclaw/` 而非正确的 `/opt/openclaw/data/.openclaw/`
+ - 新增 `/etc/profile.d/openclaw.sh` 全局环境变量脚本,SSH 登录后自动设置正确的 PATH、OPENCLAW_HOME 等变量
+ - 升级时自动迁移 `/root/.openclaw/` 下的 skills、sessions、openclaw.json 到正确路径
+ - 用户现在可以直接运行 `npm`、`npx`、`openclaw config` 等命令
+
+### 新增
+- **全局环境变量**: `/etc/profile.d/openclaw.sh` 为 SSH 用户提供:
+ - PATH 包含 Node.js 和 OpenClaw bin 目录
+ - OPENCLAW_HOME、OPENCLAW_STATE_DIR、OPENCLAW_CONFIG_PATH 正确指向安装路径
+ - `openclaw` 命令别名(当全局安装时)
+- **查看日志功能**: 主菜单新增「查看日志」选项,显示最近 100 条 OpenClaw 日志
+
+### 变更
+- Makefile 新增 `/etc/profile.d/openclaw.sh` 安装步骤
+- 依赖声明新增 `libstdcpp6`
+- **飞书 Bot 配置流程优化**: 参考 QQ Bot 实现,大幅简化配置步骤
+ - 新增 App ID 格式验证(`cli_xxx` 格式)
+ - 新增 App Secret 长度检查
+ - 使用 OpenClaw CLI 一键配置(`oc_cmd channels add --channel feishu`)
+ - 配置保存后自动验证
+ - 新增详细的事件订阅、权限配置、插件安装指引
+
+### 适配 OpenClaw v2026.3.13
+
+#### 升级说明
+- **Node.js 版本升级**: 从 22.15.1 升级到 22.16.0 (OpenClaw v2026.3.11+ 最低要求)
+- **OpenClaw 版本升级**: 从 v2026.3.8 升级到 v2026.3.13
+
+#### 重要安全修复
+- WebSocket 跨站劫持漏洞修复 (GHSA-5wcw-8jjv-m286)
+- 设备配对安全增强:切换到短期引导令牌 (GHSA-99qw-6mr3-36qr)
+- 命令审批安全加固:Unicode 不可见字符转义、执行检测规范化
+- 多渠道 Webhook 安全增强:飞书/LINE/Zalo 签名验证强化
+
+#### 新功能支持
+- **Fast Mode**: OpenAI/Anthropic 快速响应模式支持
+- **Control UI 重构**: 新版 Dashboard-v2 模块化界面
+- **Ollama 本地向导**: 支持本地或云端+本地混合模式
+- **Kubernetes 部署**: 新增 K8s 部署清单和文档
+- **Docker 时区**: 新增 `OPENCLAW_TZ` 环境变量支持
+
+#### 破坏性变更处理
+- **Cron 主动投递收紧**: 升级后建议运行 `openclaw doctor --fix` 迁移旧版 cron 存储
+- **插件安全策略**: 禁用隐式工作区插件自动加载,需显式信任决策
+- **Node.js 最低版本**: 要求 >= 22.16.0 (已在 openclaw-env 中更新)
+
+#### 配置兼容性
+- 现有配置文件完全兼容,无需手动迁移
+- 已预设 `gateway.controlUi.dangerouslyDisableDeviceAuth=true` 禁用设备认证
+- 已预设 `gateway.controlUi.allowInsecureAuth=true` 允许不安全认证
+
## [1.0.15] - 2026-03-13
### 修复
diff --git a/Makefile b/Makefile
index 23a04c5..6cdc6bb 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com>
PKG_LICENSE:=GPL-3.0
LUCI_TITLE:=OpenClaw AI 网关 LuCI 管理插件
-LUCI_DEPENDS:=+luci-compat +luci-base +curl +openssl-util +script-utils +tar
+LUCI_DEPENDS:=+luci-compat +luci-base +curl +openssl-util +script-utils +tar +libstdcpp6
LUCI_PKGARCH:=all
# 优先使用 luci.mk (feeds 模式), 不可用时回退 package.mk
@@ -52,6 +52,8 @@ define Package/$(PKG_NAME)/install
$(INSTALL_BIN) ./root/etc/uci-defaults/99-openclaw $(1)/etc/uci-defaults/99-openclaw
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./root/etc/init.d/openclaw $(1)/etc/init.d/openclaw
+ $(INSTALL_DIR) $(1)/etc/profile.d
+ $(INSTALL_DATA) ./root/etc/profile.d/openclaw.sh $(1)/etc/profile.d/openclaw.sh
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) ./root/usr/bin/openclaw-env $(1)/usr/bin/openclaw-env
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
diff --git a/README.md b/README.md
index f17246d..7752d06 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
在路由器上运行 OpenClaw,通过 LuCI 管理界面完成安装、配置和服务管理。
-

+
**系统要求**
diff --git a/VERSION b/VERSION
index a970716..227cea2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.15
+2.0.0
diff --git a/docs/UPGRADE_GUIDE_v2026.3.13.md b/docs/UPGRADE_GUIDE_v2026.3.13.md
new file mode 100644
index 0000000..218ae30
--- /dev/null
+++ b/docs/UPGRADE_GUIDE_v2026.3.13.md
@@ -0,0 +1,652 @@
+# OpenClaw 版本升级推进流程文档
+
+## 概述
+
+本文档详细记录了从 OpenClaw v2026.3.8 升级到 v2026.3.13 的完整分析和推进流程。
+
+| 项目 | 当前版本 | 目标版本 |
+|------|----------|----------|
+| OpenClaw | v2026.3.8 | v2026.3.13 |
+| 发布日期 | 2026-03-09 | 2026-03-14 |
+| Node.js 要求 | >= 22.x | >= 22.16.0 |
+
+---
+
+## 一、版本差异分析报告
+
+### 1.1 版本发布时间线
+
+| 版本 | 发布日期 | 类型 |
+|------|----------|------|
+| v2026.3.8 | 2026-03-09 | 稳定版 (当前) |
+| v2026.3.11 | 2026-03-12 | 稳定版 |
+| v2026.3.12 | 2026-03-13 | 稳定版 |
+| v2026.3.13 | 2026-03-14 | 稳定版 (目标) |
+
+### 1.2 关键变更摘要
+
+#### 🔴 重大变更 (Breaking Changes)
+
+1. **Node.js 最低版本要求提升**
+ - 旧版本: Node.js >= 22.x
+ - 新版本: Node.js >= 22.16.0
+ - **影响**: 当前项目 `NODE_VERSION="22.15.1"` 不满足要求,必须升级
+
+2. **Cron/主动投递收紧 (v2026.3.11)**
+ - 孤立的直接 cron 发送不再通过临时 agent 发送或主会话摘要通知
+ - 需要运行 `openclaw doctor --fix` 迁移旧版 cron 存储
+
+3. **插件安全策略变更 (v2026.3.12)**
+ - 禁用隐式工作区插件自动加载
+ - 克隆的仓库不能执行工作区插件代码,需要显式信任决策
+
+#### 🟡 安全修复
+
+| 编号 | 描述 | 影响范围 |
+|------|------|----------|
+| GHSA-5wcw-8jjv-m286 | WebSocket 跨站劫持漏洞修复 | Gateway 模式 |
+| GHSA-99qw-6mr3-36qr | 设备配对安全:切换到短期引导令牌 | 配对流程 |
+| GHSA-pcqg-f7rg-xfvv | 执行审批:Unicode 不可见字符转义 | 命令审批 |
+| GHSA-9r3v-37xh-2cf6 | 执行检测:Unicode 规范化 | 命令检测 |
+| GHSA-f8r2-vg7x-gh8m | 执行白名单:POSIX 大小写敏感 | 命令白名单 |
+| GHSA-r7vr-gr74-94p8 | 命令权限:/config 和 /debug 需所有者权限 | 权限控制 |
+| GHSA-rqpp-rjj8-7wv8 | Gateway 认证:清除未绑定客户端声明范围 | 认证安全 |
+| GHSA-vmhq-cqm9-6p7q | 浏览器请求:阻止持久化配置文件变更 | 浏览器控制 |
+| GHSA-2rqg-gjgv-84jm | Agent 安全:拒绝公共生成运行血统字段 | Agent 安全 |
+| GHSA-wcxr-59v9-rxr8 | 会话状态:沙箱会话树可见性强制执行 | 会话隔离 |
+| GHSA-2pwv-x786-56f8 | 设备配对:限制令牌范围 | 令牌安全 |
+| GHSA-jv4g-m82p-2j93 | WebSocket 预认证:缩短握手保留时间 | 连接安全 |
+| GHSA-6rph-mmhp-h7h9 | 代理附件:恢复媒体存储大小限制 | 文件上传 |
+| GHSA-jf5v-pqgw-gm5m | 主机环境:阻止继承 GIT_EXEC_PATH | 环境隔离 |
+| GHSA-g353-mgv3-8pcj | 飞书 Webhook:要求加密密钥 | 飞书渠道 |
+| GHSA-m69h-jm2f-2pv8 | 飞书反应:群组授权和提及门控 | 飞书渠道 |
+| GHSA-mhxh-9pjm-w7q5 | LINE Webhook:空事件也需要签名 | LINE 渠道 |
+| GHSA-5m9r-p9g7-679c | Zalo Webhook:限制无效密钥猜测速率 | Zalo 渠道 |
+
+#### 🟢 新增功能
+
+1. **Control UI/Dashboard-v2 重构** (v2026.3.12)
+ - 模块化概览、聊天、配置、agent 和会话视图
+ - 命令面板、移动端底部标签
+ - 斜杠命令、搜索、导出、固定消息等聊天工具
+
+2. **OpenAI/GPT-5.4 Fast Mode** (v2026.3.12)
+ - 可配置的会话级快速切换
+ - 跨 `/fast`、TUI、Control UI 和 ACP 支持
+ - 每模型配置默认值和 OpenAI/Codex 请求整形
+
+3. **Anthropic/Claude Fast Mode** (v2026.3.12)
+ - 映射共享 `/fast` 切换到 Anthropic API `service_tier` 请求
+ - Anthropic 和 OpenAI fast-mode 层级的实时验证
+
+4. **提供商插件架构** (v2026.3.12)
+ - Ollama、vLLM、SGLang 迁移到提供商插件架构
+ - 提供商拥有的引导、发现、模型选择器设置
+
+5. **Kubernetes 部署支持** (v2026.3.12)
+ - 原始清单、Kind 设置、部署文档
+
+6. **Ollama 本地模式向导** (v2026.3.11)
+ - 本地或云端+本地模式的首选 Ollama 设置
+ - 基于浏览器的云端登录
+ - 精选模型建议
+
+7. **Docker 时区支持** (v2026.3.13)
+ - 新增 `OPENCLAW_TZ` 环境变量
+
+8. **iOS/macOS UI 改进**
+ - iOS: 欢迎屏幕、停靠工具栏、聊天模型选择器
+ - macOS: 聊天模型选择器、思考级别选择持久化
+
+#### 🔵 Bug 修复 (精选)
+
+| 类别 | 修复内容 |
+|------|----------|
+| 模型支持 | Kimi Coding 工具调用格式修复、OpenRouter 模型 ID 规范化 |
+| Telegram | HTML 消息分块、最终预览投递、IPv4 回退重试 |
+| Discord | 回复分块、自动线程归档时长配置 |
+| 飞书 | 本地图片自动转换、非 ASCII 文件名保留 |
+| Mattermost | 块流重复消息修复、Markdown 格式保留 |
+| 会话管理 | 重置模型重新计算、会话发现、ACP 会话别名 |
+| 性能 | 构建内存回归修复 (~2x)、插件 SDK 块去重 |
+
+### 1.3 配置文件格式变化
+
+#### 新增配置项
+
+```json
+{
+ "agents": {
+ "defaults": {
+ "compaction": {
+ "postIndexSync": true
+ },
+ "memorySearch": {
+ "sync": {
+ "sessions": {
+ "postCompactionForce": false
+ }
+ }
+ }
+ }
+ },
+ "channels": {
+ "discord": {
+ "autoArchiveDuration": 60 // 60, 1440, 4320, 10080 分钟
+ }
+ }
+}
+```
+
+#### Fast Mode 使用说明
+
+Fast Mode 是 OpenClaw v2026.3.12 新增的功能,用于启用 OpenAI/Anthropic 的快速响应模式。
+
+**注意**: Fast Mode 不是通过配置文件直接设置的,而是通过以下方式启用:
+
+1. **TUI 界面**: 使用 `/fast` 命令切换
+2. **Control UI**: 在聊天界面中切换 Fast Mode 开关
+3. **API 调用**: 在请求参数中设置 `fastMode: true`
+
+**配置示例** (通过 CLI 设置):
+```bash
+# 在会话中切换 Fast Mode
+openclaw tui
+# 然后输入 /fast 命令
+
+# 或通过 Control UI (http://<设备IP>:18789) 在聊天设置中启用
+```
+
+**支持的模型**:
+- OpenAI: GPT-4/5 系列 (需要 API 支持 fast tier)
+- Anthropic: Claude 系列 (需要 API 支持 service_tier)
+
+#### 废弃/变更配置项
+
+- `channels.zalouser.dangerouslyAllowNameMatching` - 新增危险选项
+- `channels.slack.dangerouslyAllowNameMatching` - 新增危险选项
+- `channels.teams.dangerouslyAllowNameMatching` - 新增危险选项
+
+### 1.4 API 接口变动
+
+| 接口 | 变化类型 | 描述 |
+|------|----------|------|
+| `/pair` | 安全增强 | 使用短期引导令牌替代共享凭证 |
+| `sessions_spawn` | 新增 | 支持 `resumeSessionId` 参数 |
+| `sessions.patch` | 新增字段 | 支持 `spawnedBy`、`spawnDepth` 血统字段 |
+| `node.pending.*` | 新增 | 内存中待处理工作队列原语 |
+
+### 1.5 依赖项变更
+
+#### Node.js 版本要求
+
+```
+旧版本: "engines": { "node": ">=22.x" }
+新版本: "engines": { "node": ">=22.16.0" }
+```
+
+#### 包管理器
+
+```
+pnpm@10.23.0 (无变化)
+```
+
+#### 核心依赖更新 (精选)
+
+| 包名 | 变化 |
+|------|------|
+| `@agentclientprotocol/sdk` | 升级到 0.16.1 |
+| `playwright-core` | 升级到 1.58.2 |
+| `sharp` | 升级到 ^0.34.5 |
+| `hono` | 升级到 4.12.7 |
+| `zod` | 升级到 ^4.3.6 |
+
+### 1.6 二进制文件大小变化
+
+| 指标 | v2026.3.8 | v2026.3.13 | 变化 |
+|------|-----------|------------|------|
+| npm 包解压大小 | ~90MB | ~95MB | +5MB |
+| 文件数量 | ~4500 | ~4730 | +230 |
+
+---
+
+## 二、需要修改的文件清单及具体修改内容
+
+### 2.1 必须修改的文件
+
+#### 2.1.1 `root/usr/bin/openclaw-env`
+
+**修改原因**: Node.js 版本要求提升
+
+```diff
+- NODE_VERSION="${NODE_VERSION:-22.15.1}"
++ NODE_VERSION="${NODE_VERSION:-22.16.0}"
+
+- # 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
+- OC_TESTED_VERSION="2026.3.8"
++ # 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
++ OC_TESTED_VERSION="2026.3.13"
+```
+
+#### 2.1.2 `CHANGELOG.md`
+
+**新增内容**:
+
+```markdown
+## [2.1.0] - 2026-03-XX
+
+### 适配 OpenClaw v2026.3.13
+
+#### 升级说明
+- **Node.js 版本升级**: 从 22.15.1 升级到 22.16.0 (OpenClaw 最低要求)
+- **OpenClaw 版本升级**: 从 v2026.3.8 升级到 v2026.3.13
+
+#### 重要变更
+- 安全修复: WebSocket 跨站劫持漏洞、设备配对安全增强
+- 新功能: Control UI 重构、Fast Mode 支持、Ollama 本地向导
+- 插件安全: 禁用隐式工作区插件自动加载
+
+#### 配置迁移
+- 升级后建议运行 `openclaw doctor --fix` 迁移旧版 cron 存储
+```
+
+#### 2.1.3 `VERSION`
+
+```diff
+- 2.0.0
++ 2.1.0
+```
+
+### 2.2 建议修改的文件
+
+#### 2.2.1 `root/usr/share/openclaw/oc-config.sh`
+
+**检查点**:
+1. 确认 `openclaw doctor --fix` 命令在升级后可用
+2. 检查是否有新增的配置项需要在菜单中展示
+3. 确认 Fast Mode 配置是否需要 UI 入口
+
+**建议新增菜单项**:
+
+```bash
+# 在模型配置菜单中新增 Fast Mode 开关
+configure_fast_mode() {
+ local current
+ current=$(get_config ".params.fastMode // false")
+
+ if [ "$current" = "true" ]; then
+ status="已启用"
+ else
+ status="已禁用"
+ fi
+
+ echo ""
+ echo "=== Fast Mode 配置 ==="
+ echo "当前状态: $status"
+ echo ""
+ echo "Fast Mode 可启用 OpenAI/Anthropic 的快速响应模式"
+ echo "需要 API 密钥支持相应的服务层级"
+ echo ""
+ echo "1) 启用"
+ echo "2) 禁用"
+ echo "0) 返回"
+ echo ""
+ read -p "请选择: " choice
+
+ case $choice in
+ 1) set_config ".params.fastMode = true" ;;
+ 2) set_config ".params.fastMode = false" ;;
+ 0) return ;;
+ esac
+}
+```
+
+#### 2.2.2 `luasrc/model/cbi/openclaw/basic.lua`
+
+**检查点**:
+1. 确认配置项与新版 OpenClaw 兼容
+2. 考虑新增 Fast Mode 配置 UI
+
+#### 2.2.3 `README.md`
+
+**更新内容**:
+- 更新版本号引用
+- 添加升级注意事项
+- 更新 Node.js 版本要求说明
+
+### 2.3 需要验证的文件
+
+| 文件 | 验证内容 |
+|------|----------|
+| `root/etc/init.d/openclaw` | 启动脚本兼容性 |
+| `scripts/build_run.sh` | 构建脚本 Node.js 版本 |
+| `scripts/build_ipk.sh` | IPK 构建脚本 |
+
+---
+
+## 三、升级测试方案
+
+### 3.1 测试环境准备
+
+#### 3.1.1 测试矩阵
+
+| 环境 | 架构 | libc | 固件 |
+|------|------|------|------|
+| x86_64-glibc | x86_64 | glibc | Debian/Ubuntu |
+| x86_64-musl | x86_64 | musl | OpenWrt/iStoreOS |
+| aarch64-musl | aarch64 | musl | OpenWrt/iStoreOS |
+
+#### 3.1.2 测试前准备
+
+```bash
+# 1. 备份当前配置
+cp -r /opt/openclaw/data/.openclaw /tmp/openclaw-backup
+
+# 2. 记录当前版本
+openclaw --version
+
+# 3. 检查 Node.js 版本
+node --version
+```
+
+### 3.2 升级测试步骤
+
+#### 3.2.1 全新安装测试
+
+```bash
+# 1. 清理旧环境
+rm -rf /opt/openclaw
+
+# 2. 安装新版本
+openclaw-env setup
+
+# 3. 验证版本
+openclaw --version # 应显示 2026.3.13
+node --version # 应显示 v22.16.0 或更高
+
+# 4. 运行引导
+openclaw onboard
+
+# 5. 基础功能测试
+openclaw doctor
+```
+
+#### 3.2.2 升级安装测试
+
+```bash
+# 1. 从 v2026.3.8 升级
+export OC_VERSION=2026.3.13
+openclaw-env upgrade
+
+# 2. 验证版本
+openclaw --version
+
+# 3. 运行迁移
+openclaw doctor --fix
+
+# 4. 验证配置
+openclaw config list
+```
+
+#### 3.2.3 配置兼容性测试
+
+```bash
+# 1. 恢复旧配置
+cp -r /tmp/openclaw-backup/* /opt/openclaw/data/.openclaw/
+
+# 2. 重启服务
+/etc/init.d/openclaw restart
+
+# 3. 检查日志
+logread -f | grep -i openclaw
+
+# 4. 验证渠道
+openclaw channels list
+```
+
+### 3.3 功能回归测试清单
+
+| 测试项 | 测试内容 | 预期结果 | 状态 |
+|--------|----------|----------|------|
+| 基础启动 | Gateway 启动 | 正常启动 | [ ] |
+| 模型配置 | 设置活动模型 | 配置保存成功 | [ ] |
+| Telegram | 消息收发 | 双向通信正常 | [ ] |
+| QQ Bot | 消息收发 | 双向通信正常 | [ ] |
+| 飞书 | 消息收发 | 双向通信正常 | [ ] |
+| Discord | 消息收发 | 双向通信正常 | [ ] |
+| Doctor | 健康检查 | 无错误报告 | [ ] |
+| Doctor --fix | 迁移修复 | 成功执行 | [ ] |
+| Fast Mode | 开关切换 | 配置生效 | [ ] |
+| LuCI 界面 | 页面加载 | 正常显示 | [ ] |
+| 日志查看 | 日志输出 | 正常显示 | [ ] |
+| 备份恢复 | 配置备份/恢复 | 功能正常 | [ ] |
+
+### 3.4 性能测试
+
+```bash
+# 1. 内存占用
+ps aux | grep node
+
+# 2. 启动时间
+time /etc/init.d/openclaw start
+
+# 3. 响应延迟
+curl -w "@curl-format.txt" -o /dev/null -s http://localhost:3000/health
+```
+
+### 3.5 回滚策略
+
+#### 3.5.1 自动回滚脚本
+
+```bash
+#!/bin/sh
+# rollback.sh - OpenClaw 版本回滚脚本
+
+echo "=== OpenClaw 版本回滚 ==="
+
+# 1. 停止服务
+/etc/init.d/openclaw stop
+
+# 2. 备份当前状态
+cp -r /opt/openclaw/data/.openclaw /tmp/openclaw-failed-upgrade
+
+# 3. 恢复旧版本
+export OC_VERSION=2026.3.8
+openclaw-env upgrade
+
+# 4. 恢复配置
+cp -r /tmp/openclaw-backup/* /opt/openclaw/data/.openclaw/
+
+# 5. 重启服务
+/etc/init.d/openclaw start
+
+# 6. 验证
+openclaw --version
+echo "回滚完成"
+```
+
+#### 3.5.2 手动回滚步骤
+
+```bash
+# 1. 停止服务
+/etc/init.d/openclaw stop
+
+# 2. 卸载新版本
+npm uninstall -g openclaw --prefix /opt/openclaw/global
+
+# 3. 安装旧版本
+npm install -g openclaw@2026.3.8 --prefix /opt/openclaw/global
+
+# 4. 恢复 Node.js (如需要)
+# 重新运行 openclaw-env node 安装 22.15.1
+
+# 5. 恢复配置
+cp -r /tmp/openclaw-backup/* /opt/openclaw/data/.openclaw/
+
+# 6. 重启服务
+/etc/init.d/openclaw start
+```
+
+---
+
+## 四、发布前检查清单
+
+### 4.1 代码检查
+
+- [ ] 所有文件修改已提交
+- [ ] CHANGELOG.md 已更新
+- [ ] VERSION 文件已更新
+- [ ] README.md 已更新
+
+### 4.2 测试检查
+
+- [ ] x86_64-glibc 环境测试通过
+- [ ] x86_64-musl 环境测试通过
+- [ ] aarch64-musl 环境测试通过
+- [ ] 全新安装测试通过
+- [ ] 升级安装测试通过
+- [ ] 配置兼容性测试通过
+- [ ] 功能回归测试通过
+- [ ] 性能测试无退化
+
+### 4.3 文档检查
+
+- [ ] 升级指南已更新
+- [ ] 用户文档已更新
+- [ ] API 文档已更新 (如有变化)
+
+### 4.4 构建检查
+
+- [ ] IPK 构建成功
+- [ ] 离线安装包构建成功
+- [ ] 文件大小合理
+
+### 4.5 安全检查
+
+- [ ] 无已知安全漏洞
+- [ ] 敏感信息已清理
+- [ ] 权限设置正确
+
+---
+
+## 五、用户升级指南草稿
+
+### OpenClaw v2026.3.13 升级指南
+
+#### 升级前须知
+
+1. **Node.js 版本要求**: 本次升级要求 Node.js >= 22.16.0
+2. **配置兼容性**: 现有配置文件兼容,无需手动迁移
+3. **建议备份**: 升级前建议备份当前配置
+
+#### 升级方式
+
+##### 方式一:通过 LuCI 界面升级
+
+1. 登录 LuCI 管理界面
+2. 进入「服务」→「OpenClaw」
+3. 点击「系统管理」→「升级 OpenClaw」
+4. 等待升级完成
+5. 验证版本号已更新
+
+##### 方式二:通过命令行升级
+
+```bash
+# SSH 登录后执行
+openclaw-env upgrade
+
+# 升级后运行迁移
+openclaw doctor --fix
+
+# 验证版本
+openclaw --version
+```
+
+##### 方式三:指定版本升级
+
+```bash
+# 指定升级到 v2026.3.13
+export OC_VERSION=2026.3.13
+openclaw-env upgrade
+```
+
+#### 升级后验证
+
+```bash
+# 1. 检查服务状态
+/etc/init.d/openclaw status
+
+# 2. 检查版本
+openclaw --version
+
+# 3. 运行健康检查
+openclaw doctor
+
+# 4. 检查日志
+logread | grep -i openclaw | tail -20
+```
+
+#### 新功能体验
+
+1. **Fast Mode**: 在模型配置中启用快速响应模式
+2. **Control UI**: 访问 `http://<设备IP>:3000` 体验新版控制面板
+3. **Ollama 本地向导**: 运行 `openclaw onboard` 配置本地模型
+
+#### 常见问题
+
+**Q: 升级后服务无法启动?**
+
+A: 检查 Node.js 版本是否满足要求:
+```bash
+node --version # 应显示 v22.16.0 或更高
+```
+
+**Q: 升级后配置丢失?**
+
+A: 配置文件位于 `/opt/openclaw/data/.openclaw/`,检查是否正确恢复。
+
+**Q: 如何回滚到旧版本?**
+
+A: 执行以下命令:
+```bash
+export OC_VERSION=2026.3.8
+openclaw-env upgrade
+```
+
+#### 安全改进说明
+
+本次升级包含多项安全修复,建议所有用户尽快升级:
+
+- WebSocket 跨站劫持漏洞修复
+- 设备配对安全增强
+- 命令审批安全加固
+- 多个渠道 Webhook 安全增强
+
+---
+
+## 六、附录
+
+### A. 版本发布说明链接
+
+- [v2026.3.13 Release Notes](https://github.com/openclaw/openclaw/releases/tag/v2026.3.13-1)
+- [v2026.3.12 Release Notes](https://github.com/openclaw/openclaw/releases/tag/v2026.3.12)
+- [v2026.3.11 Release Notes](https://github.com/openclaw/openclaw/releases/tag/v2026.3.11)
+
+### B. 相关 Issue 和 PR
+
+本次升级涉及的主要变更:
+- Node.js 最低版本要求: #45640
+- Fast Mode 支持: 多个 PR
+- 安全修复: 多个 GHSA 编号
+
+### C. 联系方式
+
+如有问题,请通过以下方式反馈:
+- GitHub Issues: https://github.com/10000ge10000/luci-app-openclaw/issues
+- OpenClaw 官方: https://github.com/openclaw/openclaw/issues
+
+---
+
+**文档版本**: 1.0
+**创建日期**: 2026-03-16
+**最后更新**: 2026-03-16
diff --git a/docs/images/2.png b/docs/images/2.png
new file mode 100644
index 0000000..121ca5e
Binary files /dev/null and b/docs/images/2.png differ
diff --git a/luasrc/controller/openclaw.lua b/luasrc/controller/openclaw.lua
index 5ba6c07..4a96cca 100644
--- a/luasrc/controller/openclaw.lua
+++ b/luasrc/controller/openclaw.lua
@@ -374,6 +374,8 @@ function action_uninstall()
sys.exec("uci set openclaw.main.enabled=0; uci commit openclaw 2>/dev/null")
-- 删除 Node.js + OpenClaw 运行环境
sys.exec("rm -rf /opt/openclaw")
+ -- 清理旧数据迁移后可能残留的目录
+ sys.exec("rm -rf /root/.openclaw 2>/dev/null")
-- 清理临时文件
sys.exec("rm -f /tmp/openclaw-setup.* /tmp/openclaw-update.log /var/run/openclaw*.pid")
-- 删除 openclaw 系统用户
@@ -382,7 +384,7 @@ function action_uninstall()
http.prepare_content("application/json")
http.write_json({
status = "ok",
- message = "运行环境已卸载。Node.js、OpenClaw 及相关数据已清理。"
+ message = "运行环境已卸载。已清理: Node.js 运行环境 (/opt/openclaw)、旧数据目录 (/root/.openclaw)、临时文件。"
})
end
diff --git a/root/etc/init.d/openclaw b/root/etc/init.d/openclaw
index a4a786e..df25c8c 100755
--- a/root/etc/init.d/openclaw
+++ b/root/etc/init.d/openclaw
@@ -193,6 +193,55 @@ fi
# 同步 UCI 到 JSON
sync_uci_to_json
+ # v2026.3.13: 修复插件配置中的插件名称不匹配问题
+ # OpenClaw 加强了配置验证,plugins.allow 中的名称必须与实际插件名完全匹配
+ # 问题: 旧版本写入的是 "openclaw-qqbot",但实际插件名是 "@tencent-connect/openclaw-qqbot"
+ fix_plugin_config() {
+ local qqbot_ext_dir="${OC_DATA}/.openclaw/extensions/openclaw-qqbot"
+ [ ! -d "$qqbot_ext_dir" ] && return
+ [ ! -f "$CONFIG_FILE" ] && return
+
+ "$NODE_BIN" -e "
+ const fs=require('fs');
+ try{
+ const d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));
+ if(!d.plugins)d.plugins={};
+ let modified=false;
+
+ // 修复 plugins.allow 数组中的插件名称
+ if(Array.isArray(d.plugins.allow)){
+ const oldName='openclaw-qqbot';
+ const newName='@tencent-connect/openclaw-qqbot';
+ const idx=d.plugins.allow.indexOf(oldName);
+ if(idx!==-1){
+ if(!d.plugins.allow.includes(newName)){
+ d.plugins.allow[idx]=newName;
+ modified=true;
+ }else{
+ d.plugins.allow.splice(idx,1);
+ modified=true;
+ }
+ }
+ }
+
+ // 同时修复 plugins.entries 中的键名
+ if(d.plugins.entries && d.plugins.entries['openclaw-qqbot']){
+ if(!d.plugins.entries['@tencent-connect/openclaw-qqbot']){
+ d.plugins.entries['@tencent-connect/openclaw-qqbot']=d.plugins.entries['openclaw-qqbot'];
+ }
+ delete d.plugins.entries['openclaw-qqbot'];
+ modified=true;
+ }
+
+ if(modified){
+ fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
+ console.log('FIXED');
+ }
+ }catch(e){}
+ " 2>/dev/null && chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null
+ }
+ fix_plugin_config
+
# 修复数据目录权限 (防止 root 用户操作后留下无法读取的文件)
chown -R openclaw:openclaw "$OC_DATA" 2>/dev/null || true
diff --git a/root/etc/profile.d/openclaw.sh b/root/etc/profile.d/openclaw.sh
new file mode 100644
index 0000000..969cfcf
--- /dev/null
+++ b/root/etc/profile.d/openclaw.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# ============================================================================
+# luci-app-openclaw — 全局环境变量
+# 仅在 Node.js 已安装时生效,为 SSH 登录用户提供正确的运行环境
+# 解决 Issue #42: 统一配置文件路径,避免 /root/.openclaw 与 /opt/openclaw/data/.openclaw 混乱
+# ============================================================================
+
+NODE_BASE="/opt/openclaw/node"
+OC_GLOBAL="/opt/openclaw/global"
+OC_DATA="/opt/openclaw/data"
+
+# 检查 Node.js 是否已安装
+[ -x "${NODE_BASE}/bin/node" ] || return 0
+
+# 添加 Node.js 和 OpenClaw 到 PATH (非侵入式,检查是否已存在)
+case ":$PATH:" in
+ *":${NODE_BASE}/bin:"*) ;;
+ *) export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH" ;;
+esac
+
+# 设置 Node.js ICU 数据路径
+export NODE_ICU_DATA="${NODE_BASE}/share/icu"
+
+# 设置 OpenClaw 核心环境变量
+# 这些变量确保 openclaw 命令使用正确的配置路径
+export OPENCLAW_HOME="$OC_DATA"
+export OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw"
+export OPENCLAW_CONFIG_PATH="${OC_DATA}/.openclaw/openclaw.json"
+
+# 设置 HOME 为 OpenClaw 数据目录
+# 这是解决 Issue #42 的关键:确保 OpenClaw CLI 使用正确的配置路径
+# 注意:这会影响 cd ~ 等行为,但为了配置一致性是必要的
+export HOME="$OC_DATA"
+
+# 创建便捷别名:用户可直接运行 openclaw 命令
+if [ -x "${OC_GLOBAL}/bin/openclaw" ] || [ -x "${NODE_BASE}/bin/openclaw" ]; then
+ # openclaw 已在 PATH 中,无需别名
+ :
+else
+ # 尝试查找 openclaw 入口并创建别名
+ for _oc_dir in "${OC_GLOBAL}/lib/node_modules/openclaw" "${OC_GLOBAL}/node_modules/openclaw" "${NODE_BASE}/lib/node_modules/openclaw"; do
+ if [ -f "${_oc_dir}/openclaw.mjs" ]; then
+ alias openclaw="${NODE_BASE}/bin/node ${_oc_dir}/openclaw.mjs"
+ break
+ elif [ -f "${_oc_dir}/dist/cli.js" ]; then
+ alias openclaw="${NODE_BASE}/bin/node ${_oc_dir}/dist/cli.js"
+ break
+ fi
+ done
+fi
diff --git a/root/etc/uci-defaults/99-openclaw b/root/etc/uci-defaults/99-openclaw
index e97f476..6c1bf88 100755
--- a/root/etc/uci-defaults/99-openclaw
+++ b/root/etc/uci-defaults/99-openclaw
@@ -1,5 +1,32 @@
#!/bin/sh
-# luci-app-openclaw — 首次安装初始化脚本
+# luci-app-openclaw — 首次安装/升级初始化脚本
+
+# ── v1.0.16: 清理错误路径下的配置文件 (Issue #42) ──
+# 用户在 SSH 中直接运行 openclaw 命令时,可能创建了 /root/.openclaw/ 目录
+# 需要迁移数据并清理,避免路径混乱
+if [ -d "/root/.openclaw" ]; then
+ OC_DATA="/opt/openclaw/data"
+ # 迁移 skills 目录 (如果存在且目标不存在)
+ if [ -d "/root/.openclaw/skills" ] && [ ! -d "${OC_DATA}/.openclaw/skills" ]; then
+ mkdir -p "${OC_DATA}/.openclaw"
+ mv "/root/.openclaw/skills" "${OC_DATA}/.openclaw/" 2>/dev/null
+ chown -R openclaw:openclaw "${OC_DATA}/.openclaw/skills" 2>/dev/null
+ fi
+ # 迁移 sessions 目录 (如果存在且目标不存在)
+ if [ -d "/root/.openclaw/sessions" ] && [ ! -d "${OC_DATA}/.openclaw/sessions" ]; then
+ mkdir -p "${OC_DATA}/.openclaw"
+ mv "/root/.openclaw/sessions" "${OC_DATA}/.openclaw/" 2>/dev/null
+ chown -R openclaw:openclaw "${OC_DATA}/.openclaw/sessions" 2>/dev/null
+ fi
+ # 迁移 openclaw.json (仅当目标不存在时)
+ if [ -f "/root/.openclaw/openclaw.json" ] && [ ! -f "${OC_DATA}/.openclaw/openclaw.json" ]; then
+ mkdir -p "${OC_DATA}/.openclaw"
+ mv "/root/.openclaw/openclaw.json" "${OC_DATA}/.openclaw/" 2>/dev/null
+ chown openclaw:openclaw "${OC_DATA}/.openclaw/openclaw.json" 2>/dev/null
+ fi
+ # 清理空的旧目录
+ rmdir "/root/.openclaw" 2>/dev/null || true
+fi
# 创建 openclaw 系统用户 (无 home, 无 shell)
if ! id openclaw >/dev/null 2>&1; then
diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env
index 1f50b87..764618e 100755
--- a/root/usr/bin/openclaw-env
+++ b/root/usr/bin/openclaw-env
@@ -11,9 +11,15 @@
# ============================================================================
set -e
-NODE_VERSION="${NODE_VERSION:-22.15.1}"
+# ── Node.js 版本策略 (双版本兼容) ──
+# V2: 当前推荐版本,用于 OpenClaw v2026.3.11+ (要求 >= 22.16.0)
+# V1: 旧版兼容,用于 OpenClaw v2026.3.8 及更早版本
+NODE_VERSION_V2="22.16.0"
+NODE_VERSION_V1="22.15.1"
+# 默认使用 V2 版本 (可通过 NODE_VERSION 环境变量覆盖)
+NODE_VERSION="${NODE_VERSION:-${NODE_VERSION_V2}}"
# 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
-OC_TESTED_VERSION="2026.3.8"
+OC_TESTED_VERSION="2026.3.13"
# 用户可通过 OC_VERSION 环境变量覆盖安装版本
OC_VERSION="${OC_VERSION:-}"
NODE_BASE="/opt/openclaw/node"
@@ -155,7 +161,7 @@ download_node() {
log_warn "当前 Node.js v${current_ver}, 将更新到 v${node_ver}"
fi
- # ── 构建下载 URL 列表 (按优先级排列) ──
+ # ── 构建下载 URL 列表 (按优先级排列,支持双版本回退) ──
local mirror_list=""
local musl_tarball="node-v${node_ver}-${node_arch}-musl.tar.xz"
local glibc_tarball="node-v${node_ver}-${node_arch}.tar.xz"
@@ -166,10 +172,15 @@ download_node() {
if [ "$node_arch" = "linux-arm64" ]; then
# ARM64 musl: unofficial-builds 不提供,从项目自托管下载
- # 1) 项目自托管 ARM64 musl 构建
+ # 1) 项目自托管 ARM64 musl 构建 (当前版本)
mirror_list="${NODE_SELF_HOST}/${musl_tarball}"
# 2) unofficial-builds (留作将来可能支持)
mirror_list="$mirror_list ${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}"
+ # 3) 回退到 V1 版本 (兼容旧环境,仅当请求 V2 时)
+ if [ "$node_ver" = "$NODE_VERSION_V2" ]; then
+ local v1_tarball="node-v${NODE_VERSION_V1}-${node_arch}-musl.tar.xz"
+ mirror_list="$mirror_list ${NODE_SELF_HOST}/${v1_tarball}"
+ fi
else
# x64 musl: unofficial-builds 提供
# 1) unofficial-builds
@@ -623,30 +634,135 @@ do_factory_reset() {
log_info "出厂设置已恢复,Gateway 重启中..."
}
+# ── 离线安装 (从本地文件安装 Node.js + OpenClaw) ──
+do_setup_offline() {
+ local offline_dir="${2:-/tmp/openclaw-offline}"
+
+ if [ ! -d "$offline_dir" ]; then
+ log_error "离线安装目录不存在: $offline_dir"
+ log_error "此命令通常由离线 .run 安装器自动调用"
+ exit 1
+ fi
+
+ # 检查是否已安装
+ if [ -x "$NODE_BIN" ] && [ -n "$(find_oc_entry)" ]; then
+ log_warn "OpenClaw 运行环境已安装"
+ log_warn "如需重新离线安装,请先卸载现有环境"
+ exit 0
+ fi
+
+ echo "╔══════════════════════════════════════════════════════════════╗"
+ echo "║ 一万AI分享 OpenClaw 离线安装 ║"
+ echo "╚══════════════════════════════════════════════════════════════╝"
+ echo ""
+ echo " 架构: $(uname -m)"
+ echo " 安装路径: ${NODE_BASE}"
+ echo " 数据路径: ${OC_DATA}"
+ echo " 离线包: ${offline_dir}"
+ echo ""
+
+ # [1] 安装 Node.js
+ local node_tarball="$offline_dir/node.tar.xz"
+ if [ -f "$node_tarball" ]; then
+ echo "=== 安装 Node.js (离线) ==="
+ rm -rf "$NODE_BASE" 2>/dev/null
+ [ -d /overlay/upper ] && rm -rf "/overlay/upper${NODE_BASE}" 2>/dev/null
+ ensure_mkdir "$NODE_BASE"
+
+ if tar --strip-components=1 -xf "$node_tarball" -C "$NODE_BASE" 2>/dev/null; then
+ : # GNU tar
+ else
+ local tmp_extract="/tmp/node-extract-$$"
+ ensure_mkdir "$tmp_extract"
+ tar xf "$node_tarball" -C "$tmp_extract"
+ local top_dir=$(ls "$tmp_extract" 2>/dev/null | head -1)
+ if [ -n "$top_dir" ] && [ -d "$tmp_extract/$top_dir" ]; then
+ cp -a "$tmp_extract/$top_dir/." "$NODE_BASE/"
+ fi
+ rm -rf "$tmp_extract"
+ fi
+
+ if [ -x "$NODE_BIN" ]; then
+ log_info "Node.js $($NODE_BIN --version 2>/dev/null) 安装成功"
+ else
+ log_error "Node.js 安装失败"
+ exit 1
+ fi
+ else
+ log_error "未找到 Node.js 离线包: $node_tarball"
+ exit 1
+ fi
+
+ # [2] 安装 OpenClaw
+ local oc_tarball="$offline_dir/openclaw-deps.tar.gz"
+ if [ -f "$oc_tarball" ]; then
+ echo ""
+ echo "=== 安装 OpenClaw (离线) ==="
+ rm -rf "$OC_GLOBAL" 2>/dev/null
+ [ -d /overlay/upper ] && rm -rf "/overlay/upper${OC_GLOBAL}" 2>/dev/null
+ ensure_mkdir "$OC_GLOBAL"
+
+ tar xzf "$oc_tarball" -C "$OC_GLOBAL"
+
+ local oc_entry=$(find_oc_entry)
+ if [ -n "$oc_entry" ]; then
+ local oc_ver=$("$NODE_BIN" "$oc_entry" --version 2>/dev/null | tr -d '[:space:]')
+ log_info "OpenClaw v${oc_ver} 安装成功"
+ else
+ log_error "OpenClaw 安装验证失败"
+ exit 1
+ fi
+ else
+ log_error "未找到 OpenClaw 离线包: $oc_tarball"
+ exit 1
+ fi
+
+ # [3] 初始化
+ init_openclaw
+
+ echo ""
+ echo "╔══════════════════════════════════════════════════════════════╗"
+ echo "║ ✅ 离线安装完成! ║"
+ echo "║ ║"
+ echo "║ 下一步: ║"
+ echo "║ uci set openclaw.main.enabled=1 ║"
+ echo "║ uci commit openclaw ║"
+ echo "║ /etc/init.d/openclaw enable ║"
+ echo "║ /etc/init.d/openclaw start ║"
+ echo "║ ║"
+ echo "║ 或在 LuCI → 服务 → OpenClaw 中启用 ║"
+ echo "╚══════════════════════════════════════════════════════════════╝"
+}
+
# ── 主入口 ──
case "${1:-}" in
-setup)
-do_setup
-;;
-check)
-do_check
-;;
-upgrade)
-do_upgrade
-;;
-node)
-download_node "$NODE_VERSION"
-;;
-factory-reset)
-do_factory_reset
-;;
-*)
-echo ""
-echo " setup — 完整安装 (下载 Node.js + pnpm + OpenClaw)"
-echo " check — 检查环境状态"
-echo " upgrade — 升级 OpenClaw 到最新版"
-echo " node — 仅下载/更新 Node.js"
-echo " factory-reset — 恢复出厂设置 (清除所有配置)"
-exit 1
-;;
+ setup)
+ do_setup
+ ;;
+ setup-offline)
+ do_setup_offline "$@"
+ ;;
+ check)
+ do_check
+ ;;
+ upgrade)
+ do_upgrade
+ ;;
+ node)
+ download_node "$NODE_VERSION"
+ ;;
+ factory-reset)
+ do_factory_reset
+ ;;
+ *)
+ echo "用法: openclaw-env {setup|setup-offline|check|upgrade|node|factory-reset}"
+ echo ""
+ echo " setup — 完整安装 (下载 Node.js + pnpm + OpenClaw)"
+ echo " setup-offline — 离线安装 (从本地文件安装,由 .run 安装器调用)"
+ echo " check — 检查环境状态"
+ echo " upgrade — 升级 OpenClaw 到最新版"
+ echo " node — 仅下载/更新 Node.js"
+ echo " factory-reset — 恢复出厂设置 (清除所有配置)"
+ exit 1
+ ;;
esac
diff --git a/root/usr/share/openclaw/oc-config.sh b/root/usr/share/openclaw/oc-config.sh
index cdfc19f..f6d280b 100755
--- a/root/usr/share/openclaw/oc-config.sh
+++ b/root/usr/share/openclaw/oc-config.sh
@@ -91,20 +91,100 @@ json_get() {
json_set() {
local key="$1" value="$2"
+ local _js_err=""
+
+ # 步骤1: 确保配置文件存在
if [ ! -f "$CONFIG_FILE" ]; then
- mkdir -p "$(dirname "$CONFIG_FILE")"
+ # 检查父目录是否可创建
+ local parent_dir="$(dirname "$CONFIG_FILE")"
+ if ! mkdir -p "$parent_dir" 2>/dev/null; then
+ echo "ERROR: 无法创建配置目录 $parent_dir" >&2
+ echo "HINT: 请检查 /opt/openclaw/data 是否存在且有写权限" >&2
+ return 1
+ fi
+
+ # 检查目录权限
+ if [ ! -w "$parent_dir" ]; then
+ echo "ERROR: 配置目录 $parent_dir 不可写" >&2
+ echo "HINT: 请运行: chmod 755 $parent_dir" >&2
+ return 1
+ fi
+
+ # 尝试修复所有权
chown -R openclaw:openclaw "$OC_STATE_DIR" 2>/dev/null || true
- echo '{}' > "$CONFIG_FILE"
+
+ # 创建空配置文件
+ if ! echo '{}' > "$CONFIG_FILE" 2>/dev/null; then
+ echo "ERROR: 无法创建配置文件 $CONFIG_FILE" >&2
+ echo "HINT: 请检查磁盘空间和文件系统状态" >&2
+ return 1
+ fi
+
+ chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
fi
- _JS_KEY="$key" _JS_VAL="$value" "$NODE_BIN" -e "
+
+ # 步骤2: 检查配置文件是否可写
+ if [ ! -w "$CONFIG_FILE" ]; then
+ echo "ERROR: 配置文件 $CONFIG_FILE 不可写" >&2
+ echo "HINT: 请运行: chmod 644 $CONFIG_FILE" >&2
+ return 1
+ fi
+
+ # 步骤3: 检查 Node.js 是否可用
+ if [ ! -x "$NODE_BIN" ]; then
+ echo "ERROR: Node.js 不可用: $NODE_BIN" >&2
+ echo "HINT: 请先运行 openclaw-env setup 安装 Node.js" >&2
+ return 1
+ fi
+
+ # 步骤4: 使用临时文件传递值,避免环境变量转义问题
+ local tmp_val_file="/tmp/.oc_json_val_$$"
+ if ! printf '%s' "$value" > "$tmp_val_file" 2>/dev/null; then
+ echo "ERROR: 无法创建临时文件 $tmp_val_file" >&2
+ return 1
+ fi
+
+ # 步骤5: 执行 JSON 写入
+ _JS_KEY="$key" _JS_DEBUG="${OC_CONFIG_DEBUG:-0}" "$NODE_BIN" -e "
const fs=require('fs');let d={};
- try{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}
+ const debug=process.env._JS_DEBUG==='1';
+ try{
+ const content=fs.readFileSync('${CONFIG_FILE}','utf8');
+ d=JSON.parse(content);
+ }catch(e){
+ if(debug)console.error('JSON parse warning:',e.message);
+ }
const ks=process.env._JS_KEY.split('.');let o=d;
- for(let i=0;i/dev/null
+ try{
+ fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
+ if(debug)console.log('JSON saved successfully');
+ }catch(e){
+ console.error('ERROR: Failed to write config:',e.message);
+ process.exit(1);
+ }
+ " 2>&1
+ local _js_rc=$?
+
+ # 清理临时文件
+ rm -f "$tmp_val_file" 2>/dev/null
+
+ # 步骤6: 检查执行结果
+ if [ $_js_rc -ne 0 ]; then
+ echo "ERROR: JSON 写入失败 (exit code: $_js_rc)" >&2
+ return 1
+ fi
+
+ # 步骤7: 修复文件所有权
+ chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
+
+ return 0
}
# ── 启用 auth 插件 ──
@@ -125,6 +205,100 @@ enable_auth_plugins() {
" 2>/dev/null
}
+# ── 修复插件配置中的插件名称不匹配问题 ──
+# v2026.3.13: OpenClaw 加强了配置验证,plugins.allow 中的名称必须与实际插件名完全匹配
+# 问题: 旧版本写入的是 "openclaw-qqbot",但实际插件名是 "@tencent-connect/openclaw-qqbot"
+# 此函数会自动检测并修正不匹配的插件名称
+fix_plugin_config() {
+ [ ! -f "$CONFIG_FILE" ] && return
+ [ ! -x "$NODE_BIN" ] && return
+
+ # 检查是否存在 qqbot 插件目录
+ local qqbot_ext_dir="${OC_STATE_DIR}/extensions/openclaw-qqbot"
+ [ ! -d "$qqbot_ext_dir" ] && return
+
+ # 读取并修复 plugins.allow 中的插件名称
+ local fixed=0
+ "$NODE_BIN" -e "
+ const fs=require('fs');
+ try{
+ const d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));
+ if(!d.plugins)d.plugins={};
+
+ // 修复 plugins.allow 数组中的插件名称
+ if(Array.isArray(d.plugins.allow)){
+ const oldName='openclaw-qqbot';
+ const newName='@tencent-connect/openclaw-qqbot';
+ const idx=d.plugins.allow.indexOf(oldName);
+ if(idx!==-1){
+ // 检查是否已有正确的名称
+ if(!d.plugins.allow.includes(newName)){
+ d.plugins.allow[idx]=newName;
+ console.log('FIXED');
+ }else{
+ // 已有正确名称,删除错误的
+ d.plugins.allow.splice(idx,1);
+ console.log('REMOVED_DUPLICATE');
+ }
+ fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
+ }
+ }
+
+ // 同时修复 plugins.entries 中的键名
+ if(d.plugins.entries && d.plugins.entries['openclaw-qqbot']){
+ if(!d.plugins.entries['@tencent-connect/openclaw-qqbot']){
+ d.plugins.entries['@tencent-connect/openclaw-qqbot']=d.plugins.entries['openclaw-qqbot'];
+ }
+ delete d.plugins.entries['openclaw-qqbot'];
+ fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
+ console.log('FIXED_ENTRIES');
+ }
+ }catch(e){}
+ " 2>/dev/null | while read line; do
+ case "$line" in
+ FIXED|REMOVED_DUPLICATE|FIXED_ENTRIES)
+ echo -e " ${GREEN}✅ 已修复插件配置名称: openclaw-qqbot → @tencent-connect/openclaw-qqbot${NC}"
+ fixed=1
+ ;;
+ esac
+ done
+
+ # 确保配置文件权限正确
+ chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
+
+ return $fixed
+}
+
+# ── 确保 qqbot 插件在 plugins.allow 中 ──
+ensure_qqbot_plugin_allowed() {
+ [ ! -f "$CONFIG_FILE" ] && return
+ [ ! -x "$NODE_BIN" ] && return
+
+ "$NODE_BIN" -e "
+ const fs=require('fs');
+ try{
+ const d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));
+ if(!d.plugins)d.plugins={};
+ if(!Array.isArray(d.plugins.allow))d.plugins.allow=[];
+
+ const correctName='@tencent-connect/openclaw-qqbot';
+ const oldName='openclaw-qqbot';
+
+ // 移除旧的不正确名称
+ d.plugins.allow=d.plugins.allow.filter(n=>n!==oldName);
+
+ // 添加正确的名称(如果不存在)
+ if(!d.plugins.allow.includes(correctName)){
+ d.plugins.allow.push(correctName);
+ fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
+ console.log('ADDED');
+ }
+ }catch(e){}
+ " 2>/dev/null
+
+ chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
+}
+
# ── 模型认证: 将 API Key 写入 auth-profiles.json (而非 openclaw.json) ──
# 用法: auth_set_apikey [profile_id]
# 例: auth_set_apikey openai sk-xxx
@@ -1262,6 +1436,12 @@ configure_qq() {
fi
echo ""
fi
+
+ # v2026.3.13: 确保插件名称正确写入 plugins.allow
+ # OpenClaw 要求 plugins.allow 中的名称必须与实际插件名完全匹配
+ if [ "$plugin_installed" -eq 1 ]; then
+ ensure_qqbot_plugin_allowed
+ fi
else
echo -e " ${YELLOW}已跳过插件安装,继续配置 QQ 机器人参数。${NC}"
echo -e " ${CYAN}稍后安装命令: openclaw plugins install @tencent-connect/openclaw-qqbot@latest${NC}"
@@ -1449,27 +1629,87 @@ configure_feishu() {
echo ""
echo -e " ${BOLD}🐦 飞书 Bot 配置${NC}"
echo ""
- echo -e " ${YELLOW}获取凭据: ${CYAN}https://open.feishu.cn${NC} → 创建企业自建应用"
- echo ""
- local current_appid=$(json_get channels.feishu.appId)
- if [ -n "$current_appid" ]; then
- echo -e " ${GREEN}当前 App ID: ${current_appid}${NC}"
+ # 检查 Node.js 是否可用
+ if [ ! -x "$NODE_BIN" ]; then
+ echo -e " ${RED}❌ Node.js 不可用,请先安装运行环境${NC}"
+ return 1
fi
- prompt_with_default "请输入飞书 App ID" "" fs_appid
- prompt_with_default "请输入飞书 App Secret" "" fs_secret
- fs_appid=$(sanitize_input "$fs_appid" | tr -d '[:space:]')
- fs_secret=$(sanitize_input "$fs_secret" | tr -d '[:space:]')
+ # 检查并安装 Python 3 (飞书插件依赖)
+ if ! command -v python3 >/dev/null 2>&1; then
+ echo -e " ${YELLOW}⚠️ 飞书插件需要 Python 3 支持${NC}"
+ echo -e " ${CYAN}正在尝试安装 python3-light...${NC}"
+ opkg update >/dev/null 2>&1
+ # 使用 python3-light 减少安装体积 (约 2MB vs 完整版 30MB+)
+ opkg install python3-light 2>&1 | tail -5 || true
+ if command -v python3 >/dev/null 2>&1; then
+ echo -e " ${GREEN}✅ Python 3 安装成功${NC}"
+ else
+ echo -e " ${RED}❌ Python 3 安装失败${NC}"
+ echo -e " ${YELLOW}请手动安装: opkg install python3-light${NC}"
+ echo ""
+ prompt_with_default "是否继续尝试安装飞书? (y/n)" "n" continue_install
+ if ! confirm_yes "$continue_install"; then
+ return 1
+ fi
+ fi
+ fi
- if [ -n "$fs_appid" ] && [ -n "$fs_secret" ]; then
- json_set channels.feishu.appId "$fs_appid"
- json_set channels.feishu.appSecret "$fs_secret"
- chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
- echo -e " ${GREEN}✅ 飞书配置已保存${NC}"
- ask_restart
+ echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+ echo -e " ${CYAN}飞书官方安装向导${NC}"
+ echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+ echo ""
+ echo -e " ${YELLOW}即将执行飞书官方安装命令:${NC}"
+ echo -e " ${CYAN}npx -y @larksuite/openclaw-lark-tools install${NC}"
+ echo ""
+ echo -e " ${DIM}安装过程中可以:${NC}"
+ echo -e " ${DIM} • 新建机器人: 扫描二维码一键创建${NC}"
+ echo -e " ${DIM} • 关联已有机器人: 输入 App ID 和 App Secret${NC}"
+ echo ""
+ echo -e " ${YELLOW}提示: 若命令行出错,可在命令前增加 sudo 重新执行${NC}"
+ echo ""
+
+ prompt_with_default "是否开始安装? (y/n)" "y" do_install
+ if ! confirm_yes "$do_install"; then
+ echo -e " ${YELLOW}已取消安装${NC}"
+ return
+ fi
+
+ echo ""
+ echo -e " ${CYAN}正在启动飞书安装向导...${NC}"
+ echo ""
+
+ # 执行官方安装命令
+ cd "$OC_DATA"
+ NPX_BIN="${NODE_BASE}/bin/npx"
+ local install_rc=0
+ if [ -x "$NPX_BIN" ]; then
+ "$NPX_BIN" -y @larksuite/openclaw-lark-tools install || install_rc=$?
else
- echo -e " ${YELLOW}信息不完整,已取消。${NC}"
+ # 如果 npx 不存在,使用 node 运行
+ "$NODE_BIN" "$NODE_BASE/lib/node_modules/npm/bin/npx-cli.js" -y @larksuite/openclaw-lark-tools install || install_rc=$?
+ fi
+
+ echo ""
+ if [ $install_rc -eq 0 ]; then
+ echo -e " ${GREEN}✅ 飞书安装完成!${NC}"
+ echo ""
+ echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+ echo -e " ${YELLOW}下一步:${NC}"
+ echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+ echo ""
+ echo -e " 1. 在飞书中向机器人发送任意消息,即可开始对话"
+ echo -e " 2. 发送 ${CYAN}/feishu auth${NC} 完成批量授权"
+ echo -e " 3. 发送 ${CYAN}/feishu start${NC} 验证安装是否成功"
+ echo -e " 4. 发送 ${CYAN}学习一下我安装的新飞书插件,列出有哪些能力${NC}"
+ echo ""
+ else
+ echo -e " ${YELLOW}⚠️ 安装向导退出 (exit: $install_rc)${NC}"
+ echo ""
+ echo -e " ${CYAN}手动安装命令:${NC}"
+ echo -e " ${CYAN}npx -y @larksuite/openclaw-lark-tools install${NC}"
+ echo ""
fi
}
@@ -2086,26 +2326,161 @@ main_menu() {
echo -e "${GREEN}║${NC} ${BOLD}OpenClaw AI Gateway — OpenWrt 配置管理${NC} ${GREEN}║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
- echo -e " ${CYAN}1)${NC} 📋 查看当前配置"
- echo -e " ${CYAN}2)${NC} 🤖 配置 AI 模型提供商"
- echo -e " ${CYAN}3)${NC} 🔄 设定当前活跃模型"
- echo -e " ${CYAN}4)${NC} 📡 配置消息渠道 (QQ/Telegram/Discord/飞书/Slack)"
- echo -e " ${CYAN}5)${NC} 🔍 健康检查 / 诊断"
+ echo -e " ${DIM}━━━ AI 模型配置 ━━━${NC}"
+ echo -e " ${CYAN}1)${NC} 🤖 配置 AI 模型和提供商"
+ echo -e " ${CYAN}2)${NC} 🎯 设置活动模型"
+ echo ""
+ echo -e " ${DIM}━━━ 消息渠道 ━━━${NC}"
+ echo -e " ${CYAN}3)${NC} 📡 配置消息渠道 (电报/QQ/飞书)"
+ echo ""
+ echo -e " ${DIM}━━━ 系统管理 ━━━${NC}"
+ echo -e " ${CYAN}4)${NC} 🩺 健康检查与状态"
+ echo -e " ${CYAN}5)${NC} 📋 查看日志"
echo -e " ${CYAN}6)${NC} 🔄 重启 Gateway"
- echo -e " ${CYAN}7)${NC} 📝 查看原始配置文件"
- echo -e " ${CYAN}8)${NC} 💾 备份/还原配置"
- echo -e " ${CYAN}9)${NC} ⚠️ 恢复默认配置"
+ echo ""
+ echo -e " ${DIM}━━━ 高级选项 ━━━${NC}"
+ echo -e " ${CYAN}7)${NC} 🔧 高级配置"
+ echo -e " ${CYAN}8)${NC} ♻️ 重置配置"
+ echo -e " ${CYAN}9)${NC} 📊 显示当前配置概览"
+ echo ""
echo -e " ${CYAN}0)${NC} 退出"
echo ""
prompt_with_default "请选择" "1" menu_choice
case "$menu_choice" in
- 1) show_current_config ;;
- 2) configure_model ;;
- 3) set_active_model ;;
- 4) configure_channels ;;
- 5) health_check ;;
+ 1) configure_model ;;
+ 2) set_active_model ;;
+ 3) configure_channels ;;
+ 4) health_check ;;
+ 5)
+ echo ""
+ echo -e " ${CYAN}=== OpenClaw 日志 ===${NC}"
+ echo ""
+ logread -e openclaw 2>/dev/null | tail -100 || echo " (无法读取日志)"
+ echo ""
+ prompt_with_default "按回车继续" "" _
+ ;;
6) restart_gateway ;;
+ 7) advanced_menu ;;
+ 8) reset_to_defaults ;;
+ 9) show_current_config ;;
+ 0)
+ echo -e " ${GREEN}再见!${NC}"
+ exit 0
+ ;;
+ *) echo -e " ${YELLOW}无效选择${NC}" ;;
+ esac
+ done
+}
+
+# ── 高级配置菜单 ──
+advanced_menu() {
+ while true; do
+ local gw_port gw_bind gw_mode log_level acp_dispatch
+ gw_port=$(json_get "gateway.port" 2>/dev/null || echo "18789")
+ gw_bind=$(json_get "gateway.bind" 2>/dev/null || echo "lan")
+ gw_mode=$(json_get "gateway.mode" 2>/dev/null || echo "local")
+ log_level=$(json_get "gateway.logLevel" 2>/dev/null || echo "")
+ acp_dispatch=$(json_get "acp.dispatch.enabled" 2>/dev/null || echo "false")
+
+ echo ""
+ echo -e " ${BOLD}🔧 高级配置${NC}"
+ echo ""
+ echo -e " ${CYAN}1)${NC} Gateway 端口 ${DIM}(当前: ${gw_port})${NC}"
+ echo -e " ${CYAN}2)${NC} Gateway 绑定地址 ${DIM}(当前: ${gw_bind})${NC}"
+ echo -e " ${CYAN}3)${NC} Gateway 运行模式 ${DIM}(当前: ${gw_mode})${NC}"
+ echo -e " ${CYAN}4)${NC} 日志级别 ${DIM}(当前: ${log_level:-未设置})${NC}"
+ echo -e " ${CYAN}5)${NC} ACP Dispatch 设置 ${DIM}(当前: ${acp_dispatch})${NC}"
+ echo -e " ${CYAN}6)${NC} 官方完整配置向导 ${DIM}(oc configure)${NC}"
+ echo -e " ${CYAN}7)${NC} 查看原始配置 JSON"
+ echo -e " ${CYAN}8)${NC} 编辑配置文件 ${DIM}(vi / nano)${NC}"
+ echo -e " ${CYAN}9)${NC} 导出配置备份"
+ echo -e " ${CYAN}10)${NC} 导入配置"
+ echo -e " ${CYAN}0)${NC} 返回主菜单"
+ echo ""
+ prompt_with_default "请选择" "0" adv_choice
+
+ case "$adv_choice" in
+ 1)
+ echo ""
+ prompt_with_default "请输入 Gateway 端口" "$gw_port" new_port
+ if [ -n "$new_port" ] && [ "$new_port" != "$gw_port" ]; then
+ json_set "gateway.port" "$new_port"
+ # 同步到 UCI
+ uci set openclaw.main.port="$new_port" 2>/dev/null
+ uci commit openclaw 2>/dev/null
+ echo -e " ${GREEN}✅ 端口已设置为 ${new_port}${NC}"
+ ask_restart
+ fi
+ ;;
+ 2)
+ echo ""
+ echo -e " ${CYAN}绑定地址选项:${NC}"
+ echo " lan - 仅 LAN 接口 (推荐)"
+ echo " loopback - 仅本机访问"
+ echo " all - 所有接口 (0.0.0.0)"
+ echo ""
+ prompt_with_default "请输入绑定地址" "$gw_bind" new_bind
+ if [ -n "$new_bind" ]; then
+ case "$new_bind" in
+ lan|loopback|all)
+ json_set "gateway.bind" "$new_bind"
+ uci set openclaw.main.bind="$new_bind" 2>/dev/null
+ uci commit openclaw 2>/dev/null
+ echo -e " ${GREEN}✅ 绑定地址已设置为 ${new_bind}${NC}"
+ ask_restart
+ ;;
+ *) echo -e " ${YELLOW}无效选项${NC}" ;;
+ esac
+ fi
+ ;;
+ 3)
+ echo ""
+ echo -e " ${CYAN}运行模式选项:${NC}"
+ echo " local - 本地模式 (推荐)"
+ echo " remote - 远程模式"
+ echo ""
+ prompt_with_default "请输入运行模式" "$gw_mode" new_mode
+ if [ -n "$new_mode" ] && [ "$new_mode" != "$gw_mode" ]; then
+ json_set "gateway.mode" "$new_mode"
+ echo -e " ${GREEN}✅ 运行模式已设置为 ${new_mode}${NC}"
+ ask_restart
+ fi
+ ;;
+ 4)
+ echo ""
+ echo -e " ${CYAN}日志级别选项:${NC}"
+ echo " debug, info, warn, error"
+ echo ""
+ prompt_with_default "请输入日志级别" "${log_level:-info}" new_level
+ if [ -n "$new_level" ]; then
+ json_set "gateway.logLevel" "$new_level"
+ echo -e " ${GREEN}✅ 日志级别已设置为 ${new_level}${NC}"
+ ask_restart
+ fi
+ ;;
+ 5)
+ echo ""
+ echo -e " ${CYAN}ACP Dispatch 选项:${NC}"
+ echo " true - 启用 (可能占用大量内存)"
+ echo " false - 禁用 (推荐路由器使用)"
+ echo ""
+ prompt_with_default "请输入设置" "$acp_dispatch" new_acp
+ case "$new_acp" in
+ true|false)
+ json_set "acp.dispatch.enabled" "$new_acp"
+ echo -e " ${GREEN}✅ ACP Dispatch 已设置为 ${new_acp}${NC}"
+ ask_restart
+ ;;
+ *) echo -e " ${YELLOW}无效选项${NC}" ;;
+ esac
+ ;;
+ 6)
+ echo ""
+ echo -e " ${CYAN}启动官方配置向导...${NC}"
+ oc_cmd configure
+ ask_restart
+ ;;
7)
echo ""
echo -e " ${CYAN}配置文件路径: ${CONFIG_FILE}${NC}"
@@ -2116,18 +2491,41 @@ main_menu() {
echo " (配置文件不存在)"
fi
echo ""
- prompt_with_default "是否用 vi 编辑? (y/n)" "n" do_edit
- if confirm_yes "$do_edit"; then
+ prompt_with_default "按回车继续" "" _
+ ;;
+ 8)
+ echo ""
+ if [ -f "$CONFIG_FILE" ]; then
vi "$CONFIG_FILE"
ask_restart
+ else
+ echo -e " ${YELLOW}配置文件不存在${NC}"
fi
;;
- 8) backup_restore_menu ;;
- 9) reset_to_defaults ;;
- 0)
- echo -e " ${GREEN}再见!${NC}"
- exit 0
+ 9) backup_restore_menu ;;
+ 10)
+ echo ""
+ echo -e " ${CYAN}导入配置${NC}"
+ echo ""
+ local backup_dir="${OC_STATE_DIR}/backups"
+ if [ -d "$backup_dir" ]; then
+ echo " 可用备份:"
+ ls -lt "$backup_dir"/*.json 2>/dev/null | head -5 | while read -r line; do
+ echo " $(echo "$line" | awk '{print $NF}')"
+ done
+ fi
+ echo ""
+ prompt_with_default "请输入备份文件路径" "" import_path
+ if [ -n "$import_path" ] && [ -f "$import_path" ]; then
+ cp "$import_path" "$CONFIG_FILE"
+ chown openclaw:openclaw "$CONFIG_FILE"
+ echo -e " ${GREEN}✅ 配置已导入${NC}"
+ ask_restart
+ else
+ echo -e " ${YELLOW}文件不存在${NC}"
+ fi
;;
+ 0) return ;;
*) echo -e " ${YELLOW}无效选择${NC}" ;;
esac
done
diff --git a/scripts/build-node-musl.sh b/scripts/build-node-musl.sh
index 6fbea3c..fe2656f 100755
--- a/scripts/build-node-musl.sh
+++ b/scripts/build-node-musl.sh
@@ -1,114 +1,262 @@
#!/bin/sh
+# ============================================================================
+# Node.js ARM64 musl 构建脚本
# 在 Alpine ARM64 Docker 容器内运行
-# 环境变量: NODE_VER (目标版本号), /output (输出目录)
+#
+# 环境变量:
+# NODE_VER (目标版本号)
+# BUILD_MODE (apk|cross) - apk: 使用 Alpine apk, cross: 从官方 glibc 版本交叉编译
+# /output (输出目录)
#
# 打包策略:
+# 1. apk 模式: 使用 Alpine apk 安装 nodejs,版本受限于 Alpine 仓库
+# 2. cross 模式: 从 Node.js 官方下载 glibc 版本,转换为 musl
# 使用 patchelf 修改 node 二进制的 ELF interpreter 和 rpath,
# 使其直接使用打包的 musl 链接器和共享库,无需 LD_LIBRARY_PATH。
# 这样 process.execPath 返回正确的 node 路径,子进程 fork 也能正常工作。
# 安装路径固定为 /opt/openclaw/node (与 openclaw-env 一致)。
+# ============================================================================
set -e
INSTALL_PREFIX="/opt/openclaw/node"
+BUILD_MODE="${BUILD_MODE:-apk}"
-apk add --no-cache nodejs npm xz icu-data-full patchelf
+echo "=== Node.js ARM64 musl Build ==="
+echo " Target version: v${NODE_VER}"
+echo " Build mode: ${BUILD_MODE}"
-ACTUAL_VER=$(node --version | sed 's/^v//')
-echo "Alpine Node.js version: v${ACTUAL_VER} (requested: v${NODE_VER})"
+# ── apk 模式: 使用 Alpine 仓库的 Node.js ──
+build_apk() {
+ echo ""
+ echo "=== Building with Alpine apk mode ==="
+ apk add --no-cache nodejs npm xz icu-data-full patchelf
-# 使用实际版本号作为文件名 (Alpine apk 的 nodejs 版本可能与请求版本不同)
-if [ "$ACTUAL_VER" != "$NODE_VER" ]; then
- echo "WARNING: Actual version (${ACTUAL_VER}) differs from requested (${NODE_VER})"
- echo " Using actual version for package name"
-fi
-PKG_NAME="node-v${ACTUAL_VER}-linux-arm64-musl"
-PKG_DIR="/tmp/${PKG_NAME}"
-mkdir -p "${PKG_DIR}/bin" "${PKG_DIR}/lib/node_modules" "${PKG_DIR}/include/node"
+ ACTUAL_VER=$(node --version | sed 's/^v//')
+ echo "Alpine Node.js version: v${ACTUAL_VER} (requested: v${NODE_VER})"
-# 复制 node 二进制
-cp "$(which node)" "${PKG_DIR}/bin/node"
-chmod +x "${PKG_DIR}/bin/node"
+ # 使用实际版本号作为文件名 (Alpine apk 的 nodejs 版本可能与请求版本不同)
+ if [ "$ACTUAL_VER" != "$NODE_VER" ]; then
+ echo "WARNING: Actual version (${ACTUAL_VER}) differs from requested (${NODE_VER})"
+ echo " Using actual version for package name"
+ fi
+ PKG_NAME="node-v${ACTUAL_VER}-linux-arm64-musl"
+ PKG_DIR="/tmp/${PKG_NAME}"
+ mkdir -p "${PKG_DIR}/bin" "${PKG_DIR}/lib/node_modules" "${PKG_DIR}/include/node"
-# 收集 node 依赖的所有共享库 (Alpine node 是动态链接的)
-echo "=== Collecting shared libraries ==="
-LIB_DIR="${PKG_DIR}/lib"
-ldd "$(which node)" 2>/dev/null | while read -r line; do
- # 解析 ldd 输出: libxxx.so => /usr/lib/libxxx.so (0x...)
- lib_path=$(echo "$line" | grep -oE '/[^ ]+\.so[^ ]*' | head -1)
- if [ -n "$lib_path" ] && [ -f "$lib_path" ]; then
- cp -L "$lib_path" "$LIB_DIR/" 2>/dev/null || true
- echo " + $(basename "$lib_path")"
- fi
-done
-# 确保 musl 动态链接器也在
-if [ -f /lib/ld-musl-aarch64.so.1 ]; then
- cp -L /lib/ld-musl-aarch64.so.1 "$LIB_DIR/" 2>/dev/null || true
- echo " + ld-musl-aarch64.so.1"
-fi
-echo "Libraries collected: $(ls "$LIB_DIR"/*.so* 2>/dev/null | wc -l) files"
+ # 复制 node 二进制
+ cp "$(which node)" "${PKG_DIR}/bin/node"
+ chmod +x "${PKG_DIR}/bin/node"
-# 用 patchelf 修改 node 二进制:
-# - interpreter 指向打包的 musl 链接器 (绝对路径,对应安装后的位置)
-# - rpath 指向打包的 lib 目录
-echo "=== Patching ELF binary ==="
-patchelf --set-interpreter "${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1" "${PKG_DIR}/bin/node"
-patchelf --set-rpath "${INSTALL_PREFIX}/lib" "${PKG_DIR}/bin/node"
-echo " interpreter: ${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1"
-echo " rpath: ${INSTALL_PREFIX}/lib"
+ # 收集 node 依赖的所有共享库 (Alpine node 是动态链接的)
+ echo "=== Collecting shared libraries ==="
+ LIB_DIR="${PKG_DIR}/lib"
+ ldd "$(which node)" 2>/dev/null | while read -r line; do
+ lib_path=$(echo "$line" | grep -oE '/[^ ]+\.so[^ ]*' | head -1)
+ if [ -n "$lib_path" ] && [ -f "$lib_path" ]; then
+ cp -L "$lib_path" "$LIB_DIR/" 2>/dev/null || true
+ echo " + $(basename "$lib_path")"
+ fi
+ done
+ # 确保 musl 动态链接器也在
+ if [ -f /lib/ld-musl-aarch64.so.1 ]; then
+ cp -L /lib/ld-musl-aarch64.so.1 "$LIB_DIR/" 2>/dev/null || true
+ echo " + ld-musl-aarch64.so.1"
+ fi
+ echo "Libraries collected: $(ls "$LIB_DIR"/*.so* 2>/dev/null | wc -l) files"
-# 复制 ICU 完整数据 (npm 的 Intl.Collator 需要)
-echo "=== Copying ICU data ==="
-ICU_DAT=$(find /usr/share/icu -name "icudt*.dat" 2>/dev/null | head -1)
-if [ -n "$ICU_DAT" ] && [ -f "$ICU_DAT" ]; then
- mkdir -p "${PKG_DIR}/share/icu"
- cp "$ICU_DAT" "${PKG_DIR}/share/icu/"
- echo " + $(basename "$ICU_DAT") ($(du -h "$ICU_DAT" | cut -f1))"
-else
- echo " WARNING: ICU data file not found"
-fi
+ # 复制 ICU 完整数据
+ echo "=== Copying ICU data ==="
+ ICU_DAT=$(find /usr/share/icu -name "icudt*.dat" 2>/dev/null | head -1)
+ if [ -n "$ICU_DAT" ] && [ -f "$ICU_DAT" ]; then
+ mkdir -p "${PKG_DIR}/share/icu"
+ cp "$ICU_DAT" "${PKG_DIR}/share/icu/"
+ echo " + $(basename "$ICU_DAT") ($(du -h "$ICU_DAT" | cut -f1))"
+ else
+ echo " WARNING: ICU data file not found"
+ fi
-# 创建 node wrapper 脚本 (只设置 NODE_ICU_DATA,ELF 层面已解决链接器和库路径)
-cat > "${PKG_DIR}/bin/node-wrapper" << 'NODEWRAPPER'
+ # 复制 npm
+ if [ -d /usr/lib/node_modules/npm ]; then
+ cp -r /usr/lib/node_modules/npm "${PKG_DIR}/lib/node_modules/"
+ fi
+
+ # 返回包名供后续使用
+ echo "PKG_NAME=${PKG_NAME}" >> /tmp/build_env
+ echo "PKG_DIR=${PKG_DIR}" >> /tmp/build_env
+}
+
+# ── cross 模式: 从 Node.js 官方 glibc 版本交叉编译 ──
+build_cross() {
+ echo ""
+ echo "=== Building with cross-compilation mode ==="
+ apk add --no-cache xz patchelf curl wget ca-certificates
+
+ # 下载 Node.js 官方 ARM64 glibc 版本
+ NODE_TARBALL="node-v${NODE_VER}-linux-arm64.tar.xz"
+ NODE_URL="https://nodejs.org/dist/v${NODE_VER}/${NODE_TARBALL}"
+
+ echo "=== Downloading Node.js v${NODE_VER} ARM64 glibc ==="
+ cd /tmp
+ if ! curl -fSL -o "$NODE_TARBALL" "$NODE_URL"; then
+ echo "ERROR: Failed to download Node.js from $NODE_URL"
+ exit 1
+ fi
+
+ # 解压
+ echo "=== Extracting Node.js ==="
+ rm -rf "node-v${NODE_VER}-linux-arm64" 2>/dev/null || true
+ tar xf "$NODE_TARBALL"
+ SRC_DIR="node-v${NODE_VER}-linux-arm64"
+
+ # 创建输出目录
+ PKG_NAME="node-v${NODE_VER}-linux-arm64-musl"
+ PKG_DIR="/tmp/${PKG_NAME}"
+ rm -rf "$PKG_DIR" 2>/dev/null || true
+ mkdir -p "${PKG_DIR}/bin" "${PKG_DIR}/lib" "${PKG_DIR}/share/icu"
+
+ # 复制 node 二进制
+ echo "=== Copying Node.js binary ==="
+ cp "${SRC_DIR}/bin/node" "${PKG_DIR}/bin/node"
+ chmod +x "${PKG_DIR}/bin/node"
+
+ # 复制 npm
+ if [ -d "${SRC_DIR}/lib/node_modules/npm" ]; then
+ mkdir -p "${PKG_DIR}/lib/node_modules"
+ cp -r "${SRC_DIR}/lib/node_modules/npm" "${PKG_DIR}/lib/node_modules/"
+ fi
+
+ # 复制 ICU 数据
+ ICU_DAT=$(find "${SRC_DIR}" -name "icudt*.dat" 2>/dev/null | head -1)
+ if [ -n "$ICU_DAT" ] && [ -f "$ICU_DAT" ]; then
+ cp "$ICU_DAT" "${PKG_DIR}/share/icu/"
+ echo " + ICU data: $(basename "$ICU_DAT")"
+ fi
+
+ # ── 关键步骤: musl 库收集 ──
+ echo "=== Converting to musl libc ==="
+
+ # 复制 musl 动态链接器
+ if [ -f /lib/ld-musl-aarch64.so.1 ]; then
+ cp -L /lib/ld-musl-aarch64.so.1 "${PKG_DIR}/lib/"
+ echo " + ld-musl-aarch64.so.1"
+ else
+ echo "ERROR: musl dynamic linker not found"
+ exit 1
+ fi
+
+ # 收集 musl 版本的依赖库
+ # Node.js 官方 ARM64 版本依赖: libcrypto, libssl, libz, libstdc++, libgcc_s
+ # 这些库需要从 Alpine (musl) 版本获取
+ echo "=== Collecting musl libraries ==="
+ LIB_DIR="${PKG_DIR}/lib"
+
+ # 安装必要的库包
+ apk add --no-cache libcrypto3 libssl3 zlib libstdc++ libgcc
+
+ # 复制库文件
+ for lib in libcrypto.so.3 libssl.so.3 libz.so.1 libstdc++.so.6 libgcc_s.so.1; do
+ for libpath in /usr/lib/$lib /lib/$lib; do
+ if [ -f "$libpath" ]; then
+ cp -L "$libpath" "$LIB_DIR/" 2>/dev/null || true
+ echo " + $lib"
+ break
+ fi
+ done
+ done
+
+ # 收集所有 musl 库的依赖
+ for lib in "$LIB_DIR"/*.so*; do
+ [ -f "$lib" ] || continue
+ ldd "$lib" 2>/dev/null | while read -r line; do
+ lib_path=$(echo "$line" | grep -oE '/[^ ]+\.so[^ ]*' | head -1)
+ if [ -n "$lib_path" ] && [ -f "$lib_path" ]; then
+ lib_name=$(basename "$lib_path")
+ [ -f "$LIB_DIR/$lib_name" ] || cp -L "$lib_path" "$LIB_DIR/" 2>/dev/null || true
+ fi
+ done
+ done
+
+ echo "Libraries collected: $(ls "$LIB_DIR"/*.so* 2>/dev/null | wc -l) files"
+
+ # 返回包名供后续使用
+ echo "PKG_NAME=${PKG_NAME}" >> /tmp/build_env
+ echo "PKG_DIR=${PKG_DIR}" >> /tmp/build_env
+}
+
+# ── 公共步骤: patchelf 和打包 ──
+finalize_package() {
+ . /tmp/build_env
+
+ # 用 patchelf 修改 node 二进制
+ echo "=== Patching ELF binary ==="
+ patchelf --set-interpreter "${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1" "${PKG_DIR}/bin/node"
+ patchelf --set-rpath "${INSTALL_PREFIX}/lib" "${PKG_DIR}/bin/node"
+ echo " interpreter: ${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1"
+ echo " rpath: ${INSTALL_PREFIX}/lib"
+
+ # 创建 node wrapper 脚本
+ cat > "${PKG_DIR}/bin/node-wrapper" << 'NODEWRAPPER'
#!/bin/sh
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
export NODE_ICU_DATA="${SELF_DIR}/../share/icu"
exec "${SELF_DIR}/node" "$@"
NODEWRAPPER
-chmod +x "${PKG_DIR}/bin/node-wrapper"
+ chmod +x "${PKG_DIR}/bin/node-wrapper"
-# 复制 npm
-if [ -d /usr/lib/node_modules/npm ]; then
- cp -r /usr/lib/node_modules/npm "${PKG_DIR}/lib/node_modules/"
-fi
-
-# 创建 npm wrapper (直接调用 patchelf 后的 node,只需设置 ICU)
-cat > "${PKG_DIR}/bin/npm" << 'NPMWRAPPER'
+ # 创建 npm wrapper
+ cat > "${PKG_DIR}/bin/npm" << 'NPMWRAPPER'
#!/bin/sh
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
export NODE_ICU_DATA="${SELF_DIR}/../share/icu"
exec "${SELF_DIR}/node" "${SELF_DIR}/../lib/node_modules/npm/bin/npm-cli.js" "$@"
NPMWRAPPER
-# 创建 npx wrapper
-cat > "${PKG_DIR}/bin/npx" << 'NPXWRAPPER'
+
+ # 创建 npx wrapper
+ cat > "${PKG_DIR}/bin/npx" << 'NPXWRAPPER'
#!/bin/sh
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
export NODE_ICU_DATA="${SELF_DIR}/../share/icu"
exec "${SELF_DIR}/node" "${SELF_DIR}/../lib/node_modules/npm/bin/npx-cli.js" "$@"
NPXWRAPPER
-chmod +x "${PKG_DIR}/bin/npm" "${PKG_DIR}/bin/npx"
+ chmod +x "${PKG_DIR}/bin/npm" "${PKG_DIR}/bin/npx"
-# 验证 (需要将打包内容放到目标路径来测试 patchelf 结果)
-echo "=== Verification ==="
-mkdir -p "${INSTALL_PREFIX}"
-cp -a "${PKG_DIR}"/* "${INSTALL_PREFIX}/"
-"${INSTALL_PREFIX}/bin/node" --version
-"${INSTALL_PREFIX}/bin/node" -e "console.log('execPath:', process.execPath)"
-"${INSTALL_PREFIX}/bin/node" -e "console.log(process.arch, process.platform, process.versions.modules)"
-NODE_ICU_DATA="${INSTALL_PREFIX}/share/icu" "${INSTALL_PREFIX}/bin/npm" --version 2>/dev/null || echo "npm needs ICU data"
-rm -rf "${INSTALL_PREFIX}"
+ # 验证
+ echo "=== Verification ==="
+ mkdir -p "${INSTALL_PREFIX}"
+ cp -a "${PKG_DIR}"/* "${INSTALL_PREFIX}/"
+
+ # 设置库路径并测试
+ export LD_LIBRARY_PATH="${INSTALL_PREFIX}/lib"
+
+ "${INSTALL_PREFIX}/bin/node" --version
+ "${INSTALL_PREFIX}/bin/node" -e "console.log('execPath:', process.execPath)"
+ "${INSTALL_PREFIX}/bin/node" -e "console.log(process.arch, process.platform, process.versions.modules)"
+ NODE_ICU_DATA="${INSTALL_PREFIX}/share/icu" "${INSTALL_PREFIX}/bin/npm" --version 2>/dev/null || echo "npm needs ICU data"
+
+ rm -rf "${INSTALL_PREFIX}"
-# 打包
-cd /tmp
-tar cJf "/output/${PKG_NAME}.tar.xz" "${PKG_NAME}"
-ls -lh "/output/${PKG_NAME}.tar.xz"
-echo "=== Done: ${PKG_NAME}.tar.xz ==="
+ # 打包
+ echo "=== Creating tarball ==="
+ cd /tmp
+ tar cJf "/output/${PKG_NAME}.tar.xz" "${PKG_NAME}"
+ ls -lh "/output/${PKG_NAME}.tar.xz"
+ echo "=== Done: ${PKG_NAME}.tar.xz ==="
+}
+
+# ── 主入口 ──
+rm -f /tmp/build_env
+
+case "$BUILD_MODE" in
+ apk)
+ build_apk
+ ;;
+ cross)
+ build_cross
+ ;;
+ *)
+ echo "ERROR: Unknown BUILD_MODE: $BUILD_MODE"
+ exit 1
+ ;;
+esac
+
+finalize_package
diff --git a/scripts/build_ipk.sh b/scripts/build_ipk.sh
index a365d72..cc1c452 100755
--- a/scripts/build_ipk.sh
+++ b/scripts/build_ipk.sh
@@ -41,6 +41,11 @@ mkdir -p "$DATA_DIR/etc/init.d"
cp "$PKG_DIR/root/etc/init.d/openclaw" "$DATA_DIR/etc/init.d/"
chmod +x "$DATA_DIR/etc/init.d/openclaw"
+# profile.d (v1.0.16+: 全局环境变量)
+mkdir -p "$DATA_DIR/etc/profile.d"
+cp "$PKG_DIR/root/etc/profile.d/openclaw.sh" "$DATA_DIR/etc/profile.d/"
+chmod +x "$DATA_DIR/etc/profile.d/openclaw.sh"
+
# bin
mkdir -p "$DATA_DIR/usr/bin"
cp "$PKG_DIR/root/usr/bin/openclaw-env" "$DATA_DIR/usr/bin/"
@@ -68,6 +73,11 @@ cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$DATA_DIR/usr/share/openclaw/"
# Web PTY UI
cp -r "$PKG_DIR/root/usr/share/openclaw/ui" "$DATA_DIR/usr/share/openclaw/"
+# profile.d 环境变量脚本 (v1.0.16+)
+mkdir -p "$DATA_DIR/etc/profile.d"
+cp "$PKG_DIR/root/etc/profile.d/openclaw.sh" "$DATA_DIR/etc/profile.d/"
+chmod +x "$DATA_DIR/etc/profile.d/openclaw.sh"
+
# i18n (po2lmo 可选)
mkdir -p "$DATA_DIR/usr/lib/lua/luci/i18n"
if command -v po2lmo >/dev/null 2>&1 && [ -f "$PKG_DIR/po/zh-cn/openclaw.po" ]; then
@@ -86,7 +96,7 @@ mkdir -p "$CTRL_DIR"
cat > "$CTRL_DIR/control" << EOF
Package: ${PKG_NAME}
Version: ${PKG_VERSION}-${PKG_RELEASE}
-Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
+Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar, libstdcpp6
Source: https://github.com/10000ge10000/luci-app-openclaw
SourceName: ${PKG_NAME}
License: GPL-3.0
@@ -101,20 +111,84 @@ EOF
cat > "$CTRL_DIR/postinst" << 'EOF'
#!/bin/sh
[ -n "${IPKG_INSTROOT}" ] || {
- ( . /etc/uci-defaults/99-openclaw ) && rm -f /etc/uci-defaults/99-openclaw
+ # ══════════════════════════════════════════════════════════════
+ # 配置文件冲突处理 (opkg 将新配置保存为 .opkg 后缀)
+ # ══════════════════════════════════════════════════════════════
+ # opkg 配置文件冲突处理流程:
+ # 1. opkg 检测到 /etc/config/openclaw 已存在且内容不同
+ # 2. opkg 保留旧配置,将新配置保存为 /etc/config/openclaw-opkg
+ # 3. postinst 需要合并用户配置到新配置文件
+
+ OLD_CONFIG="/etc/config/openclaw"
+ NEW_CONFIG="/etc/config/openclaw-opkg"
+
+ if [ -f "$NEW_CONFIG" ]; then
+ echo "检测到配置文件冲突,正在智能合并..."
+
+ # 步骤1: 从旧配置中提取用户设置 (在替换之前!)
+ # 使用 sed 直接解析 UCI 格式,不依赖 uci 命令
+ USER_ENABLED=$(sed -n "s/^\s*option\s\+enabled\s\+['\"]\\?\\([^'\"]*\\)['\"]\\?.*/\\1/p" "$OLD_CONFIG" 2>/dev/null | tail -1)
+ USER_PORT=$(sed -n "s/^\s*option\s\+port\s\+['\"]\\?\\([^'\"]*\\)['\"]\\?.*/\\1/p" "$OLD_CONFIG" 2>/dev/null | tail -1)
+ USER_BIND=$(sed -n "s/^\s*option\s\+bind\s\+['\"]\\?\\([^'\"]*\\)['\"]\\?.*/\\1/p" "$OLD_CONFIG" 2>/dev/null | tail -1)
+ USER_TOKEN=$(sed -n "s/^\s*option\s\+token\s\+['\"]\\?\\([^'\"]*\\)['\"]\\?.*/\\1/p" "$OLD_CONFIG" 2>/dev/null | tail -1)
+ USER_PTY_PORT=$(sed -n "s/^\s*option\s\+pty_port\s\+['\"]\\?\\([^'\"]*\\)['\"]\\?.*/\\1/p" "$OLD_CONFIG" 2>/dev/null | tail -1)
+
+ # 步骤2: 备份旧配置 (带时间戳)
+ BAK_FILE="/etc/config/openclaw.$(date +%Y%m%d%H%M%S).bak"
+ cp "$OLD_CONFIG" "$BAK_FILE" 2>/dev/null || true
+ echo "旧配置已备份到: $BAK_FILE"
+
+ # 步骤3: 使用新配置文件
+ mv "$NEW_CONFIG" "$OLD_CONFIG" 2>/dev/null || cp "$NEW_CONFIG" "$OLD_CONFIG" 2>/dev/null || true
+ rm -f "$NEW_CONFIG" 2>/dev/null || true
+
+ # 步骤4: 合并用户设置到新配置
+ # 直接使用 sed 修改配置文件,兼容性更好
+ [ -n "$USER_ENABLED" ] && sed -i "s/^\(\s*option\s\+enabled\s\+\).*/\\1'$USER_ENABLED'/" "$OLD_CONFIG" 2>/dev/null || true
+ [ -n "$USER_PORT" ] && sed -i "s/^\(\s*option\s\+port\s\+\).*/\\1'$USER_PORT'/" "$OLD_CONFIG" 2>/dev/null || true
+ [ -n "$USER_BIND" ] && sed -i "s/^\(\s*option\s\+bind\s\+\).*/\\1'$USER_BIND'/" "$OLD_CONFIG" 2>/dev/null || true
+ [ -n "$USER_TOKEN" ] && sed -i "s/^\(\s*option\s\+token\s\+\).*/\\1'$USER_TOKEN'/" "$OLD_CONFIG" 2>/dev/null || true
+ [ -n "$USER_PTY_PORT" ] && sed -i "s/^\(\s*option\s\+pty_port\s\+\).*/\\1'$USER_PTY_PORT'/" "$OLD_CONFIG" 2>/dev/null || true
+
+ echo "配置合并完成,用户设置已保留"
+ fi
+
+ # 执行 uci-defaults 初始化脚本
+ if [ -f /etc/uci-defaults/99-openclaw ]; then
+ ( . /etc/uci-defaults/99-openclaw ) && rm -f /etc/uci-defaults/99-openclaw
+ fi
+
+ # 清理 LuCI 缓存
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* /tmp/luci-indexcache.*.json 2>/dev/null
+
# 重启 Web PTY (使其加载新文件和新 token)
PTY_PID=$(pgrep -f 'web-pty.js' 2>/dev/null | head -1)
[ -n "$PTY_PID" ] && kill "$PTY_PID" 2>/dev/null || true
+
exit 0
}
EOF
chmod +x "$CTRL_DIR/postinst"
+cat > "$CTRL_DIR/prerm" << 'EOF'
+#!/bin/sh
+[ -n "${IPKG_INSTROOT}" ] || {
+ # 升级前备份当前配置
+ if [ -f /etc/config/openclaw ]; then
+ cp /etc/config/openclaw /etc/config/openclaw.pre-upgrade.bak 2>/dev/null || true
+ fi
+}
+EOF
+chmod +x "$CTRL_DIR/prerm"
+
cat > "$CTRL_DIR/postrm" << 'EOF'
#!/bin/sh
[ -n "${IPKG_INSTROOT}" ] || {
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null
+ # 清理备份文件 (仅在完全卸载时)
+ if [ "$1" = "0" ]; then
+ rm -f /etc/config/openclaw.user.bak /etc/config/openclaw.pre-upgrade.bak 2>/dev/null
+ fi
}
EOF
chmod +x "$CTRL_DIR/postrm"
@@ -145,3 +219,8 @@ echo "文件大小: ${IPK_SIZE} bytes"
echo "安装大小: ${INSTALLED_SIZE} KB"
echo ""
echo "安装方法: opkg install ${PKG_NAME}_${PKG_VERSION}-${PKG_RELEASE}_all.ipk"
+
+# ── 同步构建 .run 包 ──
+echo ""
+echo "=== 同步构建 .run 包 ==="
+"$SCRIPT_DIR/build_run.sh" "$OUT_DIR"
diff --git a/scripts/build_run.sh b/scripts/build_run.sh
index a5b35dc..4ca6235 100755
--- a/scripts/build_run.sh
+++ b/scripts/build_run.sh
@@ -44,6 +44,11 @@ install_files() {
cp "$PKG_DIR/root/etc/init.d/openclaw" "$dest/etc/init.d/"
chmod +x "$dest/etc/init.d/openclaw"
+ # profile.d (v1.0.16+: 全局环境变量)
+ mkdir -p "$dest/etc/profile.d"
+ cp "$PKG_DIR/root/etc/profile.d/openclaw.sh" "$dest/etc/profile.d/"
+ chmod +x "$dest/etc/profile.d/openclaw.sh"
+
# bin
mkdir -p "$dest/usr/bin"
cp "$PKG_DIR/root/usr/bin/openclaw-env" "$dest/usr/bin/"
@@ -134,7 +139,7 @@ mkdir -p "$INFO_DIR"
cat > "$INFO_DIR/$PKG.control" << CTLEOF
Package: $PKG
Version: $PKG_VER
-Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
+Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar, libstdcpp6
Section: luci
Architecture: all
Installed-Size: 0
@@ -170,7 +175,7 @@ cat >> "$STATUS_FILE" << STEOF
Package: $PKG
Version: $PKG_VER
-Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
+Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar, libstdcpp6
Status: install user installed
Architecture: all
Conffiles: