using LumaTunnel.Server.Core.Runtime; using LumaTunnel.Server.Core.Services; using XFEExtension.NetCore.ServerInteractive.Utilities.Extensions; using XFEExtension.NetCore.ServerInteractive.Utilities.Server; namespace LumaTunnel.Server; public sealed partial class ServerWorker(ILogger logger) : BackgroundService { private XFEServerCore? _serverCore; private ServerRuntime? _runtime; private FileSystemWatcher? _deviceWatcher; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _runtime = await ServerRuntime.InitializeAsync(cancellationToken: stoppingToken).ConfigureAwait(false); _deviceWatcher = new FileSystemWatcher(_runtime.Paths.DataDirectory, Path.GetFileName(_runtime.Paths.DevicesFile)) { NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.CreationTime, EnableRaisingEvents = true }; _deviceWatcher.Changed += OnDeviceFileChanged; _deviceWatcher.Created += OnDeviceFileChanged; _deviceWatcher.Renamed += OnDeviceFileChanged; _serverCore = XFEServerCoreBuilder.CreateBuilder() .AddVerifyService() .AddOriginalService() .AddService() .AddService() .AddService() .Build(options => { options.AcceptGet = true; options.AcceptPost = true; options.AcceptNonStandardJson = false; options.MainEntryPoint = "api"; options.ServerCoreName = "LumaTunnelServer"; foreach (var url in _runtime.Settings.BindingUrls) options.BindIP(url); }); var server = XFEServerBuilder.CreateBuilder() .UseXFEServer() .AddServerCore(_serverCore) .Build(); using var registration = stoppingToken.Register(() => { try { _serverCore.CyberCommServer.StopCyberCommServer(); } catch (Exception exception) { LogStopFailure(logger, exception); } }); LogStarting(logger, _runtime.Settings.NodeName, _runtime.Settings.NodeId, string.Join(", ", _runtime.Settings.BindingUrls)); var flushTask = FlushLoopAsync(stoppingToken); try { await server.Start().ConfigureAwait(false); } catch (Exception) when (stoppingToken.IsCancellationRequested) { LogStopped(logger); } await flushTask.ConfigureAwait(false); } public override async Task StopAsync(CancellationToken cancellationToken) { if (_runtime?.TunnelConnections is { } tunnels) { var shutdown = tunnels.DisposeAsync().AsTask(); await Task.WhenAny(shutdown, Task.Delay(TimeSpan.FromSeconds(5), cancellationToken)).ConfigureAwait(false); } if (_runtime is not null) await _runtime.FlushAsync(cancellationToken).ConfigureAwait(false); _serverCore?.CyberCommServer.StopCyberCommServer(); if (_deviceWatcher is not null) { _deviceWatcher.EnableRaisingEvents = false; _deviceWatcher.Dispose(); _deviceWatcher = null; } await base.StopAsync(cancellationToken).ConfigureAwait(false); } private async void OnDeviceFileChanged(object sender, FileSystemEventArgs e) { try { await Task.Delay(50).ConfigureAwait(false); if (_runtime is not null) await _runtime.Devices.ReloadAsync().ConfigureAwait(false); } catch (IOException) { } } private async Task FlushLoopAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken).ConfigureAwait(false); if (_runtime is not null) await _runtime.FlushAsync(cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { } } [LoggerMessage(1, LogLevel.Warning, "Failed to stop the HTTP.sys listener cleanly.")] private static partial void LogStopFailure(ILogger logger, Exception exception); [LoggerMessage(2, LogLevel.Information, "LumaTunnel node {NodeName} ({NodeId}) is starting on {Bindings}.")] private static partial void LogStarting(ILogger logger, string nodeName, string nodeId, string bindings); [LoggerMessage(3, LogLevel.Information, "LumaTunnel listener stopped during service shutdown.")] private static partial void LogStopped(ILogger logger); }