mirror of
https://github.com/hotwa/luci-app-openclaw.git
synced 2026-04-01 13:49:51 +00:00
release: v1.0.4 — 适配 OpenClaw 2026.3.2
This commit is contained in:
42
CHANGELOG.md
42
CHANGELOG.md
@@ -4,7 +4,37 @@
|
||||
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
|
||||
|
||||
## [1.0.3] - 2026-03-02
|
||||
## [1.0.4] - 2026-03-05
|
||||
|
||||
### 适配 OpenClaw 2026.3.2
|
||||
|
||||
#### 破坏性变更修复
|
||||
- **tools.profile 默认值变更**: 2026.3.2 将 `tools.profile` 默认从 `coding` 改为 `messaging`
|
||||
- `sync_uci_to_json()` 每次启动强制写入 `tools.profile=coding`
|
||||
- `openclaw-env init_openclaw()` onboard 命令添加 `--tools-profile coding`
|
||||
- `openclaw-env do_factory_reset()` onboard 命令添加 `--tools-profile coding`
|
||||
- `oc-config.sh` 工厂重置 onboard 命令添加 `--tools-profile coding`
|
||||
- `oc-config.sh` 工厂重置配置写入新增 `tools.profile=coding`
|
||||
- **ACP dispatch 默认启用**: 2026.3.2 默认开启 ACP dispatch,路由器内存有限可能导致 OOM
|
||||
- `sync_uci_to_json()` 每次启动强制写入 `acp.dispatch.enabled=false`
|
||||
- `openclaw-env do_factory_reset()` 配置写入新增 `acp.dispatch.enabled=false`
|
||||
- `oc-config.sh` 工厂重置配置写入新增 `acp.dispatch.enabled=false`
|
||||
|
||||
#### 新增
|
||||
- 健康检查集成 `openclaw config validate --json` 官方配置验证命令
|
||||
- 健康检查新增 `gateway health --json` CLI 深度检查 (v2026.3.2 HTTP `/health` 已被 SPA 接管)
|
||||
|
||||
#### 修复
|
||||
- **Ollama 配置适配**: `api` 从废弃的 `openai-chat-completions` 改为原生 `ollama` API 类型
|
||||
- **Ollama baseUrl 格式**: 去掉 `/v1` 后缀,使用官方原生地址格式 (`http://host:11434`)
|
||||
- **Ollama apiKey 对齐**: 从 `ollama` 改为官方默认值 `ollama-local`
|
||||
- **启动自动迁移**: `sync_uci_to_json` 自动将旧版 Ollama 配置迁移到 v2026.3.2 格式
|
||||
|
||||
#### 改进
|
||||
- 配置管理页面移除「菜单功能说明」信息框,减少视觉干扰
|
||||
- `OC_TESTED_VERSION` 更新至 `2026.3.2`
|
||||
|
||||
## [1.0.3] - 2026-03-05
|
||||
|
||||
### 修复
|
||||
- **P0** 配置管理写入错误的 JSON 路径导致 Gateway 崩溃且无法恢复 (#1)
|
||||
@@ -17,10 +47,20 @@
|
||||
- **P1** `set_active_model` 手动切换模型时未注册到 `agents.defaults.models`
|
||||
|
||||
### 新增
|
||||
- **Ollama 本地模型支持**: 快速配置菜单新增 Ollama 选项 (12),支持 localhost/局域网连接、自动检测连通性、自动列出已安装模型、兼容 OpenAI chat completions 格式
|
||||
- `openclaw-env factory-reset` 非交互式恢复出厂设置命令
|
||||
- `auth_set_apikey` 函数: 正确写入 API Key 到 `auth-profiles.json`
|
||||
- `register_and_set_model` 函数: 注册模型到 `agents.defaults.models` 并设为默认
|
||||
- `register_custom_provider` 函数: 为需要 `baseUrl` 的 OpenAI 兼容供应商注册 `models.providers`
|
||||
- 「检测升级」同时检查 OpenClaw 和**插件版本** (通过 GitHub API 获取最新 release)
|
||||
- 页面加载时自动静默检查更新,有新版本时「检测升级」按钮显示橙色小红点提醒
|
||||
- 状态面板显示当前安装的插件版本号
|
||||
- 构建/安装流程部署 `VERSION` 文件到 `/usr/share/openclaw/VERSION`
|
||||
- `openclaw-env setup` 安装环境时自动安装 Gemini CLI (Google OAuth 依赖)
|
||||
|
||||
### 改进
|
||||
- 使用指南顺序调整: ② 配置管理 → ③ Web 控制台 (首次使用更合理的引导顺序)
|
||||
- Gemini CLI 安装从配置向导选项 1 移至环境安装阶段,避免进入向导时临时等待
|
||||
|
||||
## [1.0.2] - 2026-03-02
|
||||
|
||||
|
||||
1
Makefile
1
Makefile
@@ -63,6 +63,7 @@ define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DATA) ./luasrc/view/openclaw/advanced.htm $(1)/usr/lib/lua/luci/view/openclaw/advanced.htm
|
||||
$(INSTALL_DATA) ./luasrc/view/openclaw/console.htm $(1)/usr/lib/lua/luci/view/openclaw/console.htm
|
||||
$(INSTALL_DIR) $(1)/usr/share/openclaw
|
||||
$(INSTALL_DATA) ./VERSION $(1)/usr/share/openclaw/VERSION
|
||||
$(INSTALL_BIN) ./root/usr/share/openclaw/oc-config.sh $(1)/usr/share/openclaw/oc-config.sh
|
||||
$(INSTALL_DATA) ./root/usr/share/openclaw/web-pty.js $(1)/usr/share/openclaw/web-pty.js
|
||||
$(INSTALL_DIR) $(1)/usr/share/openclaw/ui
|
||||
|
||||
@@ -93,8 +93,16 @@ function action_status()
|
||||
uptime = "",
|
||||
node_version = "",
|
||||
openclaw_version = "",
|
||||
plugin_version = "",
|
||||
}
|
||||
|
||||
-- 插件版本
|
||||
local pvf = io.open("/usr/share/openclaw/VERSION", "r")
|
||||
if pvf then
|
||||
result.plugin_version = pvf:read("*a"):gsub("%s+", "")
|
||||
pvf:close()
|
||||
end
|
||||
|
||||
-- 检查 Node.js
|
||||
local node_bin = "/opt/openclaw/node/bin/node"
|
||||
local f = io.open(node_bin, "r")
|
||||
@@ -278,10 +286,10 @@ function action_check_update()
|
||||
local http = require "luci.http"
|
||||
local sys = require "luci.sys"
|
||||
|
||||
-- 当前版本
|
||||
-- 当前 OpenClaw 版本
|
||||
local current = get_openclaw_version()
|
||||
|
||||
-- 最新版本 (从 npm registry 查询)
|
||||
-- 最新 OpenClaw 版本 (从 npm registry 查询)
|
||||
local latest = sys.exec("PATH=/opt/openclaw/node/bin:/opt/openclaw/global/bin:$PATH npm view openclaw version 2>/dev/null"):gsub("%s+", "")
|
||||
|
||||
local has_update = false
|
||||
@@ -289,12 +297,42 @@ function action_check_update()
|
||||
has_update = true
|
||||
end
|
||||
|
||||
-- 插件版本检查 (从 GitHub API 获取最新 release tag)
|
||||
local plugin_current = ""
|
||||
local pf = io.open("/usr/share/openclaw/VERSION", "r")
|
||||
or io.open("/root/luci-app-openclaw/VERSION", "r")
|
||||
if pf then
|
||||
plugin_current = pf:read("*a"):gsub("%s+", "")
|
||||
pf:close()
|
||||
end
|
||||
|
||||
local plugin_latest = ""
|
||||
local plugin_has_update = false
|
||||
-- 仅在请求参数含 check_plugin=1 或 quick=1 时检查插件版本
|
||||
local check_plugin = http.formvalue("check_plugin") or ""
|
||||
local quick = http.formvalue("quick") or ""
|
||||
if check_plugin == "1" or quick == "1" then
|
||||
-- 使用 GitHub API 获取最新 release tag (轻量, 不下载任何文件)
|
||||
local gh_resp = sys.exec("curl -sf --connect-timeout 5 --max-time 10 'https://api.github.com/repos/10000ge10000/luci-app-openclaw/releases/latest' 2>/dev/null | grep -o '\"tag_name\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | head -1 | cut -d'\"' -f4")
|
||||
gh_resp = gh_resp:gsub("%s+", "")
|
||||
if gh_resp ~= "" then
|
||||
-- tag 可能是 v1.0.3 或 1.0.3
|
||||
plugin_latest = gh_resp:gsub("^v", "")
|
||||
end
|
||||
if plugin_current ~= "" and plugin_latest ~= "" and plugin_current ~= plugin_latest then
|
||||
plugin_has_update = true
|
||||
end
|
||||
end
|
||||
|
||||
http.prepare_content("application/json")
|
||||
http.write_json({
|
||||
status = "ok",
|
||||
current = current,
|
||||
latest = latest,
|
||||
has_update = has_update
|
||||
has_update = has_update,
|
||||
plugin_current = plugin_current,
|
||||
plugin_latest = plugin_latest,
|
||||
plugin_has_update = plugin_has_update
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ act.cfgvalue = function(self, section)
|
||||
html[#html+1] = '<button class="btn cbi-button cbi-button-apply" type="button" onclick="ocShowSetupDialog()" id="btn-setup" title="下载 Node.js 并安装 OpenClaw">📦 安装运行环境</button>'
|
||||
html[#html+1] = '<button class="btn cbi-button cbi-button-action" type="button" onclick="ocServiceCtl(\'restart\')">🔄 重启服务</button>'
|
||||
html[#html+1] = '<button class="btn cbi-button cbi-button-action" type="button" onclick="ocServiceCtl(\'stop\')">⏹️ 停止服务</button>'
|
||||
html[#html+1] = '<button class="btn cbi-button cbi-button-action" type="button" onclick="ocCheckUpdate()" id="btn-check-update">🔍 检测升级</button>'
|
||||
html[#html+1] = '<span style="position:relative;display:inline-block;" id="btn-check-update-wrap"><button class="btn cbi-button cbi-button-action" type="button" onclick="ocCheckUpdate()" id="btn-check-update">🔍 检测升级</button><span id="update-dot" style="display:none;position:absolute;top:-2px;right:-2px;width:10px;height:10px;background:#e36209;border-radius:50%;border:2px solid #fff;box-shadow:0 0 0 1px #e36209;"></span></span>'
|
||||
html[#html+1] = '<button class="btn cbi-button cbi-button-remove" type="button" onclick="ocUninstall()" id="btn-uninstall" title="删除 Node.js、OpenClaw 运行环境及相关数据">🗑️ 卸载环境</button>'
|
||||
html[#html+1] = '</div>'
|
||||
html[#html+1] = '<div id="action-result" style="margin-top:8px;"></div>'
|
||||
@@ -222,22 +222,37 @@ act.cfgvalue = function(self, section)
|
||||
html[#html+1] = '}catch(e){el.innerHTML="<span style=\\"color:red\\">❌ 错误</span>";}'
|
||||
html[#html+1] = '});}'
|
||||
|
||||
-- 检测升级
|
||||
-- 检测升级 (同时检查 OpenClaw + 插件版本)
|
||||
html[#html+1] = 'function ocCheckUpdate(){'
|
||||
html[#html+1] = 'var btn=document.getElementById("btn-check-update");'
|
||||
html[#html+1] = 'var el=document.getElementById("action-result");'
|
||||
html[#html+1] = 'var act=document.getElementById("oc-update-action");'
|
||||
html[#html+1] = 'btn.disabled=true;btn.textContent="⏳ 正在检测...";el.textContent="";act.style.display="none";'
|
||||
html[#html+1] = '(new XHR()).get("' .. check_url .. '",null,function(x){'
|
||||
html[#html+1] = '(new XHR()).get("' .. check_url .. '?check_plugin=1",null,function(x){'
|
||||
html[#html+1] = 'btn.disabled=false;btn.textContent="🔍 检测升级";'
|
||||
html[#html+1] = 'var dot=document.getElementById("update-dot");if(dot)dot.style.display="none";'
|
||||
html[#html+1] = 'try{var r=JSON.parse(x.responseText);'
|
||||
html[#html+1] = 'if(!r.current){el.innerHTML="<span style=\\"color:#999\\">⚠️ OpenClaw 未安装</span>";return;}'
|
||||
html[#html+1] = 'var msgs=[];'
|
||||
-- OpenClaw 版本检查
|
||||
html[#html+1] = 'if(!r.current){msgs.push("<span style=\\"color:#999\\">⚠️ OpenClaw 运行环境未安装</span>");}'
|
||||
html[#html+1] = 'else if(r.has_update){msgs.push("<span style=\\"color:#e36209\\">📦 OpenClaw: v"+r.current+" → v"+r.latest+" (有新版本)</span>");}'
|
||||
html[#html+1] = 'else{msgs.push("<span style=\\"color:green\\">✅ OpenClaw: v"+r.current+" (已是最新)</span>");}'
|
||||
-- 插件版本检查
|
||||
html[#html+1] = 'if(r.plugin_current){'
|
||||
html[#html+1] = 'if(r.plugin_has_update){msgs.push("<span style=\\"color:#e36209\\"><3E> 插件: v"+r.plugin_current+" → v"+r.plugin_latest+" (有新版本,请到 GitHub 下载更新)</span>");}'
|
||||
html[#html+1] = 'else if(r.plugin_latest){msgs.push("<span style=\\"color:green\\">✅ 插件: v"+r.plugin_current+" (已是最新)</span>");}'
|
||||
html[#html+1] = 'else{msgs.push("<span style=\\"color:#999\\">🔌 插件: v"+r.plugin_current+" (无法检查最新版本)</span>");}'
|
||||
html[#html+1] = '}'
|
||||
html[#html+1] = 'el.innerHTML=msgs.join("<br/>");'
|
||||
-- 显示 OpenClaw 升级按钮
|
||||
html[#html+1] = 'if(r.has_update){'
|
||||
html[#html+1] = 'el.innerHTML="<span style=\\"color:#e36209\\">📦 当前: v"+r.current+" → 最新: v"+r.latest+"</span>";'
|
||||
html[#html+1] = 'act.style.display="block";'
|
||||
html[#html+1] = 'act.innerHTML=\'<button class="btn cbi-button cbi-button-apply" type="button" onclick="ocDoUpdate()" id="btn-do-update">⬆️ 立即升级</button>\';'
|
||||
html[#html+1] = '}else{'
|
||||
html[#html+1] = 'el.innerHTML="<span style=\\"color:green\\">✅ 已是最新版本 (v"+r.current+")</span>";'
|
||||
html[#html+1] = 'act.innerHTML=\'<button class="btn cbi-button cbi-button-apply" type="button" onclick="ocDoUpdate()" id="btn-do-update">⬆️ 立即升级 OpenClaw</button>\';'
|
||||
html[#html+1] = '}'
|
||||
-- 插件有更新时添加 GitHub 链接
|
||||
html[#html+1] = 'if(r.plugin_has_update){'
|
||||
html[#html+1] = 'act.style.display="block";'
|
||||
html[#html+1] = 'act.innerHTML=(act.innerHTML||"")+\' <a href="https://github.com/10000ge10000/luci-app-openclaw/releases/latest" target="_blank" rel="noopener" class="btn cbi-button cbi-button-action" style="text-decoration:none;">📥 下载插件 v\'+r.plugin_latest+\'</a>\';'
|
||||
html[#html+1] = '}'
|
||||
html[#html+1] = '}catch(e){el.innerHTML="<span style=\\"color:red\\">❌ 检测失败</span>";}'
|
||||
html[#html+1] = '});}'
|
||||
@@ -334,6 +349,20 @@ act.cfgvalue = function(self, section)
|
||||
html[#html+1] = '}catch(e){el.innerHTML="<span style=\\"color:red\\">❌ 请求失败</span>";}'
|
||||
html[#html+1] = '});}'
|
||||
|
||||
-- 页面加载时静默检查是否有更新 (仅显示小红点提示)
|
||||
html[#html+1] = '(function(){'
|
||||
html[#html+1] = 'setTimeout(function(){'
|
||||
html[#html+1] = '(new XHR()).get("' .. check_url .. '?quick=1",null,function(x){'
|
||||
html[#html+1] = 'try{var r=JSON.parse(x.responseText);'
|
||||
html[#html+1] = 'if(r.has_update||r.plugin_has_update){'
|
||||
html[#html+1] = 'var dot=document.getElementById("update-dot");'
|
||||
html[#html+1] = 'if(dot)dot.style.display="block";'
|
||||
html[#html+1] = '}'
|
||||
html[#html+1] = '}catch(e){}'
|
||||
html[#html+1] = '});'
|
||||
html[#html+1] = '},2000);'
|
||||
html[#html+1] = '})();'
|
||||
|
||||
html[#html+1] = '</script>'
|
||||
return table.concat(html, "\n")
|
||||
end
|
||||
@@ -351,8 +380,8 @@ guide.cfgvalue = function()
|
||||
html[#html+1] = '<strong style="font-size:14px;">📖 使用指南</strong><br/>'
|
||||
html[#html+1] = '<span style="color:#555;">'
|
||||
html[#html+1] = '① 首次使用请点击 <b>「安装运行环境」</b>,安装完成后服务会自动启动<br/>'
|
||||
html[#html+1] = '② 进入 <b>「Web 控制台」</b> 配置 AI 模型、消息渠道,直接开始对话<br/>'
|
||||
html[#html+1] = '③ 进入 <b>「配置管理」</b> 可使用交互式向导进行高级配置</span>'
|
||||
html[#html+1] = '② 进入 <b>「配置管理」</b> 使用交互式向导快速配置 AI 模型和 API Key<br/>'
|
||||
html[#html+1] = '③ 进入 <b>「Web 控制台」</b> 配置消息渠道,直接开始对话</span>'
|
||||
html[#html+1] = '<div style="margin-top:10px;padding-top:10px;border-top:1px solid #d0e8ff;">'
|
||||
html[#html+1] = '<span style="color:#888;">有疑问?请关注B站并留言:</span>'
|
||||
html[#html+1] = '<a href="https://space.bilibili.com/59438380" target="_blank" rel="noopener" style="color:#00a1d6;font-weight:bold;text-decoration:none;">'
|
||||
|
||||
@@ -13,20 +13,6 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
|
||||
.oc-page-header h2 { font-size: 18px; font-weight: 600; color: #333; margin: 0 0 6px 0; }
|
||||
.oc-page-header p { font-size: 13px; color: #666; margin: 0; line-height: 1.6; }
|
||||
|
||||
.oc-info-box {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: #f6f8fa;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-left: 4px solid #4a90d9;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #555;
|
||||
}
|
||||
.oc-info-box ul { margin: 6px 0 0 0; padding-left: 18px; list-style: none; }
|
||||
.oc-info-box li { margin-bottom: 2px; }
|
||||
|
||||
.oc-terminal-wrap {
|
||||
border: 2px solid #2d333b;
|
||||
border-radius: 8px;
|
||||
@@ -47,15 +33,6 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
|
||||
<p>通过内嵌的交互式终端 (oc-config) 进行 OpenClaw 的完整配置管理。支持 AI 模型配置、消息渠道设置、健康检查等。</p>
|
||||
</div>
|
||||
|
||||
<div class="oc-info-box">
|
||||
<strong>💡 菜单功能说明:</strong>
|
||||
<ul>
|
||||
<li><strong>1)</strong> 查看当前配置 <strong>2)</strong> 配置 AI 模型提供商 <strong>3)</strong> 设定当前活跃模型</li>
|
||||
<li><strong>4)</strong> 配置消息渠道 <strong>5)</strong> Telegram 配对向导 <strong>6)</strong> 健康检查 / 诊断</li>
|
||||
<li><strong>7)</strong> 重启网关 <strong>8)</strong> 查看/编辑原始配置 <strong>9)</strong> 恢复默认配置</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="oc-terminal-wrap">
|
||||
<div id="oc-terminal-container">
|
||||
<div class="oc-terminal-loading" id="oc-terminal-loading">
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<tr><td>运行时间</td><td id="oc-st-uptime">-</td></tr>
|
||||
<tr><td>Node.js</td><td id="oc-st-node">-</td></tr>
|
||||
<tr><td>OpenClaw</td><td id="oc-st-ocver">-</td></tr>
|
||||
<tr><td>插件版本</td><td id="oc-st-plugin">-</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,6 +133,7 @@
|
||||
document.getElementById('oc-st-uptime').textContent = d.uptime || '-';
|
||||
document.getElementById('oc-st-node').textContent = d.node_version || '未安装';
|
||||
document.getElementById('oc-st-ocver').textContent = d.openclaw_version || '未安装';
|
||||
document.getElementById('oc-st-plugin').textContent = d.plugin_version ? ('v' + d.plugin_version) : '-';
|
||||
|
||||
} catch(e) {
|
||||
document.getElementById('oc-st-status').innerHTML = '<span class="oc-badge oc-badge-unknown">查询失败</span>';
|
||||
|
||||
@@ -114,6 +114,15 @@ d.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true;
|
||||
delete d.gateway.name;
|
||||
delete d.gateway.bonjour;
|
||||
delete d.gateway.plugins;
|
||||
// v2026.3.2: 禁用 ACP dispatch 防止路由器内存溢出
|
||||
if(!d.acp)d.acp={};
|
||||
if(!d.acp.dispatch)d.acp.dispatch={};
|
||||
d.acp.dispatch.enabled=false;
|
||||
// v2026.3.2: tools.profile 默认改为 messaging,路由器场景需强制 coding
|
||||
if(!d.tools)d.tools={};
|
||||
d.tools.profile='coding';
|
||||
// v2026.3.2: 迁移 Ollama provider 到原生 API
|
||||
if(d.models&&d.models.providers&&d.models.providers.ollama){const ol=d.models.providers.ollama;if(ol.api==='openai-chat-completions'||ol.api==='openai-completions')ol.api='ollama';if(ol.baseUrl&&ol.baseUrl.endsWith('/v1'))ol.baseUrl=ol.baseUrl.replace(/\/v1$/,'');if(ol.apiKey==='ollama')ol.apiKey='ollama-local';}
|
||||
fs.writeFileSync(f,JSON.stringify(d,null,2));
|
||||
" 2>/dev/null
|
||||
fi
|
||||
|
||||
@@ -13,7 +13,7 @@ set -e
|
||||
|
||||
NODE_VERSION="${NODE_VERSION:-22.16.0}"
|
||||
# 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
|
||||
OC_TESTED_VERSION="2026.3.1"
|
||||
OC_TESTED_VERSION="2026.3.2"
|
||||
# 用户可通过 OC_VERSION 环境变量覆盖安装版本
|
||||
OC_VERSION="${OC_VERSION:-}"
|
||||
NODE_BASE="/opt/openclaw/node"
|
||||
@@ -296,6 +296,18 @@ install_openclaw() {
|
||||
log_error "OpenClaw 安装验证失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装 Gemini CLI (官方模型配置向导的 Google Gemini OAuth 依赖)
|
||||
if [ -x "$NPM_BIN" ]; then
|
||||
echo ""
|
||||
echo "=== 安装 Gemini CLI (Google OAuth 依赖) ==="
|
||||
"$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
|
||||
log_info "Gemini CLI 安装成功"
|
||||
else
|
||||
log_warn "Gemini CLI 安装失败 (不影响核心功能,仅影响 Google Gemini OAuth 登录)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
init_openclaw() {
|
||||
@@ -314,7 +326,7 @@ init_openclaw() {
|
||||
OPENCLAW_HOME="$OC_DATA" \
|
||||
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
|
||||
OPENCLAW_CONFIG_PATH="${OC_DATA}/.openclaw/openclaw.json" \
|
||||
"$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk 2>/dev/null || true
|
||||
"$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk --tools-profile coding 2>/dev/null || true
|
||||
log_info "初始化完成"
|
||||
fi
|
||||
|
||||
@@ -526,7 +538,7 @@ do_factory_reset() {
|
||||
oc_entry=$(find "$OC_GLOBAL" -name "openclaw.mjs" -path "*/openclaw/openclaw.mjs" 2>/dev/null | head -1)
|
||||
if [ -n "$oc_entry" ]; then
|
||||
log_info "重新初始化..."
|
||||
OPENCLAW_HOME="$OC_DATA" "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk >/dev/null 2>&1 || true
|
||||
OPENCLAW_HOME="$OC_DATA" "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk --tools-profile coding >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -535,7 +547,7 @@ do_factory_reset() {
|
||||
local new_token
|
||||
new_token=$(head -c 24 /dev/urandom | hexdump -e '24/1 "%02x"' 2>/dev/null || dd if=/dev/urandom bs=24 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n' | head -c 48)
|
||||
_JS_KEY="gateway.port" _JS_VAL="18789" "$NODE_BIN" -e "const fs=require('fs');let d={};try{d=JSON.parse(fs.readFileSync('${config_file}','utf8'));}catch(e){}const ks=process.env._JS_KEY.split('.');let o=d;for(let i=0;i<ks.length-1;i++){if(!o[ks[i]]||typeof o[ks[i]]!=='object')o[ks[i]]={};o=o[ks[i]];}let v=process.env._JS_VAL;try{v=JSON.parse(v);}catch(e){}o[ks[ks.length-1]]=v;fs.writeFileSync('${config_file}',JSON.stringify(d,null,2));" 2>/dev/null
|
||||
for kv in "gateway.bind=lan" "gateway.mode=local" "gateway.auth.mode=token" "gateway.auth.token=${new_token}" "gateway.controlUi.allowInsecureAuth=true" "gateway.controlUi.dangerouslyDisableDeviceAuth=true" "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true" "gateway.tailscale.mode=off"; do
|
||||
for kv in "gateway.bind=lan" "gateway.mode=local" "gateway.auth.mode=token" "gateway.auth.token=${new_token}" "gateway.controlUi.allowInsecureAuth=true" "gateway.controlUi.dangerouslyDisableDeviceAuth=true" "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true" "gateway.tailscale.mode=off" "acp.dispatch.enabled=false" "tools.profile=coding"; do
|
||||
local k="${kv%%=*}" v="${kv#*=}"
|
||||
_JS_KEY="$k" _JS_VAL="$v" "$NODE_BIN" -e "const fs=require('fs');let d={};try{d=JSON.parse(fs.readFileSync('${config_file}','utf8'));}catch(e){}const ks=process.env._JS_KEY.split('.');let o=d;for(let i=0;i<ks.length-1;i++){if(!o[ks[i]]||typeof o[ks[i]]!=='object')o[ks[i]]={};o=o[ks[i]];}let v=process.env._JS_VAL;try{v=JSON.parse(v);}catch(e){}o[ks[ks.length-1]]=v;fs.writeFileSync('${config_file}',JSON.stringify(d,null,2));" 2>/dev/null
|
||||
done
|
||||
|
||||
@@ -359,7 +359,8 @@ configure_model() {
|
||||
echo -e " ${CYAN}9)${NC} xAI Grok (Grok-3/3-mini)"
|
||||
echo -e " ${CYAN}10)${NC} Groq (Llama 4, Llama 3.3)"
|
||||
echo -e " ${CYAN}11)${NC} 硅基流动 SiliconFlow"
|
||||
echo -e " ${CYAN}12)${NC} 自定义 OpenAI 兼容 API"
|
||||
echo -e " ${CYAN}12)${NC} Ollama (本地模型,无需 API Key)"
|
||||
echo -e " ${CYAN}13)${NC} 自定义 OpenAI 兼容 API"
|
||||
echo -e " ${CYAN}0)${NC} 返回"
|
||||
echo ""
|
||||
prompt_with_default "请选择" "1" choice
|
||||
@@ -373,14 +374,6 @@ configure_model() {
|
||||
echo -e " ${CYAN}预启用模型认证插件...${NC}"
|
||||
enable_auth_plugins
|
||||
echo ""
|
||||
# 检查并安装 gemini-cli (官方向导的 Google Gemini OAuth 依赖)
|
||||
if ! command -v gemini >/dev/null 2>&1; then
|
||||
local npm_bin="${NODE_BASE}/bin/npm"
|
||||
if [ -x "$npm_bin" ]; then
|
||||
echo -e " ${CYAN}安装 Gemini CLI (官方向导 Google OAuth 依赖)...${NC}"
|
||||
"$npm_bin" install -g @google/gemini-cli --prefix="$OC_GLOBAL" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
(oc_cmd configure --section model) || echo -e " ${YELLOW}配置向导已退出${NC}"
|
||||
echo ""
|
||||
ask_restart
|
||||
@@ -718,6 +711,122 @@ configure_model() {
|
||||
fi
|
||||
;;
|
||||
12)
|
||||
echo ""
|
||||
echo -e " ${BOLD}🦙 Ollama 本地模型配置${NC}"
|
||||
echo -e " ${YELLOW}Ollama 在本地或局域网运行大模型,无需 API Key${NC}"
|
||||
echo -e " ${YELLOW}安装 Ollama: https://ollama.com${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}连接方式:${NC}"
|
||||
echo -e " ${CYAN}a)${NC} 本机运行 (localhost:11434)"
|
||||
echo -e " ${CYAN}b)${NC} 局域网其他设备"
|
||||
echo ""
|
||||
prompt_with_default "请选择" "a" ollama_mode
|
||||
local ollama_url=""
|
||||
case "$ollama_mode" in
|
||||
b)
|
||||
prompt_with_default "Ollama 地址 (如 192.168.1.100:11434)" "" ollama_host
|
||||
if [ -n "$ollama_host" ]; then
|
||||
# 补全协议前缀
|
||||
case "$ollama_host" in
|
||||
http://*|https://*) ollama_url="${ollama_host}" ;;
|
||||
*) ollama_url="http://${ollama_host}" ;;
|
||||
esac
|
||||
# v2026.3.2: Ollama 使用原生 API,baseUrl 不带 /v1
|
||||
ollama_url=$(echo "$ollama_url" | sed 's|/v1/*$||;s|/*$||')
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
ollama_url="http://127.0.0.1:11434"
|
||||
;;
|
||||
esac
|
||||
if [ -n "$ollama_url" ]; then
|
||||
# 检测 Ollama 是否可达
|
||||
echo ""
|
||||
echo -e " ${CYAN}检测 Ollama 连通性...${NC}"
|
||||
local ollama_base=$(echo "$ollama_url" | sed 's|/v1$||')
|
||||
local ollama_check=$(curl -sf --connect-timeout 3 --max-time 5 "${ollama_base}/api/tags" 2>/dev/null || echo "")
|
||||
if [ -n "$ollama_check" ]; then
|
||||
echo -e " ${GREEN}✅ Ollama 已连接${NC}"
|
||||
# 列出已安装的模型
|
||||
local model_list=$("$NODE_BIN" -e "
|
||||
try{
|
||||
const d=JSON.parse(process.argv[1]);
|
||||
(d.models||[]).forEach((m,i)=>console.log(' '+(i+1)+') '+m.name));
|
||||
}catch(e){}
|
||||
" "$ollama_check" 2>/dev/null)
|
||||
if [ -n "$model_list" ]; then
|
||||
echo -e " ${CYAN}已安装的模型:${NC}"
|
||||
echo "$model_list"
|
||||
echo -e " ${CYAN}m)${NC} 手动输入模型名"
|
||||
echo ""
|
||||
prompt_with_default "请选择模型" "1" ollama_sel
|
||||
if [ "$ollama_sel" = "m" ]; then
|
||||
prompt_with_default "请输入模型名称" "llama3.3" model_name
|
||||
elif echo "$ollama_sel" | grep -qE '^[0-9]+$'; then
|
||||
model_name=$("$NODE_BIN" -e "
|
||||
try{
|
||||
const d=JSON.parse(process.argv[1]);
|
||||
const m=(d.models||[])[parseInt(process.argv[2])-1];
|
||||
console.log(m?m.name:'');
|
||||
}catch(e){console.log('');}
|
||||
" "$ollama_check" "$ollama_sel" 2>/dev/null)
|
||||
if [ -z "$model_name" ]; then
|
||||
echo -e " ${YELLOW}无效选择,使用默认模型${NC}"
|
||||
model_name="llama3.3"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e " ${YELLOW}未检测到已安装模型,请先在 Ollama 中拉取模型:${NC}"
|
||||
echo -e " ${CYAN} ollama pull llama3.3${NC}"
|
||||
echo ""
|
||||
prompt_with_default "请输入模型名称" "llama3.3" model_name
|
||||
fi
|
||||
else
|
||||
echo -e " ${YELLOW}⚠️ 无法连接 Ollama (${ollama_base})${NC}"
|
||||
echo -e " ${YELLOW} 请确认 Ollama 已启动并可访问${NC}"
|
||||
echo -e " ${CYAN} 提示: 如果 Ollama 在其他设备上,需设置 OLLAMA_HOST=0.0.0.0${NC}"
|
||||
echo ""
|
||||
prompt_with_default "仍要继续配置? (y/n)" "n" force_continue
|
||||
if ! confirm_yes "$force_continue"; then
|
||||
return
|
||||
fi
|
||||
prompt_with_default "请输入模型名称" "llama3.3" model_name
|
||||
fi
|
||||
if [ -n "$model_name" ]; then
|
||||
# Ollama 无需 API Key,使用占位符
|
||||
auth_set_apikey ollama "ollama-local" "ollama:local"
|
||||
# v2026.3.2: Ollama 使用原生 ollama API,不再走 OpenAI 兼容层
|
||||
_RCP_PROV="ollama" _RCP_URL="$ollama_url" _RCP_KEY="ollama-local" _RCP_MID="$model_name" _RCP_MNAME="$model_name" "$NODE_BIN" -e "
|
||||
const fs=require('fs');
|
||||
let d={};
|
||||
try{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}
|
||||
if(!d.models)d.models={};
|
||||
if(!d.models.providers)d.models.providers={};
|
||||
d.models.mode='merge';
|
||||
d.models.providers['ollama']={
|
||||
baseUrl:process.env._RCP_URL,
|
||||
apiKey:process.env._RCP_KEY,
|
||||
api:'ollama',
|
||||
models:[{
|
||||
id:process.env._RCP_MID,
|
||||
name:process.env._RCP_MNAME,
|
||||
reasoning:false,
|
||||
input:['text'],
|
||||
cost:{input:0,output:0,cacheRead:0,cacheWrite:0},
|
||||
contextWindow:128000,
|
||||
maxTokens:32000
|
||||
}]
|
||||
};
|
||||
fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
|
||||
" 2>/dev/null
|
||||
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
|
||||
register_and_set_model "ollama/${model_name}"
|
||||
echo -e " ${GREEN}✅ Ollama 已配置,活跃模型: ollama/${model_name}${NC}"
|
||||
echo -e " ${CYAN} Ollama 地址: ${ollama_url}${NC}"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
13)
|
||||
echo ""
|
||||
echo -e " ${BOLD}自定义 OpenAI 兼容 API${NC}"
|
||||
echo -e " ${YELLOW}支持任何兼容 OpenAI API 格式的服务商${NC}"
|
||||
@@ -1108,6 +1217,34 @@ health_check() {
|
||||
local gw_port=$(json_get gateway.port)
|
||||
gw_port=${gw_port:-18789}
|
||||
|
||||
# ── v2026.3.2: 使用官方 config validate 验证配置 ──
|
||||
if command -v openclaw >/dev/null 2>&1 || [ -n "$OC_ENTRY" ]; then
|
||||
echo -e " ${CYAN}验证配置文件格式...${NC}"
|
||||
local validate_out=""
|
||||
validate_out=$(oc_cmd config validate --json 2>/dev/null) || true
|
||||
if [ -n "$validate_out" ]; then
|
||||
local has_errors=$("$NODE_BIN" -e "
|
||||
try{const r=JSON.parse(process.argv[1]);
|
||||
if(r.valid===true){console.log('OK');}
|
||||
else if(r.errors&&r.errors.length>0){r.errors.forEach(e=>console.log('ERR:'+e.message));}
|
||||
else{console.log('OK');}}catch(e){console.log('SKIP');}
|
||||
" "$validate_out" 2>/dev/null)
|
||||
if [ "$has_errors" = "OK" ]; then
|
||||
echo -e " ${GREEN}✅ 配置文件格式有效${NC}"
|
||||
elif [ "$has_errors" = "SKIP" ]; then
|
||||
echo -e " ${YELLOW}⚠️ 无法解析验证结果,跳过${NC}"
|
||||
else
|
||||
echo -e " ${RED}❌ 配置文件存在错误:${NC}"
|
||||
echo "$has_errors" | while IFS= read -r line; do
|
||||
echo -e " ${YELLOW}• ${line#ERR:}${NC}"
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo -e " ${YELLOW}⚠️ config validate 不可用,跳过格式验证${NC}"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ── 自动修复: 移除旧版错误写入的顶层 models.xxx 无效键 ──
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
local has_bad_models=$("$NODE_BIN" -e "
|
||||
@@ -1152,6 +1289,19 @@ health_check() {
|
||||
echo -e " ${RED}❌ HTTP 响应异常 (${http_code})${NC}"
|
||||
fi
|
||||
|
||||
# v2026.3.2: 使用 gateway health --json 做深度健康检查 (HTTP /health 已被 SPA 接管)
|
||||
local health_resp=$(oc_cmd gateway health --json 2>/dev/null)
|
||||
if [ -n "$health_resp" ]; then
|
||||
local health_ok=$("$NODE_BIN" -e "try{const h=JSON.parse(process.argv[1]);console.log(h.ok?'ok':'fail');}catch(e){console.log('parse_error');}" "$health_resp" 2>/dev/null)
|
||||
if [ "$health_ok" = "ok" ]; then
|
||||
echo -e " ${GREEN}✅ Gateway 健康检查正常${NC}"
|
||||
elif [ "$health_ok" = "parse_error" ]; then
|
||||
echo -e " ${YELLOW}⚠️ Gateway 健康检查响应无法解析${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠️ Gateway 健康检查异常${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
echo -e " ${GREEN}✅ 配置文件存在${NC}"
|
||||
else
|
||||
@@ -1296,9 +1446,9 @@ reset_to_defaults() {
|
||||
local _node_bin
|
||||
_node_bin=$(which node 2>/dev/null || echo "$NODE_BIN")
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout 10 sh -c "\"$_node_bin\" \"$OC_ENTRY\" onboard --non-interactive --accept-risk" >/dev/null 2>&1 || true
|
||||
timeout 10 sh -c "\"$_node_bin\" \"$OC_ENTRY\" onboard --non-interactive --accept-risk --tools-profile coding" >/dev/null 2>&1 || true
|
||||
else
|
||||
"$_node_bin" "$OC_ENTRY" onboard --non-interactive --accept-risk >/dev/null 2>&1 &
|
||||
"$_node_bin" "$OC_ENTRY" onboard --non-interactive --accept-risk --tools-profile coding >/dev/null 2>&1 &
|
||||
local _ob_pid=$!
|
||||
sleep 10
|
||||
kill "$_ob_pid" 2>/dev/null || true
|
||||
@@ -1318,6 +1468,8 @@ reset_to_defaults() {
|
||||
json_set gateway.controlUi.dangerouslyDisableDeviceAuth true
|
||||
json_set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true
|
||||
json_set gateway.tailscale.mode off
|
||||
json_set acp.dispatch.enabled false
|
||||
json_set tools.profile coding
|
||||
|
||||
# 同步 token 到 UCI
|
||||
. /lib/functions.sh 2>/dev/null || true
|
||||
|
||||
@@ -60,6 +60,7 @@ cp "$PKG_DIR/luasrc/view/openclaw/"*.htm "$DATA_DIR/usr/lib/lua/luci/view/opencl
|
||||
|
||||
# oc-config assets
|
||||
mkdir -p "$DATA_DIR/usr/share/openclaw"
|
||||
cp "$PKG_DIR/VERSION" "$DATA_DIR/usr/share/openclaw/VERSION"
|
||||
cp "$PKG_DIR/root/usr/share/openclaw/oc-config.sh" "$DATA_DIR/usr/share/openclaw/"
|
||||
chmod +x "$DATA_DIR/usr/share/openclaw/oc-config.sh"
|
||||
cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$DATA_DIR/usr/share/openclaw/"
|
||||
|
||||
@@ -62,6 +62,7 @@ install_files() {
|
||||
|
||||
# oc-config assets
|
||||
mkdir -p "$dest/usr/share/openclaw"
|
||||
cp "$PKG_DIR/VERSION" "$dest/usr/share/openclaw/VERSION"
|
||||
cp "$PKG_DIR/root/usr/share/openclaw/oc-config.sh" "$dest/usr/share/openclaw/"
|
||||
chmod +x "$dest/usr/share/openclaw/oc-config.sh"
|
||||
cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$dest/usr/share/openclaw/"
|
||||
|
||||
Reference in New Issue
Block a user