Add automatic daemon startup feature to client

- Client now checks if daemon is running and starts it if needed
- Finds daemon binary based on client executable location
- Waits for daemon to start up before continuing
- Also implements proper handling of --shutdown flag
- Improves user experience by removing need to manually start daemon
main
John Kenyon 2025-04-07 05:17:30 +00:00
parent 39d7302a4e
commit 16720f5ad2
1 changed files with 66 additions and 6 deletions

View File

@ -3,7 +3,7 @@
//! This is the client component of the Cypraea shell. It connects to the daemon, //! This is the client component of the Cypraea shell. It connects to the daemon,
//! sends commands, and displays output to the user. //! sends commands, and displays output to the user.
use anyhow::{Context, Result}; use anyhow::{anyhow, Context, Result};
use clap::Parser; use clap::Parser;
use cypraea_common::paths; use cypraea_common::paths;
use cypraea_common::protocol::{ClientMessage, DaemonMessage}; use cypraea_common::protocol::{ClientMessage, DaemonMessage};
@ -66,12 +66,72 @@ async fn main() -> Result<()> {
.to_string(), .to_string(),
}; };
// Connect to the daemon // Handle shutdown request if specified
let stream = UnixStream::connect(&socket_path) if args.shutdown {
.await // First try to connect to the daemon
.context("Failed to connect to daemon")?; match UnixStream::connect(&socket_path).await {
Ok(stream) => {
info!("Connected to daemon at {}", socket_path);
info!("Connected to daemon at {}", socket_path); // Send the shutdown message
let mut writer = tokio::io::BufWriter::new(stream);
let shutdown_msg = ClientMessage::Shutdown;
send_message(&mut writer, &shutdown_msg).await
.context("Failed to send shutdown message")?;
println!("Daemon shutdown requested");
return Ok(());
},
Err(_) => {
println!("No daemon running at {}", socket_path);
return Ok(());
}
}
}
// Try to connect to the daemon
let stream = match UnixStream::connect(&socket_path).await {
Ok(stream) => {
info!("Connected to daemon at {}", socket_path);
stream
},
Err(_e) => {
// Daemon not running, try to start it
info!("Daemon not running, attempting to start it");
// Find the daemon binary (should be in the same directory as the client)
let current_exe = std::env::current_exe()
.context("Failed to get current executable path")?;
let daemon_dir = current_exe.parent()
.context("Failed to get parent directory of current executable")?;
let daemon_path = daemon_dir.join("cypraeadd");
if !daemon_path.exists() {
return Err(anyhow!("Daemon binary not found at {}", daemon_path.display()));
}
// Start the daemon process
let _daemon_process = tokio::process::Command::new(&daemon_path)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.context("Failed to start daemon process")?;
// Give the daemon a moment to start up
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
// Try to connect again
match UnixStream::connect(&socket_path).await {
Ok(stream) => {
info!("Successfully connected to newly started daemon");
stream
},
Err(e) => {
return Err(anyhow!("Failed to connect to daemon after starting it: {}", e));
}
}
}
};
// Split the stream into reader and writer // Split the stream into reader and writer
let (reader, writer) = tokio::io::split(stream); let (reader, writer) = tokio::io::split(stream);