mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-07-04 02:02:56 +00:00
Improved dev experience & allow native .NET debugging (#460)
This commit is contained in:
parent
c0cf620fe3
commit
4ca1fd54d9
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
# Ignore any startup.env file:
|
||||
startup.env
|
||||
|
||||
# Ignore pdfium library:
|
||||
libpdfium.dylib
|
||||
libpdfium.so
|
||||
|
@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tauri Dev" type="ShConfigurationType">
|
||||
<configuration default="false" name="[1] Start Tauri" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="cargo tauri dev --no-watch" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="SCRIPT_PATH" value="" />
|
27
app/.run/[2] Start .NET Server.run.xml
Normal file
27
app/.run/[2] Start .NET Server.run.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="[2] Start .NET Server" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/MindWork AI Studio/bin/Debug/net9.0/osx-arm64/mindworkAIStudio" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/MindWork AI Studio" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="ENV_FILE_PATHS" value="" />
|
||||
<option name="REDIRECT_INPUT_PATH" value="" />
|
||||
<option name="PTY_MODE" value="Auto" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="AUTO_ATTACH_CHILDREN" value="0" />
|
||||
<option name="MIXED_MODE_DEBUG" value="0" />
|
||||
<option name="RUNTIME_TYPE" value="coreclr" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/MindWork AI Studio/MindWork AI Studio.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net9.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Collect I18N content" run_configuration_type="ShConfigurationType" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -25,13 +25,13 @@ internal sealed class Program
|
||||
public static IServiceProvider SERVICE_PROVIDER = null!;
|
||||
public static ILoggerFactory LOGGER_FACTORY = null!;
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
public static async Task Main()
|
||||
{
|
||||
if(args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Error: Please provide the port of the runtime API.");
|
||||
return;
|
||||
}
|
||||
#if DEBUG
|
||||
// Read the environment variables from the .env file:
|
||||
var envFilePath = Path.Combine("..", "..", "startup.env");
|
||||
await EnvFile.Apply(envFilePath);
|
||||
#endif
|
||||
|
||||
// Read the secret key for the IPC from the AI_STUDIO_SECRET_KEY environment variable:
|
||||
var secretPasswordEncoded = Environment.GetEnvironmentVariable("AI_STUDIO_SECRET_PASSWORD");
|
||||
@ -58,6 +58,13 @@ internal sealed class Program
|
||||
return;
|
||||
}
|
||||
|
||||
var rustApiPort = Environment.GetEnvironmentVariable("AI_STUDIO_API_PORT");
|
||||
if(string.IsNullOrWhiteSpace(rustApiPort))
|
||||
{
|
||||
Console.WriteLine("Error: The AI_STUDIO_API_PORT environment variable is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
var apiToken = Environment.GetEnvironmentVariable("AI_STUDIO_API_TOKEN");
|
||||
if(string.IsNullOrWhiteSpace(apiToken))
|
||||
{
|
||||
@ -67,7 +74,6 @@ internal sealed class Program
|
||||
|
||||
API_TOKEN = apiToken;
|
||||
|
||||
var rustApiPort = args[0];
|
||||
using var rust = new RustService(rustApiPort, certificateFingerprint);
|
||||
var appPort = await rust.GetAppPort();
|
||||
if(appPort == 0)
|
||||
|
41
app/MindWork AI Studio/Tools/EnvFile.cs
Normal file
41
app/MindWork AI Studio/Tools/EnvFile.cs
Normal file
@ -0,0 +1,41 @@
|
||||
#if DEBUG
|
||||
using System.Text;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Read environment variables for the application from an .env file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We consider this feature a security issue. Therefore, it is only
|
||||
/// available in DEBUG mode. To ensure this, we remove the code
|
||||
/// from any release build.
|
||||
/// </remarks>
|
||||
public static class EnvFile
|
||||
{
|
||||
public static async Task Apply(string filePath)
|
||||
{
|
||||
if(!File.Exists(filePath))
|
||||
{
|
||||
Console.WriteLine($"Error: The .env file '{filePath}' does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = await File.ReadAllLinesAsync(filePath, Encoding.UTF8);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line) || line.Trim().StartsWith('#'))
|
||||
continue;
|
||||
|
||||
var parts = line.Split(['='], 2);
|
||||
if (parts.Length != 2)
|
||||
continue;
|
||||
|
||||
var key = parts[0].Trim();
|
||||
var value = parts[1].Trim();
|
||||
|
||||
Environment.SetEnvironmentVariable(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -34,54 +34,65 @@ pub fn dotnet_port(_token: APIToken) -> String {
|
||||
format!("{dotnet_server_port}")
|
||||
}
|
||||
|
||||
/// Creates the startup environment file for the .NET server in the development
|
||||
/// environment. The file is created in the root directory of the repository.
|
||||
/// Creating that env file on a production environment would be a security
|
||||
/// issue, since it contains the secret password and salt in plain text.
|
||||
/// Anyone could read that file and decrypt the secret communication
|
||||
/// between the .NET server and the Tauri app.
|
||||
///
|
||||
/// Therefore, we not only create the file in the development environment
|
||||
/// but also remove that code from any production build.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn create_startup_env_file() {
|
||||
|
||||
// Get the secret password & salt and convert it to a base64 string:
|
||||
let secret_password = BASE64_STANDARD.encode(ENCRYPTION.secret_password);
|
||||
let secret_key_salt = BASE64_STANDARD.encode(ENCRYPTION.secret_key_salt);
|
||||
let api_port = *API_SERVER_PORT;
|
||||
|
||||
warn!(Source = "Bootloader .NET"; "Development environment detected; create the startup env file at '../startup.env'.");
|
||||
let env_file_path = std::path::PathBuf::from("..").join("startup.env");
|
||||
let mut env_file = std::fs::File::create(env_file_path).unwrap();
|
||||
let env_file_content = format!(
|
||||
"AI_STUDIO_SECRET_PASSWORD={secret_password}\n\
|
||||
AI_STUDIO_SECRET_KEY_SALT={secret_key_salt}\n\
|
||||
AI_STUDIO_CERTIFICATE_FINGERPRINT={cert_fingerprint}\n\
|
||||
AI_STUDIO_API_PORT={api_port}\n\
|
||||
AI_STUDIO_API_TOKEN={api_token}",
|
||||
|
||||
cert_fingerprint = CERTIFICATE_FINGERPRINT.get().unwrap(),
|
||||
api_token = API_TOKEN.to_hex_text()
|
||||
);
|
||||
|
||||
std::io::Write::write_all(&mut env_file, env_file_content.as_bytes()).unwrap();
|
||||
info!(Source = "Bootloader .NET"; "The startup env file was created successfully.");
|
||||
}
|
||||
|
||||
/// Starts the .NET server in a separate process.
|
||||
pub fn start_dotnet_server() {
|
||||
|
||||
// Get the secret password & salt and convert it to a base64 string:
|
||||
let secret_password = BASE64_STANDARD.encode(ENCRYPTION.secret_password);
|
||||
let secret_key_salt = BASE64_STANDARD.encode(ENCRYPTION.secret_key_salt);
|
||||
let api_port = *API_SERVER_PORT;
|
||||
|
||||
let dotnet_server_environment = HashMap::from_iter([
|
||||
(String::from("AI_STUDIO_SECRET_PASSWORD"), secret_password),
|
||||
(String::from("AI_STUDIO_SECRET_KEY_SALT"), secret_key_salt),
|
||||
(String::from("AI_STUDIO_CERTIFICATE_FINGERPRINT"), CERTIFICATE_FINGERPRINT.get().unwrap().to_string()),
|
||||
(String::from("AI_STUDIO_API_PORT"), format!("{api_port}")),
|
||||
(String::from("AI_STUDIO_API_TOKEN"), API_TOKEN.to_hex_text().to_string()),
|
||||
]);
|
||||
|
||||
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;
|
||||
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")
|
||||
|
||||
// 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()])
|
||||
|
||||
.envs(dotnet_server_environment)
|
||||
.spawn()
|
||||
.expect("Failed to spawn .NET server process.")
|
||||
}
|
||||
|
||||
false => {
|
||||
Command::new_sidecar("mindworkAIStudioServer")
|
||||
.expect("Failed to create sidecar")
|
||||
|
||||
// Provide the runtime API server port to the .NET server:
|
||||
.args([format!("{api_port}").as_str()])
|
||||
|
||||
.envs(dotnet_server_environment)
|
||||
.spawn()
|
||||
.expect("Failed to spawn .NET server process.")
|
||||
}
|
||||
};
|
||||
let (mut rx, child) = Command::new_sidecar("mindworkAIStudioServer")
|
||||
.expect("Failed to create sidecar")
|
||||
.envs(dotnet_server_environment)
|
||||
.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}.");
|
||||
@ -140,7 +151,7 @@ pub async fn dotnet_ready(_token: APIToken) {
|
||||
// held.
|
||||
{
|
||||
let mut initialized = DOTNET_INITIALIZED.lock().unwrap();
|
||||
if *initialized {
|
||||
if !is_dev() && *initialized {
|
||||
error!("Anyone tried to initialize the runtime twice. This is not intended.");
|
||||
return;
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ use mindwork_ai_studio::log::init_logging;
|
||||
use mindwork_ai_studio::metadata::MetaData;
|
||||
use mindwork_ai_studio::runtime_api::start_runtime_api;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use mindwork_ai_studio::dotnet::create_startup_env_file;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let metadata = MetaData::init_from_string(include_str!("../../metadata.txt"));
|
||||
@ -44,6 +47,12 @@ async fn main() {
|
||||
|
||||
generate_certificate();
|
||||
start_runtime_api();
|
||||
start_dotnet_server();
|
||||
|
||||
if is_dev() {
|
||||
create_startup_env_file();
|
||||
} else {
|
||||
start_dotnet_server();
|
||||
}
|
||||
|
||||
start_tauri();
|
||||
}
|
Loading…
Reference in New Issue
Block a user