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 管理界面完成安装、配置和服务管理。
- OpenClaw LuCI 管理界面 + 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: