mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-07 05:42:53 +08:00
紧急修复: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:
@@ -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)
|
||||
|
||||
98
build.sh
98
build.sh
@@ -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}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawpanel",
|
||||
"version": "0.4.5",
|
||||
"version": "0.4.6",
|
||||
"private": true,
|
||||
"description": "ClawPanel - OpenClaw 可视化管理面板,基于 Tauri v2 的跨平台桌面应用",
|
||||
"type": "module",
|
||||
|
||||
@@ -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
2
src-tauri/Cargo.lock
generated
@@ -328,7 +328,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clawpanel"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clawpanel"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
edition = "2021"
|
||||
description = "ClawPanel - OpenClaw 可视化管理面板"
|
||||
authors = ["qingchencloud"]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(¤t);
|
||||
}
|
||||
parts.join(":")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let pf = std::env::var("ProgramFiles").unwrap_or_else(|_| r"C:\Program Files".into());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user