SetupServer.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Jellyfin.Networking.Manager;
  8. using MediaBrowser.Common.Configuration;
  9. using MediaBrowser.Common.Net;
  10. using Microsoft.AspNetCore.Builder;
  11. using Microsoft.AspNetCore.Diagnostics.HealthChecks;
  12. using Microsoft.AspNetCore.Hosting;
  13. using Microsoft.AspNetCore.Http;
  14. using Microsoft.Extensions.DependencyInjection;
  15. using Microsoft.Extensions.Diagnostics.HealthChecks;
  16. using Microsoft.Extensions.Hosting;
  17. using SQLitePCL;
  18. namespace Jellyfin.Server.ServerSetupApp;
  19. /// <summary>
  20. /// Creates a fake application pipeline that will only exist for as long as the main app is not started.
  21. /// </summary>
  22. public sealed class SetupServer : IDisposable
  23. {
  24. private IHost? _startupServer;
  25. private bool _disposed;
  26. /// <summary>
  27. /// Starts the Bind-All Setup aspcore server to provide a reflection on the current core setup.
  28. /// </summary>
  29. /// <param name="networkManagerFactory">The networkmanager.</param>
  30. /// <param name="applicationPaths">The application paths.</param>
  31. /// <returns>A Task.</returns>
  32. public async Task RunAsync(Func<INetworkManager?> networkManagerFactory, IApplicationPaths applicationPaths)
  33. {
  34. ThrowIfDisposed();
  35. _startupServer = Host.CreateDefaultBuilder()
  36. .UseConsoleLifetime()
  37. .ConfigureServices(serv =>
  38. {
  39. serv.AddHealthChecks()
  40. .AddCheck<SetupHealthcheck>("StartupCheck");
  41. })
  42. .ConfigureWebHostDefaults(webHostBuilder =>
  43. {
  44. webHostBuilder
  45. .UseKestrel()
  46. .Configure(app =>
  47. {
  48. app.UseHealthChecks("/health");
  49. app.Map("/startup/logger", loggerRoute =>
  50. {
  51. loggerRoute.Run(async context =>
  52. {
  53. var networkManager = networkManagerFactory();
  54. if (context.Connection.RemoteIpAddress is null || networkManager is null || !networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress))
  55. {
  56. context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
  57. return;
  58. }
  59. var logfilePath = Directory.EnumerateFiles(applicationPaths.LogDirectoryPath).Select(e => new FileInfo(e)).OrderBy(f => f.CreationTimeUtc).FirstOrDefault()?.FullName;
  60. if (logfilePath is not null)
  61. {
  62. await context.Response.SendFileAsync(logfilePath, CancellationToken.None).ConfigureAwait(false);
  63. }
  64. });
  65. });
  66. app.Run((context) =>
  67. {
  68. context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
  69. context.Response.Headers.RetryAfter = new Microsoft.Extensions.Primitives.StringValues("60");
  70. context.Response.WriteAsync("<p>Jellyfin Server still starting. Please wait.</p>");
  71. var networkManager = networkManagerFactory();
  72. if (networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress))
  73. {
  74. context.Response.WriteAsync("<p>You can download the current logfiles <a href='/startup/logger'>here</a>.</p>");
  75. }
  76. return Task.CompletedTask;
  77. });
  78. });
  79. })
  80. .Build();
  81. await _startupServer.StartAsync().ConfigureAwait(false);
  82. }
  83. /// <summary>
  84. /// Stops the Setup server.
  85. /// </summary>
  86. /// <returns>A task. Duh.</returns>
  87. public async Task StopAsync()
  88. {
  89. ThrowIfDisposed();
  90. if (_startupServer is null)
  91. {
  92. throw new InvalidOperationException("Tried to stop a non existing startup server");
  93. }
  94. await _startupServer.StopAsync().ConfigureAwait(false);
  95. _startupServer.Dispose();
  96. }
  97. /// <inheritdoc/>
  98. public void Dispose()
  99. {
  100. if (_disposed)
  101. {
  102. return;
  103. }
  104. _disposed = true;
  105. _startupServer?.Dispose();
  106. }
  107. private void ThrowIfDisposed()
  108. {
  109. ObjectDisposedException.ThrowIf(_disposed, this);
  110. }
  111. private class SetupHealthcheck : IHealthCheck
  112. {
  113. public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
  114. {
  115. return Task.FromResult(HealthCheckResult.Degraded("Server is still starting up."));
  116. }
  117. }
  118. }