From b4e959ec995daf8a4b078c4962506fea7ba8c840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Thu, 5 Mar 2026 23:41:55 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B4=A7=E6=80=A5=E4=BF=AE=E5=A4=8D=EF=BC=9Amo?= =?UTF-8?q?de=20=E5=AD=97=E6=AE=B5=E4=BD=8D=E7=BD=AE=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=20Gateway=20=E6=97=A0=E6=B3=95=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=20(v0.4.6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: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 --- CHANGELOG.md | 7 +++ build.sh | 98 ++++++++++++++++++++++--------- package.json | 2 +- scripts/dev-api.js | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands/config.rs | 2 +- src-tauri/src/commands/mod.rs | 54 ++++++++++++++++- src-tauri/src/commands/service.rs | 33 ++++++++++- src-tauri/tauri.conf.json | 2 +- src/pages/dashboard.js | 5 +- src/pages/setup.js | 5 +- 12 files changed, 175 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 957a062..800ccee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/build.sh b/build.sh index f5f9901..66a2e40 100644 --- a/build.sh +++ b/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 — 指定 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}" diff --git a/package.json b/package.json index 4e1e1a9..8e37372 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clawpanel", - "version": "0.4.5", + "version": "0.4.6", "private": true, "description": "ClawPanel - OpenClaw 可视化管理面板,基于 Tauri v2 的跨平台桌面应用", "type": "module", diff --git a/scripts/dev-api.js b/scripts/dev-api.js index 49d13c5..e34c611 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -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 } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7379e19..3532275 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -328,7 +328,7 @@ dependencies = [ [[package]] name = "clawpanel" -version = "0.4.5" +version = "0.4.6" dependencies = [ "base64 0.22.1", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 049869b..6e3057b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clawpanel" -version = "0.4.5" +version = "0.4.6" edition = "2021" description = "ClawPanel - OpenClaw 可视化管理面板" authors = ["qingchencloud"] diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index f284be4..3fd93dd 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -658,9 +658,9 @@ pub fn init_openclaw_config() -> Result { 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 } diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index dd62dfb..b61babe 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -35,7 +35,7 @@ pub fn enhanced_path() -> String { }) .flatten(); - #[cfg(not(target_os = "windows"))] + #[cfg(target_os = "macos")] { let mut extra: Vec = vec![ "/usr/local/bin".into(), @@ -69,6 +69,58 @@ pub fn enhanced_path() -> String { parts.join(":") } + #[cfg(target_os = "linux")] + { + let mut extra: Vec = 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()); diff --git a/src-tauri/src/commands/service.rs b/src-tauri/src/commands/service.rs index 5a1d75b..58b38af 100644 --- a/src-tauri/src/commands/service.rs +++ b/src-tauri/src/commands/service.rs @@ -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::(&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) { + 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); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 8602b19..2d86e3b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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", diff --git a/src/pages/dashboard.js b/src/pages/dashboard.js index 1cd3898..92ea487 100644 --- a/src/pages/dashboard.js +++ b/src/pages/dashboard.js @@ -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' diff --git a/src/pages/setup.js b/src/pages/setup.js index 07bd12b..8c684e7 100644 --- a/src/pages/setup.js +++ b/src/pages/setup.js @@ -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') }