diff --git a/Agent/Agent.csproj b/Agent/Agent.csproj index 9bf238e52..57945a43f 100644 --- a/Agent/Agent.csproj +++ b/Agent/Agent.csproj @@ -23,16 +23,19 @@ - - - - - - - - - - + + + + + + + + + + + + + diff --git a/Agent/Program.cs b/Agent/Program.cs index 6c6297302..c57e10da2 100644 --- a/Agent/Program.cs +++ b/Agent/Program.cs @@ -14,131 +14,115 @@ using Remotely.Agent.Services.Linux; using Remotely.Agent.Services.MacOS; using Remotely.Agent.Services.Windows; +using Microsoft.Extensions.Hosting; +using System.Linq; -namespace Remotely.Agent -{ - public class Program - { +namespace Remotely.Agent; - public static IServiceProvider Services { get; set; } +public class Program +{ + [Obsolete("Remove this when all services are in DI behind interfaces.")] + public static IServiceProvider Services { get; set; } - public static async Task Main(string[] args) + public static async Task Main(string[] args) + { + try { - try - { - // TODO: Convert to generic host. - BuildServices(); + var host = Host + .CreateDefaultBuilder(args) + .UseWindowsService() + .UseSystemd() + .ConfigureServices(RegisterServices) + .Build(); - await Init(); + await host.StartAsync(); - await Task.Delay(-1); + await Init(host.Services); - } - catch (Exception ex) - { - Logger.Write(ex); - throw; - } + await host.WaitForShutdownAsync(); } - - private static void BuildServices() + catch (Exception ex) { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddHttpClient(); - serviceCollection.AddLogging(builder => - { - builder.AddConsole().AddDebug(); - var version = typeof(Program).Assembly.GetName().Version?.ToString() ?? "0.0.0"; - builder.AddProvider(new FileLoggerProvider("Remotely_Agent", version)); - }); - - // TODO: All these should be registered as interfaces. - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddHostedService(services => services.GetRequiredService()); - serviceCollection.AddScoped(); - serviceCollection.AddTransient(); - serviceCollection.AddTransient(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - - if (OperatingSystem.IsWindows()) - { - serviceCollection.AddScoped(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - } - else if (OperatingSystem.IsLinux()) - { - serviceCollection.AddScoped(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - } - else if (OperatingSystem.IsMacOS()) - { - serviceCollection.AddScoped(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - } - else - { - throw new NotSupportedException("Operating system not supported."); - } - - Services = serviceCollection.BuildServiceProvider(); + var version = typeof(Program).Assembly.GetName().Version?.ToString() ?? "0.0.0"; + var logger = new FileLogger("Remotely_Agent", version, "Main"); + logger.LogError(ex, "Error during agent startup."); + throw; } + } - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + private static void RegisterServices(IServiceCollection services) + { + services.AddHttpClient(); + services.AddLogging(builder => { - Logger.Write(e.ExceptionObject as Exception); + builder.AddConsole().AddDebug(); + var version = typeof(Program).Assembly.GetName().Version?.ToString() ?? "0.0.0"; + builder.AddProvider(new FileLoggerProvider("Remotely_Agent", version)); + }); + + // TODO: All these should be registered as interfaces. + services.AddSingleton(); + services.AddSingleton(); + services.AddHostedService(services => services.GetRequiredService()); + services.AddScoped(); + services.AddTransient(); + services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + if (OperatingSystem.IsWindows()) + { + services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); } - - private static async Task Init() + else if (OperatingSystem.IsLinux()) { - try - { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - SetWorkingDirectory(); - - - if (OperatingSystem.IsWindows() && - Process.GetCurrentProcess().SessionId == 0) - { - _ = Task.Run(StartService); - } - - await Services.GetRequiredService().BeginChecking(); - - await Services.GetRequiredService().Connect(); - } - catch (Exception ex) - { - Logger.Write(ex); - } + services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); } - - private static void SetWorkingDirectory() + else if (OperatingSystem.IsMacOS()) { - var assemblyPath = System.Reflection.Assembly.GetExecutingAssembly().Location; - var assemblyDir = Path.GetDirectoryName(assemblyPath); - Directory.SetCurrentDirectory(assemblyDir); + services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); } + else + { + throw new NotSupportedException("Operating system not supported."); + } + } - [SupportedOSPlatform("windows")] - private static void StartService() + private static async Task Init(IServiceProvider services) + { + AppDomain.CurrentDomain.UnhandledException += (sender, ex) => { - try + var logger = services.GetRequiredService>(); + if (ex.ExceptionObject is Exception exception) { - ServiceBase.Run(new WindowsService()); + logger.LogError(exception, "Unhandled exception in AppDomain."); } - catch (Exception ex) + else { - Logger.Write(ex, "Failed to start service.", EventType.Warning); + logger.LogError("Unhandled exception in AppDomain."); } - } + }; + + SetWorkingDirectory(); + + await services.GetRequiredService().BeginChecking(); + + await services.GetRequiredService().Connect(); + } + + private static void SetWorkingDirectory() + { + var exePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First(); + var exeDir = Path.GetDirectoryName(exePath); + Directory.SetCurrentDirectory(exeDir); } } diff --git a/Agent/Services/AgentHubConnection.cs b/Agent/Services/AgentHubConnection.cs index 4d25bdc62..ff0f1432c 100644 --- a/Agent/Services/AgentHubConnection.cs +++ b/Agent/Services/AgentHubConnection.cs @@ -95,7 +95,7 @@ public async Task Connect() _hubConnection = new HubConnectionBuilder() .WithUrl(_connectionInfo.Host + "/hubs/service") - .WithAutomaticReconnect(new RetryPolicy()) + .WithAutomaticReconnect(new RetryPolicy(_logger)) .AddMessagePackProtocol() .Build(); @@ -521,6 +521,13 @@ private async Task VerifyServer() private class RetryPolicy : IRetryPolicy { + private readonly ILogger _logger; + + public RetryPolicy(ILogger logger) + { + _logger = logger; + } + public TimeSpan? NextRetryDelay(RetryContext retryContext) { if (retryContext.PreviousRetryCount == 0) @@ -528,7 +535,8 @@ private class RetryPolicy : IRetryPolicy return TimeSpan.FromSeconds(3); } - var waitSeconds = Math.Min(30, retryContext.PreviousRetryCount * 5); + var waitSeconds = Random.Shared.Next(3, 10); + _logger.LogDebug("Attempting to reconnect in {seconds} seconds.", waitSeconds); return TimeSpan.FromSeconds(waitSeconds); } } diff --git a/Agent/Services/CpuUtilizationSampler.cs b/Agent/Services/CpuUtilizationSampler.cs index bace9bb7f..4f2b488c8 100644 --- a/Agent/Services/CpuUtilizationSampler.cs +++ b/Agent/Services/CpuUtilizationSampler.cs @@ -29,6 +29,9 @@ public CpuUtilizationSampler(ILogger logger) protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + // Allow host startup to continue immediately. + await Task.Yield(); + while (!stoppingToken.IsCancellationRequested) { try diff --git a/Agent/Services/Windows/DeviceInfoGeneratorWin.cs b/Agent/Services/Windows/DeviceInfoGeneratorWin.cs index 762b68aaf..ff2cb180e 100644 --- a/Agent/Services/Windows/DeviceInfoGeneratorWin.cs +++ b/Agent/Services/Windows/DeviceInfoGeneratorWin.cs @@ -29,7 +29,6 @@ public Task CreateDevice(string deviceId, string orgId) try { - var (usedStorage, totalStorage) = GetSystemDriveInfo(); var (usedMemory, totalMemory) = GetMemoryInGB(); diff --git a/Desktop.Linux/Program.cs b/Desktop.Linux/Program.cs index 84b67e77c..17e5139aa 100644 --- a/Desktop.Linux/Program.cs +++ b/Desktop.Linux/Program.cs @@ -12,10 +12,11 @@ using Immense.RemoteControl.Desktop.Startup; using Remotely.Shared.Utilities; using Immense.RemoteControl.Desktop.Shared.Startup; +using System.Linq; var version = typeof(Program).Assembly.GetName().Version?.ToString() ?? "0.0.0"; var logger = new FileLogger("Remotely_Desktop", version, "Program.cs"); -var filePath = Process.GetCurrentProcess()?.MainModule?.FileName; +var filePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First(); var serverUrl = Debugger.IsAttached ? "http://localhost:5000" : string.Empty; var getEmbeddedResult = await EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath); if (getEmbeddedResult.IsSuccess) diff --git a/Desktop.Win/Program.cs b/Desktop.Win/Program.cs index db92b98fc..1fd67c4b5 100644 --- a/Desktop.Win/Program.cs +++ b/Desktop.Win/Program.cs @@ -13,10 +13,11 @@ using Remotely.Shared.Utilities; using Immense.RemoteControl.Desktop.Windows.Startup; using Immense.RemoteControl.Desktop.Shared.Startup; +using System.Linq; var version = typeof(Program).Assembly.GetName().Version?.ToString() ?? "0.0.0"; var logger = new FileLogger("Remotely_Desktop", version, "Program.cs"); -var filePath = Process.GetCurrentProcess()?.MainModule?.FileName; +var filePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First(); var serverUrl = Debugger.IsAttached ? "https://localhost:5001" : string.Empty; var getEmbeddedResult = await EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath); if (getEmbeddedResult.IsSuccess) diff --git a/Remotely.sln.startup.json b/Remotely.sln.startup.json index 25b0894c6..6c33f5d7e 100644 --- a/Remotely.sln.startup.json +++ b/Remotely.sln.startup.json @@ -64,6 +64,16 @@ "ProfileName": "Desktop.Win" } } + }, + "Server+Agent": { + "Projects": { + "Server\\Server.csproj": { + "ProfileName": "Server" + }, + "Agent": { + "ProfileName": "Agent" + } + } } } } diff --git a/Server/API/RemoteControlController.cs b/Server/API/RemoteControlController.cs index ac17ad025..a93dcb04a 100644 --- a/Server/API/RemoteControlController.cs +++ b/Server/API/RemoteControlController.cs @@ -128,7 +128,7 @@ private async Task InitiateRemoteControl(string deviceID, string { UnattendedSessionId = sessionId, UserConnectionId = HttpContext.Connection.Id, - ServiceConnectionId = serviceConnectionId, + AgentConnectionId = serviceConnectionId, DeviceId = deviceID, OrganizationId = orgID }; @@ -137,7 +137,7 @@ private async Task InitiateRemoteControl(string deviceID, string { if (v is RemoteControlSessionEx ex) { - ex.ServiceConnectionId = HttpContext.Connection.Id; + ex.AgentConnectionId = HttpContext.Connection.Id; return ex; } v.Dispose(); diff --git a/Server/Hubs/CircuitConnection.cs b/Server/Hubs/CircuitConnection.cs index e79202bb4..4e0ff3dfe 100644 --- a/Server/Hubs/CircuitConnection.cs +++ b/Server/Hubs/CircuitConnection.cs @@ -256,7 +256,7 @@ public async Task> RemoteControl(string deviceId, { UnattendedSessionId = sessionId, UserConnectionId = ConnectionId, - ServiceConnectionId = serviceConnectionId, + AgentConnectionId = serviceConnectionId, DeviceId = deviceId, ViewOnly = viewOnly, OrganizationId = User.OrganizationID diff --git a/Server/Models/RemoteControlSessionEx.cs b/Server/Models/RemoteControlSessionEx.cs index 18188391e..689fd7193 100644 --- a/Server/Models/RemoteControlSessionEx.cs +++ b/Server/Models/RemoteControlSessionEx.cs @@ -5,10 +5,7 @@ namespace Remotely.Server.Models { public class RemoteControlSessionEx : RemoteControlSession { - public string ServiceConnectionId { get; set; } = string.Empty; - public string UserConnectionId { get; set; } = string.Empty; public string DeviceId { get; set; } = string.Empty; - public bool ViewOnly { get; set; } public string OrganizationId { get; set; } = string.Empty; } } diff --git a/Server/Services/RcImplementations/HubEventHandler.cs b/Server/Services/RcImplementations/HubEventHandler.cs index bc30ce985..c99141275 100644 --- a/Server/Services/RcImplementations/HubEventHandler.cs +++ b/Server/Services/RcImplementations/HubEventHandler.cs @@ -39,7 +39,7 @@ public Task ChangeWindowsSession(RemoteControlSession session, string viewerConn } return _serviceHub.Clients - .Client(ex.ServiceConnectionId) + .Client(ex.AgentConnectionId) .SendAsync("ChangeWindowsSession", viewerConnectionId, ex.UnattendedSessionId, @@ -59,7 +59,7 @@ public Task InvokeCtrlAltDel(RemoteControlSession session, string viewerConnecti return Task.CompletedTask; } - return _serviceHub.Clients.Client(ex.ServiceConnectionId).SendAsync("CtrlAltDel"); + return _serviceHub.Clients.Client(ex.AgentConnectionId).SendAsync("CtrlAltDel"); } public Task NotifySessionChanged(RemoteControlSession session, SessionSwitchReasonEx reason, int currentSessionId) @@ -79,7 +79,7 @@ public Task NotifySessionChanged(RemoteControlSession session, SessionSwitchReas case SessionSwitchReasonEx.SessionLock: case SessionSwitchReasonEx.SessionRemoteControl: return _serviceHub.Clients - .Client(ex.ServiceConnectionId) + .Client(ex.AgentConnectionId) .SendAsync("RestartScreenCaster", ex.ViewerList, ex.UnattendedSessionId, @@ -108,13 +108,13 @@ public Task RestartScreenCaster(RemoteControlSession session, HashSet vi } return _serviceHub.Clients - .Client(ex.ServiceConnectionId) + .Client(ex.AgentConnectionId) .SendAsync("RestartScreenCaster", viewerList, ex.UnattendedSessionId, ex.AccessKey, ex.UserConnectionId, - ex.RequesterUserName, + ex.RequesterName, ex.OrganizationName, ex.OrganizationId); } diff --git a/Shared/Services/FileLogger.cs b/Shared/Services/FileLogger.cs index c207221d0..01d5542d4 100644 --- a/Shared/Services/FileLogger.cs +++ b/Shared/Services/FileLogger.cs @@ -59,7 +59,8 @@ private static string LogsFolderPath } private string LogPath => Path.Combine(LogsFolderPath, _componentName, $"LogFile_{DateTime.Now:yyyy-MM-dd}.log"); - public IDisposable BeginScope(TState state) + public IDisposable? BeginScope(TState state) + where TState : notnull { _scopeStack.Push($"{state}"); return new NoopDisposable(); diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index 39d4f853e..aaa2b1585 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit 39d4f853eaa2a87590fb3a56e35db244f6eae08e +Subproject commit aaa2b1585d62ab87911c8396060e1a445687c74e