99 lines
3.7 KiB
Rust
99 lines
3.7 KiB
Rust
//! Server rebuild and restart logic shared between the MCP tool and Matrix bot command.
|
|
|
|
use crate::agents::AgentPool;
|
|
use crate::slog;
|
|
use std::path::Path;
|
|
|
|
/// Rebuild the server binary and re-exec.
|
|
///
|
|
/// 1. Gracefully stops all running agents (kills PTY children).
|
|
/// 2. Runs `cargo build [-p storkit]` from the workspace root, matching
|
|
/// the current build profile (debug or release).
|
|
/// 3. If the build fails, returns the build error (server stays up).
|
|
/// 4. If the build succeeds, re-execs the process with the new binary via
|
|
/// `std::os::unix::process::CommandExt::exec()`.
|
|
pub async fn rebuild_and_restart(agents: &AgentPool, project_root: &Path) -> Result<String, String> {
|
|
slog!("[rebuild] Rebuild and restart requested");
|
|
|
|
// 1. Gracefully stop all running agents.
|
|
let running_count = agents
|
|
.list_agents()
|
|
.unwrap_or_default()
|
|
.iter()
|
|
.filter(|a| a.status == crate::agents::AgentStatus::Running)
|
|
.count();
|
|
if running_count > 0 {
|
|
slog!("[rebuild] Stopping {running_count} running agent(s) before rebuild");
|
|
}
|
|
agents.kill_all_children();
|
|
|
|
// 2. Find the workspace root (parent of the server binary's source).
|
|
// CARGO_MANIFEST_DIR at compile time points to the `server/` crate;
|
|
// the workspace root is its parent.
|
|
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let workspace_root = manifest_dir
|
|
.parent()
|
|
.ok_or_else(|| "Cannot determine workspace root from CARGO_MANIFEST_DIR".to_string())?;
|
|
|
|
slog!(
|
|
"[rebuild] Building server from workspace root: {}",
|
|
workspace_root.display()
|
|
);
|
|
|
|
// 3. Build the server binary, matching the current build profile so the
|
|
// re-exec via current_exe() picks up the new binary.
|
|
let build_args: Vec<&str> = if cfg!(debug_assertions) {
|
|
vec!["build", "-p", "storkit"]
|
|
} else {
|
|
vec!["build", "--release", "-p", "storkit"]
|
|
};
|
|
slog!("[rebuild] cargo {}", build_args.join(" "));
|
|
let output = tokio::task::spawn_blocking({
|
|
let workspace_root = workspace_root.to_path_buf();
|
|
move || {
|
|
std::process::Command::new("cargo")
|
|
.args(&build_args)
|
|
.current_dir(&workspace_root)
|
|
.output()
|
|
}
|
|
})
|
|
.await
|
|
.map_err(|e| format!("Build task panicked: {e}"))?
|
|
.map_err(|e| format!("Failed to run cargo build: {e}"))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
slog!("[rebuild] Build failed:\n{stderr}");
|
|
return Err(format!("Build failed:\n{stderr}"));
|
|
}
|
|
|
|
slog!("[rebuild] Build succeeded, re-execing with new binary");
|
|
|
|
// 4. Re-exec with the new binary.
|
|
// Collect current argv so we preserve any CLI arguments (e.g. project path).
|
|
let current_exe =
|
|
std::env::current_exe().map_err(|e| format!("Cannot determine current executable: {e}"))?;
|
|
let args: Vec<String> = std::env::args().collect();
|
|
|
|
// Remove the port file before re-exec so the new process can write its own.
|
|
let port_file = project_root.join(".storkit_port");
|
|
if port_file.exists() {
|
|
let _ = std::fs::remove_file(&port_file);
|
|
}
|
|
// Also check cwd for port file.
|
|
let cwd_port_file = std::path::Path::new(".storkit_port");
|
|
if cwd_port_file.exists() {
|
|
let _ = std::fs::remove_file(cwd_port_file);
|
|
}
|
|
|
|
// Use exec() to replace the current process.
|
|
// This never returns on success.
|
|
use std::os::unix::process::CommandExt;
|
|
let err = std::process::Command::new(¤t_exe)
|
|
.args(&args[1..])
|
|
.exec();
|
|
|
|
// If we get here, exec() failed.
|
|
Err(format!("Failed to exec new binary: {err}"))
|
|
}
|