You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Lidarr/src/NzbDrone.Host/Bootstrap.cs

277 lines
10 KiB

using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using Microsoft.Extensions.Logging;
using NLog;
using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions;
namespace NzbDrone.Host
{
public static class Bootstrap
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Bootstrap));
public static readonly List<string> ASSEMBLIES = new List<string>
{
"Lidarr.Host",
"Lidarr.Core",
"Lidarr.SignalR",
"Lidarr.Api.V1",
"Lidarr.Http"
};
public static void Start(string[] args, Action<IHostBuilder> trayCallback = null)
{
try
{
Logger.Info("Starting Lidarr - {0} - Version {1}",
Environment.ProcessPath,
Assembly.GetExecutingAssembly().GetName().Version);
var startupContext = new StartupContext(args);
LongPathSupport.Enable();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var appMode = GetApplicationMode(startupContext);
var config = GetConfiguration(startupContext);
switch (appMode)
{
case ApplicationModes.Service:
{
Logger.Debug("Service selected");
CreateConsoleHostBuilder(args, startupContext).UseWindowsService().Build().Run();
break;
}
case ApplicationModes.Interactive:
{
Logger.Debug(trayCallback != null ? "Tray selected" : "Console selected");
var builder = CreateConsoleHostBuilder(args, startupContext);
if (trayCallback != null)
{
trayCallback(builder);
}
builder.Build().Run();
break;
}
// Utility mode
default:
{
new HostBuilder()
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.ConfigureContainer<IContainer>(c =>
{
c.AutoAddServices(Bootstrap.ASSEMBLIES)
.AddNzbDroneLogger()
.AddDatabase()
.AddStartupContext(startupContext)
.Resolve<UtilityModeRouter>()
.Route(appMode);
})
.ConfigureServices(services =>
{
services.Configure<PostgresOptions>(config.GetSection("Lidarr:Postgres"));
}).Build();
break;
}
}
}
catch (InvalidConfigFileException ex)
{
throw new LidarrStartupException(ex);
}
catch (AccessDeniedConfigFileException ex)
{
throw new LidarrStartupException(ex);
}
catch (TerminateApplicationException ex)
{
Logger.Info(ex.Message);
LogManager.Configuration = null;
}
// Make sure there are no lingering database connections
GC.Collect();
GC.WaitForPendingFinalizers();
SQLiteConnection.ClearAllPools();
}
public static IHostBuilder CreateConsoleHostBuilder(string[] args, StartupContext context)
{
var config = GetConfiguration(context);
var bindAddress = config.GetValue(nameof(ConfigFileProvider.BindAddress), "*");
var port = config.GetValue(nameof(ConfigFileProvider.Port), 8686);
var sslPort = config.GetValue(nameof(ConfigFileProvider.SslPort), 6868);
var enableSsl = config.GetValue(nameof(ConfigFileProvider.EnableSsl), false);
var sslCertPath = config.GetValue<string>(nameof(ConfigFileProvider.SslCertPath));
var sslCertPassword = config.GetValue<string>(nameof(ConfigFileProvider.SslCertPassword));
var urls = new List<string> { BuildUrl("http", bindAddress, port) };
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
{
urls.Add(BuildUrl("https", bindAddress, sslPort));
}
return new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.ConfigureLogging(logging =>
{
logging.AddFilter("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", LogLevel.None);
})
.ConfigureContainer<IContainer>(c =>
{
c.AutoAddServices(Bootstrap.ASSEMBLIES)
.AddNzbDroneLogger()
.AddDatabase()
.AddStartupContext(context);
})
.ConfigureServices(services =>
{
services.Configure<PostgresOptions>(config.GetSection("Lidarr:Postgres"));
})
.ConfigureWebHost(builder =>
{
builder.UseConfiguration(config);
builder.UseUrls(urls.ToArray());
builder.UseKestrel(options =>
{
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
{
options.ConfigureHttpsDefaults(configureOptions =>
{
configureOptions.ServerCertificate = ValidateSslCertificate(sslCertPath, sslCertPassword);
});
}
});
builder.ConfigureKestrel(serverOptions =>
{
serverOptions.AllowSynchronousIO = true;
serverOptions.Limits.MaxRequestBodySize = null;
});
builder.UseStartup<Startup>();
});
}
public static ApplicationModes GetApplicationMode(IStartupContext startupContext)
{
if (startupContext.Help)
{
return ApplicationModes.Help;
}
if (OsInfo.IsWindows && startupContext.RegisterUrl)
{
return ApplicationModes.RegisterUrl;
}
if (OsInfo.IsWindows && startupContext.InstallService)
{
return ApplicationModes.InstallService;
}
if (OsInfo.IsWindows && startupContext.UninstallService)
{
return ApplicationModes.UninstallService;
}
// IsWindowsService can throw sometimes, so wrap it
var isWindowsService = false;
try
{
isWindowsService = WindowsServiceHelpers.IsWindowsService();
}
catch (Exception e)
{
Logger.Error(e, "Failed to get service status");
}
if (OsInfo.IsWindows && isWindowsService)
{
return ApplicationModes.Service;
}
return ApplicationModes.Interactive;
}
private static IConfiguration GetConfiguration(StartupContext context)
{
var appFolder = new AppFolderInfo(context);
var configPath = appFolder.GetConfigPath();
try
{
return new ConfigurationBuilder()
.AddXmlFile(configPath, optional: true, reloadOnChange: false)
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
.AddEnvironmentVariables()
.Build();
}
catch (InvalidDataException ex)
{
Logger.Error(ex, ex.Message);
throw new InvalidConfigFileException($"{configPath} is corrupt or invalid. Please delete the config file and Lidarr will recreate it.", ex);
}
}
private static string BuildUrl(string scheme, string bindAddress, int port)
{
return $"{scheme}://{bindAddress}:{port}";
}
private static X509Certificate2 ValidateSslCertificate(string cert, string password)
{
X509Certificate2 certificate;
try
{
certificate = new X509Certificate2(cert, password, X509KeyStorageFlags.DefaultKeySet);
}
catch (CryptographicException ex)
{
if (ex.HResult == 0x2 || ex.HResult == 0x2006D080)
{
throw new LidarrStartupException(ex,
$"The SSL certificate file {cert} does not exist");
}
throw new LidarrStartupException(ex);
}
return certificate;
}
}
}