< Summary - Jellyfin

Information
Class: Jellyfin.Server.Program
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Program.cs
Line coverage
23%
Covered lines: 38
Uncovered lines: 127
Coverable lines: 165
Total lines: 377
Line coverage: 23%
Branch coverage
2%
Covered branches: 1
Total branches: 48
Branch coverage: 2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 4/9/2026 - 12:14:15 AM Line coverage: 86.3% (19/22) Branch coverage: 50% (2/4) Total lines: 3574/14/2026 - 12:13:23 AM Line coverage: 86.3% (19/22) Branch coverage: 50% (2/4) Total lines: 3584/19/2026 - 12:14:27 AM Line coverage: 24.2% (38/157) Branch coverage: 6.6% (2/30) Total lines: 3585/7/2026 - 12:15:44 AM Line coverage: 23.7% (38/160) Branch coverage: 4.1% (2/48) Total lines: 3705/20/2026 - 12:15:44 AM Line coverage: 23.7% (38/160) Branch coverage: 2% (1/48) Total lines: 3706/28/2026 - 12:15:35 AM Line coverage: 23% (38/165) Branch coverage: 2% (1/48) Total lines: 377 4/9/2026 - 12:14:15 AM Line coverage: 86.3% (19/22) Branch coverage: 50% (2/4) Total lines: 3574/14/2026 - 12:13:23 AM Line coverage: 86.3% (19/22) Branch coverage: 50% (2/4) Total lines: 3584/19/2026 - 12:14:27 AM Line coverage: 24.2% (38/157) Branch coverage: 6.6% (2/30) Total lines: 3585/7/2026 - 12:15:44 AM Line coverage: 23.7% (38/160) Branch coverage: 4.1% (2/48) Total lines: 3705/20/2026 - 12:15:44 AM Line coverage: 23.7% (38/160) Branch coverage: 2% (1/48) Total lines: 3706/28/2026 - 12:15:35 AM Line coverage: 23% (38/165) Branch coverage: 2% (1/48) Total lines: 377

Coverage delta

