Add EnterpriseEnvironmentService and plugin management

This commit is contained in:
Thorsten Sommer 2025-06-01 20:48:49 +02:00
parent 944f4a836e
commit 72ea91d5aa
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 188 additions and 0 deletions

View File

@ -133,6 +133,7 @@ internal sealed class Program
builder.Services.AddTransient<AgentTextContentCleaner>(); builder.Services.AddTransient<AgentTextContentCleaner>();
builder.Services.AddHostedService<UpdateService>(); builder.Services.AddHostedService<UpdateService>();
builder.Services.AddHostedService<TemporaryChatService>(); builder.Services.AddHostedService<TemporaryChatService>();
builder.Services.AddHostedService<EnterpriseEnvironmentService>();
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()
.AddInteractiveServerComponents() .AddInteractiveServerComponents()
.AddHubOptions(options => .AddHubOptions(options =>

View File

@ -0,0 +1,56 @@
using System.IO.Compression;
namespace AIStudio.Tools.PluginSystem;
public static partial class PluginFactory
{
public static async Task<bool> TryDownloadingConfigPluginAsync(Guid configPlugId, string configServerUrl, CancellationToken cancellationToken = default)
{
if (!IS_INITIALIZED)
return false;
LOG.LogInformation($"Downloading configuration plugin with ID: {configPlugId} from server: {configServerUrl}");
var tempDownloadFile = Path.GetTempFileName();
try
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"{configServerUrl}/{configPlugId}.zip", cancellationToken);
if (response.IsSuccessStatusCode)
{
await using var tempFileStream = File.Create(tempDownloadFile);
await response.Content.CopyToAsync(tempFileStream, cancellationToken);
var pluginDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString());
if(Directory.Exists(pluginDirectory))
Directory.Delete(pluginDirectory, true);
Directory.CreateDirectory(pluginDirectory);
ZipFile.ExtractToDirectory(tempDownloadFile, pluginDirectory);
LOG.LogInformation($"Configuration plugin with ID='{configPlugId}' downloaded and extracted successfully to '{pluginDirectory}'.");
}
else
LOG.LogError($"Failed to download the enterprise configuration plugin. HTTP Status: {response.StatusCode}");
}
catch (Exception e)
{
LOG.LogError(e, "An error occurred while downloading or extracting the enterprise configuration plugin.");
}
finally
{
if (File.Exists(tempDownloadFile))
{
try
{
File.Delete(tempDownloadFile);
}
catch (Exception e)
{
LOG.LogError(e, "Failed to delete the temporary download file.");
}
}
}
return true;
}
}

View File

@ -0,0 +1,54 @@
namespace AIStudio.Tools.PluginSystem;
public static partial class PluginFactory
{
public static void RemovePluginAsync(Guid pluginId)
{
if (!IS_INITIALIZED)
return;
LOG.LogWarning($"Try to remove plugin with ID: {pluginId}");
//
// Remove the plugin from the available plugins list:
//
var availablePluginToRemove = AVAILABLE_PLUGINS.FirstOrDefault(p => p.Id == pluginId);
if (availablePluginToRemove == null)
{
LOG.LogWarning($"No plugin found with ID: {pluginId}");
return;
}
AVAILABLE_PLUGINS.Remove(availablePluginToRemove);
//
// Remove the plugin from the running plugins list:
//
var runningPluginToRemove = RUNNING_PLUGINS.FirstOrDefault(p => p.Id == pluginId);
if (runningPluginToRemove == null)
LOG.LogWarning($"No running plugin found with ID: {pluginId}");
else
RUNNING_PLUGINS.Remove(runningPluginToRemove);
//
// Delete the plugin directory:
//
var pluginDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, availablePluginToRemove.Id.ToString());
if (Directory.Exists(pluginDirectory))
{
try
{
Directory.Delete(pluginDirectory, true);
LOG.LogInformation($"Plugin directory '{pluginDirectory}' deleted successfully.");
}
catch (Exception ex)
{
LOG.LogError(ex, $"Failed to delete plugin directory '{pluginDirectory}'.");
}
}
else
LOG.LogWarning($"Plugin directory '{pluginDirectory}' does not exist.");
LOG.LogInformation($"Plugin with ID: {pluginId} removed successfully.");
}
}

