diff options
| author | JPVenson <github@jpb.email> | 2024-10-26 10:31:01 +0000 |
|---|---|---|
| committer | JPVenson <github@jpb.email> | 2024-10-26 10:31:01 +0000 |
| commit | 1e7acec01799e3cfe6fd2a8630dbd8f6e3338251 (patch) | |
| tree | f2b6c9461b6c506f0684690bfa35c0b3c41b8b9e /Jellyfin.Server/ServerSetupApp/SetupServer.cs | |
| parent | 3ceb8337e7c98fef47bd68753fbdf3b6ab9ecf5a (diff) | |
Added Setup overlay app to communicate status of startup
Diffstat (limited to 'Jellyfin.Server/ServerSetupApp/SetupServer.cs')
| -rw-r--r-- | Jellyfin.Server/ServerSetupApp/SetupServer.cs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/Jellyfin.Server/ServerSetupApp/SetupServer.cs b/Jellyfin.Server/ServerSetupApp/SetupServer.cs new file mode 100644 index 000000000..61fe0fdd8 --- /dev/null +++ b/Jellyfin.Server/ServerSetupApp/SetupServer.cs @@ -0,0 +1,131 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Networking.Manager; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using SQLitePCL; + +namespace Jellyfin.Server.ServerSetupApp; + +/// <summary> +/// Creates a fake application pipeline that will only exist for as long as the main app is not started. +/// </summary> +public sealed class SetupServer : IDisposable +{ + private IHost? _startupServer; + private bool _disposed; + + /// <summary> + /// Starts the Bind-All Setup aspcore server to provide a reflection on the current core setup. + /// </summary> + /// <param name="networkManagerFactory">The networkmanager.</param> + /// <param name="applicationPaths">The application paths.</param> + /// <returns>A Task.</returns> + public async Task RunAsync(Func<INetworkManager?> networkManagerFactory, IApplicationPaths applicationPaths) + { + ThrowIfDisposed(); + _startupServer = Host.CreateDefaultBuilder() + .UseConsoleLifetime() + .ConfigureServices(serv => + { + serv.AddHealthChecks() + .AddCheck<SetupHealthcheck>("StartupCheck"); + }) + .ConfigureWebHostDefaults(webHostBuilder => + { + webHostBuilder + .UseKestrel() + .Configure(app => + { + app.UseHealthChecks("/health"); + + app.Map("/startup/logger", loggerRoute => + { + loggerRoute.Run(async context => + { + var networkManager = networkManagerFactory(); + if (context.Connection.RemoteIpAddress is null || networkManager is null || !networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress)) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + + var logfilePath = Directory.EnumerateFiles(applicationPaths.LogDirectoryPath).Select(e => new FileInfo(e)).OrderBy(f => f.CreationTimeUtc).FirstOrDefault()?.FullName; + if (logfilePath is not null) + { + await context.Response.SendFileAsync(logfilePath, CancellationToken.None).ConfigureAwait(false); + } + }); + }); + + app.Run((context) => + { + context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + context.Response.Headers.RetryAfter = new Microsoft.Extensions.Primitives.StringValues("60"); + context.Response.WriteAsync("<p>Jellyfin Server still starting. Please wait.</p>"); + var networkManager = networkManagerFactory(); + if (networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress)) + { + context.Response.WriteAsync("<p>You can download the current logfiles <a href='/startup/logger'>here</a>.</p>"); + } + + return Task.CompletedTask; + }); + }); + }) + .Build(); + await _startupServer.StartAsync().ConfigureAwait(false); + } + + /// <summary> + /// Stops the Setup server. + /// </summary> + /// <returns>A task. Duh.</returns> + public async Task StopAsync() + { + ThrowIfDisposed(); + if (_startupServer is null) + { + throw new InvalidOperationException("Tried to stop a non existing startup server"); + } + + await _startupServer.StopAsync().ConfigureAwait(false); + _startupServer.Dispose(); + } + + /// <inheritdoc/> + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _startupServer?.Dispose(); + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_disposed, this); + } + + private class SetupHealthcheck : IHealthCheck + { + public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + return Task.FromResult(HealthCheckResult.Degraded("Server is still starting up.")); + } + } +} |
