2019-01-13 19:30:41 +00:00
using System ;
2020-06-15 15:06:57 +00:00
using System.Collections.Generic ;
2019-01-13 19:30:41 +00:00
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
2019-01-12 22:58:58 +00:00
using System.Threading ;
2019-01-13 19:30:41 +00:00
using System.Threading.Tasks ;
2019-01-28 20:58:47 +00:00
using CommandLine ;
2019-01-13 19:30:41 +00:00
using Emby.Server.Implementations ;
2023-01-15 16:46:30 +00:00
using Jellyfin.Server.Extensions ;
2023-01-14 20:47:26 +00:00
using Jellyfin.Server.Helpers ;
2021-05-11 21:26:00 +00:00
using Jellyfin.Server.Implementations ;
2019-01-13 19:30:41 +00:00
using MediaBrowser.Common.Configuration ;
2023-01-15 20:39:57 +00:00
using MediaBrowser.Controller ;
2021-05-11 21:26:00 +00:00
using Microsoft.EntityFrameworkCore ;
2019-01-13 19:30:41 +00:00
using Microsoft.Extensions.Configuration ;
2019-02-03 16:09:12 +00:00
using Microsoft.Extensions.DependencyInjection ;
2020-03-21 20:31:22 +00:00
using Microsoft.Extensions.Hosting ;
2019-01-13 19:30:41 +00:00
using Microsoft.Extensions.Logging ;
2019-10-26 21:58:23 +00:00
using Microsoft.Extensions.Logging.Abstractions ;
2019-01-13 19:30:41 +00:00
using Serilog ;
2019-09-11 17:31:35 +00:00
using Serilog.Extensions.Logging ;
2022-02-14 13:39:33 +00:00
using static MediaBrowser . Controller . Extensions . ConfigurationExtensions ;
2019-01-13 19:30:41 +00:00
using ILogger = Microsoft . Extensions . Logging . ILogger ;
namespace Jellyfin.Server
{
2019-08-11 13:11:53 +00:00
/// <summary>
/// Class containing the entry point of the application.
/// </summary>
2019-01-13 19:30:41 +00:00
public static class Program
{
2020-03-05 17:09:33 +00:00
/// <summary>
2020-03-06 18:07:34 +00:00
/// The name of logging configuration file containing application defaults.
2020-03-05 17:09:33 +00:00
/// </summary>
2020-05-29 09:28:19 +00:00
public const string LoggingConfigFileDefault = "logging.default.json" ;
2020-03-06 18:07:34 +00:00
/// <summary>
2020-03-08 14:46:13 +00:00
/// The name of the logging configuration file containing the system-specific override settings.
2020-03-06 18:07:34 +00:00
/// </summary>
2020-05-29 09:28:19 +00:00
public const string LoggingConfigFileSystem = "logging.json" ;
2020-03-05 17:09:33 +00:00
2019-01-12 22:58:58 +00:00
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory ( ) ;
2023-01-15 20:39:57 +00:00
private static CancellationTokenSource _tokenSource = new ( ) ;
private static long _startTimestamp ;
2019-10-26 21:58:23 +00:00
private static ILogger _logger = NullLogger . Instance ;
2019-01-13 19:30:41 +00:00
private static bool _restartOnShutdown ;
2019-08-11 13:11:53 +00:00
/// <summary>
/// The entry point of the application.
/// </summary>
/// <param name="args">The command line arguments passed.</param>
/// <returns><see cref="Task" />.</returns>
2019-02-24 02:16:19 +00:00
public static Task Main ( string [ ] args )
2019-01-13 19:30:41 +00:00
{
2020-06-15 15:06:57 +00:00
static Task ErrorParsingArguments ( IEnumerable < Error > errors )
2019-01-28 14:51:31 +00:00
{
2020-06-15 15:06:57 +00:00
Environment . ExitCode = 1 ;
return Task . CompletedTask ;
2019-01-28 14:51:31 +00:00
}
2019-01-28 13:41:37 +00:00
// Parse the command line arguments and either start the app or exit indicating error
2019-02-24 02:16:19 +00:00
return Parser . Default . ParseArguments < StartupOptions > ( args )
2020-06-15 15:06:57 +00:00
. MapResult ( StartApp , ErrorParsingArguments ) ;
2019-01-28 13:41:37 +00:00
}
2019-01-13 19:30:41 +00:00
2019-08-11 13:11:53 +00:00
/// <summary>
/// Shuts down the application.
/// </summary>
internal static void Shutdown ( )
2019-02-13 16:19:55 +00:00
{
if ( ! _tokenSource . IsCancellationRequested )
{
_tokenSource . Cancel ( ) ;
}
}
2019-08-11 13:11:53 +00:00
/// <summary>
/// Restarts the application.
/// </summary>
internal static void Restart ( )
2019-02-13 16:19:55 +00:00
{
_restartOnShutdown = true ;
Shutdown ( ) ;
}
2019-01-28 13:41:37 +00:00
private static async Task StartApp ( StartupOptions options )
{
2023-01-15 20:39:57 +00:00
_startTimestamp = Stopwatch . GetTimestamp ( ) ;
2019-10-26 21:58:23 +00:00
2019-10-29 16:49:41 +00:00
// Log all uncaught exceptions to std error
2019-10-26 21:58:23 +00:00
static void UnhandledExceptionToConsole ( object sender , UnhandledExceptionEventArgs e ) = >
2023-01-15 20:39:57 +00:00
Console . Error . WriteLine ( "Unhandled Exception\n" + e . ExceptionObject ) ;
2019-10-26 21:58:23 +00:00
AppDomain . CurrentDomain . UnhandledException + = UnhandledExceptionToConsole ;
2023-01-14 20:47:26 +00:00
ServerApplicationPaths appPaths = StartupHelpers . CreateApplicationPaths ( options ) ;
2019-01-18 10:10:45 +00:00
2019-01-13 19:30:41 +00:00
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment . SetEnvironmentVariable ( "JELLYFIN_LOG_DIR" , appPaths . LogDirectoryPath ) ;
2019-02-08 09:13:58 +00:00
2020-11-24 15:25:32 +00:00
// Enable cl-va P010 interop for tonemapping on Intel VAAPI
Environment . SetEnvironmentVariable ( "NEOReadDebugKeys" , "1" ) ;
Environment . SetEnvironmentVariable ( "EnableExtendedVaFormats" , "1" ) ;
2023-01-14 20:47:26 +00:00
await StartupHelpers . InitLoggingConfigFile ( appPaths ) . ConfigureAwait ( false ) ;
2020-03-15 14:34:09 +00:00
// Create an instance of the application configuration to use for application startup
IConfiguration startupConfig = CreateAppConfiguration ( options , appPaths ) ;
2019-02-08 09:13:58 +00:00
2023-01-14 20:47:26 +00:00
StartupHelpers . InitializeLoggingFramework ( startupConfig , appPaths ) ;
2019-01-13 19:30:41 +00:00
_logger = _loggerFactory . CreateLogger ( "Main" ) ;
2019-10-26 21:58:23 +00:00
// Log uncaught exceptions to the logging instead of std error
AppDomain . CurrentDomain . UnhandledException - = UnhandledExceptionToConsole ;
2021-08-04 12:40:09 +00:00
AppDomain . CurrentDomain . UnhandledException + = ( _ , e )
2019-01-13 19:30:41 +00:00
= > _logger . LogCritical ( ( Exception ) e . ExceptionObject , "Unhandled Exception" ) ;
2019-01-12 22:58:58 +00:00
// Intercept Ctrl+C and Ctrl+Break
2021-08-04 12:40:09 +00:00
Console . CancelKeyPress + = ( _ , e ) = >
2019-01-12 22:58:58 +00:00
{
2019-01-13 00:05:25 +00:00
if ( _tokenSource . IsCancellationRequested )
{
return ; // Already shutting down
}
2019-02-13 16:19:55 +00:00
2019-01-12 22:58:58 +00:00
e . Cancel = true ;
_logger . LogInformation ( "Ctrl+C, shutting down" ) ;
2019-01-13 00:05:25 +00:00
Environment . ExitCode = 128 + 2 ;
2019-01-12 22:58:58 +00:00
Shutdown ( ) ;
} ;
2019-01-12 22:31:45 +00:00
// Register a SIGTERM handler
2021-08-04 12:40:09 +00:00
AppDomain . CurrentDomain . ProcessExit + = ( _ , _ ) = >
2019-01-12 22:31:45 +00:00
{
2019-01-12 22:58:58 +00:00
if ( _tokenSource . IsCancellationRequested )
{
return ; // Already shutting down
}
2019-02-13 16:19:55 +00:00
2019-01-12 22:31:45 +00:00
_logger . LogInformation ( "Received a SIGTERM signal, shutting down" ) ;
2019-01-13 00:05:25 +00:00
Environment . ExitCode = 128 + 15 ;
2019-01-12 22:31:45 +00:00
Shutdown ( ) ;
} ;
2019-09-27 21:58:04 +00:00
_logger . LogInformation (
"Jellyfin version: {Version}" ,
2019-10-26 21:58:23 +00:00
Assembly . GetEntryAssembly ( ) ! . GetName ( ) . Version ! . ToString ( 3 ) ) ;
2019-01-13 19:30:41 +00:00
2019-03-07 16:39:40 +00:00
ApplicationHost . LogEnvironmentInfo ( _logger , appPaths ) ;
2019-01-13 19:30:41 +00:00
2021-11-02 15:02:52 +00:00
// If hosting the web client, validate the client content path
if ( startupConfig . HostWebClient ( ) )
{
2023-01-15 20:39:57 +00:00
var webContentPath = appPaths . WebPath ;
2021-11-02 15:02:52 +00: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 20:39:57 +00:00
"'{ConfigKey}=false' in your config settings" ,
2021-11-02 15:02:52 +00:00
webContentPath ,
2022-02-14 13:39:33 +00:00
HostWebClientKey ) ;
2021-11-02 15:02:52 +00:00
Environment . ExitCode = 1 ;
return ;
}
}
2023-01-14 20:47:26 +00:00
StartupHelpers . PerformStaticInitialization ( ) ;
2021-11-24 12:00:12 +00:00
Migrations . MigrationRunner . RunPreStartup ( appPaths , _loggerFactory ) ;
2019-01-13 19:30:41 +00:00
2023-01-15 20:39:57 +00:00
do
{
_restartOnShutdown = false ;
await StartServer ( appPaths , options , startupConfig ) . ConfigureAwait ( false ) ;
if ( _restartOnShutdown )
{
_tokenSource = new CancellationTokenSource ( ) ;
_startTimestamp = Stopwatch . GetTimestamp ( ) ;
}
} while ( _restartOnShutdown ) ;
}
private static async Task StartServer ( IServerApplicationPaths appPaths , StartupOptions options , IConfiguration startupConfig )
{
2019-08-18 18:01:08 +00:00
var appHost = new CoreAppHost (
2019-01-13 19:30:41 +00:00
appPaths ,
_loggerFactory ,
options ,
2021-11-02 15:02:52 +00:00
startupConfig ) ;
2020-03-25 17:52:14 +00:00
2023-01-15 20:39:57 +00:00
IHost ? host = null ;
2019-08-18 18:01:08 +00:00
try
2019-01-13 19:30:41 +00:00
{
2023-01-15 20:39:57 +00:00
host = Host . CreateDefaultBuilder ( )
2023-01-12 16:51:12 +00:00
. ConfigureServices ( services = > appHost . Init ( services ) )
2023-01-15 16:46:30 +00:00
. ConfigureWebHostDefaults ( webHostBuilder = > webHostBuilder . ConfigureWebHostBuilder ( appHost , startupConfig , appPaths , _logger ) )
2023-01-12 03:07:41 +00:00
. ConfigureAppConfiguration ( config = > config . ConfigureAppConfiguration ( options , appPaths , startupConfig ) )
. UseSerilog ( )
. Build ( ) ;
2019-11-24 14:27:58 +00:00
2023-01-12 03:07:41 +00: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 ;
2022-10-21 09:55:32 +00:00
2020-04-05 00:21:48 +00:00
await appHost . InitializeServices ( ) . ConfigureAwait ( false ) ;
2020-03-05 17:09:33 +00:00
Migrations . MigrationRunner . Run ( appHost , _loggerFactory ) ;
2019-11-24 14:27:58 +00:00
try
{
2023-01-12 03:07:41 +00:00
await host . StartAsync ( _tokenSource . Token ) . ConfigureAwait ( false ) ;
2022-01-16 21:32:10 +00:00
2022-10-13 16:19:11 +00:00
if ( ! OperatingSystem . IsWindows ( ) & & startupConfig . UseUnixSocket ( ) )
2022-01-16 21:32:10 +00:00
{
2023-01-15 16:46:30 +00:00
var socketPath = StartupHelpers . GetUnixSocketPath ( startupConfig , appPaths ) ;
2022-01-16 21:32:10 +00:00
2023-01-15 16:46:30 +00:00
StartupHelpers . SetUnixSocketPermissions ( startupConfig , socketPath , _logger ) ;
2022-01-16 21:32:10 +00:00
}
2019-11-24 14:27:58 +00:00
}
2021-09-20 20:27:20 +00:00
catch ( Exception ex ) when ( ex is not TaskCanceledException )
2019-11-24 14:27:58 +00:00
{
2023-01-15 20:39:57 +00: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" ) ;
2019-11-24 14:27:58 +00:00
throw ;
}
2019-01-13 19:30:41 +00:00
2021-02-23 16:30:24 +00:00
await appHost . RunStartupTasksAsync ( _tokenSource . Token ) . ConfigureAwait ( false ) ;
2019-01-25 20:33:58 +00:00
2023-01-15 20:39:57 +00:00
_logger . LogInformation ( "Startup complete {Time:g}" , Stopwatch . GetElapsedTime ( _startTimestamp ) ) ;
2019-09-28 22:29:28 +00:00
2019-08-18 18:01:08 +00:00
// Block main thread until shutdown
await Task . Delay ( - 1 , _tokenSource . Token ) . ConfigureAwait ( false ) ;
}
catch ( TaskCanceledException )
{
// Don't throw on cancellation
}
catch ( Exception ex )
{
2023-01-15 20:39:57 +00:00
_logger . LogCritical ( ex , "Error while starting server" ) ;
2019-08-18 18:01:08 +00:00
}
finally
{
2022-02-04 19:36:17 +00:00
// Don't throw additional exception if startup failed.
2022-12-05 14:01:13 +00:00
if ( appHost . ServiceProvider is not null )
2021-05-11 21:26:00 +00:00
{
2022-02-04 19:36:17 +00:00
_logger . LogInformation ( "Running query planner optimizations in the database... This might take a while" ) ;
// Run before disposing the application
2023-01-16 17:14:44 +00:00
var context = await appHost . ServiceProvider . GetRequiredService < IDbContextFactory < JellyfinDbContext > > ( ) . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
2022-10-21 09:55:32 +00:00
await using ( context . ConfigureAwait ( false ) )
2022-02-04 19:36:17 +00:00
{
2022-10-21 09:55:32 +00:00
if ( context . Database . IsSqlite ( ) )
{
await context . Database . ExecuteSqlRawAsync ( "PRAGMA optimize" ) . ConfigureAwait ( false ) ;
}
2022-02-04 19:36:17 +00:00
}
2021-05-11 21:26:00 +00:00
}
2021-05-24 08:48:01 +00:00
2022-07-24 16:35:46 +00:00
await appHost . DisposeAsync ( ) . ConfigureAwait ( false ) ;
2023-01-15 20:39:57 +00:00
host ? . Dispose ( ) ;
2019-01-13 19:30:41 +00:00
}
}
2020-04-20 18:58:00 +00:00
/// <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 )
2020-02-28 22:18:22 +00:00
{
2019-02-08 09:13:58 +00:00
return new ConfigurationBuilder ( )
2020-03-15 14:34:09 +00:00
. ConfigureAppConfiguration ( commandLineOpts , appPaths )
2020-02-28 22:18:22 +00:00
. Build ( ) ;
}
2020-03-15 14:34:09 +00:00
private static IConfigurationBuilder ConfigureAppConfiguration (
this IConfigurationBuilder config ,
StartupOptions commandLineOpts ,
IApplicationPaths appPaths ,
IConfiguration ? startupConfig = null )
2020-02-28 22:18:22 +00:00
{
2020-03-21 17:25:09 +00:00
// Use the swagger API page as the default redirect path if not hosting the web client
2020-02-25 16:02:51 +00:00
var inMemoryDefaultConfig = ConfigurationOptions . DefaultConfiguration ;
2022-12-05 14:01:13 +00:00
if ( startupConfig is not null & & ! startupConfig . HostWebClient ( ) )
2020-02-25 16:02:51 +00:00
{
2022-02-14 13:39:33 +00:00
inMemoryDefaultConfig [ DefaultRedirectKey ] = "api-docs/swagger" ;
2020-02-25 16:02:51 +00:00
}
2020-02-28 22:18:22 +00:00
return config
2019-02-08 09:13:58 +00:00
. SetBasePath ( appPaths . ConfigurationDirectoryPath )
2020-02-25 16:02:51 +00:00
. AddInMemoryCollection ( inMemoryDefaultConfig )
2020-03-06 18:28:36 +00:00
. AddJsonFile ( LoggingConfigFileDefault , optional : false , reloadOnChange : true )
2020-03-08 14:46:13 +00:00
. AddJsonFile ( LoggingConfigFileSystem , optional : true , reloadOnChange : true )
2020-03-15 14:34:09 +00:00
. AddEnvironmentVariables ( "JELLYFIN_" )
. AddInMemoryCollection ( commandLineOpts . ConvertToConfig ( ) ) ;
2019-02-08 09:13:58 +00:00
}
2019-01-13 19:30:41 +00:00
}
}