From d7fdef39d65dbf256e842efd70fbb1f517e61980 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 5 Feb 2026 23:31:02 +0100 Subject: [PATCH] agent session id persistence --- .../Monitoring/MonitoringService.cs | 38 ++++++- .../Storage/SessionPersistence.cs | 100 ++++++++++++++++++ .../AutopilotMonitorService.cs | 10 +- src/Agent/AutopilotMonitor.Agent/Program.cs | 13 ++- 4 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 src/Agent/AutopilotMonitor.Agent.Core/Storage/SessionPersistence.cs diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/MonitoringService.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/MonitoringService.cs index 69553477..45fa6108 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/MonitoringService.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/MonitoringService.cs @@ -403,12 +403,20 @@ namespace AutopilotMonitor.Agent.Core.Monitoring } // Check for enrollment completion events - if (_configuration.SelfDestructOnComplete && - (evt.EventType == "enrollment_complete" || evt.EventType == "enrollment_failed")) + if (evt.EventType == "enrollment_complete" || evt.EventType == "enrollment_failed") { _logger.Info($"Enrollment completion detected: {evt.EventType}"); - Task.Run(() => HandleEnrollmentComplete()); - return; // Don't continue with normal event processing + + // ALWAYS delete session ID when enrollment is complete/failed + // This ensures a new session will be created on next enrollment + DeleteSessionId(); + + // Only trigger self-destruct if configured + if (_configuration.SelfDestructOnComplete) + { + Task.Run(() => HandleEnrollmentComplete()); + return; // Don't continue with normal event processing + } } // Immediate upload for: @@ -499,6 +507,26 @@ namespace AutopilotMonitor.Agent.Core.Monitoring } } + /// + /// Deletes the persisted session ID file + /// Should be called when enrollment is complete/failed + /// + private void DeleteSessionId() + { + _logger.Info("Deleting persisted session ID..."); + try + { + var dataDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor"); + var sessionPersistence = new SessionPersistence(dataDirectory); + sessionPersistence.DeleteSession(); + _logger.Info("Session ID deleted successfully"); + } + catch (Exception ex) + { + _logger.Warning($"Failed to delete session ID: {ex.Message}"); + } + } + /// /// Handles enrollment completion and triggers self-destruct sequence /// @@ -520,7 +548,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring // Give a moment for final upload to complete await Task.Delay(2000); - // Step 3: Execute self-destruct + // Step 3: Execute self-destruct or cleanup if (_configuration.SelfDestructOnComplete) { ExecuteSelfDestruct(); diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Storage/SessionPersistence.cs b/src/Agent/AutopilotMonitor.Agent.Core/Storage/SessionPersistence.cs new file mode 100644 index 00000000..4f4361bc --- /dev/null +++ b/src/Agent/AutopilotMonitor.Agent.Core/Storage/SessionPersistence.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; + +namespace AutopilotMonitor.Agent.Core.Storage +{ + /// + /// Manages persistent session ID storage to maintain tracking across agent restarts + /// + public class SessionPersistence + { + private readonly string _sessionFilePath; + private readonly object _lockObject = new object(); + + public SessionPersistence(string dataDirectory) + { + if (string.IsNullOrWhiteSpace(dataDirectory)) + { + throw new ArgumentNullException(nameof(dataDirectory)); + } + + if (!Directory.Exists(dataDirectory)) + { + Directory.CreateDirectory(dataDirectory); + } + + _sessionFilePath = Path.Combine(dataDirectory, "session.id"); + } + + /// + /// Loads existing session ID from disk or creates a new one if none exists + /// + /// Session ID (GUID string) + public string LoadOrCreateSessionId() + { + lock (_lockObject) + { + try + { + // Check if session file exists and is valid + if (File.Exists(_sessionFilePath)) + { + var sessionId = File.ReadAllText(_sessionFilePath).Trim(); + + // Validate it's a valid GUID format + if (Guid.TryParse(sessionId, out _)) + { + return sessionId; + } + } + + // Create new session ID if file doesn't exist or is invalid + var newSessionId = Guid.NewGuid().ToString(); + File.WriteAllText(_sessionFilePath, newSessionId); + return newSessionId; + } + catch (Exception ex) + { + // If we can't read/write the session file, generate a new ID in memory + // Log this error if logger is available + throw new InvalidOperationException($"Failed to load or create session ID at {_sessionFilePath}", ex); + } + } + } + + /// + /// Deletes the persisted session ID file + /// This should be called when enrollment is complete + /// + public void DeleteSession() + { + lock (_lockObject) + { + try + { + if (File.Exists(_sessionFilePath)) + { + File.Delete(_sessionFilePath); + } + } + catch (Exception) + { + // Suppress deletion errors - not critical if cleanup fails + // The file will be overwritten on next start anyway + } + } + } + + /// + /// Checks if a persisted session ID exists + /// + /// True if session file exists + public bool SessionExists() + { + lock (_lockObject) + { + return File.Exists(_sessionFilePath); + } + } + } +} diff --git a/src/Agent/AutopilotMonitor.Agent/AutopilotMonitorService.cs b/src/Agent/AutopilotMonitor.Agent/AutopilotMonitorService.cs index 7a9aca97..0fbc1f0e 100644 --- a/src/Agent/AutopilotMonitor.Agent/AutopilotMonitorService.cs +++ b/src/Agent/AutopilotMonitor.Agent/AutopilotMonitorService.cs @@ -3,6 +3,7 @@ using System.ServiceProcess; using AutopilotMonitor.Agent.Core.Configuration; using AutopilotMonitor.Agent.Core.Logging; using AutopilotMonitor.Agent.Core.Monitoring; +using AutopilotMonitor.Agent.Core.Storage; namespace AutopilotMonitor.Agent { @@ -61,12 +62,15 @@ namespace AutopilotMonitor.Agent private AgentConfiguration LoadConfiguration() { - // TODO: Load from registry or config file - // For now, return a default configuration + // Load or create persisted session ID + var dataDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor"); + var sessionPersistence = new SessionPersistence(dataDirectory); + var sessionId = sessionPersistence.LoadOrCreateSessionId(); + return new AgentConfiguration { ApiBaseUrl = Environment.GetEnvironmentVariable("AUTOPILOT_MONITOR_API") ?? "http://localhost:7071", - SessionId = Guid.NewGuid().ToString(), + SessionId = sessionId, TenantId = "default-tenant", SpoolDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Spool"), LogDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Logs"), diff --git a/src/Agent/AutopilotMonitor.Agent/Program.cs b/src/Agent/AutopilotMonitor.Agent/Program.cs index 5a3a82f8..3a1536f4 100644 --- a/src/Agent/AutopilotMonitor.Agent/Program.cs +++ b/src/Agent/AutopilotMonitor.Agent/Program.cs @@ -6,6 +6,7 @@ using System.ServiceProcess; using AutopilotMonitor.Agent.Core.Configuration; using AutopilotMonitor.Agent.Core.Logging; using AutopilotMonitor.Agent.Core.Monitoring; +using AutopilotMonitor.Agent.Core.Storage; using Microsoft.Win32; using Newtonsoft.Json; @@ -264,10 +265,20 @@ namespace AutopilotMonitor.Agent } } + // Load or create persisted session ID + var dataDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor"); + var sessionPersistence = new SessionPersistence(dataDirectory); + var sessionId = sessionPersistence.LoadOrCreateSessionId(); + + var sessionStatus = sessionPersistence.SessionExists() && File.GetCreationTime(Path.Combine(dataDirectory, "session.id")) < DateTime.Now.AddMinutes(-1) + ? "Restored from previous session" + : "New session created"; + Console.WriteLine($"Session ID: {sessionId} ({sessionStatus})"); + return new AgentConfiguration { ApiBaseUrl = apiBaseUrl, - SessionId = Guid.NewGuid().ToString(), + SessionId = sessionId, TenantId = tenantId, SpoolDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Spool"), LogDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Logs"),