diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx
index 7a0c35c1c..abe0011a4 100644
--- a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx
+++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx
@@ -21,7 +21,7 @@ function PageHeaderActionsMenu(props: PageHeaderActionsMenuProps) {
const dispatch = useDispatch();
- const { authentication, isDocker } = useSelector(
+ const { authentication, isContainerized } = useSelector(
(state: AppState) => state.system.status.item
);
@@ -48,24 +48,22 @@ function PageHeaderActionsMenu(props: PageHeaderActionsMenuProps) {
{translate('KeyboardShortcuts')}
- {isDocker ? null : (
- <>
-
+
-
+
-
- >
+ {isContainerized ? null : (
+
)}
{formsAuth ? (
diff --git a/frontend/src/System/Plugins/PluginsConnector.js b/frontend/src/System/Plugins/PluginsConnector.js
index 3882cd10d..3825cd259 100644
--- a/frontend/src/System/Plugins/PluginsConnector.js
+++ b/frontend/src/System/Plugins/PluginsConnector.js
@@ -123,12 +123,10 @@ class PluginsConnector extends Component {
console.log('Plugin match result:', pluginMatch);
}
pluginDetailsUrl = url;
- } else {
- if (command && command.message) {
- const pluginMatch = command.message.match(/Plugin \[([^/]+)\/([^\]]+)\] v([0-9.]+) uninstalled/);
- if (pluginMatch) {
- pluginVersion = pluginMatch[3];
- }
+ } else if (command && command.message) {
+ const pluginMatch = command.message.match(/Plugin \[([^/]+)\/([^\]]+)\] v([0-9.]+) uninstalled/);
+ if (pluginMatch) {
+ pluginVersion = pluginMatch[3];
}
}
}
diff --git a/frontend/src/System/Status/About/About.js b/frontend/src/System/Status/About/About.js
index 8af5b4717..2a89ae3b3 100644
--- a/frontend/src/System/Status/About/About.js
+++ b/frontend/src/System/Status/About/About.js
@@ -20,7 +20,7 @@ class About extends Component {
packageVersion,
packageAuthor,
isNetCore,
- isDocker,
+ isContainerized,
runtimeVersion,
databaseVersion,
databaseType,
@@ -58,7 +58,7 @@ class About extends Component {
}
{
- isDocker &&
+ isContainerized &&
versionAdapters, Logger logger)
FullName = Name;
}
- if (IsLinux &&
- (File.Exists("/.dockerenv") ||
- (File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))))
+ if (IsLinux)
{
- IsDocker = true;
+ IsDocker = File.Exists("/.dockerenv") ||
+ (File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"));
+ IsPodman = File.Exists("/run/.containerenv") ||
+ Environment.GetEnvironmentVariable("container") != null;
+
+ IsContainerized = IsDocker || IsPodman || Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") is "true" or "1";
}
}
}
@@ -94,6 +99,8 @@ public interface IOsInfo
string Name { get; }
string FullName { get; }
bool IsDocker { get; }
+ bool IsPodman { get; }
+ bool IsContainerized { get; }
}
public enum Os
diff --git a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
index 703cd123f..78c056bd9 100644
--- a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
+++ b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs
@@ -12,11 +12,13 @@ namespace NzbDrone.Common.EnvironmentInfo
public class RuntimeInfo : IRuntimeInfo
{
private readonly Logger _logger;
+ private readonly IOsInfo _osInfo;
private readonly DateTime _startTime = DateTime.UtcNow;
- public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null)
+ public RuntimeInfo(Logger logger, IOsInfo osInfo, IHostLifetime hostLifetime = null)
{
_logger = logger;
+ _osInfo = osInfo;
IsWindowsService = hostLifetime is WindowsServiceLifetime;
IsStarting = true;
@@ -83,6 +85,30 @@ public bool IsAdmin
public bool IsWindowsService { get; private set; }
+ public bool IsContainerized => _osInfo.IsContainerized;
+
+ public bool IsSystemdService
+ {
+ get
+ {
+ if (!OsInfo.IsLinux)
+ {
+ return false;
+ }
+
+ try
+ {
+ var invocationId = Environment.GetEnvironmentVariable("INVOCATION_ID");
+ return !string.IsNullOrEmpty(invocationId);
+ }
+ catch (Exception ex)
+ {
+ _logger.Warn(ex, "Error checking if system is running under systemd");
+ return false;
+ }
+ }
+ }
+
public bool IsStarting { get; set; }
public bool IsExiting { get; set; }
diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
index 36c3b1ec0..ae78881a6 100644
--- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
+++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
@@ -223,6 +223,13 @@ public static void ConfigureConsoleLayout(ColoredConsoleTarget target, ConsoleLo
_ => NzbDroneLogger.CleansingConsoleLayout
};
}
+
+ public static void ResetAllTargets(IStartupContext startupContext, bool updateApp, bool inConsole)
+ {
+ LogManager.Configuration = new LoggingConfiguration();
+ _isConfigured = false;
+ Register(startupContext, updateApp, inConsole);
+ }
}
public enum ConsoleLogFormat
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
index 3f8d4c227..1042ef9a9 100644
--- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
@@ -173,7 +173,7 @@ public void UpdateScope(IOsInfo osInfo)
{
SentrySdk.ConfigureScope(scope =>
{
- scope.SetTag("is_docker", $"{osInfo.IsDocker}");
+ scope.SetTag("is_containerized", $"{osInfo.IsContainerized}");
});
}
diff --git a/src/NzbDrone.Common/Messaging/LifecycleEventAttribute.cs b/src/NzbDrone.Common/Messaging/LifecycleEventAttribute.cs
new file mode 100644
index 000000000..b2101ff3e
--- /dev/null
+++ b/src/NzbDrone.Common/Messaging/LifecycleEventAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace NzbDrone.Common.Messaging
+{
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class LifecycleEventAttribute : Attribute
+ {
+ }
+}
diff --git a/src/NzbDrone.Common/Reflection/ReflectionExtensions.cs b/src/NzbDrone.Common/Reflection/ReflectionExtensions.cs
index 235ab5b1d..af73c75a5 100644
--- a/src/NzbDrone.Common/Reflection/ReflectionExtensions.cs
+++ b/src/NzbDrone.Common/Reflection/ReflectionExtensions.cs
@@ -7,6 +7,13 @@ namespace NzbDrone.Common.Reflection
{
public static class ReflectionExtensions
{
+ private static HashSet _currentAssemblies = new HashSet();
+
+ public static void SetCurrentAssemblies(IEnumerable assemblies)
+ {
+ _currentAssemblies = new HashSet(assemblies);
+ }
+
public static List GetSimpleProperties(this Type type)
{
var properties = type.GetProperties();
@@ -93,7 +100,18 @@ private static bool ShouldUseAssembly(Assembly assembly)
}
var name = assembly.GetName();
- return name.Name == "Lidarr.Core" || name.Name.Contains("Lidarr.Plugin");
+
+ if (name.Name == "Lidarr.Core")
+ {
+ return true;
+ }
+
+ if (name.Name.Contains("Lidarr.Plugin"))
+ {
+ return _currentAssemblies.Count == 0 || _currentAssemblies.Contains(assembly);
+ }
+
+ return false;
}
public static bool HasAttribute(this Type type)
diff --git a/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs b/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs
index 7982c1d36..35fc8ecca 100644
--- a/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs
+++ b/src/NzbDrone.Core/Lifecycle/ApplicationShutdownRequested.cs
@@ -2,6 +2,7 @@
namespace NzbDrone.Core.Lifecycle
{
+ [LifecycleEvent]
public class ApplicationShutdownRequested : IEvent
{
public bool Restarting { get; }
diff --git a/src/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs b/src/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs
index 8e0d45e15..f6e458c1f 100644
--- a/src/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs
+++ b/src/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs
@@ -2,6 +2,7 @@
namespace NzbDrone.Core.Lifecycle
{
+ [LifecycleEvent]
public class ApplicationStartedEvent : IEvent
{
}
diff --git a/src/NzbDrone.Core/Lifecycle/ApplicationStartingEvent.cs b/src/NzbDrone.Core/Lifecycle/ApplicationStartingEvent.cs
index 464ff5ff4..eb54ea5ee 100644
--- a/src/NzbDrone.Core/Lifecycle/ApplicationStartingEvent.cs
+++ b/src/NzbDrone.Core/Lifecycle/ApplicationStartingEvent.cs
@@ -2,6 +2,7 @@
namespace NzbDrone.Core.Lifecycle
{
+ [LifecycleEvent]
public class ApplicationStartingEvent : IEvent
{
}
diff --git a/src/NzbDrone.Core/Lifecycle/LifecycleService.cs b/src/NzbDrone.Core/Lifecycle/LifecycleService.cs
index 9ed36a42e..b3be3bec1 100644
--- a/src/NzbDrone.Core/Lifecycle/LifecycleService.cs
+++ b/src/NzbDrone.Core/Lifecycle/LifecycleService.cs
@@ -1,10 +1,8 @@
-using NLog;
-using NzbDrone.Common;
+using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Lifecycle.Commands;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
-using IServiceProvider = NzbDrone.Common.IServiceProvider;
namespace NzbDrone.Core.Lifecycle
{
@@ -18,41 +16,28 @@ public class LifecycleService : ILifecycleService, IExecute, IE
{
private readonly IEventAggregator _eventAggregator;
private readonly IRuntimeInfo _runtimeInfo;
- private readonly IServiceProvider _serviceProvider;
private readonly Logger _logger;
public LifecycleService(IEventAggregator eventAggregator,
IRuntimeInfo runtimeInfo,
- IServiceProvider serviceProvider,
Logger logger)
{
_eventAggregator = eventAggregator;
_runtimeInfo = runtimeInfo;
- _serviceProvider = serviceProvider;
_logger = logger;
}
public void Shutdown()
{
_logger.Info("Shutdown requested.");
- _eventAggregator.PublishEvent(new ApplicationShutdownRequested());
-
- if (_runtimeInfo.IsWindowsService)
- {
- _serviceProvider.Stop(ServiceProvider.SERVICE_NAME);
- }
+ _eventAggregator.PublishEvent(new ApplicationShutdownRequested(false));
}
public void Restart()
{
_logger.Info("Restart requested.");
-
+ _runtimeInfo.RestartPending = true;
_eventAggregator.PublishEvent(new ApplicationShutdownRequested(true));
-
- if (_runtimeInfo.IsWindowsService)
- {
- _serviceProvider.Restart(ServiceProvider.SERVICE_NAME);
- }
}
public void Execute(ShutdownCommand message)
diff --git a/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs b/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs
index c2a5dbab0..4d21f8d07 100644
--- a/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs
+++ b/src/NzbDrone.Core/Messaging/Events/EventAggregator.cs
@@ -5,7 +5,9 @@
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.EnsureThat;
+using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Reflection;
using NzbDrone.Common.TPL;
namespace NzbDrone.Core.Messaging.Events
@@ -14,6 +16,7 @@ public class EventAggregator : IEventAggregator
{
private readonly Logger _logger;
private readonly IServiceFactory _serviceFactory;
+ private readonly IRuntimeInfo _runtimeInfo;
private readonly TaskFactory _taskFactory;
private readonly Dictionary _eventSubscribers;
@@ -39,10 +42,11 @@ public EventSubscribers(IServiceFactory serviceFactory)
}
}
- public EventAggregator(Logger logger, IServiceFactory serviceFactory)
+ public EventAggregator(Logger logger, IServiceFactory serviceFactory, IRuntimeInfo runtimeInfo)
{
_logger = logger;
_serviceFactory = serviceFactory;
+ _runtimeInfo = runtimeInfo;
_taskFactory = new TaskFactory();
_eventSubscribers = new Dictionary();
}
@@ -54,6 +58,12 @@ public void PublishEvent(TEvent @event)
var eventName = GetEventName(@event.GetType());
+ if (_runtimeInfo.IsExiting && @event.GetType().HasAttribute())
+ {
+ _logger.Warn("Event {0} blocked due to application shutdown", eventName);
+ return;
+ }
+
/*
int workerThreads;
int completionPortThreads;
@@ -78,7 +88,15 @@ public void PublishEvent(TEvent @event)
{
if (!_eventSubscribers.TryGetValue(eventName, out var target))
{
- _eventSubscribers[eventName] = target = new EventSubscribers(_serviceFactory);
+ try
+ {
+ _eventSubscribers[eventName] = target = new EventSubscribers(_serviceFactory);
+ }
+ catch (Exception ex)
+ {
+ _logger.Warn(ex, "Unable to resolve event subscribers for {0}, container may be disposed", eventName);
+ return;
+ }
}
subscribers = target as EventSubscribers;
diff --git a/src/NzbDrone.Host/AppLifetime.cs b/src/NzbDrone.Host/AppLifetime.cs
index 45616e36b..bad3df945 100644
--- a/src/NzbDrone.Host/AppLifetime.cs
+++ b/src/NzbDrone.Host/AppLifetime.cs
@@ -18,7 +18,6 @@ public class AppLifetime : IHostedService, IHandle
private readonly IRuntimeInfo _runtimeInfo;
private readonly IStartupContext _startupContext;
private readonly IBrowserService _browserService;
- private readonly IProcessProvider _processProvider;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@@ -36,7 +35,6 @@ public AppLifetime(IHostApplicationLifetime appLifetime,
_runtimeInfo = runtimeInfo;
_startupContext = startupContext;
_browserService = browserService;
- _processProvider = processProvider;
_eventAggregator = eventAggregator;
_logger = logger;
@@ -70,12 +68,13 @@ private void OnAppStarted()
private void OnAppStopped()
{
- if (_runtimeInfo.RestartPending && !_runtimeInfo.IsWindowsService)
+ if (_runtimeInfo.RestartPending)
{
- var restartArgs = GetRestartArgs();
-
- _logger.Info("Attempting restart with arguments: {0}", restartArgs);
- _processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, restartArgs);
+ _logger.Info("Restart pending.");
+ }
+ else
+ {
+ _logger.Info("Application stopped without restart pending");
}
}
@@ -87,33 +86,21 @@ private void Shutdown()
_appLifetime.StopApplication();
}
- private string GetRestartArgs()
- {
- var args = _startupContext.PreservedArguments;
-
- args += " /restart";
-
- if (!args.Contains("/nobrowser"))
- {
- args += " /nobrowser";
- }
-
- return args;
- }
-
[EventHandleOrder(EventHandleOrder.Last)]
public void Handle(ApplicationShutdownRequested message)
{
- if (!_runtimeInfo.IsWindowsService)
+ if (message.Restarting)
{
- if (message.Restarting)
- {
- _runtimeInfo.RestartPending = true;
- }
-
- LogManager.Configuration = null;
- Shutdown();
+ _runtimeInfo.RestartPending = true;
+ _logger.Debug("Restart requested");
}
+ else
+ {
+ _logger.Debug("Shutdown requested");
+ LogManager.Configuration = null;
+ }
+
+ Shutdown();
}
}
}
diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs
index d92f3ca68..7904e4a1c 100644
--- a/src/NzbDrone.Host/Bootstrap.cs
+++ b/src/NzbDrone.Host/Bootstrap.cs
@@ -8,6 +8,7 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using System.Threading;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
@@ -25,6 +26,7 @@
using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Options;
+using NzbDrone.Common.Reflection;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions;
using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions;
@@ -51,18 +53,15 @@ public static void Start(string[] args, Action trayCallback = null
var appMode = GetApplicationMode(startupContext);
var config = GetConfiguration(startupContext);
- switch (appMode)
+ if (appMode is not(ApplicationModes.Interactive or ApplicationModes.Service))
{
- case ApplicationModes.Service:
- StartService(startupContext);
- break;
- case ApplicationModes.Interactive:
- StartInteractive(startupContext, trayCallback);
- break;
- default:
- StartUtility(startupContext, appMode, config);
- break;
+ RunUtilityMode(appMode, startupContext, config);
+ return;
}
+
+ RunHostUntilShutdown(args, startupContext, appMode, trayCallback);
+
+ Logger.Info("Lidarr has shut down completely");
}
catch (InvalidConfigFileException ex)
{
@@ -84,88 +83,12 @@ public static void Start(string[] args, Action trayCallback = null
SQLiteConnection.ClearAllPools();
}
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void StartService(StartupContext context)
+ private static void RunUtilityMode(ApplicationModes appMode, StartupContext startupContext, IConfiguration config)
{
- Logger.Debug("Service selected");
+ Logger.Debug("Utility mode: {0}", appMode);
- var success = StartService(context, true, out var pluginRefs);
-
- if (!success)
- {
- var unloadSuccess = PluginLoader.UnloadPlugins(pluginRefs);
-
- if (unloadSuccess)
- {
- StartService(context, false, out _);
- }
- }
-
- CreateConsoleHostBuilder(context, false, out _).UseWindowsService().Build().Run();
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void StartInteractive(StartupContext context, Action trayCallback)
- {
- Logger.Debug(trayCallback != null ? "Tray selected" : "Console selected");
-
- var success = StartInteractive(context, trayCallback, true, out var pluginRefs);
-
- if (!success)
- {
- var unloadSuccess = PluginLoader.UnloadPlugins(pluginRefs);
-
- if (unloadSuccess)
- {
- StartInteractive(context, trayCallback, false, out _);
- }
- }
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static bool StartService(StartupContext context, bool usePlugins, out List pluginRefs)
- {
- var builder = CreateConsoleHostBuilder(context, usePlugins, out pluginRefs).UseWindowsService();
-
- return RunBuilder(builder, usePlugins);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static bool StartInteractive(StartupContext context, Action trayCallback, bool usePlugins, out List pluginRefs)
- {
- var builder = CreateConsoleHostBuilder(context, usePlugins, out pluginRefs);
-
- if (trayCallback != null)
- {
- trayCallback(builder);
- }
-
- return RunBuilder(builder, usePlugins);
- }
-
- private static bool RunBuilder(IHostBuilder builder, bool usePlugins)
- {
- try
- {
- using var host = builder.Build();
- host.Run();
- }
- catch (Exception e)
- {
- if (usePlugins)
- {
- Logger.Warn(e, "Error starting with plugins enabled");
- }
-
- return false;
- }
-
- return true;
- }
-
- private static void StartUtility(StartupContext context, ApplicationModes mode, IConfiguration config)
- {
var assemblies = AssemblyLoader.LoadBaseAssemblies();
+
new HostBuilder()
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.ConfigureContainer(c =>
@@ -173,9 +96,9 @@ private static void StartUtility(StartupContext context, ApplicationModes mode,
c.AutoAddServices(assemblies)
.AddNzbDroneLogger()
.AddDatabase()
- .AddStartupContext(context)
+ .AddStartupContext(startupContext)
.Resolve()
- .Route(mode);
+ .Route(appMode);
if (config.GetValue(nameof(ConfigFileProvider.LogDbEnabled), true))
{
@@ -194,10 +117,72 @@ private static void StartUtility(StartupContext context, ApplicationModes mode,
services.Configure(config.GetSection("Lidarr:Server"));
services.Configure(config.GetSection("Lidarr:Log"));
services.Configure(config.GetSection("Lidarr:Update"));
- }).Build();
+ })
+ .Build();
}
- private static IHostBuilder CreateConsoleHostBuilder(StartupContext context, bool usePlugins, out List pluginRef)
+ private static void RunHostUntilShutdown(string[] args, StartupContext startupContext, ApplicationModes appMode, Action trayCallback)
+ {
+ Logger.Debug("Starting in {0} mode", trayCallback != null ? "Tray" : appMode.ToString());
+
+ bool shouldRestart;
+ do
+ {
+ var success = RunHost(args, startupContext, trayCallback, true, out var pluginRefs, out shouldRestart);
+
+ if (!success)
+ {
+ var unloadSuccess = PluginLoader.UnloadPlugins(pluginRefs);
+
+ if (unloadSuccess)
+ {
+ RunHost(args, startupContext, trayCallback, false, out _, out shouldRestart);
+ }
+ }
+
+ if (shouldRestart)
+ {
+ Logger.Info("Application restart requested, reinitializing host");
+ PluginLoader.UnloadPlugins(pluginRefs);
+ NzbDroneLogger.ResetAllTargets(startupContext, false, true);
+ Thread.Sleep(1000);
+ }
+ }
+ while (shouldRestart);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static bool RunHost(string[] args, StartupContext startupContext, Action trayCallback, bool usePlugins, out List pluginRefs, out bool shouldRestart)
+ {
+ shouldRestart = false;
+
+ var builder = CreateConsoleHostBuilder(args, startupContext, usePlugins, out pluginRefs);
+ trayCallback?.Invoke(builder);
+
+ if (OsInfo.IsWindows && WindowsServiceHelpers.IsWindowsService())
+ {
+ builder.UseWindowsService();
+ }
+
+ try
+ {
+ using var host = builder.Build();
+ shouldRestart = RunWithRestartCheck(host);
+ }
+ catch (Exception e)
+ {
+ if (usePlugins)
+ {
+ Logger.Warn(e, "Error starting with plugins enabled");
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public static IHostBuilder CreateConsoleHostBuilder(string[] args, StartupContext context, bool usePlugins, out List pluginRef)
{
var config = GetConfiguration(context);
@@ -224,7 +209,13 @@ private static IHostBuilder CreateConsoleHostBuilder(StartupContext context, boo
var pluginPaths = new AppFolderInfo(context).GetPluginAssemblies().ToList();
(var plugins, pluginRef) = PluginLoader.LoadPlugins(pluginPaths);
- assemblies.AddRange(plugins.Where(x => x != null));
+ var loadedPlugins = plugins.Where(x => x != null).ToList();
+ assemblies.AddRange(loadedPlugins);
+ ReflectionExtensions.SetCurrentAssemblies(loadedPlugins);
+ }
+ else
+ {
+ ReflectionExtensions.SetCurrentAssemblies(Enumerable.Empty());
}
return new HostBuilder()
@@ -367,5 +358,20 @@ private static X509Certificate2 ValidateSslCertificate(string cert, string passw
return certificate;
}
+
+ private static bool RunWithRestartCheck(IHost host)
+ {
+ var shouldRestart = false;
+
+ var lifetime = host.Services.GetRequiredService();
+ lifetime.ApplicationStopped.Register(() =>
+ {
+ var runtimeInfo = host.Services.GetRequiredService();
+ shouldRestart = runtimeInfo.RestartPending;
+ });
+
+ host.Run();
+ return shouldRestart;
+ }
}
}
diff --git a/src/NzbDrone.Host/RestartableServiceLifetime.cs b/src/NzbDrone.Host/RestartableServiceLifetime.cs
new file mode 100644
index 000000000..48af1cba3
--- /dev/null
+++ b/src/NzbDrone.Host/RestartableServiceLifetime.cs
@@ -0,0 +1,249 @@
+using System;
+using System.ServiceProcess;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Hosting.Internal;
+using Microsoft.Extensions.Hosting.WindowsServices;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using NLog;
+using NzbDrone.Common.Instrumentation;
+
+namespace NzbDrone.Host
+{
+ public class RestartableServiceLifetime : IHostLifetime, IDisposable
+ {
+ private static readonly object StaticLock = new object();
+ private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(RestartableServiceLifetime));
+ private static WindowsServiceWrapper _singletonService;
+ private static bool _serviceBaseRunCalled;
+
+ private readonly IHostEnvironment _environment;
+ private readonly bool _isWindowsService;
+ private readonly ConsoleLifetime _consoleLifetime;
+ private readonly TaskCompletionSource