jellyfin/Jellyfin.Server/Program.cs

238 lines
10 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using CommandLine;
using Emby.Server.Implementations;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Helpers;
using Jellyfin.Server.Implementations;
using MediaBrowser.Common.Configuration;
2023-01-15 15:39:57 -05:00
using MediaBrowser.Controller;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Serilog;
2019-09-11 13:31:35 -04:00
using Serilog.Extensions.Logging;
2022-02-14 08:39:33 -05:00
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Jellyfin.Server
{
/// <summary>
/// Class containing the entry point of the application.
/// </summary>
public static class Program
{
2020-03-05 12:09:33 -05:00
/// <summary>
/// The name of logging configuration file containing application defaults.
2020-03-05 12:09:33 -05:00
/// </summary>
public const string LoggingConfigFileDefault = "logging.default.json";
/// <summary>
/// The name of the logging configuration file containing the system-specific override settings.
/// </summary>
public const string LoggingConfigFileSystem = "logging.json";
2020-03-05 12:09:33 -05:00
private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory();
2023-01-15 15:39:57 -05:00
private static long _startTimestamp;
private static ILogger _logger = NullLogger.Instance;
private static bool _restartOnShutdown;
/// <summary>
/// The entry point of the application.
/// </summary>
/// <param name="args">The command line arguments passed.</param>
/// <returns><see cref="Task" />.</returns>
2019-02-23 21:16:19 -05:00
public static Task Main(string[] args)
{
static Task ErrorParsingArguments(IEnumerable<Error> errors)
{
Environment.ExitCode = 1;
return Task.CompletedTask;
}
// Parse the command line arguments and either start the app or exit indicating error
2019-02-23 21:16:19 -05:00
return Parser.Default.ParseArguments<StartupOptions>(args)
.MapResult(StartApp, ErrorParsingArguments);
}
private static async Task StartApp(StartupOptions options)
{
2023-01-15 15:39:57 -05:00
_startTimestamp = Stopwatch.GetTimestamp();
ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
2020-11-24 10:25:32 -05:00
// Enable cl-va P010 interop for tonemapping on Intel VAAPI
Environment.SetEnvironmentVariable("NEOReadDebugKeys", "1");
Environment.SetEnvironmentVariable("EnableExtendedVaFormats", "1");
await StartupHelpers.InitLoggingConfigFile(appPaths).ConfigureAwait(false);
// Create an instance of the application configuration to use for application startup
IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths);
_logger = _loggerFactory.CreateLogger("Main");
2023-09-22 09:50:29 -04:00
// Use the logging framework for uncaught exceptions instead of std error
2021-08-04 08:40:09 -04:00
AppDomain.CurrentDomain.UnhandledException += (_, e)
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
2019-09-27 17:58:04 -04:00
_logger.LogInformation(
"Jellyfin version: {Version}",
Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3));
StartupHelpers.LogEnvironmentInfo(_logger, appPaths);
2021-11-02 11:02:52 -04:00
// If hosting the web client, validate the client content path
if (startupConfig.HostWebClient())
{
2023-01-15 15:39:57 -05:00
var webContentPath = appPaths.WebPath;
2021-11-02 11:02:52 -04:00
if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any())
{
_logger.LogError(
"The server is expected to host the web client, but the provided content directory is either " +
"invalid or empty: {WebContentPath}. If you do not want to host the web client with the " +
"server, you may set the '--nowebclient' command line flag, or set" +
2023-01-15 15:39:57 -05:00
"'{ConfigKey}=false' in your config settings",
2021-11-02 11:02:52 -04:00
webContentPath,
2022-02-14 08:39:33 -05:00
HostWebClientKey);
2021-11-02 11:02:52 -04:00
Environment.ExitCode = 1;
return;
}
}
StartupHelpers.PerformStaticInitialization();
2021-11-24 07:00:12 -05:00
Migrations.MigrationRunner.RunPreStartup(appPaths, _loggerFactory);
2023-01-15 15:39:57 -05:00
do
{
await StartServer(appPaths, options, startupConfig).ConfigureAwait(false);
if (_restartOnShutdown)
{
_startTimestamp = Stopwatch.GetTimestamp();
}
} while (_restartOnShutdown);
}
private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration startupConfig)
{
using var appHost = new CoreAppHost(
appPaths,
_loggerFactory,
options,
2021-11-02 11:02:52 -04:00
startupConfig);
2023-01-15 15:39:57 -05:00
IHost? host = null;
try
{
2023-01-15 15:39:57 -05:00
host = Host.CreateDefaultBuilder()
.UseConsoleLifetime()
2023-01-12 11:51:12 -05:00
.ConfigureServices(services => appHost.Init(services))
.ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger))
2023-01-11 22:07:41 -05:00
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig))
.UseSerilog()
.Build();
2023-01-11 22:07:41 -05:00
// Re-use the host service provider in the app host since ASP.NET doesn't allow a custom service collection.
appHost.ServiceProvider = host.Services;
await appHost.InitializeServices().ConfigureAwait(false);
2020-03-05 12:09:33 -05:00
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
try
{
await host.StartAsync().ConfigureAwait(false);
2022-10-13 12:19:11 -04:00
if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket())
{
var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths);
StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger);
}
}
catch (Exception)
{
2023-01-15 15:39:57 -05:00
_logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again");
throw;
}
await appHost.RunStartupTasksAsync().ConfigureAwait(false);
2023-01-15 15:39:57 -05:00
_logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp));
2019-09-28 18:29:28 -04:00
await host.WaitForShutdownAsync().ConfigureAwait(false);
_restartOnShutdown = appHost.ShouldRestart;
}
catch (Exception ex)
{
2023-01-15 15:39:57 -05:00
_logger.LogCritical(ex, "Error while starting server");
}
finally
{
// Don't throw additional exception if startup failed.
2022-12-05 09:01:13 -05:00
if (appHost.ServiceProvider is not null)
{
_logger.LogInformation("Running query planner optimizations in the database... This might take a while");
// Run before disposing the application
2023-01-16 12:14:44 -05:00
var context = await appHost.ServiceProvider.GetRequiredService<IDbContextFactory<JellyfinDbContext>>().CreateDbContextAsync().ConfigureAwait(false);
await using (context.ConfigureAwait(false))
{
if (context.Database.IsSqlite())
{
await context.Database.ExecuteSqlRawAsync("PRAGMA optimize").ConfigureAwait(false);
}
}
}
2021-05-24 04:48:01 -04:00
2023-01-15 15:39:57 -05:00
host?.Dispose();
}
}
/// <summary>
/// Create the application configuration.
/// </summary>
/// <param name="commandLineOpts">The command line options passed to the program.</param>
/// <param name="appPaths">The application paths.</param>
/// <returns>The application configuration.</returns>
public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
{
return new ConfigurationBuilder()
.ConfigureAppConfiguration(commandLineOpts, appPaths)
.Build();
}
private static IConfigurationBuilder ConfigureAppConfiguration(
this IConfigurationBuilder config,
StartupOptions commandLineOpts,
IApplicationPaths appPaths,
IConfiguration? startupConfig = null)
{
// Use the swagger API page as the default redirect path if not hosting the web client
var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
2022-12-05 09:01:13 -05:00
if (startupConfig is not null && !startupConfig.HostWebClient())
{
2022-02-14 08:39:33 -05:00
inMemoryDefaultConfig[DefaultRedirectKey] = "api-docs/swagger";
}
return config
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddInMemoryCollection(inMemoryDefaultConfig)
.AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
.AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
.AddEnvironmentVariables("JELLYFIN_")
.AddInMemoryCollection(commandLineOpts.ConvertToConfig());
}
}
}