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; /// /// Creates a fake application pipeline that will only exist for as long as the main app is not started. /// public sealed class SetupServer : IDisposable { private IHost? _startupServer; private bool _disposed; /// /// Starts the Bind-All Setup aspcore server to provide a reflection on the current core setup. /// /// The networkmanager. /// The application paths. /// A Task. public async Task RunAsync(Func networkManagerFactory, IApplicationPaths applicationPaths) { ThrowIfDisposed(); _startupServer = Host.CreateDefaultBuilder() .UseConsoleLifetime() .ConfigureServices(serv => { serv.AddHealthChecks() .AddCheck("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("

Jellyfin Server still starting. Please wait.

"); var networkManager = networkManagerFactory(); if (networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress)) { context.Response.WriteAsync("

You can download the current logfiles here.

"); } return Task.CompletedTask; }); }); }) .Build(); await _startupServer.StartAsync().ConfigureAwait(false); } /// /// Stops the Setup server. /// /// A task. Duh. 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); } /// public void Dispose() { if (_disposed) { return; } _disposed = true; _startupServer?.Dispose(); } private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); } private class SetupHealthcheck : IHealthCheck { public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { return Task.FromResult(HealthCheckResult.Degraded("Server is still starting up.")); } } }