release v2.0.0: 适配 OpenClaw v2026.3.13

重大变更:
- 配置管理菜单重构,更清晰的导航结构
- 新增高级配置菜单
- 新增全局环境变量 /etc/profile.d/openclaw.sh

修复:
- QQ 机器人插件配置名称不匹配 (#XX)
- 安装运行环境报错缺少 libstdcpp6 (#28)
- 环境变量路径混乱 (#42)

新增:
- 查看日志功能
- 飞书 Bot 配置流程优化

适配:
- Node.js 版本升级到 22.16.0
- OpenClaw 版本升级到 v2026.3.13
- 依赖声明新增 libstdcpp6
This commit is contained in:
10000ge10000
2026-03-17 01:51:20 +08:00
parent 3ebc36687a
commit 1df1a4170b
16 changed files with 1885 additions and 174 deletions

View File

@@ -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 设备上自动从此处下载合适的版本。

View File

@@ -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
### 修复

View File

@@ -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

View File

@@ -10,7 +10,7 @@
在路由器上运行 OpenClaw通过 LuCI 管理界面完成安装、配置和服务管理。
<div align="center">
<img src="docs/images/1.png" alt="OpenClaw LuCI 管理界面" width="800" style="border-radius:8px;" />
<img src="docs/images/2.png" alt="OpenClaw LuCI 管理界面" width="800" style="border-radius:8px;" />
</div>
**系统要求**

View File

@@ -1 +1 @@
1.0.15
2.0.0

View File

@@ -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

BIN
docs/images/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,11 +634,114 @@ 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
;;
setup-offline)
do_setup_offline "$@"
;;
check)
do_check
;;
@@ -641,8 +755,10 @@ 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"

View File

@@ -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")"
chown -R openclaw:openclaw "$OC_STATE_DIR" 2>/dev/null || true
echo '{}' > "$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
_JS_KEY="$key" _JS_VAL="$value" "$NODE_BIN" -e "
# 检查目录权限
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
# 创建空配置文件
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
# 步骤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<ks.length-1;i++){if(!o[ks[i]]||typeof o[ks[i]]!=='object')o[ks[i]]={};o=o[ks[i]];}
let v=process.env._JS_VAL;try{v=JSON.parse(v);}catch(e){}
for(let i=0;i<ks.length-1;i++){
if(!o[ks[i]]||typeof o[ks[i]]!=='object')o[ks[i]]={};
o=o[ks[i]];
}
// 读取值并作为字符串保存
let v=fs.readFileSync('${tmp_val_file}','utf8');
o[ks[ks.length-1]]=v;
try{
fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
" 2>/dev/null
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 <provider> <api_key> [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:]')
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
# 检查并安装 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 " ${YELLOW}信息不完整,已取消。${NC}"
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
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
# 如果 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

View File

@@ -1,16 +1,34 @@
#!/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}"
echo "=== Node.js ARM64 musl Build ==="
echo " Target version: v${NODE_VER}"
echo " Build mode: ${BUILD_MODE}"
# ── apk 模式: 使用 Alpine 仓库的 Node.js ──
build_apk() {
echo ""
echo "=== Building with Alpine apk mode ==="
apk add --no-cache nodejs npm xz icu-data-full patchelf
ACTUAL_VER=$(node --version | sed 's/^v//')
@@ -33,7 +51,6 @@ chmod +x "${PKG_DIR}/bin/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
@@ -47,16 +64,7 @@ if [ -f /lib/ld-musl-aarch64.so.1 ]; then
fi
echo "Libraries collected: $(ls "$LIB_DIR"/*.so* 2>/dev/null | wc -l) files"
# 用 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"
# 复制 ICU 完整数据 (npm 的 Intl.Collator 需要)
# 复制 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
@@ -67,7 +75,126 @@ else
echo " WARNING: ICU data file not found"
fi
# 创建 node wrapper 脚本 (只设置 NODE_ICU_DATAELF 层面已解决链接器和库路径)
# 复制 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)"
@@ -76,18 +203,14 @@ exec "${SELF_DIR}/node" "$@"
NODEWRAPPER
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)
# 创建 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'
#!/bin/sh
@@ -97,18 +220,43 @@ 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"
# 验证 (需要将打包内容放到目标路径来测试 patchelf 结果)
# 验证
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}"
# 打包
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

View File

@@ -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}" ] || {
# ══════════════════════════════════════════════════════════════
# 配置文件冲突处理 (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"

View File

@@ -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: