紧急修复:mode 字段位置错误导致 Gateway 无法启动 (v0.4.6)

根因:openclaw.json 的 mode 属于 gateway 对象内部,不是顶层字段。
OpenClaw zod-schema 对顶层 mode 报 Unrecognized key 错误。

修复:
- config.rs init_openclaw_config: mode 移入 gateway 对象
- dev-api.js init_openclaw_config: 同上
- dashboard.js 自愈: config.mode → config.gateway.mode
- dashboard.js 自愈: 自动删除旧版错误的顶层 mode 字段
- setup.js 安装流程: config.mode → config.gateway.mode
This commit is contained in:
晴天
2026-03-05 23:41:55 +08:00
parent 8ba25a25e0
commit b4e959ec99
12 changed files with 175 additions and 39 deletions

View File

@@ -5,6 +5,13 @@
格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [0.4.6] - 2026-03-06
### 修复 (Bug Fixes)
- **严重mode 字段位置错误导致 Gateway 无法启动** — `"mode": "local"` 被错误写入 `openclaw.json` 顶层OpenClaw 报 `Unrecognized key: "mode"`。正确位置是 `gateway.mode`已修复所有写入点init_openclaw_config、dashboard 自愈、setup 安装流程)
- **旧版配置自动修复** — 仪表盘加载时自动删除错误的顶层 `mode` 字段并移入 `gateway.mode`,已安装用户无需手动编辑
## [0.4.5] - 2026-03-06
### 修复 (Bug Fixes)

View File

@@ -1,18 +1,22 @@
#!/usr/bin/env bash
# ClawPanel 本地构建脚本macOS / Linux
# 用法:
# ./build.sh — 构建当前平台安装包(默认)
# ./build.sh --debug — Debug 构建(快,不打包)
# ./build.sh --clean — 清理 Rust 编译缓存后构建
# ./build.sh — 构建当前平台安装包(默认)
# ./build.sh --debug — Debug 构建(快,不打包)
# ./build.sh --clean — 清理 Rust 编译缓存后构建
# ./build.sh --target <triple> — 指定 Rust target如 x86_64-unknown-linux-gnu
set -euo pipefail
DEBUG=false
CLEAN=false
TARGET=""
for arg in "$@"; do
case "$arg" in
--debug) DEBUG=true ;;
--clean) CLEAN=true ;;
while [[ $# -gt 0 ]]; do
case "$1" in
--debug) DEBUG=true; shift ;;
--clean) CLEAN=true; shift ;;
--target) TARGET="$2"; shift 2 ;;
*) shift ;;
esac
done
@@ -24,17 +28,22 @@ ok() { echo -e " ${GREEN}✓ $1${RESET}"; }
fail() { echo -e " ${RED}$1${RESET}"; exit 1; }
echo ""
ARCH=$(uname -m)
OS=$(uname)
echo -e " ${MAGENTA}ClawPanel 构建工具${RESET}"
echo -e " ${GRAY}─────────────────────────────────────${RESET}"
if [[ "$(uname)" == "Darwin" ]]; then
ARCH=$(uname -m)
if [[ "$OS" == "Darwin" ]]; then
if [[ "$ARCH" == "arm64" ]]; then
echo -e " ${GRAY}平台: macOS Apple Silicon (aarch64)${RESET}"
else
echo -e " ${GRAY}平台: macOS Intel (x86_64)${RESET}"
fi
else
echo -e " ${GRAY}平台: Linux x86_64${RESET}"
echo -e " ${GRAY}平台: Linux ${ARCH}${RESET}"
fi
if [[ -n "$TARGET" ]]; then
echo -e " ${CYAN}目标: $TARGET${RESET}"
fi
echo -e " ${GRAY}跨平台构建 (其他平台) 请推送 tag 触发 GitHub Actions${RESET}"
echo ""
@@ -62,17 +71,35 @@ if [[ "$(uname)" == "Darwin" ]]; then
fi
# Linux 额外检测
if [[ "$(uname)" == "Linux" ]]; then
MISSING=()
for pkg in libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev; do
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then
MISSING+=("$pkg")
if [[ "$OS" == "Linux" ]]; then
if command -v dpkg &>/dev/null; then
# Debian/Ubuntu
MISSING=()
for pkg in libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev; do
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then
MISSING+=("$pkg")
fi
done
if [ ${#MISSING[@]} -gt 0 ]; then
echo -e " ${RED}✗ 缺少系统依赖: ${MISSING[*]}${RESET}"
echo -e " 运行: sudo apt-get install -y ${MISSING[*]} libayatana-appindicator3-dev librsvg2-dev patchelf"
exit 1
fi
done
if [ ${#MISSING[@]} -gt 0 ]; then
echo -e " ${RED}✗ 缺少系统依赖: ${MISSING[*]}${RESET}"
echo -e " 运行: sudo apt-get install -y ${MISSING[*]} libayatana-appindicator3-dev librsvg2-dev patchelf"
exit 1
elif command -v rpm &>/dev/null; then
# Fedora/RHEL/CentOS
MISSING=()
for pkg in webkit2gtk4.1-devel openssl-devel gtk3-devel; do
if ! rpm -q "$pkg" &>/dev/null 2>&1; then
MISSING+=("$pkg")
fi
done
if [ ${#MISSING[@]} -gt 0 ]; then
echo -e " ${RED}✗ 缺少系统依赖: ${MISSING[*]}${RESET}"
echo -e " 运行: sudo dnf install -y ${MISSING[*]} libayatana-appindicator-gtk3-devel librsvg2-devel patchelf"
exit 1
fi
else
echo -e " ${GRAY}⚠ 无法自动检测系统依赖,请确保已安装 WebKit2GTK 4.1、OpenSSL、GTK3 开发包${RESET}"
fi
fi
@@ -98,14 +125,23 @@ fi
START_TIME=$(date +%s)
# 构建参数
BUILD_ARGS=""
if [[ -n "$TARGET" ]]; then
rustup target add "$TARGET" 2>/dev/null || true
BUILD_ARGS="--target $TARGET"
fi
if [ "$DEBUG" = true ]; then
step "Debug 构建(不打包安装器)"
npm run tauri build -- --debug
npm run tauri build -- --debug $BUILD_ARGS
else
step "Release 构建"
# macOS Apple Silicon: 同时构建 ARM64 + Intel Universal Binary可选
if [[ "$(uname)" == "Darwin" ]] && [[ "$(uname -m)" == "arm64" ]]; then
# 确保 Intel target 已安装
if [[ -n "$TARGET" ]]; then
echo -e " ${GRAY}目标: $TARGET${RESET}"
npm run tauri build -- $BUILD_ARGS
elif [[ "$OS" == "Darwin" ]] && [[ "$ARCH" == "arm64" ]]; then
# macOS Apple Silicon: 构建 ARM64 版本
rustup target add x86_64-apple-darwin 2>/dev/null || true
echo -e " ${GRAY}构建 ARM64 版本...${RESET}"
npm run tauri build -- --target aarch64-apple-darwin
@@ -124,10 +160,18 @@ echo -e " ${GREEN}✅ 构建成功!耗时 ${ELAPSED}s${RESET}"
echo -e " ${GRAY}─────────────────────────────────────${RESET}"
if [ "$DEBUG" = true ]; then
echo -e " 可执行文件: src-tauri/target/debug/clawpanel"
if [[ -n "$TARGET" ]]; then
echo -e " 可执行文件: src-tauri/target/$TARGET/debug/clawpanel"
else
echo -e " 可执行文件: src-tauri/target/debug/clawpanel"
fi
else
BUNDLE_DIR="src-tauri/target/release/bundle"
if [[ "$(uname)" == "Darwin" ]]; then
if [[ -n "$TARGET" ]]; then
BUNDLE_DIR="src-tauri/target/$TARGET/release/bundle"
else
BUNDLE_DIR="src-tauri/target/release/bundle"
fi
if [[ "$OS" == "Darwin" ]]; then
DMG=$(find "$BUNDLE_DIR/dmg" -name "*.dmg" 2>/dev/null | head -1)
APP=$(find "$BUNDLE_DIR/macos" -name "*.app" -maxdepth 1 2>/dev/null | head -1)
[ -n "$DMG" ] && echo -e " DMG: ${GRAY}$DMG${RESET}"

View File

@@ -1,6 +1,6 @@
{
"name": "clawpanel",
"version": "0.4.5",
"version": "0.4.6",
"private": true,
"description": "ClawPanel - OpenClaw 可视化管理面板,基于 Tauri v2 的跨平台桌面应用",
"type": "module",

View File

@@ -590,9 +590,9 @@ const handlers = {
const defaultConfig = {
"$schema": "https://openclaw.ai/schema/config.json",
meta: { lastTouchedVersion: "2026.1.1" },
mode: "local",
models: { providers: {} },
gateway: {
mode: "local",
port: 18789,
auth: { mode: "none" },
controlUi: { allowedOrigins: ["*"], allowInsecureAuth: true }

2
src-tauri/Cargo.lock generated
View File

@@ -328,7 +328,7 @@ dependencies = [
[[package]]
name = "clawpanel"
version = "0.4.5"
version = "0.4.6"
dependencies = [
"base64 0.22.1",
"chrono",

View File

@@ -1,6 +1,6 @@
[package]
name = "clawpanel"
version = "0.4.5"
version = "0.4.6"
edition = "2021"
description = "ClawPanel - OpenClaw 可视化管理面板"
authors = ["qingchencloud"]

View File

@@ -658,9 +658,9 @@ pub fn init_openclaw_config() -> Result<Value, String> {
let default_config = serde_json::json!({
"$schema": "https://openclaw.ai/schema/config.json",
"meta": { "lastTouchedVersion": "2026.1.1" },
"mode": "local",
"models": { "providers": {} },
"gateway": {
"mode": "local",
"port": 18789,
"auth": { "mode": "none" },
"controlUi": { "allowedOrigins": ["*"], "allowInsecureAuth": true }

View File

@@ -35,7 +35,7 @@ pub fn enhanced_path() -> String {
})
.flatten();
#[cfg(not(target_os = "windows"))]
#[cfg(target_os = "macos")]
{
let mut extra: Vec<String> = vec![
"/usr/local/bin".into(),
@@ -69,6 +69,58 @@ pub fn enhanced_path() -> String {
parts.join(":")
}
#[cfg(target_os = "linux")]
{
let mut extra: Vec<String> = vec![
"/usr/local/bin".into(),
"/usr/bin".into(),
"/snap/bin".into(),
format!("{}/.local/bin", home.display()),
format!("{}/.nvm/current/bin", home.display()),
format!("{}/.volta/bin", home.display()),
format!("{}/.nodenv/shims", home.display()),
format!("{}/.fnm/current/bin", home.display()),
format!("{}/n/bin", home.display()),
];
// NVM_DIR 环境变量(用户可能自定义了 nvm 安装目录)
let nvm_dir = std::env::var("NVM_DIR")
.ok()
.map(std::path::PathBuf::from)
.unwrap_or_else(|| home.join(".nvm"));
let nvm_versions = nvm_dir.join("versions/node");
if nvm_versions.is_dir() {
if let Ok(entries) = std::fs::read_dir(&nvm_versions) {
for entry in entries.flatten() {
let bin = entry.path().join("bin");
if bin.is_dir() {
extra.push(bin.to_string_lossy().to_string());
}
}
}
}
// nodesource / 手动安装的 Node.js 可能在 /usr/local/lib/nodejs/ 下
let nodejs_lib = std::path::Path::new("/usr/local/lib/nodejs");
if nodejs_lib.is_dir() {
if let Ok(entries) = std::fs::read_dir(nodejs_lib) {
for entry in entries.flatten() {
let bin = entry.path().join("bin");
if bin.is_dir() {
extra.push(bin.to_string_lossy().to_string());
}
}
}
}
let mut parts: Vec<&str> = vec![];
if let Some(ref cp) = custom_path {
parts.push(cp.as_str());
}
parts.extend(extra.iter().map(|s| s.as_str()));
if !current.is_empty() {
parts.push(&current);
}
parts.join(":")
}
#[cfg(target_os = "windows")]
{
let pf = std::env::var("ProgramFiles").unwrap_or_else(|_| r"C:\Program Files".into());

View File

@@ -381,6 +381,7 @@ mod platform {
pub async fn is_cli_installed() -> bool {
Command::new("openclaw")
.arg("--version")
.env("PATH", crate::commands::enhanced_path())
.output()
.await
.map(|o| o.status.success())
@@ -391,14 +392,42 @@ mod platform {
vec!["ai.openclaw.gateway".to_string()]
}
/// 从 openclaw.json 读取 gateway 端口fallback 到 18789
fn read_gateway_port() -> u16 {
let config_path = crate::commands::openclaw_dir().join("openclaw.json");
if let Ok(content) = std::fs::read_to_string(&config_path) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
if let Some(port) = val
.get("gateway")
.and_then(|g| g.get("port"))
.and_then(|p| p.as_u64())
{
if port > 0 && port < 65536 {
return port as u16;
}
}
}
}
18789
}
pub async fn check_service_status(_uid: u32, _label: &str) -> (bool, Option<u32>) {
let port = read_gateway_port();
let addr = format!("127.0.0.1:{port}");
match std::net::TcpStream::connect_timeout(
&"127.0.0.1:18789".parse().unwrap(),
&addr
.parse()
.unwrap_or_else(|_| "127.0.0.1:18789".parse().unwrap()),
std::time::Duration::from_secs(2),
) {
Ok(_) => (true, None),
Err(_) => {
if let Ok(output) = Command::new("openclaw").arg("health").output().await {
if let Ok(output) = Command::new("openclaw")
.arg("health")
.env("PATH", crate::commands::enhanced_path())
.output()
.await
{
let text = String::from_utf8_lossy(&output.stdout);
if output.status.success() && !text.contains("not running") {
return (true, None);

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
"productName": "ClawPanel",
"version": "0.4.5",
"version": "0.4.6",
"identifier": "ai.openclaw.clawpanel",
"build": {
"frontendDist": "../dist",

View File

@@ -79,7 +79,10 @@ async function loadDashboardData(page) {
// 自愈:补全关键默认值
if (config) {
let patched = false
if (!config.mode) { config.mode = 'local'; patched = true }
if (!config.gateway) config.gateway = {}
if (!config.gateway.mode) { config.gateway.mode = 'local'; patched = true }
// 修复旧版错误mode 不应在顶层OpenClaw 不认识)
if (config.mode) { delete config.mode; patched = true }
if (!config.tools || config.tools.profile !== 'full') {
config.tools = { profile: 'full', sessions: { visibility: 'all' }, ...(config.tools || {}) }
config.tools.profile = 'full'

View File

@@ -326,8 +326,9 @@ function bindEvents(page, nodeOk) {
const config = await api.readOpenclawConfig()
if (config) {
let patched = false
if (!config.mode) {
config.mode = 'local'
if (!config.gateway) config.gateway = {}
if (!config.gateway.mode) {
config.gateway.mode = 'local'
patched = true
modal.appendLog('✅ 已设置 Gateway 运行模式为 local')
}