11 Commits

Author SHA1 Message Date
mm644706215
47576601d7 fix: route update cache to install root 2026-03-31 16:57:45 +08:00
mm644706215
12a4b836c0 Merge upstream/main into main 2026-03-31 14:26:35 +08:00
10000ge10000
4d565ee785 chore: 整理文档结构,排除内部笔记
- 移动 docs/*.md 到 docs/notes/ 目录
- 更新 .gitignore 排除 docs/notes/
2026-03-31 11:45:00 +08:00
10000ge10000
6db958e7c4 release v2.0.1: 适配 OpenClaw v2026.3.28
- 更新 OC_TESTED_VERSION 到 2026.3.28
- 磁盘空间要求从 1.5GB 提升到 2GB (包体积约 200MB)
- 新增 Mistral AI 配置支持 (mistral-large/medium/small, codestral)
- 新增百度千帆配置支持 (ernie-4.0/3.5/speed)
- 更新 .gitignore 排除 .claude/ 本地设置目录
2026-03-31 11:27:49 +08:00
mm644706215
156837877d fix: open console in a new window 2026-03-26 14:08:52 +08:00
mm644706215
2816ba19c3 fix: normalize shell scripts to lf 2026-03-26 11:42:45 +08:00
mm644706215
5e7d0e4b95 chore: bump plugin version to 2.0.1 2026-03-26 11:29:40 +08:00
mm644706215
5c0a368604 feat: upgrade arm64 musl node runtime to 24.14.1 2026-03-25 22:19:24 +08:00
mm644706215
e73a574db0 fix: add gitea fallback for arm64 musl node downloads 2026-03-22 23:30:57 +08:00
mm644706215
182623f67c Auto-link legacy ARM64 musl node assets 2026-03-21 15:38:26 +08:00
mm644706215
40e0704dc9 Use hotwa node-bins release by default 2026-03-21 15:30:55 +08:00
21 changed files with 617 additions and 739 deletions

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
*.sh text eol=lf
*.bash text eol=lf
Makefile text eol=lf
VERSION text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.lua text eol=lf
*.htm text eol=lf
*.md text eol=lf

View File

@@ -9,7 +9,7 @@ on:
default: true default: true
type: boolean type: boolean
build_v2: build_v2:
description: 'Build V2 (22.16.0) - Current version' description: 'Build V2 (24.14.1) - Current LTS version'
required: false required: false
default: true default: true
type: boolean type: boolean
@@ -89,7 +89,7 @@ jobs:
name: node-arm64-musl-v1 name: node-arm64-musl-v1
path: dist/*.tar.xz path: dist/*.tar.xz
# ── V2 构建: 使用交叉编译模式 (22.16.0) ── # ── V2 构建: 使用 Alpine edge apk 模式 (24.14.1 LTS) ──
build-v2: build-v2:
name: Build Node.js V2 (Current) name: Build Node.js V2 (Current)
if: ${{ inputs.build_v2 }} if: ${{ inputs.build_v2 }}
@@ -108,23 +108,26 @@ jobs:
with: with:
platforms: linux/arm64 platforms: linux/arm64
- name: Build Node.js V2 ARM64 musl (apk current mode) - name: Build Node.js V2 ARM64 musl (apk lts mode)
run: | run: |
NODE_VER="22.16.0" NODE_VER="24.14.1"
mkdir -p dist mkdir -p dist
echo "=== Building Node.js v${NODE_VER}+ ARM64 musl (apk current mode) ===" echo "=== Building Node.js v${NODE_VER} ARM64 musl (apk lts mode) ==="
docker run --rm --platform linux/arm64 \ docker run --rm --platform linux/arm64 \
-v "$PWD/dist:/output" \ -v "$PWD/dist:/output" \
-v "$PWD/scripts/build-node-musl.sh:/build-node-musl.sh:ro" \ -v "$PWD/scripts/build-node-musl.sh:/build-node-musl.sh:ro" \
-e "NODE_VER=${NODE_VER}" \ -e "NODE_VER=${NODE_VER}" \
-e "BUILD_MODE=apk" \ -e "BUILD_MODE=apk" \
-e "PKG_TYPE=current" \ -e "PKG_TYPE=lts" \
alpine:3.21 sh /build-node-musl.sh alpine:edge sh /build-node-musl.sh
- name: Build info - name: Build info
id: build_info id: build_info
run: | run: |
EXPECTED_ARTIFACT="node-v24.14.1-linux-arm64-musl.tar.xz"
test -f "dist/${EXPECTED_ARTIFACT}"
ARTIFACT_NAME=$(ls dist/*.tar.xz | head -1 | xargs basename) ARTIFACT_NAME=$(ls dist/*.tar.xz | head -1 | xargs basename)
test "${ARTIFACT_NAME}" = "${EXPECTED_ARTIFACT}"
echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT" echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
echo "Artifact: ${ARTIFACT_NAME}" echo "Artifact: ${ARTIFACT_NAME}"
@@ -211,13 +214,13 @@ jobs:
| 文件 | 版本 | 用途 | | 文件 | 版本 | 用途 |
|------|------|------| |------|------|------|
| `node-v23.x.x-linux-arm64-musl.tar.xz` | V2 (当前) | OpenClaw v2026.3.11+ (要求 >= 22.16.0) | | `node-v24.14.1-linux-arm64-musl.tar.xz` | V2 (当前默认) | OpenClaw v2026.3.11+ (默认下载,满足 >= 22.16.0) |
| `node-v22.x.x-linux-arm64-musl.tar.xz` | V1 (旧版) | OpenClaw v2026.3.8 及更早版本 | | `node-v22.x.x-linux-arm64-musl.tar.xz` | V1 (旧版) | OpenClaw v2026.3.8 及更早版本 |
## 构建模式 ## 构建模式
两种版本均使用 **Alpine apk 模式** 构建: 两种版本均使用 **Alpine apk 模式** 构建:
- **V2**: 使用 Alpine `nodejs-current` 包 (v23.x)满足 OpenClaw >= 22.16.0 的要求 - **V2**: 使用 Alpine `edge` 的 `nodejs` LTS 包 (v24.x)当前默认发布 `node-v24.14.1-linux-arm64-musl.tar.xz`
- **V1**: 使用 Alpine `nodejs` LTS 包 (v22.x),兼容旧版 OpenClaw - **V1**: 使用 Alpine `nodejs` LTS 包 (v22.x),兼容旧版 OpenClaw
`openclaw-env setup` 会在 ARM64 musl 设备上自动从此处下载合适的版本 `openclaw-env setup` 会在 ARM64 musl 设备上默认直连下载 V2 当前版本,必要时再回退到 Release 元数据解析

6
.gitignore vendored
View File

@@ -21,3 +21,9 @@ Thumbs.db
*.swp *.swp
*.swo *.swo
*~ *~
# Claude Code local settings
.claude/
# Internal notes (not for release)
docs/notes/

View File

@@ -4,6 +4,130 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
## [2.0.1] - 2026-03-30
### 适配 OpenClaw v2026.3.28
#### 版本变更
- **OC_TESTED_VERSION**: 从 2026.3.13 更新到 2026.3.28
- **磁盘空间要求**: 从 1.5GB 提升到 2GB (OpenClaw v2026.3.28 包体积约 200MB)
#### 兼容性分析
- **Node.js 版本**: v2026.3.28 要求 >= 22.14.0 (降低了要求,从 v2026.3.13 的 >= 22.16.0)
- **入口文件**: 无变化,仍为 `openclaw.mjs`
- **配置 Schema**: 向后兼容,无需迁移
- **API**: 向后兼容
#### 包体积变化
- v2026.3.13: ~94MB (4,730 文件)
- v2026.3.28: ~200MB (19,887 文件)
- 文件数量增加 4x+,包体积增加 2x+
#### 新增 Plugin SDK 导出 (20+)
新增 AI 提供商原生支持 SDK:
- `plugin-sdk/xai` — xAI (Grok) API 支持
- `plugin-sdk/vllm` — vLLM 高性能推理引擎支持
- `plugin-sdk/ollama` — Ollama 本地模型原生 SDK
- `plugin-sdk/openai` — OpenAI 原生 SDK
- `plugin-sdk/sglang` — SGLang 推理引擎支持
- `plugin-sdk/chutes` — Chutes AI 平台支持
- `plugin-sdk/google` — Google AI SDK
- `plugin-sdk/nvidia` — NVIDIA NIM API 支持
- `plugin-sdk/venice` — Venice AI 支持
- `plugin-sdk/minimax` — MiniMax API 原生 SDK
- `plugin-sdk/mistral` — Mistral AI 原生 SDK
- `plugin-sdk/qianfan` — 百度千帆大模型 SDK
新增功能模块 SDK:
- `plugin-sdk/zod` — Zod schema 验证支持
- `plugin-sdk/setup` — 安装配置向导 SDK
- `plugin-sdk/routing` — 模型路由配置 SDK
- `plugin-sdk/speech` — 语音处理 SDK
- `plugin-sdk/browser` — 浏览器自动化 SDK
新增顶级导出:
- `extension-api` — 扩展 API 入口 (用于插件开发)
#### 移除的依赖
以下渠道依赖被移除 (功能整合到核心或不再维护):
- `grammy` — Telegram Bot 框架 (改用内置实现)
- `@grammyjs/runner` — Telegram 运行器
- `@grammyjs/transformer-throttler` — Telegram 限流器
- `@whiskeysockets/baileys` — WhatsApp Web API (改用 matrix-js-sdk)
#### 新增依赖
核心依赖:
- `uuid@^13.0.0` — UUID 生成
- `gaxios@7.1.4` — Google API HTTP 客户端
- `matrix-js-sdk@41.2.0` — Matrix 协议支持 (替代 WhatsApp)
- `@anthropic-ai/vertex-sdk@^0.14.4` — Anthropic Vertex AI 支持
#### 依赖版本升级
核心依赖:
- `ws`: 8.19.0 → 8.20.0
- `hono`: 4.12.7 → 4.12.9
- `file-type`: 21.3.2 → 22.0.0
- `undici`: 7.24.1 → 7.24.6
- `sqlite-vec`: 0.1.7-alpha.2 → 0.1.7
AI/ML 依赖:
- `@mariozechner/pi-ai`: 0.58.0 → 0.63.1
- `@mariozechner/pi-tui`: 0.58.0 → 0.63.1
- `@mariozechner/pi-agent-core`: 0.58.0 → 0.63.1
- `@mariozechner/pi-coding-agent`: 0.58.0 → 0.63.1
- `@modelcontextprotocol/sdk`: 1.27.1 → 1.28.0
- `@agentclientprotocol/sdk`: 0.16.1 → 0.17.0
- `@aws-sdk/client-bedrock`: 3.1009.0 → 3.1019.0
#### pnpm 配置变更
新增 `ignoredBuiltDependencies`:
- `@discordjs/opus` — 跳过构建
- `koffi` — 跳过构建
新增 `onlyBuiltDependencies`:
- `@tloncorp/tlon-skill` — 需要构建
#### 中间版本变更 (v2026.3.22 ~ v2026.3.24)
v2026.3.22:
- Node.js 最低版本从 22.16.0 降低到 22.14.0
- 大量 plugin-sdk 模块重构
v2026.3.23:
- 修复版本发布问题
- 稳定性改进
v2026.3.24:
- 依赖安全更新
- 性能优化
#### 升级建议
1. **磁盘空间**: 确保至少 2GB 可用空间
2. **Node.js**: v22.16.0 完全兼容,无需降级
3. **配置迁移**: 现有配置向后兼容,无需手动干预
4. **备份**: 升级前建议执行 `openclaw backup create --only-config`
### hotwa 增强
#### 新增
- **ARM64 musl Node.js 双源下载**: `openclaw-env` 现在默认优先从 GitHub `node-bins` release 下载 Node.js失败后自动回退到 Gitea 镜像 release
- **Node.js 24 预编译资产**: 新增 `node-v24.14.1-linux-arm64-musl.tar.xz`,供 ARM64 OpenWrt/iStoreOS 设备直接使用
#### 变更
- **插件版本升级**: LuCI 插件包版本提升到 `2.0.1`,便于 iStore / opkg 识别升级
- **默认 Node.js 版本升级**: ARM64 musl 默认下载版本从 `23.2.0` 升级到 `24.14.1`
- **构建链路更新**: V2 Node.js musl 资产改为使用 Alpine `edge``nodejs` LTS 包构建,并校验产物必须精确命中 `24.14.1`
#### 兼容补充
- **适配 OpenClaw 3.22-2**: 插件侧安装脚本与默认运行时已按 OpenClaw `2026.3.22` 版本链路完成同步LuCI 页面中的已验证版本同步更新
- **Gitea 镜像同步**: `node-bins` release 的 GitHub / Gitea 两侧资产与说明已保持一致,确保 ARM64 musl 设备在主源异常时仍可回退下载
## [2.0.0] - 2026-03-16 ## [2.0.0] - 2026-03-16
### 重大变更 ### 重大变更

View File

@@ -1 +1 @@
2.0.0 2.0.1

View File

@@ -1,652 +0,0 @@
# 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

View File

@@ -924,16 +924,16 @@ end
-- ═══════════════════════════════════════════ -- ═══════════════════════════════════════════
-- 系统配置检测 API (安装前检测) -- 系统配置检测 API (安装前检测)
-- 检测内存和磁盘空间是否满足最低要求 -- 检测内存和磁盘空间是否满足最低要求
-- 要求: 内存 > 1GB, 磁盘可用空间 > 1.5GB -- 要求: 内存 > 1GB, 磁盘可用空间 > 2GB (OpenClaw v2026.3.28+ 包体积约 200MB)
-- ═══════════════════════════════════════════ -- ═══════════════════════════════════════════
function action_check_system() function action_check_system()
local http = require "luci.http" local http = require "luci.http"
local sys = require "luci.sys" local sys = require "luci.sys"
local valid_root, install_root, root_error = normalize_requested_install_root(http.formvalue("install_root")) local valid_root, install_root, root_error = normalize_requested_install_root(http.formvalue("install_root"))
-- 最低要求配置 -- 最低要求配置 (v2026.3.28: 包体积 ~200MB, 建议 2GB 可用空间)
local MIN_MEMORY_MB = 1024 -- 1GB local MIN_MEMORY_MB = 1024 -- 1GB
local MIN_DISK_MB = 1536 -- 1.5GB local MIN_DISK_MB = 2048 -- 2GB
local paths = get_runtime_paths(install_root) local paths = get_runtime_paths(install_root)
local result = { local result = {

View File

@@ -138,7 +138,7 @@ act.cfgvalue = function(self, section)
html[#html+1] = 'logEl.textContent+="检测目录: "+(r.install_root||installRoot)+"\\n";' html[#html+1] = 'logEl.textContent+="检测目录: "+(r.install_root||installRoot)+"\\n";'
html[#html+1] = 'logEl.textContent+="实际安装: "+(r.oc_root||_ocLastInstallPath)+"\\n";' html[#html+1] = 'logEl.textContent+="实际安装: "+(r.oc_root||_ocLastInstallPath)+"\\n";'
html[#html+1] = 'logEl.textContent+="内存: "+r.memory_mb+" MB (需要 ≥ 1024 MB) — "+(r.memory_ok?"✅ 通过":"❌ 不达标")+"\\n";' html[#html+1] = 'logEl.textContent+="内存: "+r.memory_mb+" MB (需要 ≥ 1024 MB) — "+(r.memory_ok?"✅ 通过":"❌ 不达标")+"\\n";'
html[#html+1] = 'logEl.textContent+="磁盘: "+r.disk_mb+" MB 可用"+(r.disk_path?" [检测路径 "+r.disk_path+"]":"")+" (需要 ≥ 1536 MB) — "+(r.disk_ok?"✅ 通过":"❌ 不达标")+"\\n";' html[#html+1] = 'logEl.textContent+="磁盘: "+r.disk_mb+" MB 可用"+(r.disk_path?" [检测路径 "+r.disk_path+"]":"")+" (需要 ≥ 2048 MB) — "+(r.disk_ok?"✅ 通过":"❌ 不达标")+"\\n";'
html[#html+1] = 'logEl.textContent+="\\n";' html[#html+1] = 'logEl.textContent+="\\n";'
html[#html+1] = 'if(!r.pass){' html[#html+1] = 'if(!r.pass){'
html[#html+1] = 'ocCloseSetupDialog();' html[#html+1] = 'ocCloseSetupDialog();'
@@ -253,7 +253,7 @@ act.cfgvalue = function(self, section)
html[#html+1] = '}' html[#html+1] = '}'
-- ARM64 musl 专属下载问题 -- ARM64 musl 专属下载问题
html[#html+1] = 'if(ll.indexOf("arm64 musl")>=0&&(ll.indexOf("release api")>=0||ll.indexOf("node-bins")>=0||ll.indexOf("未找到兼容的 arm64 musl node.js 资产")>=0||ll.indexOf("无法获取 arm64 musl node.js 发布元数据")>=0)){' html[#html+1] = 'if(ll.indexOf("arm64 musl")>=0&&(ll.indexOf("release api")>=0||ll.indexOf("node-bins")>=0||ll.indexOf("未找到兼容的 arm64 musl node.js 资产")>=0||ll.indexOf("无法获取 arm64 musl node.js 发布元数据")>=0)){'
html[#html+1] = 'reasons.push("🧩 <b>ARM64 musl Node.js 资产不可用</b> — 当前设备依赖仓库发布的 <code>node-bins</code> 资产,而不是通用 Node 镜像。<br/>&nbsp;&nbsp;💡 解决: 检查 <code>10000ge10000/luci-app-openclaw</code> 的 <code>node-bins</code> release 是否存在满足 <code>>=22.16.0</code> 的 <code>linux-arm64-musl</code> 资产,并确认路由器可访问 GitHub API 与 release 页面。");' html[#html+1] = 'reasons.push("🧩 <b>ARM64 musl Node.js 资产不可用</b> — 当前设备依赖仓库发布的 <code>node-bins</code> 资产,而不是通用 Node 镜像。<br/>&nbsp;&nbsp;💡 解决: 检查 <code>hotwa/luci-app-openclaw</code> 的 <code>node-bins</code> release 是否存在满足 <code>>=22.16.0</code> 的 <code>linux-arm64-musl</code> 资产,并确认路由器可访问 GitHub API 与 release 页面。");'
html[#html+1] = '}' html[#html+1] = '}'
-- 通用网络问题 -- 通用网络问题
html[#html+1] = 'if((ll.indexOf("could not resolve")>=0||ll.indexOf("connection timed out")>=0||ll.indexOf("curl")>=0&&ll.indexOf("fail")>=0||ll.indexOf("wget")>=0&&ll.indexOf("fail")>=0||ll.indexOf("所有镜像均下载失败")>=0)&&!(ll.indexOf("arm64 musl")>=0&&(ll.indexOf("release api")>=0||ll.indexOf("node-bins")>=0||ll.indexOf("未找到兼容的 arm64 musl node.js 资产")>=0||ll.indexOf("无法获取 arm64 musl node.js 发布元数据")>=0))){' html[#html+1] = 'if((ll.indexOf("could not resolve")>=0||ll.indexOf("connection timed out")>=0||ll.indexOf("curl")>=0&&ll.indexOf("fail")>=0||ll.indexOf("wget")>=0&&ll.indexOf("fail")>=0||ll.indexOf("所有镜像均下载失败")>=0)&&!(ll.indexOf("arm64 musl")>=0&&(ll.indexOf("release api")>=0||ll.indexOf("node-bins")>=0||ll.indexOf("未找到兼容的 arm64 musl node.js 资产")>=0||ll.indexOf("无法获取 arm64 musl node.js 发布元数据")>=0))){'

View File

@@ -51,17 +51,53 @@ local port = uci:get("openclaw", "main", "port") or "18789"
box-shadow: 0 1px 4px rgba(0,0,0,0.06); box-shadow: 0 1px 4px rgba(0,0,0,0.06);
} }
#oc-console-iframe { width: 100%; height: 700px; border: none; display: block; }
.oc-console-loading { .oc-console-loading {
display: flex; flex-direction: column; align-items: center; justify-content: center; display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 300px; color: #666; font-size: 14px; background: #fafafa; min-height: 320px; color: #666; font-size: 14px; background: #fafafa;
padding: 32px 24px;
text-align: center;
} }
.oc-console-loading .spinner { .oc-console-loading .spinner {
width: 32px; height: 32px; border: 3px solid #e0e0e0; border-top: 3px solid #4a90d9; width: 32px; height: 32px; border: 3px solid #e0e0e0; border-top: 3px solid #4a90d9;
border-radius: 50%; animation: oc-spin .8s linear infinite; margin-bottom: 12px; border-radius: 50%; animation: oc-spin .8s linear infinite; margin-bottom: 12px;
} }
@keyframes oc-spin { to { transform: rotate(360deg); } } @keyframes oc-spin { to { transform: rotate(360deg); } }
.oc-console-note {
max-width: 640px;
line-height: 1.8;
}
.oc-console-note strong {
color: #333;
}
.oc-console-note code {
padding: 2px 6px;
background: #f0f3f6;
border-radius: 4px;
font-family: monospace;
}
.oc-console-actions {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
margin-top: 16px;
}
.oc-console-link {
font-size: 12px;
color: #666;
word-break: break-all;
margin-top: 14px;
}
.oc-console-link a {
color: #4a90d9;
text-decoration: none;
}
</style> </style>
<div class="oc-page-header"> <div class="oc-page-header">
@@ -107,13 +143,28 @@ local port = uci:get("openclaw", "main", "port") or "18789"
var openBtn = document.getElementById('oc-console-open-btn'); var openBtn = document.getElementById('oc-console-open-btn');
function getConsoleUrl() { function getConsoleUrl() {
var proto = window.location.protocol;
var host = window.location.hostname; var host = window.location.hostname;
var url = proto + '//' + host + ':' + gwPort + '/'; var url = 'http://' + host + ':' + gwPort + '/';
if (gwToken) url += '#token=' + encodeURIComponent(gwToken); if (gwToken) url += '#token=' + encodeURIComponent(gwToken);
return url; return url;
} }
function showOpenMessage(url) {
loading.innerHTML =
'<div class="oc-console-note">' +
'<div style="font-size:42px;margin-bottom:12px;">🪟</div>' +
'<div style="font-size:16px;margin-bottom:8px;"><strong>请在新窗口中打开 OpenClaw 控制台</strong></div>' +
'<div style="font-size:13px;color:#666;">' +
'OpenClaw 控制台会返回浏览器安全头,拒绝被 LuCI 页面内嵌。' +
'请点击上方「新窗口打开」访问控制台。' +
'</div>' +
'<div class="oc-console-actions">' +
'<a class="btn cbi-button cbi-button-action" href="' + url + '" target="_blank" rel="noopener">↗ 新窗口打开</a>' +
'</div>' +
'<div class="oc-console-link">直接地址:<a href="' + url + '" target="_blank" rel="noopener">' + url + '</a></div>' +
'</div>';
}
function checkAndLoad() { function checkAndLoad() {
addrEl.textContent = window.location.hostname + ':' + gwPort; addrEl.textContent = window.location.hostname + ':' + gwPort;
@@ -135,7 +186,7 @@ local port = uci:get("openclaw", "main", "port") or "18789"
statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>'; statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>';
openBtn.href = url; openBtn.href = url;
openBtn.style.display = ''; openBtn.style.display = '';
showIframe(url); showOpenMessage(url);
} else if (d.gateway_starting) { } else if (d.gateway_starting) {
statusTextEl.innerHTML = '<span style="color:#9a6700;">⏳ 网关正在启动</span>'; statusTextEl.innerHTML = '<span style="color:#9a6700;">⏳ 网关正在启动</span>';
openBtn.style.display = 'none'; openBtn.style.display = 'none';
@@ -160,21 +211,6 @@ local port = uci:get("openclaw", "main", "port") or "18789"
}); });
} }
function showIframe(url) {
loading.style.display = 'none';
var existing = document.getElementById('oc-console-iframe');
if (existing) return;
var iframe = document.createElement('iframe');
iframe.id = 'oc-console-iframe';
iframe.src = url;
iframe.style.width = '100%';
iframe.style.height = '700px';
iframe.style.border = 'none';
iframe.setAttribute('allowfullscreen', 'true');
container.appendChild(iframe);
}
checkAndLoad(); checkAndLoad();
})(); })();
//]]> //]]>

View File

@@ -175,6 +175,9 @@ echo "openclaw 已禁用。请在 /etc/config/openclaw 中设置 enabled 为 1"
return 0 return 0
} }
mkdir -p "${OC_DATA}/.npm" "${OC_DATA}/.cache/corepack" "${OC_DATA}/tmp" 2>/dev/null
chown -R openclaw:openclaw "${OC_DATA}/.npm" "${OC_DATA}/.cache" "${OC_DATA}/tmp" 2>/dev/null || true
# 检查 Node.js # 检查 Node.js
if [ ! -x "$NODE_BIN" ]; then if [ ! -x "$NODE_BIN" ]; then
echo "未找到 Node.js: $NODE_BIN" echo "未找到 Node.js: $NODE_BIN"
@@ -311,6 +314,16 @@ NODE_ICU_DATA="${NODE_BASE}/share/icu" \
NODE_BASE="$NODE_BASE" \ NODE_BASE="$NODE_BASE" \
OC_GLOBAL="$OC_GLOBAL" \ OC_GLOBAL="$OC_GLOBAL" \
OC_DATA="$OC_DATA" \ OC_DATA="$OC_DATA" \
NPM_CONFIG_PREFIX="$OC_GLOBAL" \
npm_config_prefix="$OC_GLOBAL" \
NPM_CONFIG_CACHE="${OC_DATA}/.npm" \
npm_config_cache="${OC_DATA}/.npm" \
XDG_CACHE_HOME="${OC_DATA}/.cache" \
COREPACK_HOME="${OC_DATA}/.cache/corepack" \
PNPM_HOME="${OC_GLOBAL}/bin" \
TMPDIR="${OC_DATA}/tmp" \
TMP="${OC_DATA}/tmp" \
TEMP="${OC_DATA}/tmp" \
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
procd_set_param user openclaw procd_set_param user openclaw
procd_set_param respawn 3600 10 5 procd_set_param respawn 3600 10 5
@@ -345,6 +358,16 @@ HOME="$OC_DATA" \
OPENCLAW_HOME="$OC_DATA" \ OPENCLAW_HOME="$OC_DATA" \
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \ OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \ OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
NPM_CONFIG_PREFIX="$OC_GLOBAL" \
npm_config_prefix="$OC_GLOBAL" \
NPM_CONFIG_CACHE="${OC_DATA}/.npm" \
npm_config_cache="${OC_DATA}/.npm" \
XDG_CACHE_HOME="${OC_DATA}/.cache" \
COREPACK_HOME="${OC_DATA}/.cache/corepack" \
PNPM_HOME="${OC_GLOBAL}/bin" \
TMPDIR="${OC_DATA}/tmp" \
TMP="${OC_DATA}/tmp" \
TEMP="${OC_DATA}/tmp" \
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
procd_set_param respawn 3600 5 5 procd_set_param respawn 3600 5 5
procd_set_param stdout 1 procd_set_param stdout 1

View File

@@ -19,6 +19,16 @@ esac
# 设置 Node.js ICU 数据路径 # 设置 Node.js ICU 数据路径
export NODE_ICU_DATA="${NODE_BASE}/share/icu" export NODE_ICU_DATA="${NODE_BASE}/share/icu"
export NPM_CONFIG_PREFIX="$OC_GLOBAL"
export npm_config_prefix="$OC_GLOBAL"
export NPM_CONFIG_CACHE="${OC_DATA}/.npm"
export npm_config_cache="${OC_DATA}/.npm"
export XDG_CACHE_HOME="${OC_DATA}/.cache"
export COREPACK_HOME="${OC_DATA}/.cache/corepack"
export PNPM_HOME="${OC_GLOBAL}/bin"
export TMPDIR="${OC_DATA}/tmp"
export TMP="${OC_DATA}/tmp"
export TEMP="${OC_DATA}/tmp"
# 设置 OpenClaw 核心环境变量 # 设置 OpenClaw 核心环境变量
# 这些变量确保 openclaw 命令使用正确的配置路径 # 这些变量确保 openclaw 命令使用正确的配置路径

View File

@@ -74,6 +74,9 @@ if oc_install_root_uses_opt_workaround "$OPENCLAW_INSTALL_ROOT"; then
fi fi
fi fi
mkdir -p "${OC_DATA}/.openclaw" mkdir -p "${OC_DATA}/.openclaw"
mkdir -p "${OC_DATA}/.npm"
mkdir -p "${OC_DATA}/.cache/corepack"
mkdir -p "${OC_DATA}/tmp"
mkdir -p "$NODE_BASE" mkdir -p "$NODE_BASE"
mkdir -p "$OC_GLOBAL" mkdir -p "$OC_GLOBAL"
chown -R openclaw:openclaw "$OC_ROOT" 2>/dev/null || true chown -R openclaw:openclaw "$OC_ROOT" 2>/dev/null || true

View File

@@ -15,14 +15,14 @@ set -e
. /usr/libexec/openclaw-node.sh . /usr/libexec/openclaw-node.sh
# ── Node.js 版本策略 ── # ── Node.js 版本策略 ──
# V2: 当前推荐版本,用于 OpenClaw v2026.3.11+ (要求 >= 22.16.0) # V2: 当前推荐版本,用于 OpenClaw v2026.3.22+ (默认下载 24.14.1,满足 >= 22.14.0)
# V1: 保留给显式指定旧版环境时使用,不再作为 V2 的自动回退 # V1: 保留给显式指定旧版环境时使用,不再作为 V2 的自动回退
NODE_VERSION_V2="22.16.0" NODE_VERSION_V2="24.14.1"
NODE_VERSION_V1="22.15.1" NODE_VERSION_V1="22.15.1"
# 默认使用 V2 版本 (可通过 NODE_VERSION 环境变量覆盖) # 默认使用 V2 版本 (可通过 NODE_VERSION 环境变量覆盖)
NODE_VERSION="${NODE_VERSION:-${NODE_VERSION_V2}}" NODE_VERSION="${NODE_VERSION:-${NODE_VERSION_V2}}"
# 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试) # 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
OC_TESTED_VERSION="2026.3.13" OC_TESTED_VERSION="2026.3.28"
# 用户可通过 OC_VERSION 环境变量覆盖安装版本 # 用户可通过 OC_VERSION 环境变量覆盖安装版本
OC_VERSION="${OC_VERSION:-}" OC_VERSION="${OC_VERSION:-}"
oc_load_paths "$OPENCLAW_INSTALL_ROOT" oc_load_paths "$OPENCLAW_INSTALL_ROOT"
@@ -55,16 +55,29 @@ PNPM_BIN="${OC_GLOBAL}/bin/pnpm"
# Node.js 下载源 # Node.js 下载源
OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}" OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}"
OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-10000ge10000/luci-app-openclaw}" OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-hotwa/luci-app-openclaw}"
NODE_MIRROR="${NODE_MIRROR:-https://nodejs.org/dist}" NODE_MIRROR="${NODE_MIRROR:-https://nodejs.org/dist}"
NODE_MIRROR_CN="https://npmmirror.com/mirrors/node" NODE_MIRROR_CN="https://npmmirror.com/mirrors/node"
NODE_MUSL_MIRROR="https://unofficial-builds.nodejs.org/download/release" NODE_MUSL_MIRROR="https://unofficial-builds.nodejs.org/download/release"
NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}" NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}"
NODE_SELF_HOST_FALLBACK="${NODE_SELF_HOST_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/download/node-bins}"
NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}" NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}"
NODE_RELEASE_API_FALLBACK="${NODE_RELEASE_API_FALLBACK:-https://gitea.jmsu.top/api/v1/repos/lingyuzeng/luci-app-openclaw/releases/tags/node-bins}"
NODE_RELEASE_PAGE="${NODE_RELEASE_PAGE:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/tag/node-bins}" NODE_RELEASE_PAGE="${NODE_RELEASE_PAGE:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/tag/node-bins}"
NODE_RELEASE_PAGE_FALLBACK="${NODE_RELEASE_PAGE_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/tag/node-bins}"
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH" export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH"
export NODE_ICU_DATA="${NODE_BASE}/share/icu" export NODE_ICU_DATA="${NODE_BASE}/share/icu"
export NPM_CONFIG_PREFIX="${OC_GLOBAL}"
export npm_config_prefix="${OC_GLOBAL}"
export NPM_CONFIG_CACHE="${OC_DATA}/.npm"
export npm_config_cache="${OC_DATA}/.npm"
export XDG_CACHE_HOME="${OC_DATA}/.cache"
export COREPACK_HOME="${OC_DATA}/.cache/corepack"
export PNPM_HOME="${OC_GLOBAL}/bin"
export TMPDIR="${OC_DATA}/tmp"
export TMP="${OC_DATA}/tmp"
export TEMP="${OC_DATA}/tmp"
log_info() { echo " [✓] $1"; } log_info() { echo " [✓] $1"; }
log_warn() { echo " [!] $1"; } log_warn() { echo " [!] $1"; }
@@ -82,32 +95,36 @@ resolve_arm64_musl_node_url() {
local node_ver="$1" local node_ver="$1"
local release_json="/tmp/openclaw-node-bins-release.json" local release_json="/tmp/openclaw-node-bins-release.json"
local asset_url="" local asset_url=""
local release_api=""
echo " 正在从 ${NODE_RELEASE_API} 获取 ARM64 musl 版本列表..." >&2 for release_api in "$NODE_RELEASE_API" "$NODE_RELEASE_API_FALLBACK"; do
if ! download_url_to_file "$NODE_RELEASE_API" "$release_json"; then [ -n "$release_api" ] || continue
echo " 正在从 ${release_api} 获取 ARM64 musl 版本列表..." >&2
if ! download_url_to_file "$release_api" "$release_json"; then
rm -f "$release_json"
continue
fi
asset_url=$(oc_select_node_release_asset_url "$release_json" "linux-arm64" "$node_ver" || true)
rm -f "$release_json" rm -f "$release_json"
log_error "无法获取 ARM64 musl Node.js 发布元数据" >&2
echo " 仓库: ${OPENCLAW_NODE_BINS_REPO}" >&2
echo " 最低要求: v${node_ver}" >&2
echo " Release API: ${NODE_RELEASE_API}" >&2
echo " Release 页面: ${NODE_RELEASE_PAGE}" >&2
echo " 请先发布 node-bins 中满足要求的 ARM64 musl 资产,或通过环境变量覆盖下载源" >&2
return 1
fi
asset_url=$(oc_select_node_release_asset_url "$release_json" "linux-arm64" "$node_ver" || true) if [ -n "$asset_url" ]; then
rm -f "$release_json" printf '%s\n' "$asset_url"
return 0
if [ -n "$asset_url" ]; then fi
printf '%s\n' "$asset_url" done
return 0
fi
log_error "未找到兼容的 ARM64 musl Node.js 资产" >&2 log_error "未找到兼容的 ARM64 musl Node.js 资产" >&2
echo " 仓库: ${OPENCLAW_NODE_BINS_REPO}" >&2 echo " 仓库: ${OPENCLAW_NODE_BINS_REPO}" >&2
echo " 最低要求: v${node_ver}" >&2 echo " 最低要求: v${node_ver}" >&2
echo " Release API: ${NODE_RELEASE_API}" >&2 echo " Release API: ${NODE_RELEASE_API}" >&2
if [ -n "$NODE_RELEASE_API_FALLBACK" ] && [ "$NODE_RELEASE_API_FALLBACK" != "$NODE_RELEASE_API" ]; then
echo " Fallback API: ${NODE_RELEASE_API_FALLBACK}" >&2
fi
echo " Release 页面: ${NODE_RELEASE_PAGE}" >&2 echo " Release 页面: ${NODE_RELEASE_PAGE}" >&2
if [ -n "$NODE_RELEASE_PAGE_FALLBACK" ] && [ "$NODE_RELEASE_PAGE_FALLBACK" != "$NODE_RELEASE_PAGE" ]; then
echo " Fallback 页面: ${NODE_RELEASE_PAGE_FALLBACK}" >&2
fi
echo " 请先发布 node-bins 中满足要求的 ARM64 musl 资产,或通过环境变量覆盖下载源" >&2 echo " 请先发布 node-bins 中满足要求的 ARM64 musl 资产,或通过环境变量覆盖下载源" >&2
return 1 return 1
} }
@@ -125,6 +142,14 @@ ensure_mkdir() {
fi fi
} }
ensure_runtime_cache_dirs() {
ensure_mkdir "$OC_DATA"
ensure_mkdir "${OC_DATA}/.npm"
ensure_mkdir "${OC_DATA}/.cache"
ensure_mkdir "${OC_DATA}/.cache/corepack"
ensure_mkdir "${OC_DATA}/tmp"
}
# 检测 C 运行时类型 (glibc vs musl) # 检测 C 运行时类型 (glibc vs musl)
detect_libc() { detect_libc() {
if ldd --version 2>&1 | grep -qi musl; then if ldd --version 2>&1 | grep -qi musl; then
@@ -152,16 +177,26 @@ $ver_dir"
done done
local d local d
echo "$search_dirs" | while read -r d; do local found=""
while IFS= read -r d; do
[ -z "$d" ] && continue [ -z "$d" ] && continue
if [ -f "${d}/openclaw.mjs" ]; then if [ -f "${d}/openclaw.mjs" ]; then
echo "${d}/openclaw.mjs" found="${d}/openclaw.mjs"
return break
elif [ -f "${d}/dist/cli.js" ]; then elif [ -f "${d}/dist/cli.js" ]; then
echo "${d}/dist/cli.js" found="${d}/dist/cli.js"
return break
fi fi
done done <<EOF
$search_dirs
EOF
if [ -n "$found" ]; then
printf '%s\n' "$found"
return 0
fi
return 1
} }
detect_arch() { detect_arch() {
@@ -219,11 +254,20 @@ download_node() {
echo "" echo ""
echo "=== 下载 Node.js v${node_ver} (${node_arch}, musl libc) ===" echo "=== 下载 Node.js v${node_ver} (${node_arch}, musl libc) ==="
if [ "$node_arch" = "linux-arm64" ]; then if [ "$node_arch" = "linux-arm64" ]; then
# ARM64 musl: 从当前维护仓库的 node-bins release 动态选择兼容资产 # ARM64 musl: GitHub 直链优先Gitea 镜像兜底API 解析作为兼容补充
arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver") || exit 1 mirror_list="${NODE_SELF_HOST}/${musl_tarball}"
mirror_list="${arm64_musl_url}" if [ -n "$NODE_SELF_HOST_FALLBACK" ] && [ "$NODE_SELF_HOST_FALLBACK" != "$NODE_SELF_HOST" ]; then
else mirror_list="$mirror_list ${NODE_SELF_HOST_FALLBACK}/${musl_tarball}"
fi
arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver" 2>/dev/null || true)
if [ -n "$arm64_musl_url" ] && [ "$arm64_musl_url" != "${NODE_SELF_HOST}/${musl_tarball}" ]; then
case " $mirror_list " in
*" ${arm64_musl_url} "*) ;;
*) mirror_list="$mirror_list ${arm64_musl_url}" ;;
esac
fi
else
# x64 musl: unofficial-builds 提供 # x64 musl: unofficial-builds 提供
# 1) unofficial-builds # 1) unofficial-builds
mirror_list="${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}" mirror_list="${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}"
@@ -307,6 +351,22 @@ download_node() {
log_info "Node.js ${installed_ver} 安装成功" log_info "Node.js ${installed_ver} 安装成功"
return 0 return 0
fi fi
if [ "$libc_type" = "musl" ] && [ "$node_arch" = "linux-arm64" ] && \
[ "$OPENCLAW_INSTALL_ROOT" != "/opt" ] && oc_node_requires_opt_compat "$NODE_BIN"; then
log_warn "检测到旧版 ARM64 musl Node.js 资产,尝试创建 /opt 兼容链接"
if oc_ensure_opt_compat_link "$OC_ROOT"; then
installed_ver=$(oc_read_node_version "$NODE_BIN" || true)
if [ -n "$installed_ver" ] && oc_node_version_ge "$installed_ver" "$node_ver"; then
log_warn "已启用兼容链接: /opt/openclaw -> ${OC_ROOT}"
log_info "Node.js ${installed_ver} 安装成功"
return 0
fi
else
log_warn "无法创建 /opt/openclaw 兼容链接,请检查该路径是否已被其他安装占用"
fi
fi
if [ -n "$installed_ver" ]; then if [ -n "$installed_ver" ]; then
log_error "Node.js 版本过低: v${installed_ver} < v${node_ver}" log_error "Node.js 版本过低: v${installed_ver} < v${node_ver}"
else else
@@ -326,6 +386,7 @@ install_pnpm() {
# 使用 npm 安装 pnpm 到全局目录 # 使用 npm 安装 pnpm 到全局目录
ensure_mkdir "$OC_GLOBAL" ensure_mkdir "$OC_GLOBAL"
ensure_runtime_cache_dirs
"$NPM_BIN" install -g pnpm --prefix="$OC_GLOBAL" 2>/dev/null "$NPM_BIN" install -g pnpm --prefix="$OC_GLOBAL" 2>/dev/null
if [ -x "$OC_GLOBAL/bin/pnpm" ]; then if [ -x "$OC_GLOBAL/bin/pnpm" ]; then
@@ -380,6 +441,7 @@ install_openclaw() {
local npm_ok=0 local npm_ok=0
if [ -x "$NPM_BIN" ]; then if [ -x "$NPM_BIN" ]; then
ensure_mkdir "$OC_GLOBAL" ensure_mkdir "$OC_GLOBAL"
ensure_runtime_cache_dirs
"$NPM_BIN" install -g "$oc_pkg" --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -10 "$NPM_BIN" install -g "$oc_pkg" --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -10
# 检查是否安装成功 # 检查是否安装成功
if [ -n "$(find_oc_entry)" ]; then if [ -n "$(find_oc_entry)" ]; then
@@ -416,6 +478,7 @@ install_openclaw() {
if [ -x "$NPM_BIN" ]; then if [ -x "$NPM_BIN" ]; then
echo "" echo ""
echo "=== 安装 Gemini CLI (Google OAuth 依赖) ===" echo "=== 安装 Gemini CLI (Google OAuth 依赖) ==="
ensure_runtime_cache_dirs
"$NPM_BIN" install -g @google/gemini-cli --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -3 "$NPM_BIN" install -g @google/gemini-cli --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -3
if command -v gemini >/dev/null 2>&1 || [ -x "$OC_GLOBAL/bin/gemini" ]; then if command -v gemini >/dev/null 2>&1 || [ -x "$OC_GLOBAL/bin/gemini" ]; then
log_info "Gemini CLI 安装成功" log_info "Gemini CLI 安装成功"
@@ -586,6 +649,7 @@ do_upgrade() {
echo "=== 正在升级 OpenClaw ===" echo "=== 正在升级 OpenClaw ==="
echo "" echo ""
ensure_runtime_cache_dirs
"$NPM_BIN" install -g openclaw@latest --prefix="$OC_GLOBAL" $install_flags 2>&1 "$NPM_BIN" install -g openclaw@latest --prefix="$OC_GLOBAL" $install_flags 2>&1
# 验证升级结果 # 验证升级结果

View File

@@ -1,6 +1,8 @@
#!/bin/sh #!/bin/sh
# Shared OpenClaw Node.js runtime/version helpers. # Shared OpenClaw Node.js runtime/version helpers.
OPENCLAW_LEGACY_ARM64_MUSL_INTERPRETER="${OPENCLAW_LEGACY_ARM64_MUSL_INTERPRETER:-/opt/openclaw/node/lib/ld-musl-aarch64.so.1}"
oc_normalize_node_version() { oc_normalize_node_version() {
local version="${1:-}" local version="${1:-}"
local old_ifs local old_ifs
@@ -67,6 +69,15 @@ oc_read_node_version() {
oc_normalize_node_version "$version" oc_normalize_node_version "$version"
} }
oc_node_requires_opt_compat() {
local node_bin="${1:-}"
[ -n "$node_bin" ] || return 1
[ -f "$node_bin" ] || return 1
grep -aqF "$OPENCLAW_LEGACY_ARM64_MUSL_INTERPRETER" "$node_bin"
}
oc_select_node_release_asset_url() { oc_select_node_release_asset_url() {
local json_file="${1:-}" local json_file="${1:-}"
local node_arch="${2:-}" local node_arch="${2:-}"

View File

@@ -2,6 +2,7 @@
# Shared OpenClaw install-root and derived-path helpers. # Shared OpenClaw install-root and derived-path helpers.
OPENCLAW_DEFAULT_INSTALL_ROOT="${OPENCLAW_DEFAULT_INSTALL_ROOT:-/opt}" OPENCLAW_DEFAULT_INSTALL_ROOT="${OPENCLAW_DEFAULT_INSTALL_ROOT:-/opt}"
OPENCLAW_OPT_COMPAT_ROOT="${OPENCLAW_OPT_COMPAT_ROOT:-/opt}"
oc_normalize_install_root() { oc_normalize_install_root() {
local path="$1" local path="$1"
@@ -66,6 +67,30 @@ oc_install_root_uses_opt_workaround() {
[ "$(oc_normalize_install_root "${1:-$OPENCLAW_INSTALL_ROOT}")" = "/opt" ] [ "$(oc_normalize_install_root "${1:-$OPENCLAW_INSTALL_ROOT}")" = "/opt" ]
} }
oc_ensure_opt_compat_link() {
local target_root="$1"
local compat_root compat_link current_target
target_root="$(oc_normalize_install_root "$target_root")"
[ "$target_root" = "/opt" ] && return 0
compat_root="$(oc_normalize_install_root "$OPENCLAW_OPT_COMPAT_ROOT")"
compat_link="${compat_root}/openclaw"
if [ -L "$compat_link" ]; then
current_target="$(readlink "$compat_link" 2>/dev/null || true)"
[ "$current_target" = "$target_root" ] && return 0
return 1
fi
if [ -e "$compat_link" ]; then
return 1
fi
mkdir -p "$compat_root"
ln -s "$target_root" "$compat_link"
}
oc_print_env() { oc_print_env() {
oc_load_paths "$1" oc_load_paths "$1"
cat <<EOF cat <<EOF
@@ -74,5 +99,6 @@ OC_ROOT=$OC_ROOT
NODE_BASE=$NODE_BASE NODE_BASE=$NODE_BASE
OC_GLOBAL=$OC_GLOBAL OC_GLOBAL=$OC_GLOBAL
OC_DATA=$OC_DATA OC_DATA=$OC_DATA
OPENCLAW_OPT_COMPAT_ROOT=$OPENCLAW_OPT_COMPAT_ROOT
EOF EOF
} }

View File

@@ -40,6 +40,16 @@ export OPENCLAW_HOME="$OC_DATA"
export OPENCLAW_STATE_DIR="$OC_STATE_DIR" export OPENCLAW_STATE_DIR="$OC_STATE_DIR"
export OPENCLAW_CONFIG_PATH="$CONFIG_FILE" export OPENCLAW_CONFIG_PATH="$CONFIG_FILE"
export NODE_ICU_DATA="${NODE_BASE}/share/icu" export NODE_ICU_DATA="${NODE_BASE}/share/icu"
export NPM_CONFIG_PREFIX="$OC_GLOBAL"
export npm_config_prefix="$OC_GLOBAL"
export NPM_CONFIG_CACHE="${OC_DATA}/.npm"
export npm_config_cache="${OC_DATA}/.npm"
export XDG_CACHE_HOME="${OC_DATA}/.cache"
export COREPACK_HOME="${OC_DATA}/.cache/corepack"
export PNPM_HOME="${OC_GLOBAL}/bin"
export TMPDIR="${OC_DATA}/tmp"
export TMP="${OC_DATA}/tmp"
export TEMP="${OC_DATA}/tmp"
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# ── 查找 openclaw 入口 ── # ── 查找 openclaw 入口 ──
@@ -646,7 +656,9 @@ configure_model() {
echo -e " ${CYAN}11)${NC} 硅基流动 SiliconFlow" echo -e " ${CYAN}11)${NC} 硅基流动 SiliconFlow"
echo -e " ${CYAN}12)${NC} Ollama (本地模型,无需 API Key)" echo -e " ${CYAN}12)${NC} Ollama (本地模型,无需 API Key)"
echo -e " ${CYAN}13)${NC} 腾讯云 Coding Plan (HY T1/TurboS/GLM-5/Kimi)" echo -e " ${CYAN}13)${NC} 腾讯云 Coding Plan (HY T1/TurboS/GLM-5/Kimi)"
echo -e " ${CYAN}14)${NC} 自定义 OpenAI 兼容 API" echo -e " ${CYAN}14)${NC} Mistral AI (Mistral Large, Codestral)"
echo -e " ${CYAN}15)${NC} 百度千帆 (ERNIE-4.0, ERNIE-3.5)"
echo -e " ${CYAN}16)${NC} 自定义 OpenAI 兼容 API"
echo -e " ${CYAN}0)${NC} 返回" echo -e " ${CYAN}0)${NC} 返回"
echo "" echo ""
prompt_with_default "请选择" "1" choice prompt_with_default "请选择" "1" choice
@@ -1263,6 +1275,70 @@ configure_model() {
fi fi
;; ;;
14) 14)
echo ""
echo -e " ${BOLD}Mistral AI 配置${NC}"
echo -e " ${YELLOW}获取 API Key: https://console.mistral.ai/api-keys/${NC}"
echo ""
prompt_with_default "请输入 Mistral API Key" "" api_key
if [ -n "$api_key" ]; then
auth_set_apikey mistral "$api_key"
echo ""
echo -e " ${CYAN}可用模型:${NC}"
echo -e " ${CYAN}a)${NC} mistral-large-latest — 旗舰模型,最强性能 (推荐)"
echo -e " ${CYAN}b)${NC} mistral-medium-latest — 均衡模型"
echo -e " ${CYAN}c)${NC} codestral-latest — 代码专用,极速补全"
echo -e " ${CYAN}d)${NC} mistral-small-latest — 轻量快速"
echo -e " ${CYAN}e)${NC} 手动输入模型名"
echo ""
prompt_with_default "请选择模型" "a" model_choice
case "$model_choice" in
a) model_name="mistral-large-latest" ;;
b) model_name="mistral-medium-latest" ;;
c) model_name="codestral-latest" ;;
d) model_name="mistral-small-latest" ;;
e) prompt_with_default "请输入模型名称" "mistral-large-latest" model_name ;;
*) model_name="mistral-large-latest" ;;
esac
register_custom_provider mistral "https://api.mistral.ai/v1" "$api_key" "$model_name" "$model_name"
register_and_set_model "mistral/${model_name}"
echo -e " ${GREEN}✅ Mistral AI 已配置,活跃模型: mistral/${model_name}${NC}"
fi
;;
15)
echo ""
echo -e " ${BOLD}百度千帆大模型配置${NC}"
echo -e " ${YELLOW}获取 API Key: https://console.bce.baidu.com/qianfan/ais/console/onlineService${NC}"
echo ""
echo -e " ${DIM}提示: 需要 API Key (Access Token) 和可选的 Secret Key${NC}"
echo ""
prompt_with_default "请输入百度千帆 API Key (Access Token)" "" api_key
if [ -n "$api_key" ]; then
auth_set_apikey qianfan "$api_key"
echo ""
echo -e " ${CYAN}可用模型:${NC}"
echo -e " ${CYAN}a)${NC} ernie-4.0-8k — 文心一言 4.0 (推荐)"
echo -e " ${CYAN}b)${NC} ernie-3.5-8k — 文心一言 3.5"
echo -e " ${CYAN}c)${NC} ernie-4.0-turbo-8k — 文心一言 4.0 Turbo"
echo -e " ${CYAN}d)${NC} ernie-speed-8k — 文心一言 Speed 极速"
echo -e " ${CYAN}e)${NC} 手动输入模型名"
echo ""
prompt_with_default "请选择模型" "a" model_choice
case "$model_choice" in
a) model_name="ernie-4.0-8k" ;;
b) model_name="ernie-3.5-8k" ;;
c) model_name="ernie-4.0-turbo-8k" ;;
d) model_name="ernie-speed-8k" ;;
e) prompt_with_default "请输入模型名称" "ernie-4.0-8k" model_name ;;
*) model_name="ernie-4.0-8k" ;;
esac
# 百度千帆使用 OpenAI 兼容接口
# 注: OpenClaw v2026.3.28+ 支持 qianfan 原生 provider
register_custom_provider qianfan "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop" "$api_key" "$model_name" "$model_name"
register_and_set_model "qianfan/${model_name}"
echo -e " ${GREEN}✅ 百度千帆已配置,活跃模型: qianfan/${model_name}${NC}"
fi
;;
16)
echo "" echo ""
echo -e " ${BOLD}自定义 OpenAI 兼容 API${NC}" echo -e " ${BOLD}自定义 OpenAI 兼容 API${NC}"
echo -e " ${YELLOW}支持任何兼容 OpenAI API 格式的服务商${NC}" echo -e " ${YELLOW}支持任何兼容 OpenAI API 格式的服务商${NC}"

View File

@@ -200,6 +200,16 @@ class PtySession {
OPENCLAW_HOME: OC_DATA, OPENCLAW_HOME: OC_DATA,
OPENCLAW_STATE_DIR: `${OC_DATA}/.openclaw`, OPENCLAW_STATE_DIR: `${OC_DATA}/.openclaw`,
OPENCLAW_CONFIG_PATH: `${OC_DATA}/.openclaw/openclaw.json`, OPENCLAW_CONFIG_PATH: `${OC_DATA}/.openclaw/openclaw.json`,
NPM_CONFIG_PREFIX: OC_GLOBAL,
npm_config_prefix: OC_GLOBAL,
NPM_CONFIG_CACHE: `${OC_DATA}/.npm`,
npm_config_cache: `${OC_DATA}/.npm`,
XDG_CACHE_HOME: `${OC_DATA}/.cache`,
COREPACK_HOME: `${OC_DATA}/.cache/corepack`,
PNPM_HOME: `${OC_GLOBAL}/bin`,
TMPDIR: `${OC_DATA}/tmp`,
TMP: `${OC_DATA}/tmp`,
TEMP: `${OC_DATA}/tmp`,
PATH: `${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, PATH: `${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,
}; };
// 检测 script 命令是否可用 (OpenWrt 默认不包含 util-linux-script) // 检测 script 命令是否可用 (OpenWrt 默认不包含 util-linux-script)

View File

@@ -0,0 +1,30 @@
#!/bin/sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
CONSOLE_VIEW="$REPO_ROOT/luasrc/view/openclaw/console.htm"
fail() {
echo "FAIL: $1" >&2
exit 1
}
grep -Fq "http://' + host + ':' + gwPort + '/'" "$CONSOLE_VIEW" || fail "console view should force http for the gateway URL"
grep -Fq '请点击上方「新窗口打开」访问控制台。' "$CONSOLE_VIEW" || fail "console view should explain that the console must open in a new window"
if grep -Fq "document.createElement('iframe')" "$CONSOLE_VIEW"; then
fail "console view should not embed the OpenClaw UI in an iframe"
fi
if grep -Fq 'window.location.protocol' "$CONSOLE_VIEW"; then
fail "console view should not reuse the LuCI page protocol for the gateway URL"
fi
cr=$(printf '\r')
if LC_ALL=C grep -q "$cr" "$CONSOLE_VIEW"; then
fail "console view should use LF line endings"
fi
echo "ok"

View File

@@ -12,6 +12,12 @@ BUILD_RUN="$REPO_ROOT/scripts/build_run.sh"
ENV_SCRIPT="$REPO_ROOT/root/usr/bin/openclaw-env" ENV_SCRIPT="$REPO_ROOT/root/usr/bin/openclaw-env"
CONTROLLER_SCRIPT="$REPO_ROOT/luasrc/controller/openclaw.lua" CONTROLLER_SCRIPT="$REPO_ROOT/luasrc/controller/openclaw.lua"
BASIC_LUA="$REPO_ROOT/luasrc/model/cbi/openclaw/basic.lua" BASIC_LUA="$REPO_ROOT/luasrc/model/cbi/openclaw/basic.lua"
PROFILE_SCRIPT="$REPO_ROOT/root/etc/profile.d/openclaw.sh"
UCI_DEFAULTS_SCRIPT="$REPO_ROOT/root/etc/uci-defaults/99-openclaw"
INIT_SCRIPT="$REPO_ROOT/root/etc/init.d/openclaw"
PATHS_HELPER="$REPO_ROOT/root/usr/libexec/openclaw-paths.sh"
NODE_HELPER="$REPO_ROOT/root/usr/libexec/openclaw-node.sh"
WEB_PTY_SCRIPT="$REPO_ROOT/root/usr/share/openclaw/web-pty.js"
fail() { fail() {
echo "FAIL: $1" >&2 echo "FAIL: $1" >&2
@@ -28,18 +34,40 @@ grep -Fq 'verify_prefix /opt/openclaw/node' "$WORKFLOW" || fail "workflow should
grep -Fq 'verify_prefix /tmp/custom-openclaw-root/openclaw/node' "$WORKFLOW" || fail "workflow should verify custom install path" grep -Fq 'verify_prefix /tmp/custom-openclaw-root/openclaw/node' "$WORKFLOW" || fail "workflow should verify custom install path"
grep -Fq 'oc_node_version_ge "$installed_ver" "$node_ver"' "$ENV_SCRIPT" || fail "installer should enforce minimum node version after extraction" grep -Fq 'oc_node_version_ge "$installed_ver" "$node_ver"' "$ENV_SCRIPT" || fail "installer should enforce minimum node version after extraction"
grep -Fq 'NODE_VERSION_V2="24.14.1"' "$ENV_SCRIPT" || fail "installer should default V2 to Node.js 24.14.1"
grep -Fq "description: 'Build V2 (24.14.1) - Current LTS version'" "$WORKFLOW" || fail "workflow should describe V2 as Node.js 24.14.1 LTS"
grep -Fq 'NODE_VER="24.14.1"' "$WORKFLOW" || fail "workflow should request Node.js 24.14.1 for V2"
grep -Fq 'Build Node.js V2 ARM64 musl (apk lts mode)' "$WORKFLOW" || fail "workflow should build V2 in apk lts mode"
grep -Fq 'PKG_TYPE=lts' "$WORKFLOW" || fail "workflow should use Alpine nodejs LTS package for V2"
grep -Fq 'alpine:edge sh /build-node-musl.sh' "$WORKFLOW" || fail "workflow should build V2 from Alpine edge to get the latest Node.js 24 LTS package"
grep -Fq 'EXPECTED_ARTIFACT="node-v24.14.1-linux-arm64-musl.tar.xz"' "$WORKFLOW" || fail "workflow should require the exact Node.js 24.14.1 V2 artifact name"
grep -Fq '`node-v24.14.1-linux-arm64-musl.tar.xz`' "$WORKFLOW" || fail "release notes should mention the Node.js 24.14.1 ARM64 musl tarball"
if grep -Fq 'mirror_list="$mirror_list ${NODE_SELF_HOST}/${v1_tarball}"' "$ENV_SCRIPT"; then if grep -Fq 'mirror_list="$mirror_list ${NODE_SELF_HOST}/${v1_tarball}"' "$ENV_SCRIPT"; then
fail "installer should not auto-fallback from V2 to V1 tarball" fail "installer should not auto-fallback from V2 to V1 tarball"
fi fi
grep -Fq 'OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should keep hotwa as primary app repo" grep -Fq 'OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should keep hotwa as primary app repo"
grep -Fq 'OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-10000ge10000/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should default ARM64 musl node-bins to 10000ge10000 repo" grep -Fq 'OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-hotwa/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should default ARM64 musl node-bins to hotwa repo"
grep -Fq 'NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release URL from 10000ge10000 repo" grep -Fq 'NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release URL from hotwa repo"
grep -Fq 'NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release API from 10000ge10000 repo" grep -Fq 'NODE_SELF_HOST_FALLBACK="${NODE_SELF_HOST_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should define a Gitea mirror fallback for ARM64 musl downloads"
grep -Fq 'NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release API from hotwa repo"
grep -Fq 'NODE_RELEASE_API_FALLBACK="${NODE_RELEASE_API_FALLBACK:-https://gitea.jmsu.top/api/v1/repos/lingyuzeng/luci-app-openclaw/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should define a Gitea release API fallback"
grep -Fq 'NODE_RELEASE_PAGE_FALLBACK="${NODE_RELEASE_PAGE_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/tag/node-bins}"' "$ENV_SCRIPT" || fail "installer should define a Gitea release page fallback"
grep -Fq 'for release_api in "$NODE_RELEASE_API" "$NODE_RELEASE_API_FALLBACK"; do' "$ENV_SCRIPT" || fail "installer should retry ARM64 musl release API using the Gitea mirror"
grep -Fq 'oc_select_node_release_asset_url' "$ENV_SCRIPT" || fail "installer should dynamically select ARM64 musl asset" grep -Fq 'oc_select_node_release_asset_url' "$ENV_SCRIPT" || fail "installer should dynamically select ARM64 musl asset"
grep -Fq 'arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver") || exit 1' "$ENV_SCRIPT" || fail "installer should resolve ARM64 musl asset dynamically" grep -Fq 'oc_node_requires_opt_compat "$NODE_BIN"' "$ENV_SCRIPT" || fail "installer should detect legacy opt-bound ARM64 musl node assets"
if grep -Fq 'mirror_list="${NODE_SELF_HOST}/${musl_tarball}"' "$ENV_SCRIPT"; then grep -Fq 'oc_ensure_opt_compat_link "$OC_ROOT"' "$ENV_SCRIPT" || fail "installer should create /opt compatibility symlink for legacy assets"
fail "installer should not hardcode exact ARM64 musl asset path" grep -Fq 'mirror_list="${NODE_SELF_HOST}/${musl_tarball}"' "$ENV_SCRIPT" || fail "installer should default ARM64 musl downloads to direct release asset URL"
grep -Fq 'mirror_list="$mirror_list ${NODE_SELF_HOST_FALLBACK}/${musl_tarball}"' "$ENV_SCRIPT" || fail "installer should try the Gitea mirror after GitHub"
grep -Fq 'arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver" 2>/dev/null || true)' "$ENV_SCRIPT" || fail "installer should keep API-based ARM64 musl asset discovery as fallback"
grep -Fq 'while IFS= read -r d; do' "$ENV_SCRIPT" || fail "installer should traverse OpenClaw entry candidates without a pipeline subshell"
if grep -Fq 'echo "$search_dirs" | while read -r d; do' "$ENV_SCRIPT"; then
fail "installer should not rely on a pipeline subshell for OpenClaw entry lookup"
fi fi
grep -Fq 'NPM_CONFIG_PREFIX="${OC_GLOBAL}"' "$ENV_SCRIPT" || fail "installer should force npm global prefix into custom install root"
grep -Fq 'NPM_CONFIG_CACHE="${OC_DATA}/.npm"' "$ENV_SCRIPT" || fail "installer should force npm cache into custom data root"
grep -Fq 'XDG_CACHE_HOME="${OC_DATA}/.cache"' "$ENV_SCRIPT" || fail "installer should force generic caches into custom data root"
grep -Fq 'COREPACK_HOME="${OC_DATA}/.cache/corepack"' "$ENV_SCRIPT" || fail "installer should force corepack cache into custom data root"
grep -Fq 'TMPDIR="${OC_DATA}/tmp"' "$ENV_SCRIPT" || fail "installer should force temp files into custom data root"
grep -Fq 'openclaw-paths.sh' "$MAKEFILE" || fail "package makefile should install path helper" grep -Fq 'openclaw-paths.sh' "$MAKEFILE" || fail "package makefile should install path helper"
grep -Fq 'openclaw-node.sh' "$MAKEFILE" || fail "package makefile should install node helper" grep -Fq 'openclaw-node.sh' "$MAKEFILE" || fail "package makefile should install node helper"
@@ -54,12 +82,39 @@ grep -Fq 'openclaw/paths.lua' "$BUILD_IPK" || fail "ipk builder should package L
grep -Fq 'openclaw-paths.sh' "$BUILD_RUN" || fail "run builder should package path helper" grep -Fq 'openclaw-paths.sh' "$BUILD_RUN" || fail "run builder should package path helper"
grep -Fq 'openclaw-node.sh' "$BUILD_RUN" || fail "run builder should package node helper" grep -Fq 'openclaw-node.sh' "$BUILD_RUN" || fail "run builder should package node helper"
grep -Fq 'openclaw/paths.lua' "$BUILD_RUN" || fail "run builder should package Lua path helper" grep -Fq 'openclaw/paths.lua' "$BUILD_RUN" || fail "run builder should package Lua path helper"
python - "$ENV_SCRIPT" "$PROFILE_SCRIPT" "$UCI_DEFAULTS_SCRIPT" "$INIT_SCRIPT" "$PATHS_HELPER" "$NODE_HELPER" "$BUILD_IPK" "$BUILD_RUN" "$BUILD_SCRIPT" <<'PY' || fail "shell-oriented source files must use LF line endings"
from pathlib import Path
import sys
bad = []
for arg in sys.argv[1:]:
data = Path(arg).read_bytes()
if b"\r\n" in data:
bad.append(arg)
if bad:
print("CRLF detected in:", file=sys.stderr)
for path in bad:
print(path, file=sys.stderr)
raise SystemExit(1)
PY
grep -Fq 'local GITHUB_REPO = "hotwa/luci-app-openclaw"' "$CONTROLLER_SCRIPT" || fail "controller should default to hotwa repo" grep -Fq 'local GITHUB_REPO = "hotwa/luci-app-openclaw"' "$CONTROLLER_SCRIPT" || fail "controller should default to hotwa repo"
grep -Fq 'local GITHUB_RELEASES_URL = "https://github.com/" .. GITHUB_REPO .. "/releases"' "$CONTROLLER_SCRIPT" || fail "controller should derive release URLs from hotwa repo" grep -Fq 'local GITHUB_RELEASES_URL = "https://github.com/" .. GITHUB_REPO .. "/releases"' "$CONTROLLER_SCRIPT" || fail "controller should derive release URLs from hotwa repo"
grep -Fq 'local GITHUB_API_RELEASES_URL = "https://api.github.com/repos/" .. GITHUB_REPO .. "/releases"' "$CONTROLLER_SCRIPT" || fail "controller should derive API URLs from hotwa repo" grep -Fq 'local GITHUB_API_RELEASES_URL = "https://api.github.com/repos/" .. GITHUB_REPO .. "/releases"' "$CONTROLLER_SCRIPT" || fail "controller should derive API URLs from hotwa repo"
grep -Fq 'export NPM_CONFIG_PREFIX="$OC_GLOBAL"' "$PROFILE_SCRIPT" || fail "shell profile should export npm prefix into custom install root"
grep -Fq 'export NPM_CONFIG_CACHE="${OC_DATA}/.npm"' "$PROFILE_SCRIPT" || fail "shell profile should export npm cache into custom data root"
grep -Fq 'export XDG_CACHE_HOME="${OC_DATA}/.cache"' "$PROFILE_SCRIPT" || fail "shell profile should export cache home into custom data root"
grep -Fq 'NPM_CONFIG_PREFIX="$OC_GLOBAL" \' "$INIT_SCRIPT" || fail "service environment should pass npm prefix into custom install root"
grep -Fq 'NPM_CONFIG_CACHE="${OC_DATA}/.npm" \' "$INIT_SCRIPT" || fail "service environment should pass npm cache into custom data root"
grep -Fq 'XDG_CACHE_HOME="${OC_DATA}/.cache" \' "$INIT_SCRIPT" || fail "service environment should pass cache home into custom data root"
grep -Fq 'NPM_CONFIG_PREFIX: OC_GLOBAL' "$WEB_PTY_SCRIPT" || fail "web PTY environment should pass npm prefix into custom install root"
grep -Fq 'NPM_CONFIG_CACHE: `${OC_DATA}/.npm`' "$WEB_PTY_SCRIPT" || fail "web PTY environment should pass npm cache into custom data root"
grep -Fq 'COREPACK_HOME: `${OC_DATA}/.cache/corepack`' "$WEB_PTY_SCRIPT" || fail "web PTY environment should pass corepack cache into custom data root"
grep -Fq "https://github.com/hotwa/luci-app-openclaw/releases/latest" "$BASIC_LUA" || fail "UI should link manual download to hotwa repo" grep -Fq "https://github.com/hotwa/luci-app-openclaw/releases/latest" "$BASIC_LUA" || fail "UI should link manual download to hotwa repo"
grep -Fq "ARM64 musl" "$BASIC_LUA" || fail "UI should mention ARM64 musl specific guidance" grep -Fq "ARM64 musl" "$BASIC_LUA" || fail "UI should mention ARM64 musl specific guidance"
grep -Fq "10000ge10000/luci-app-openclaw" "$BASIC_LUA" || fail "UI should point ARM64 musl guidance at the live node-bins repo" grep -Fq "hotwa/luci-app-openclaw" "$BASIC_LUA" || fail "UI should point ARM64 musl guidance at hotwa repo"
if grep -Fq 'NODE_MIRROR=https://npmmirror.com/mirrors/node openclaw-env setup' "$BASIC_LUA"; then if grep -Fq 'NODE_MIRROR=https://npmmirror.com/mirrors/node openclaw-env setup' "$BASIC_LUA"; then
fail "UI should not recommend NODE_MIRROR for ARM64 musl node download failures" fail "UI should not recommend NODE_MIRROR for ARM64 musl node download failures"
fi fi

View File

@@ -18,10 +18,10 @@ if oc_normalize_node_version "broken-version" >/dev/null 2>&1; then
fail "invalid version should not normalize" fail "invalid version should not normalize"
fi fi
oc_node_version_ge "22.16.0" "22.16.0" || fail "exact version should satisfy requirement" oc_node_version_ge "24.14.1" "24.14.1" || fail "exact version should satisfy requirement"
oc_node_version_ge "22.16.1" "22.16.0" || fail "newer patch should satisfy requirement" oc_node_version_ge "24.14.2" "24.14.1" || fail "newer patch should satisfy requirement"
oc_node_version_ge "23.0.0" "22.16.0" || fail "newer major should satisfy requirement" oc_node_version_ge "25.0.0" "24.14.1" || fail "newer major should satisfy requirement"
if oc_node_version_ge "22.15.1" "22.16.0"; then if oc_node_version_ge "24.14.0" "24.14.1"; then
fail "older minor version should not satisfy requirement" fail "older minor version should not satisfy requirement"
fi fi
@@ -51,17 +51,28 @@ if oc_read_node_version "$tmpdir/node-bad" >/dev/null 2>&1; then
fail "broken node binary should not be accepted" fail "broken node binary should not be accepted"
fi fi
cat > "$tmpdir/node-legacy-opt" <<'EOF'
#!/bin/sh
/opt/openclaw/node/lib/ld-musl-aarch64.so.1
EOF
chmod +x "$tmpdir/node-legacy-opt"
oc_node_requires_opt_compat "$tmpdir/node-legacy-opt" || fail "legacy opt-bound node binary should be detected"
if oc_node_requires_opt_compat "$tmpdir/node-ok" >/dev/null 2>&1; then
fail "modern runnable node helper should not require opt compatibility"
fi
cat > "$tmpdir/node-bins-release.json" <<'EOF' cat > "$tmpdir/node-bins-release.json" <<'EOF'
{ {
"tag_name": "node-bins", "tag_name": "node-bins",
"assets": [ "assets": [
{ {
"name": "node-v22.15.1-linux-arm64-musl.tar.xz", "name": "node-v22.15.1-linux-arm64-musl.tar.xz",
"browser_download_url": "https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins/node-v22.15.1-linux-arm64-musl.tar.xz" "browser_download_url": "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v22.15.1-linux-arm64-musl.tar.xz"
}, },
{ {
"name": "node-v23.2.0-linux-arm64-musl.tar.xz", "name": "node-v24.14.1-linux-arm64-musl.tar.xz",
"browser_download_url": "https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins/node-v23.2.0-linux-arm64-musl.tar.xz" "browser_download_url": "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz"
}, },
{ {
"name": "node-v22.16.0-linux-x64-musl.tar.xz", "name": "node-v22.16.0-linux-x64-musl.tar.xz",
@@ -71,10 +82,25 @@ cat > "$tmpdir/node-bins-release.json" <<'EOF'
} }
EOF EOF
selected_url=$(oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "22.16.0") || fail "select compatible ARM64 musl asset" selected_url=$(oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "24.14.1") || fail "select compatible ARM64 musl asset"
[ "$selected_url" = "https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins/node-v23.2.0-linux-arm64-musl.tar.xz" ] || fail "selected asset should be newest compatible ARM64 musl release" [ "$selected_url" = "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz" ] || fail "selected asset should be newest compatible ARM64 musl release"
if oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "24.0.0" >/dev/null 2>&1; then cat > "$tmpdir/gitea-node-bins-release.json" <<'EOF'
{
"tag_name": "node-bins",
"assets": [
{
"name": "node-v24.14.1-linux-arm64-musl.tar.xz",
"browser_download_url": "http://100.64.0.27:8418/lingyuzeng/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz"
}
]
}
EOF
gitea_selected_url=$(oc_select_node_release_asset_url "$tmpdir/gitea-node-bins-release.json" "linux-arm64" "24.14.1") || fail "select compatible ARM64 musl asset from Gitea release JSON"
[ "$gitea_selected_url" = "http://100.64.0.27:8418/lingyuzeng/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz" ] || fail "selected Gitea asset should preserve browser_download_url"
if oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "24.14.2" >/dev/null 2>&1; then
fail "asset selection should fail when no compatible version exists" fail "asset selection should fail when no compatible version exists"
fi fi

View File

@@ -27,4 +27,22 @@ trap 'rm -rf "$tmpdir"' EXIT INT TERM
existing=$(oc_find_existing_path "$tmpdir/missing/nested") existing=$(oc_find_existing_path "$tmpdir/missing/nested")
[ "$existing" = "$tmpdir" ] || fail "nearest existing path" [ "$existing" = "$tmpdir" ] || fail "nearest existing path"
export OPENCLAW_OPT_COMPAT_ROOT="$tmpdir/compat-opt"
target_root="$tmpdir/install-root/openclaw"
mkdir -p "$target_root"
oc_ensure_opt_compat_link "$target_root" || fail "compat symlink should be created for custom install root"
[ -L "$OPENCLAW_OPT_COMPAT_ROOT/openclaw" ] || fail "compat symlink should exist"
[ "$(readlink "$OPENCLAW_OPT_COMPAT_ROOT/openclaw")" = "$target_root" ] || fail "compat symlink should point to install root"
oc_ensure_opt_compat_link "$target_root" || fail "compat symlink should be idempotent"
conflict_root="$tmpdir/conflict-openclaw"
mkdir -p "$conflict_root"
rm -f "$OPENCLAW_OPT_COMPAT_ROOT/openclaw"
ln -s "$conflict_root" "$OPENCLAW_OPT_COMPAT_ROOT/openclaw"
if oc_ensure_opt_compat_link "$target_root" >/dev/null 2>&1; then
fail "compat symlink should fail when pointing at another install root"
fi
echo "ok" echo "ok"