using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using ResourceMonitorService.Configuration; using ResourceMonitorService.Models; using ResourceMonitorService.Services; using System.Diagnostics; namespace ResourceMonitorService { public class Worker : BackgroundService { private readonly ILogger _logger; private readonly IHostApplicationLifetime _lifetime; private readonly ISystemInfoService _systemInfoService; private readonly IResourceMonitorService _resourceMonitorService; private readonly IGameDetectionService _gameDetectionService; private readonly IAlertService _alertService; private readonly ApiSettings _apiSettings; private readonly MonitoringSettings _monitoringSettings; public Worker( ILogger logger, IHostApplicationLifetime lifetime, ISystemInfoService systemInfoService, IResourceMonitorService resourceMonitorService, IGameDetectionService gameDetectionService, IAlertService alertService, IOptions apiSettings, IOptions monitoringSettings) { _logger = logger; _lifetime = lifetime; _systemInfoService = systemInfoService; _resourceMonitorService = resourceMonitorService; _gameDetectionService = gameDetectionService; _alertService = alertService; _apiSettings = apiSettings.Value; _monitoringSettings = monitoringSettings.Value; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Resource Monitor Service starting..."); var builder = WebApplication.CreateBuilder(); // Configure CORS builder.Services.AddCors(options => { options.AddPolicy("AllowedOrigins", policy => policy.WithOrigins(_apiSettings.AllowedOrigins.ToArray()) .AllowAnyHeader() .AllowAnyMethod()); }); builder.Services.AddControllers().AddNewtonsoftJson(); var app = builder.Build(); // API Key middleware (if enabled) if (_apiSettings.RequireApiKey) { app.Use(async (context, next) => { if (!context.Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey) || extractedApiKey != _apiSettings.ApiKey) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync("Unauthorized: Invalid API Key"); return; } await next(); }); } app.UseCors("AllowedOrigins"); // Enhanced API endpoints ConfigureApiEndpoints(app); // Start background monitoring _ = Task.Run(async () => await BackgroundMonitoringLoop(stoppingToken), stoppingToken); // Start the web application _ = app.RunAsync(stoppingToken); await Task.Delay(Timeout.Infinite, stoppingToken); } private void ConfigureApiEndpoints(WebApplication app) { var basePath = _apiSettings.BasePath; // System information endpoints app.MapGet($"{basePath}/system-info", async () => Results.Ok(await _systemInfoService.GetSystemInfoAsync())); // Resource usage endpoints app.MapGet($"{basePath}/resource-usage", async () => Results.Ok(await _resourceMonitorService.GetResourceUsageAsync())); app.MapGet($"{basePath}/cpu-usage", async () => Results.Ok(await _resourceMonitorService.GetCpuUsageAsync())); app.MapGet($"{basePath}/memory-usage", async () => Results.Ok(await _resourceMonitorService.GetMemoryUsageAsync())); app.MapGet($"{basePath}/gpu-usage", async () => Results.Ok(await _resourceMonitorService.GetGpuUsageAsync())); app.MapGet($"{basePath}/disk-usage", async () => Results.Ok(await _resourceMonitorService.GetDiskUsageAsync())); app.MapGet($"{basePath}/network-usage", async () => Results.Ok(await _resourceMonitorService.GetNetworkUsageAsync())); app.MapGet($"{basePath}/top-processes", async (int count = 10) => Results.Ok(await _resourceMonitorService.GetTopProcessesAsync(count))); // Game detection endpoints app.MapGet($"{basePath}/current-game", async () => Results.Ok(await _gameDetectionService.GetCurrentlyRunningGameAsync())); app.MapGet($"{basePath}/all-games", async () => Results.Ok(await _gameDetectionService.GetAllDetectedGamesAsync())); app.MapGet($"{basePath}/fullscreen-status", async () => Results.Ok(new { IsFullscreen = await _gameDetectionService.IsGameRunningFullscreenAsync() })); // Alert endpoints app.MapGet($"{basePath}/alerts/active", async () => Results.Ok(await _alertService.GetActiveAlertsAsync())); app.MapGet($"{basePath}/alerts/history", async (int count = 100) => Results.Ok(await _alertService.GetAlertHistoryAsync(count))); app.MapPost($"{basePath}/alerts/{{alertId}}/resolve", async (string alertId) => { await _alertService.ResolveAlertAsync(alertId); return Results.Ok(new { Message = "Alert resolved successfully" }); }); app.MapGet($"{basePath}/alerts/enabled", async () => Results.Ok(new { Enabled = await _alertService.IsAlertingEnabledAsync() })); // Process management endpoints (enhanced) app.MapPost($"{basePath}/process/kill", async (HttpContext context) => { try { var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); var request = JsonConvert.DeserializeObject(body); int processId = request?.ProcessId ?? 0; bool force = request?.Force ?? false; if (processId <= 0) { return Results.BadRequest("Invalid process ID"); } var processes = Process.GetProcesses().Where(p => p.Id == processId).ToArray(); if (processes.Length == 0) { return Results.NotFound($"No process found with ID {processId}"); } var process = processes[0]; var processName = process.ProcessName; if (force) { process.Kill(true); // Force kill entire process tree } else { process.CloseMainWindow(); // Try graceful close first // Wait a bit for graceful close await Task.Delay(3000); if (!process.HasExited) { process.Kill(); } } _logger.LogWarning("Process {ProcessName} (ID: {ProcessId}) was terminated", processName, processId); return Results.Ok(new { Message = $"Process {processName} (ID: {processId}) has been terminated." }); } catch (Exception ex) { _logger.LogError(ex, "Error terminating process"); return Results.Problem(ex.Message); } }); // Enhanced shutdown/restart endpoints app.MapPost($"{basePath}/system/shutdown", async (HttpContext context) => { try { var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); var request = JsonConvert.DeserializeObject(body); string action = request?.Action?.ToString()?.ToLower() ?? "shutdown"; int delaySeconds = request?.DelaySeconds ?? 0; string message = request?.Message?.ToString() ?? "System shutdown initiated by Resource Monitor"; if (action != "shutdown" && action != "restart" && action != "cancel") { return Results.BadRequest("Invalid action. Use 'shutdown', 'restart', or 'cancel'."); } if (action == "cancel") { var cancelProcess = new ProcessStartInfo { FileName = "shutdown", Arguments = "/a", CreateNoWindow = true, UseShellExecute = false }; Process.Start(cancelProcess); _logger.LogWarning("System shutdown cancelled"); return Results.Ok(new { Message = "Shutdown cancelled." }); } if (delaySeconds < 0) { return Results.BadRequest("Delay must be a non-negative integer."); } string shutdownCommand = action == "shutdown" ? $"/s /f /t {delaySeconds} /c \"{message}\"" : $"/r /f /t {delaySeconds} /c \"{message}\""; var processStartInfo = new ProcessStartInfo { FileName = "shutdown", Arguments = shutdownCommand, CreateNoWindow = true, UseShellExecute = false }; Process.Start(processStartInfo); _logger.LogWarning("System {Action} initiated with {Delay} seconds delay", action, delaySeconds); return Results.Ok(new { Message = $"{action.ToUpper()} command executed with a delay of {delaySeconds} seconds.", Action = action, DelaySeconds = delaySeconds, Timestamp = DateTime.Now }); } catch (Exception ex) { _logger.LogError(ex, "Error executing system command"); return Results.Problem(ex.Message); } }); // VM-specific endpoints for Unraid app.MapGet($"{basePath}/vm/info", async () => { var systemInfo = await _systemInfoService.GetSystemInfoAsync(); return Results.Ok(new { IsVirtualMachine = systemInfo.IsVirtualMachine, HypervisorVendor = systemInfo.HypervisorVendor, Uptime = systemInfo.Uptime, BootTime = systemInfo.BootTime, MachineName = systemInfo.MachineName, Domain = systemInfo.Domain }); }); // Performance history endpoint (simple in-memory storage) app.MapGet($"{basePath}/performance/history", async (int minutes = 60) => { // This would ideally be stored in a database or time-series database // For now, return current snapshot with timestamp var usage = await _resourceMonitorService.GetResourceUsageAsync(); return Results.Ok(new { Current = usage, Message = "Historical data not implemented yet - showing current values" }); }); // Health check endpoint app.MapGet($"{basePath}/health", async () => { var systemInfo = await _systemInfoService.GetSystemInfoAsync(); var alerts = await _alertService.GetActiveAlertsAsync(); return Results.Ok(new { Status = "Healthy", Timestamp = DateTime.Now, Uptime = systemInfo.Uptime, ActiveAlerts = alerts.Count, MonitoringEnabled = new { GPU = _monitoringSettings.EnableGpuMonitoring, Disk = _monitoringSettings.EnableDiskMonitoring, Network = _monitoringSettings.EnableNetworkMonitoring, Temperature = _monitoringSettings.EnableTemperatureMonitoring, Processes = _monitoringSettings.EnableProcessMonitoring, Games = _monitoringSettings.EnableGameDetection, Alerts = _monitoringSettings.EnableAlerts } }); }); // Service control endpoints app.MapPost($"{basePath}/service/stop", () => { _logger.LogWarning("Service stop requested via API"); _lifetime.StopApplication(); return Results.Ok(new { Message = "Stopping the service..." }); }); // Root endpoint app.MapGet("/", () => Results.Ok(new { Service = "Resource Monitor Service", Version = "2.0.0", Status = "Running", Timestamp = DateTime.Now, ApiBasePath = _apiSettings.BasePath, Documentation = $"{_apiSettings.BasePath}/health" })); _logger.LogInformation("API endpoints configured. Base path: {BasePath}", _apiSettings.BasePath); } private async Task BackgroundMonitoringLoop(CancellationToken cancellationToken) { _logger.LogInformation("Background monitoring started"); while (!cancellationToken.IsCancellationRequested) { try { // Get current resource usage var resourceUsage = await _resourceMonitorService.GetResourceUsageAsync(); // Add current game info if game detection is enabled if (_monitoringSettings.EnableGameDetection) { resourceUsage.RunningGame = await _gameDetectionService.GetCurrentlyRunningGameAsync(); } // Check for alerts if (_monitoringSettings.EnableAlerts) { await _alertService.CheckAndGenerateAlertsAsync(resourceUsage); } // Log performance metrics occasionally if (DateTime.Now.Second % 30 == 0) // Every 30 seconds { _logger.LogDebug("Performance: CPU: {CpuUsage:F1}%, Memory: {MemoryUsage:F1}%, GPU: {GpuUsage}%", resourceUsage.CPU.Usage, resourceUsage.Memory.UsagePercentage, resourceUsage.GPU.Usage); } } catch (Exception ex) { _logger.LogError(ex, "Error in background monitoring loop"); } await Task.Delay(_monitoringSettings.UpdateIntervalMs, cancellationToken); } _logger.LogInformation("Background monitoring stopped"); } } }