diff options
Diffstat (limited to 'Jellyfin.Server/Migrations')
3 files changed, 199 insertions, 9 deletions
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 7365c8dbc..e9a45c140 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -1,6 +1,11 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using Emby.Server.Implementations; +using Emby.Server.Implementations.Serialization; using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -12,6 +17,14 @@ namespace Jellyfin.Server.Migrations public sealed class MigrationRunner { /// <summary> + /// The list of known pre-startup migrations, in order of applicability. + /// </summary> + private static readonly Type[] _preStartupMigrationTypes = + { + typeof(PreStartupRoutines.CreateNetworkConfiguration) + }; + + /// <summary> /// The list of known migrations, in order of applicability. /// </summary> private static readonly Type[] _migrationTypes = @@ -41,17 +54,55 @@ namespace Jellyfin.Server.Migrations .Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m)) .OfType<IMigrationRoutine>() .ToArray(); + var migrationOptions = host.ConfigurationManager.GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey); + HandleStartupWizardCondition(migrations, migrationOptions, host.ConfigurationManager.Configuration.IsStartupWizardCompleted, logger); + PerformMigrations(migrations, migrationOptions, options => host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, options), logger); + } - if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) + /// <summary> + /// Run all needed pre-startup migrations. + /// </summary> + /// <param name="appPaths">Application paths.</param> + /// <param name="loggerFactory">Factory for making the logger.</param> + public static void RunPreStartup(ServerApplicationPaths appPaths, ILoggerFactory loggerFactory) + { + var logger = loggerFactory.CreateLogger<MigrationRunner>(); + var migrations = _preStartupMigrationTypes + .Select(m => Activator.CreateInstance(m, appPaths, loggerFactory)) + .OfType<IMigrationRoutine>() + .ToArray(); + + var xmlSerializer = new MyXmlSerializer(); + var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); + var migrationOptions = File.Exists(migrationConfigPath) + ? (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)! + : new MigrationOptions(); + + // We have to deserialize it manually since the configuration manager may overwrite it + var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath) + ? (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)! + : new ServerConfiguration(); + + HandleStartupWizardCondition(migrations, migrationOptions, serverConfig.IsStartupWizardCompleted, logger); + PerformMigrations(migrations, migrationOptions, options => xmlSerializer.SerializeToFile(options, migrationConfigPath), logger); + } + + private static void HandleStartupWizardCondition(IEnumerable<IMigrationRoutine> migrations, MigrationOptions migrationOptions, bool isStartWizardCompleted, ILogger logger) + { + if (isStartWizardCompleted || migrationOptions.Applied.Count != 0) { - // If startup wizard is not finished, this is a fresh install. - // Don't run any migrations, just mark all of them as applied. - logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); - migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name))); - host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + return; } + // If startup wizard is not finished, this is a fresh install. + var onlyOldInstalls = migrations.Where(m => !m.PerformOnNewInstall).ToArray(); + logger.LogInformation("Marking following migrations as applied because this is a fresh install: {@OnlyOldInstalls}", onlyOldInstalls.Select(m => m.Name)); + migrationOptions.Applied.AddRange(onlyOldInstalls.Select(m => (m.Id, m.Name))); + } + + private static void PerformMigrations(IMigrationRoutine[] migrations, MigrationOptions migrationOptions, Action<MigrationOptions> saveConfiguration, ILogger logger) + { var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); for (var i = 0; i < migrations.Length; i++) @@ -78,7 +129,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name)); - host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + saveConfiguration(migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs new file mode 100644 index 000000000..5e601ca84 --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -0,0 +1,138 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// <inheritdoc /> +public class CreateNetworkConfiguration : IMigrationRoutine +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger<CreateNetworkConfiguration> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="CreateNetworkConfiguration"/> class. + /// </summary> + /// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param> + /// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param> + public CreateNetworkConfiguration(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger<CreateNetworkConfiguration>(); + } + + /// <inheritdoc /> + public Guid Id => Guid.Parse("9B354818-94D5-4B68-AC49-E35CB85F9D84"); + + /// <inheritdoc /> + public string Name => nameof(CreateNetworkConfiguration); + + /// <inheritdoc /> + public bool PerformOnNewInstall => false; + + /// <inheritdoc /> + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "network.xml"); + if (File.Exists(path)) + { + _logger.LogDebug("Network configuration file already exists, skipping"); + return; + } + + var serverConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("ServerConfiguration")); + using var xmlReader = XmlReader.Create(_applicationPaths.SystemConfigurationFilePath); + var networkSettings = serverConfigSerializer.Deserialize(xmlReader); + + var networkConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("NetworkConfiguration")); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkSettings); + } + +#pragma warning disable + public sealed class OldNetworkConfiguration + { + public const int DefaultHttpPort = 8096; + + public const int DefaultHttpsPort = 8920; + + private string _baseUrl = string.Empty; + + public bool RequireHttps { get; set; } + + public string CertificatePath { get; set; } = string.Empty; + + public string CertificatePassword { get; set; } = string.Empty; + + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[^1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + public int HttpServerPortNumber { get; set; } = DefaultHttpPort; + + public int HttpsPortNumber { get; set; } = DefaultHttpsPort; + + public bool EnableHttps { get; set; } + + public int PublicPort { get; set; } = DefaultHttpPort; + + public bool EnableIPV6 { get; set; } + + public bool EnableIPV4 { get; set; } = true; + + public bool IgnoreVirtualInterfaces { get; set; } = true; + + public string VirtualInterfaceNames { get; set; } = "vEthernet*"; + + public bool TrustAllIP6Interfaces { get; set; } + + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>(); + + public string[] RemoteIPFilter { get; set; } = Array.Empty<string>(); + + public bool IsRemoteIPFilterBlacklist { get; set; } + + public bool EnableUPnP { get; set; } + + public bool EnableRemoteAccess { get; set; } = true; + + public string[] LocalNetworkSubnets { get; set; } = Array.Empty<string>(); + + public string[] LocalNetworkAddresses { get; set; } = Array.Empty<string>(); + + public string[] KnownProxies { get; set; } = Array.Empty<string>(); + } +#pragma warning restore +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 6ff59626d..74f2349f5 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -9,7 +9,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Dto; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -114,13 +114,14 @@ namespace Jellyfin.Server.Migrations.Routines } var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version) + && !string.IsNullOrEmpty(version) ? chromecastDict[version] : ChromecastVersion.Stable; dto.CustomPrefs.Remove("chromecastVersion"); var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client) { - IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null, + IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : null, ShowBackdrop = dto.ShowBackdrop, ShowSidebar = dto.ShowSidebar, ScrollDirection = dto.ScrollDirection, |
