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