mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 21:59:48 +00:00
Refactored .NET server start
In the dev and production environment gets the .NET server started by Rust. Also, the secret IPC key gets set by using a local env variable.
This commit is contained in:
parent
2e6de53095
commit
200b87f47a
@ -11,7 +11,13 @@ using System.Reflection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
#endif
|
||||
|
||||
var rustApiPort = args.Length > 0 ? args[0] : "5000";
|
||||
if(args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Please provide the port of the runtime API.");
|
||||
return;
|
||||
}
|
||||
|
||||
var rustApiPort = args[0];
|
||||
using var rust = new Rust(rustApiPort);
|
||||
var appPort = await rust.GetAppPort();
|
||||
if(appPort == 0)
|
||||
@ -20,6 +26,14 @@ if(appPort == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the secret key for the IPC from the AI_STUDIO_SECRET_KEY environment variable:
|
||||
var secretKey = Environment.GetEnvironmentVariable("AI_STUDIO_SECRET_KEY");
|
||||
if(string.IsNullOrWhiteSpace(secretKey))
|
||||
{
|
||||
Console.WriteLine("The AI_STUDIO_SECRET_KEY environment variable is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Services.AddMudServices(config =>
|
||||
{
|
||||
|
@ -32,10 +32,6 @@ static DOTNET_SERVER: Lazy<Arc<Mutex<Option<CommandChild>>>> = Lazy::new(|| Arc:
|
||||
// do not start the server in the development environment.
|
||||
static DOTNET_SERVER_PORT: Lazy<u16> = Lazy::new(|| get_available_port().unwrap());
|
||||
|
||||
// Our runtime API server. We need it as a static variable because we need to
|
||||
// shut it down when the app is closed.
|
||||
static API_SERVER: Lazy<Arc<Mutex<Option<rocket::Rocket<rocket::Ignite>>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
|
||||
|
||||
// The port used for the runtime API server. In the development environment, we use a fixed
|
||||
// port, in the production environment we use the next available port. This differentiation
|
||||
// is necessary because we cannot communicate the port to the .NET server in the development
|
||||
@ -132,46 +128,85 @@ async fn main() {
|
||||
// Start the runtime API server in a separate thread. This is necessary
|
||||
// because the server is blocking, and we need to run the Tauri app in
|
||||
// parallel:
|
||||
let api_server_spawn_clone = API_SERVER.clone();
|
||||
//
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let api_server = rocket::custom(figment)
|
||||
_ = rocket::custom(figment)
|
||||
.mount("/", routes![dotnet_port, dotnet_ready])
|
||||
.ignite().await.unwrap()
|
||||
.launch().await.unwrap();
|
||||
|
||||
// We need to save the server to shut it down later:
|
||||
*api_server_spawn_clone.lock().unwrap() = Some(api_server);
|
||||
//
|
||||
// Generate a secret key for the AES encryption for the IPC channel:
|
||||
//
|
||||
let mut secret_key = [0u8; 512]; // 512 bytes = 4096 bits
|
||||
|
||||
// We use a cryptographically secure pseudo-random number generator
|
||||
// to generate the secret key. ChaCha20Rng is the algorithm of our choice:
|
||||
let mut rng = rand_chacha::ChaChaRng::from_entropy();
|
||||
|
||||
// Fill the secret key with random bytes:
|
||||
rng.fill_bytes(&mut secret_key);
|
||||
|
||||
// Convert the secret key to a hexadecimal string:
|
||||
let secret_key = secret_key.iter().fold(String::new(), |mut out, b| {
|
||||
_ = write!(out, "{b:02X}");
|
||||
out
|
||||
});
|
||||
|
||||
// Arc for the server process to stop it later:
|
||||
info!("Secret key for the IPC channel was generated successfully.");
|
||||
info!("Try to start the .NET server...");
|
||||
let server_spawn_clone = DOTNET_SERVER.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let api_port = *API_SERVER_PORT;
|
||||
|
||||
// Channel to communicate with the server process:
|
||||
let (sender, mut receiver) = tauri::async_runtime::channel(100);
|
||||
let (mut rx, child) = match is_dev() {
|
||||
true => {
|
||||
// We are in the development environment, so we try to start a process
|
||||
// with `dotnet run` in the `../app/MindWork AI Studio` directory. But
|
||||
// we cannot issue a sidecar, because we cannot use any command for the
|
||||
// sidecar (see Tauri configuration). Thus, we use a standard Rust process:
|
||||
warn!(Source = "Bootloader .NET"; "Development environment detected; start .NET server using 'dotnet run'.");
|
||||
Command::new("dotnet")
|
||||
|
||||
if is_prod() {
|
||||
info!("Try to start the .NET server...");
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let api_port = *API_SERVER_PORT;
|
||||
let (mut rx, child) = Command::new_sidecar("mindworkAIStudioServer")
|
||||
.expect("Failed to create sidecar")
|
||||
.args([format!("{api_port}").as_str()])
|
||||
.spawn()
|
||||
.expect("Failed to spawn .NET server process.");
|
||||
// Start the .NET server in the `../app/MindWork AI Studio` directory.
|
||||
// We provide the runtime API server port to the .NET server:
|
||||
.args(["run", "--project", "../app/MindWork AI Studio", "--", format!("{api_port}").as_str()])
|
||||
|
||||
let server_pid = child.pid();
|
||||
info!(".NET server process started with PID={server_pid}.");
|
||||
// Provide the secret key for the IPC channel to the .NET server by using
|
||||
// an environment variable. We must use a HashMap for this:
|
||||
.envs(HashMap::from_iter(once((
|
||||
String::from("AI_STUDIO_SECRET_KEY"),
|
||||
secret_key
|
||||
))))
|
||||
.spawn()
|
||||
.expect("Failed to spawn .NET server process.")
|
||||
}
|
||||
|
||||
// Save the server process to stop it later:
|
||||
*server_spawn_clone.lock().unwrap() = Some(child);
|
||||
false => {
|
||||
Command::new_sidecar("mindworkAIStudioServer")
|
||||
.expect("Failed to create sidecar")
|
||||
|
||||
// TODO: Migrate to runtime API server:
|
||||
while let Some(CommandEvent::Stdout(line)) = rx.recv().await {
|
||||
let line_lower = line.to_lowercase();
|
||||
let line_cleared = line_lower.trim();
|
||||
match line_cleared
|
||||
{
|
||||
"rust/tauri server started" => _ = sender.send(ServerEvent::Started).await,
|
||||
// Provide the runtime API server port to the .NET server:
|
||||
.args([format!("{api_port}").as_str()])
|
||||
|
||||
// Provide the secret key for the IPC channel to the .NET server by using
|
||||
// an environment variable. We must use a HashMap for this:
|
||||
.envs(HashMap::from_iter(once((
|
||||
String::from("AI_STUDIO_SECRET_KEY"),
|
||||
secret_key
|
||||
))))
|
||||
.spawn()
|
||||
.expect("Failed to spawn .NET server process.")
|
||||
}
|
||||
};
|
||||
|
||||
let server_pid = child.pid();
|
||||
info!(Source = "Bootloader .NET"; "The .NET server process started with PID={server_pid}.");
|
||||
|
||||
// Save the server process to stop it later:
|
||||
*server_spawn_clone.lock().unwrap() = Some(child);
|
||||
|
||||
// Log the output of the .NET server:
|
||||
while let Some(CommandEvent::Stdout(line)) = rx.recv().await {
|
||||
|
||||
_ if line_cleared.contains("fail") || line_cleared.contains("error") || line_cleared.contains("exception") => _ = sender.send(ServerEvent::Error(line)).await,
|
||||
_ if line_cleared.contains("warn") => _ = sender.send(ServerEvent::Warning(line)).await,
|
||||
@ -400,12 +435,6 @@ fn stop_servers() {
|
||||
} else {
|
||||
warn!("The .NET server process was not started or is already stopped.");
|
||||
}
|
||||
|
||||
if let Some(api_server) = API_SERVER.lock().unwrap().take() {
|
||||
_ = api_server.shutdown();
|
||||
} else {
|
||||
warn!("The API server was not started or is already stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
Loading…
Reference in New Issue
Block a user