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