Coverage delta 63 -63

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Main(...)100%210%
StartApp()0%110100%
StartServer()0%1190340%
ApplyStartupMigrationAsync()100%11100%
ApplyCoreMigrationsAsync()100%11100%
CreateAppConfiguration(...)100%11100%
ConfigureAppConfiguration(...)25%4490%
PrepareDatabaseProvider(...)100%11100%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Program.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics;
 4using System.Globalization;
 5using System.IO;
 6using System.Linq;
 7using System.Reflection;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using CommandLine;
 11using Emby.Server.Implementations;
 12using Emby.Server.Implementations.Configuration;
 13using Emby.Server.Implementations.Serialization;
 14using Jellyfin.Database.Implementations;
 15using Jellyfin.Server.Extensions;
 16using Jellyfin.Server.Helpers;
 17using Jellyfin.Server.Implementations.DatabaseConfiguration;
 18using Jellyfin.Server.Implementations.Extensions;
 19using Jellyfin.Server.Implementations.StorageHelpers;
 20using Jellyfin.Server.Implementations.SystemBackupService;
 21using Jellyfin.Server.Migrations;
 22using Jellyfin.Server.Migrations.Stages;
 23using Jellyfin.Server.ServerSetupApp;
 24using MediaBrowser.Common.Configuration;
 25using MediaBrowser.Common.Net;
 26using MediaBrowser.Controller;
 27using Microsoft.AspNetCore.Hosting;
 28using Microsoft.EntityFrameworkCore;
 29using Microsoft.Extensions.Configuration;
 30using Microsoft.Extensions.DependencyInjection;
 31using Microsoft.Extensions.Hosting;
 32using Microsoft.Extensions.Logging;
 33using Microsoft.Extensions.Logging.Abstractions;
 34using Serilog;
 35using Serilog.Extensions.Logging;
 36using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 37using ILogger = Microsoft.Extensions.Logging.ILogger;
 38
 39namespace Jellyfin.Server
 40{
 41    /// <summary>
 42    /// Class containing the entry point of the application.
 43    /// </summary>
 44    public static class Program
 45    {
 46        /// <summary>
 47        /// The name of logging configuration file containing application defaults.
 48        /// </summary>
 49        public const string LoggingConfigFileDefault = "logging.default.json";
 50
 51        /// <summary>
 52        /// The name of the logging configuration file containing the system-specific override settings.
 53        /// </summary>
 54        public const string LoggingConfigFileSystem = "logging.json";
 55
 156        private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory();
 57        private static SetupServer? _setupServer;
 58        private static CoreAppHost? _appHost;
 159        private static IHost? _jellyfinHost = null;
 60        private static long _startTimestamp;
 161        private static ILogger _logger = NullLogger.Instance;
 62        private static bool _restartOnShutdown;
 63        private static IStartupLogger<JellyfinMigrationService>? _migrationLogger;
 64        private static string? _restoreFromBackup;
 65
 66        /// <summary>
 67        /// The entry point of the application.
 68        /// </summary>
 69        /// <param name="args">The command line arguments passed.</param>
 70        /// <returns><see cref="Task" />.</returns>
 71        public static Task Main(string[] args)
 72        {
 73            static Task ErrorParsingArguments(IEnumerable<Error> errors)
 74            {
 75                Environment.ExitCode = 1;
 76                return Task.CompletedTask;
 77            }
 78
 79            // Parse the command line arguments and either start the app or exit indicating error
 080            return Parser.Default.ParseArguments<StartupOptions>(args)
 081                .MapResult(StartApp, ErrorParsingArguments);
 82        }
 83
 84        private static async Task StartApp(StartupOptions options)
 85        {
 086            _restoreFromBackup = options.RestoreArchive;
 087            _startTimestamp = Stopwatch.GetTimestamp();
 088            ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options);
 089            appPaths.MakeSanityCheckOrThrow();
 90
 91            // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
 092            Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
 93
 94            // Enable cl-va P010 interop for tonemapping on Intel VAAPI
 095            Environment.SetEnvironmentVariable("NEOReadDebugKeys", "1");
 096            Environment.SetEnvironmentVariable("EnableExtendedVaFormats", "1");
 97
 098            await StartupHelpers.InitLoggingConfigFile(appPaths).ConfigureAwait(false);
 99
 100            // Create an instance of the application configuration to use for application startup
 0101            IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
 0102            StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths);
 0103            _setupServer = new SetupServer(static () => _jellyfinHost?.Services?.GetService<INetworkManager>(), appPaths
 0104            await _setupServer.RunAsync().ConfigureAwait(false);
 0105            _logger = _loggerFactory.CreateLogger("Main");
 0106            StartupLogger.Logger = new StartupLogger(_logger);
 107
 108            // Use the logging framework for uncaught exceptions instead of std error
 0109            AppDomain.CurrentDomain.UnhandledException += (_, e)
 0110                => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
 111
 0112            _logger.LogInformation(
 0113                "Jellyfin version: {Version}",
 0114                Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3));
 115
 0116            StartupHelpers.LogEnvironmentInfo(_logger, appPaths);
 117
 118            // If hosting the web client, validate the client content path
 0119            if (startupConfig.HostWebClient())
 120            {
 0121                var webContentPath = appPaths.WebPath;
 0122                if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any())
 123                {
 0124                    _logger.LogError(
 0125                        "The server is expected to host the web client, but the provided content directory is either " +
 0126                        "invalid or empty: {WebContentPath}. If you do not want to host the web client with the " +
 0127                        "server, you may set the '--nowebclient' command line flag, or set" +
 0128                        "'{ConfigKey}=false' in your config settings",
 0129                        webContentPath,
 0130                        HostWebClientKey);
 0131                    Environment.ExitCode = 1;
 0132                    return;
 133                }
 134            }
 135
 0136            SetupServer.ReportActivity(StartupActivity.CheckingStorage);
 0137            StorageHelper.TestCommonPathsForStorageCapacity(appPaths, StartupLogger.Logger.With(_loggerFactory.CreateLog
 138
 0139            StartupHelpers.PerformStaticInitialization();
 140
 0141            SetupServer.ReportActivity(StartupActivity.Initializing);
 0142            await ApplyStartupMigrationAsync(appPaths, startupConfig, options).ConfigureAwait(false);
 143
 144            do
 145            {
 0146                await StartServer(appPaths, options, startupConfig).ConfigureAwait(false);
 147
 0148                if (_restartOnShutdown)
 149                {
 0150                    _startTimestamp = Stopwatch.GetTimestamp();
 0151                    await _setupServer.StopAsync().ConfigureAwait(false);
 0152                    await _setupServer.RunAsync().ConfigureAwait(false);
 153                }
 0154            } while (_restartOnShutdown);
 155
 0156            _setupServer.Dispose();
 0157        }
 158
 159        private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration s
 160        {
 0161            using CoreAppHost appHost = new CoreAppHost(
 0162                            appPaths,
 0163                            _loggerFactory,
 0164                            options,
 0165                            startupConfig);
 0166            var configurationCompleted = false;
 167            try
 168            {
 0169                _jellyfinHost = Host.CreateDefaultBuilder()
 0170                    .UseConsoleLifetime()
 0171                    .ConfigureServices(services => appHost.Init(services))
 0172                    .ConfigureWebHostDefaults(webHostBuilder =>
 0173                    {
 0174                        webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger);
 0175                        if (bool.TryParse(Environment.GetEnvironmentVariable("JELLYFIN_ENABLE_IIS"), out var iisEnabled)
 0176                        {
 0177                            _logger.LogCritical("UNSUPPORTED HOSTING ENVIRONMENT Microsoft Internet Information Services
 0178                            webHostBuilder.UseIIS();
 0179                        }
 0180                    })
 0181                    .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConf
 0182                    .UseSerilog()
 0183                    .ConfigureServices(e => e
 0184                        .RegisterStartupLogger()
 0185                        .AddSingleton<IServiceCollection>(e))
 0186                    .Build();
 187
 188                /*
 189                 * Initialize the transcode path marker so we avoid starting Jellyfin in a broken state.
 190                 * This should really be a part of IApplicationPaths but this path is configured differently.
 191                 */
 0192                _ = appHost.ConfigurationManager.GetTranscodePath();
 193
 194                // Re-use the host service provider in the app host since ASP.NET doesn't allow a custom service collect
 0195                appHost.ServiceProvider = _jellyfinHost.Services;
 0196                PrepareDatabaseProvider(appHost.ServiceProvider);
 197
 0198                if (!string.IsNullOrWhiteSpace(_restoreFromBackup))
 199                {
 0200                    SetupServer.ReportActivity(StartupActivity.RestoringBackup);
 0201                    await appHost.ServiceProvider.GetService<IBackupService>()!.RestoreBackupAsync(_restoreFromBackup).C
 0202                    _restoreFromBackup = null;
 0203                    _restartOnShutdown = true;
 0204                    return;
 205                }
 206
 0207                var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(appHost.Servi
 0208                SetupServer.ReportActivity(StartupActivity.PreparingMigrations);
 0209                await jellyfinMigrationService.PrepareSystemForMigration(_logger).ConfigureAwait(false);
 210                // "Preparing migrations" carries through the DB read; per-migration progress is reported
 211                // as "Running migration X of Y" from inside the step once the pending set is known.
 0212                await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.
 213
 0214                SetupServer.ReportActivity(StartupActivity.InitializingServices);
 0215                await appHost.InitializeServices(startupConfig).ConfigureAwait(false);
 0216                _appHost = appHost;
 217
 0218                await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.AppInitialisation, appHost.S
 0219                await jellyfinMigrationService.CleanupSystemAfterMigration(_logger).ConfigureAwait(false);
 220                try
 221                {
 0222                    configurationCompleted = true;
 0223                    await _setupServer!.StopAsync().ConfigureAwait(false);
 224
 0225                    if (options.StartupMode is null or Configuration.StartupMode.MediaServer)
 226                    {
 0227                        await _jellyfinHost.StartAsync().ConfigureAwait(false);
 228
 0229                        if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket())
 230                        {
 0231                            var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths);
 232
 0233                            StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger);
 234                        }
 235                    }
 0236                }
 0237                catch (Exception)
 238                {
 0239                    _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bin
 0240                    throw;
 241                }
 242
 0243                if (options.StartupMode is null or Configuration.StartupMode.MediaServer)
 244                {
 0245                    await appHost.RunStartupTasksAsync().ConfigureAwait(false);
 0246                    _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp));
 247
 0248                    await _jellyfinHost.WaitForShutdownAsync().ConfigureAwait(false);
 249                }
 250
 0251                _restartOnShutdown = appHost.ShouldRestart;
 0252                _restoreFromBackup = appHost.RestoreBackupPath;
 0253            }
 0254            catch (Exception ex)
 255            {
 0256                _restartOnShutdown = false;
 0257                _logger.LogCritical(ex, "Error while starting server");
 0258                if (_setupServer!.IsAlive && !configurationCompleted)
 259                {
 0260                    _setupServer!.SoftStop();
 0261                    if (options.StartupMode is null or Configuration.StartupMode.MediaServer)
 262                    {
 0263                        await Task.Delay(TimeSpan.FromMinutes(10)).ConfigureAwait(false);
 264                    }
 265
 0266                    await _setupServer!.StopAsync().ConfigureAwait(false);
 267                }
 268            }
 269            finally
 270            {
 271                // Don't throw additional exception if startup failed.
 0272                if (appHost.ServiceProvider is not null)
 273                {
 0274                    _logger.LogInformation("Running query planner optimizations in the database... This might take a whi
 275
 0276                    var databaseProvider = appHost.ServiceProvider.GetRequiredService<IJellyfinDatabaseProvider>();
 0277                    using var shutdownSource = new CancellationTokenSource();
 0278                    shutdownSource.CancelAfter((int)TimeSpan.FromSeconds(60).TotalMicroseconds);
 0279                    await databaseProvider.RunShutdownTask(shutdownSource.Token).ConfigureAwait(false);
 0280                }
 281
 0282                _appHost = null;
 0283                _jellyfinHost?.Dispose();
 0284                _jellyfinHost = null;
 285            }
 0286        }
 287
 288        /// <summary>
 289        /// [Internal]Runs the startup Migrations.
 290        /// </summary>
 291        /// <remarks>
 292        /// Not intended to be used other then by jellyfin and its tests.
 293        /// </remarks>
 294        /// <param name="appPaths">Application Paths.</param>
 295        /// <param name="startupConfig">Startup Config.</param>
 296        /// <param name="startupOptions">The applications startup options.</param>
 297        /// <returns>A task.</returns>
 298        public static async Task ApplyStartupMigrationAsync(ServerApplicationPaths appPaths, IConfiguration startupConfi
 299        {
 22300            _migrationLogger = StartupLogger.Logger.BeginGroup<JellyfinMigrationService>($"Migration Service");
 22301            var startupConfigurationManager = new ServerConfigurationManager(appPaths, _loggerFactory, new MyXmlSerializ
 22302            startupConfigurationManager.AddParts([new DatabaseConfigurationFactory()]);
 22303            var migrationStartupServiceProvider = new ServiceCollection()
 22304                .AddLogging(d => d.AddSerilog())
 22305                .AddJellyfinDbContext(startupConfigurationManager, startupConfig)
 22306                .AddSingleton<IApplicationPaths>(appPaths)
 22307                .AddSingleton<ServerApplicationPaths>(appPaths)
 22308                .RegisterStartupLogger();
 309
 22310            migrationStartupServiceProvider.AddSingleton(migrationStartupServiceProvider);
 22311            var startupService = migrationStartupServiceProvider.BuildServiceProvider();
 312
 22313            PrepareDatabaseProvider(startupService);
 314
 22315            var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(startupService);
 22316            await jellyfinMigrationService.CheckFirstTimeRunOrMigration(appPaths, startupOptions).ConfigureAwait(false);
 22317            await jellyfinMigrationService.MigrateStepAsync(Migrations.Stages.JellyfinMigrationStageTypes.PreInitialisat
 22318        }
 319
 320        /// <summary>
 321        /// [Internal]Runs the Jellyfin migrator service with the Core stage.
 322        /// </summary>
 323        /// <remarks>
 324        /// Not intended to be used other then by jellyfin and its tests.
 325        /// </remarks>
 326        /// <param name="serviceProvider">The service provider.</param>
 327        /// <param name="jellyfinMigrationStage">The stage to run.</param>
 328        /// <returns>A task.</returns>
 329        public static async Task ApplyCoreMigrationsAsync(IServiceProvider serviceProvider, Migrations.Stages.JellyfinMi
 330        {
 44331            var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(serviceProvider, 
 44332            await jellyfinMigrationService.MigrateStepAsync(jellyfinMigrationStage, serviceProvider).ConfigureAwait(fals
 44333        }
 334
 335        /// <summary>
 336        /// Create the application configuration.
 337        /// </summary>
 338        /// <param name="commandLineOpts">The command line options passed to the program.</param>
 339        /// <param name="appPaths">The application paths.</param>
 340        /// <returns>The application configuration.</returns>
 341        public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
 342        {
 22343            return new ConfigurationBuilder()
 22344                .ConfigureAppConfiguration(commandLineOpts, appPaths)
 22345                .Build();
 346        }
 347
 348        private static IConfigurationBuilder ConfigureAppConfiguration(
 349            this IConfigurationBuilder config,
 350            StartupOptions commandLineOpts,
 351            IApplicationPaths appPaths,
 352            IConfiguration? startupConfig = null)
 353        {
 354            // Use the swagger API page as the default redirect path if not hosting the web client
 22355            var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
 22356            if (startupConfig is not null && !startupConfig.HostWebClient())
 357            {
 0358                inMemoryDefaultConfig[DefaultRedirectKey] = "api-docs/swagger";
 359            }
 360
 22361            return config
 22362                .SetBasePath(appPaths.ConfigurationDirectoryPath)
 22363                .AddInMemoryCollection(inMemoryDefaultConfig)
 22364                .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
 22365                .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
 22366                .AddEnvironmentVariables("JELLYFIN_")
 22367                .AddInMemoryCollection(commandLineOpts.ConvertToConfig());
 368        }
 369
 370        private static void PrepareDatabaseProvider(IServiceProvider services)
 371        {
 22372            var factory = services.GetRequiredService<IDbContextFactory<JellyfinDbContext>>();
 22373            var provider = services.GetRequiredService<IJellyfinDatabaseProvider>();
 22374            provider.DbContextFactory = factory;
 22375        }
 376    }
 377}