diff options
9 files changed, 90 insertions, 15 deletions
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index c3cdcc222..be2d198ef 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.AppBase /// <summary> /// Gets the path to the web UI resources folder. /// </summary> - /// <value>The web UI resources path.</value> + /// <value>The web UI resources path, or null if the server is not hosting any web content.</value> public string WebPath { get; } /// <summary> diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index f5da0d018..b17c2b270 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -30,6 +30,16 @@ namespace Emby.Server.Implementations.Browser } /// <summary> + /// Opens the swagger API page. + /// </summary> + /// <param name="appHost">The app host.</param> + public static void OpenSwaggerPage(IServerApplicationHost appHost) + { + var url = appHost.GetLocalApiUrl("localhost") + "/swagger/index.html"; + OpenUrl(appHost, url); + } + + /// <summary> /// Opens the URL. /// </summary> /// <param name="appHost">The application host instance.</param> diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index d0f3d6723..a7e9369cf 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,13 +1,22 @@ using System.Collections.Generic; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations { + /// <summary> + /// Static class containing the default configuration options for the web server. + /// </summary> public static class ConfigurationOptions { - public static Dictionary<string, string> Configuration => new Dictionary<string, string> + /// <summary> + /// Gets a new copy of the default configuration options. + /// </summary> + public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string> { - { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, + { NoWebContentKey, bool.FalseString }, + { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" } }; diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 5f2d629fe..7c2b3cd5f 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -2,7 +2,9 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Plugins; +using Microsoft.Extensions.Configuration; namespace Emby.Server.Implementations.EntryPoints { @@ -36,7 +38,11 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (!_config.Configuration.IsStartupWizardCompleted) + if (_appHost.Resolve<IConfiguration>().IsNoWebContent()) + { + BrowserLauncher.OpenSwaggerPage(_appHost); + } + else if (!_config.Configuration.IsStartupWizardCompleted) { BrowserLauncher.OpenWebApp(_appHost); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 85602a67f..546a59517 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -30,6 +30,12 @@ namespace Emby.Server.Implementations.HttpServer { public class HttpListenerHost : IHttpServer, IDisposable { + /// <summary> + /// The key for a setting that specifies the default redirect path + /// to use for requests where the URL base prefix is invalid or missing. + /// </summary> + public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -58,7 +64,7 @@ namespace Emby.Server.Implementations.HttpServer _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration[DefaultRedirectKey]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index e9e852349..9450fee70 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -13,12 +13,13 @@ using System.Threading.Tasks; using CommandLine; using Emby.Drawing; using Emby.Server.Implementations; +using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Globalization; +using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -188,7 +189,7 @@ namespace Jellyfin.Server ServiceCollection serviceCollection = new ServiceCollection(); await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false); - var webHost = CreateWebHostBuilder(appHost, serviceCollection, appPaths).Build(); + var webHost = CreateWebHostBuilder(appHost, serviceCollection, startupConfig, appPaths).Build(); // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = webHost.Services; @@ -233,9 +234,13 @@ namespace Jellyfin.Server } } - private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection, IApplicationPaths appPaths) + private static IWebHostBuilder CreateWebHostBuilder( + ApplicationHost appHost, + IServiceCollection serviceCollection, + IConfiguration startupConfig, + IApplicationPaths appPaths) { - return new WebHostBuilder() + var webhostBuilder = new WebHostBuilder() .UseKestrel(options => { var addresses = appHost.ServerConfigurationManager @@ -275,13 +280,29 @@ namespace Jellyfin.Server }) .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths)) .UseSerilog() - .UseContentRoot(appHost.ContentRoot) .ConfigureServices(services => { // Merge the external ServiceCollection into ASP.NET DI services.TryAdd(serviceCollection); }) .UseStartup<Startup>(); + + if (!startupConfig.IsNoWebContent()) + { + // Fail startup if the web content does not exist + if (!Directory.Exists(appHost.ContentRoot) || !Directory.GetFiles(appHost.ContentRoot).Any()) + { + throw new InvalidOperationException( + "The server is expected to host web content, but the provided content directory is either " + + $"invalid or empty: {appHost.ContentRoot}. If you do not want to host web content with the " + + $"server, you may set the '{MediaBrowser.Controller.Extensions.ConfigurationExtensions.NoWebContentKey}' flag."); + } + + // Configure the web host to host the static web content + webhostBuilder.UseContentRoot(appHost.ContentRoot); + } + + return webhostBuilder; } /// <summary> @@ -398,9 +419,8 @@ namespace Jellyfin.Server // webDir // IF --webdir // ELSE IF $JELLYFIN_WEB_DIR - // ELSE use <bindir>/jellyfin-web + // ELSE <bindir>/jellyfin-web var webDir = options.WebDir; - if (string.IsNullOrEmpty(webDir)) { webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); @@ -480,9 +500,16 @@ namespace Jellyfin.Server private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths) { + // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content + var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; + if (string.IsNullOrEmpty(appPaths.WebPath)) + { + inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; + } + return config .SetBasePath(appPaths.ConfigurationDirectoryPath) - .AddInMemoryCollection(ConfigurationOptions.Configuration) + .AddInMemoryCollection(inMemoryDefaultConfig) .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true) .AddEnvironmentVariables("JELLYFIN_"); diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 76c9b4b26..1a9ac09ee 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.Extensions @@ -8,6 +9,11 @@ namespace MediaBrowser.Controller.Extensions public static class ConfigurationExtensions { /// <summary> + /// The key for a setting that indicates whether the application should host static web content. + /// </summary> + public const string NoWebContentKey = "nowebcontent"; + + /// <summary> /// The key for the FFmpeg probe size option. /// </summary> public const string FfmpegProbeSizeKey = "FFmpeg:probesize"; @@ -18,6 +24,16 @@ namespace MediaBrowser.Controller.Extensions public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; /// <summary> + /// Retrieves a config value indicating whether the application should not host + /// static web content from the <see cref="IConfiguration"/>. + /// </summary> + /// <param name="configuration">The configuration to retrieve the value from.</param> + /// <returns>The parsed config value.</returns> + /// <exception cref="FormatException">The config value is not a valid bool string. See <see cref="bool.Parse(string)"/>.</exception> + public static bool IsNoWebContent(this IConfiguration configuration) + => configuration.GetValue<bool>(NoWebContentKey); + + /// <summary> /// Retrieves the FFmpeg probe size from the <see cref="IConfiguration" />. /// </summary> /// <param name="configuration">This configuration.</param> diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 88e9055e8..bcca9e4a1 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -9,6 +9,7 @@ <ItemGroup> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.1" /> + <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.1" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index c8f18e69e..7a8a4d3a5 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -149,9 +149,9 @@ namespace MediaBrowser.Model.Configuration public bool EnableDashboardResponseCaching { get; set; } /// <summary> - /// Allows the dashboard to be served from a custom path. + /// Gets or sets a custom path to serve the dashboard from. /// </summary> - /// <value>The dashboard source path.</value> + /// <value>The dashboard source path, or null if the default path should be used.</value> public string DashboardSourcePath { get; set; } /// <summary> |
