mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-06 20:02:49 +08:00
修复:macOS Gateway 启动失败 Bootstrap failed: 5 (v0.4.8)
根因: plist 二进制路径过期(nvm/fnm 切版本后),launchctl 找不到程序 修复: launchctl bootstrap/kickstart 失败时回退到 CLI 直接 spawn Gateway 覆盖: start_service_impl + restart_service_impl 均有 fallback
This commit is contained in:
@@ -5,6 +5,12 @@
|
||||
格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
||||
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [0.4.8] - 2026-03-06
|
||||
|
||||
### 修复 (Bug Fixes)
|
||||
|
||||
- **macOS Gateway 启动失败 (Bootstrap failed: 5)** — plist 二进制路径过期(如 nvm/fnm 切版本后)导致 `launchctl bootstrap` 报 I/O error。新增回退机制:launchctl 失败时自动改用 CLI 直接启动 Gateway,启动和重启均适用
|
||||
|
||||
## [0.4.7] - 2026-03-06
|
||||
|
||||
### 修复 (Bug Fixes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawpanel",
|
||||
"version": "0.4.7",
|
||||
"version": "0.4.8",
|
||||
"private": true,
|
||||
"description": "ClawPanel - OpenClaw 可视化管理面板,基于 Tauri v2 的跨平台桌面应用",
|
||||
"type": "module",
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -328,7 +328,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clawpanel"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clawpanel"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
edition = "2021"
|
||||
description = "ClawPanel - OpenClaw 可视化管理面板"
|
||||
authors = ["qingchencloud"]
|
||||
|
||||
@@ -100,12 +100,60 @@ mod platform {
|
||||
(running, pid)
|
||||
}
|
||||
|
||||
/// launchctl 失败时的回退:直接通过 CLI spawn Gateway 进程
|
||||
fn start_gateway_direct() -> Result<(), String> {
|
||||
let enhanced = crate::commands::enhanced_path();
|
||||
|
||||
let log_dir = dirs::home_dir()
|
||||
.unwrap_or_default()
|
||||
.join(".openclaw")
|
||||
.join("logs");
|
||||
fs::create_dir_all(&log_dir).ok();
|
||||
|
||||
let stdout_log = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_dir.join("gateway.log"))
|
||||
.map_err(|e| format!("创建日志文件失败: {e}"))?;
|
||||
|
||||
let stderr_log = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_dir.join("gateway.err.log"))
|
||||
.map_err(|e| format!("创建错误日志文件失败: {e}"))?;
|
||||
|
||||
Command::new("openclaw")
|
||||
.arg("gateway")
|
||||
.env("PATH", &enhanced)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(stdout_log)
|
||||
.stderr(stderr_log)
|
||||
.spawn()
|
||||
.map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
"OpenClaw CLI 未找到,请确认已安装并重启 ClawPanel。".to_string()
|
||||
} else {
|
||||
format!("启动 Gateway 失败: {e}")
|
||||
}
|
||||
})?;
|
||||
|
||||
// 等 Gateway 初始化
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_service_impl(label: &str) -> Result<(), String> {
|
||||
let uid = current_uid()?;
|
||||
let path = plist_path(label);
|
||||
let domain_target = format!("gui/{}", uid);
|
||||
let service_target = format!("gui/{}/{}", uid, label);
|
||||
|
||||
// 先尝试 plist 文件是否存在
|
||||
if !std::path::Path::new(&path).exists() {
|
||||
// plist 不存在,直接用 CLI 启动
|
||||
return start_gateway_direct();
|
||||
}
|
||||
|
||||
let bootstrap_out = Command::new("launchctl")
|
||||
.args(["bootstrap", &domain_target, &path])
|
||||
.output()
|
||||
@@ -114,7 +162,8 @@ mod platform {
|
||||
if !bootstrap_out.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&bootstrap_out.stderr);
|
||||
if !stderr.contains("already bootstrapped") && !stderr.trim().is_empty() {
|
||||
return Err(format!("启动 {label} 失败: {stderr}"));
|
||||
// launchctl 失败(如 plist 二进制路径过期),回退到直接启动
|
||||
return start_gateway_direct();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +175,8 @@ mod platform {
|
||||
if !kickstart_out.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&kickstart_out.stderr);
|
||||
if !stderr.trim().is_empty() {
|
||||
return Err(format!("kickstart {label} 失败: {stderr}"));
|
||||
// kickstart 也失败,回退到直接启动
|
||||
return start_gateway_direct();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +211,7 @@ mod platform {
|
||||
let domain_target = format!("gui/{}", uid);
|
||||
let service_target = format!("gui/{}/{}", uid, label);
|
||||
|
||||
// 先停
|
||||
let _ = Command::new("launchctl")
|
||||
.args(["bootout", &service_target])
|
||||
.output();
|
||||
@@ -174,6 +225,11 @@ mod platform {
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
|
||||
// plist 不存在,直接用 CLI 启动
|
||||
if !std::path::Path::new(&path).exists() {
|
||||
return start_gateway_direct();
|
||||
}
|
||||
|
||||
let bootstrap_out = Command::new("launchctl")
|
||||
.args(["bootstrap", &domain_target, &path])
|
||||
.output()
|
||||
@@ -182,7 +238,8 @@ mod platform {
|
||||
if !bootstrap_out.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&bootstrap_out.stderr);
|
||||
if !stderr.contains("already bootstrapped") && !stderr.trim().is_empty() {
|
||||
return Err(format!("重启 {label} 失败 (bootstrap): {stderr}"));
|
||||
// launchctl 失败,回退到直接启动
|
||||
return start_gateway_direct();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +251,8 @@ mod platform {
|
||||
if !kickstart_out.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&kickstart_out.stderr);
|
||||
if !stderr.trim().is_empty() {
|
||||
return Err(format!("重启 {label} 失败 (kickstart): {stderr}"));
|
||||
// kickstart 也失败,回退到直接启动
|
||||
return start_gateway_direct();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
|
||||
"productName": "ClawPanel",
|
||||
"version": "0.4.7",
|
||||
"version": "0.4.8",
|
||||
"identifier": "ai.openclaw.clawpanel",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
|
||||
Reference in New Issue
Block a user