View File

@ -12,6 +12,7 @@ public static partial class PluginFactory
private static string DATA_DIR = string.Empty; private static string DATA_DIR = string.Empty;
private static string PLUGINS_ROOT = string.Empty; private static string PLUGINS_ROOT = string.Empty;
private static string INTERNAL_PLUGINS_ROOT = string.Empty; private static string INTERNAL_PLUGINS_ROOT = string.Empty;
private static string CONFIGURATION_PLUGINS_ROOT = string.Empty;
private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; private static FileSystemWatcher HOT_RELOAD_WATCHER = null!;
private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE; private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE;
@ -29,6 +30,7 @@ public static partial class PluginFactory
DATA_DIR = SettingsManager.DataDirectory!; DATA_DIR = SettingsManager.DataDirectory!;
PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins");
INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal");
CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config");
if (!Directory.Exists(PLUGINS_ROOT)) if (!Directory.Exists(PLUGINS_ROOT))
Directory.CreateDirectory(PLUGINS_ROOT); Directory.CreateDirectory(PLUGINS_ROOT);

View File

@ -0,0 +1,75 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.Services;
public sealed class EnterpriseEnvironmentService(ILogger<TemporaryChatService> logger, RustService rustService) : BackgroundService
{
public static EnterpriseEnvironment CURRENT_ENVIRONMENT;
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromMinutes(16);
#region Overrides of BackgroundService
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("The enterprise environment service was initialized.");
await this.StartUpdating();
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(CHECK_INTERVAL, stoppingToken);
await this.StartUpdating();
}
}
#endregion
private async Task StartUpdating()
{
try
{
logger.LogInformation("Starting update of the enterprise environment.");
var enterpriseRemoveConfigId = await rustService.EnterpriseEnvRemoveConfigId();
var isPlugin2RemoveInUse = PluginFactory.AvailablePlugins.Any(plugin => plugin.Id == enterpriseRemoveConfigId);
if (enterpriseRemoveConfigId != Guid.Empty && isPlugin2RemoveInUse)
{
logger.LogWarning($"The enterprise environment configuration ID '{enterpriseRemoveConfigId}' must be removed.");
PluginFactory.RemovePluginAsync(enterpriseRemoveConfigId);
}
var enterpriseConfigServerUrl = await rustService.EnterpriseEnvConfigServerUrl();
var enterpriseConfigId = await rustService.EnterpriseEnvConfigId();
var nextEnterpriseEnvironment = new EnterpriseEnvironment(enterpriseConfigServerUrl, enterpriseConfigId);
if (CURRENT_ENVIRONMENT != nextEnterpriseEnvironment)
{
logger.LogInformation("The enterprise environment has changed. Updating the current environment.");
CURRENT_ENVIRONMENT = nextEnterpriseEnvironment;
switch (enterpriseConfigServerUrl)
{
case null when enterpriseConfigId == Guid.Empty:
logger.LogInformation("AI Studio runs without an enterprise configuration.");
break;
case null:
logger.LogWarning($"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}'), but the configuration server URL is not set.");
break;
case not null when enterpriseConfigId == Guid.Empty:
logger.LogWarning($"AI Studio runs with an enterprise configuration server URL ('{enterpriseConfigServerUrl}'), but the configuration ID is not set.");
break;
default:
logger.LogInformation($"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}') and configuration server URL ('{enterpriseConfigServerUrl}').");
await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseConfigId, enterpriseConfigServerUrl);
break;
}
}
}
catch (Exception e)
{
logger.LogError(e, "An error occurred while updating the enterprise environment.");
}
}
}