diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index dea9b6682a..ff7ee085f8 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
{
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
- { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index 0b9f805389..e7e72c686b 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -36,11 +36,6 @@ namespace Emby.Server.Implementations
///
string RestartArgs { get; }
- ///
- /// Gets the value of the --plugin-manifest-url command line option.
- ///
- string PluginManifestUrl { get; }
-
///
/// Gets the value of the --published-server-url command line option.
///
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 80326fddf2..b1bab8cdd2 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -19,6 +19,7 @@ using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Configuration;
@@ -31,11 +32,6 @@ namespace Emby.Server.Implementations.Updates
///
public class InstallationManager : IInstallationManager
{
- ///
- /// The key for a setting that specifies a URL for the plugin repository JSON manifest.
- ///
- public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
-
///
/// The logger.
///
@@ -122,16 +118,14 @@ namespace Emby.Server.Implementations.Updates
public IEnumerable CompletedInstallations => _completedInstallationsInternal;
///
- public async Task> GetAvailablePackages(CancellationToken cancellationToken = default)
+ public async Task> GetPackages(string manifest, CancellationToken cancellationToken = default)
{
- var manifestUrl = _appConfig.GetValue(PluginManifestUrlKey);
-
try
{
using (var response = await _httpClient.SendAsync(
new HttpRequestOptions
{
- Url = manifestUrl,
+ Url = manifest,
CancellationToken = cancellationToken,
CacheMode = CacheMode.Unconditional,
CacheLength = TimeSpan.FromMinutes(3)
@@ -145,25 +139,35 @@ namespace Emby.Server.Implementations.Updates
}
catch (SerializationException ex)
{
- const string LogTemplate =
- "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
- "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
- PluginManifestUrlKey + ", please ensure that it is correct.";
- _logger.LogError(ex, LogTemplate, manifestUrl);
- throw;
+ _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
+ return Enumerable.Empty();
}
}
}
catch (UriFormatException ex)
{
- const string LogTemplate =
- "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
- "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
- _logger.LogError(ex, LogTemplate, manifestUrl);
- throw;
+ _logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest);
+ return Enumerable.Empty();
+ }
+ catch (HttpException ex)
+ {
+ _logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
+ return Enumerable.Empty();
}
}
+ ///
+ public async Task> GetAvailablePackages(CancellationToken cancellationToken = default)
+ {
+ var result = new List();
+ foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories)
+ {
+ result.AddRange(await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true));
+ }
+
+ return result.AsReadOnly();
+ }
+
///
public IEnumerable FilterPackages(
IEnumerable availablePackages,
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index c98263223f..d633c554de 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.CreateUserLoggingConfigFile),
typeof(Routines.MigrateActivityLogDb),
typeof(Routines.RemoveDuplicateExtras),
+ typeof(Routines.AddDefaultPluginRepository),
typeof(Routines.MigrateUserDb)
};
diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
new file mode 100644
index 0000000000..a9d5ad16a1
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
@@ -0,0 +1,42 @@
+using System;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Updates;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ ///
+ /// Migration to initialize system configuration with the default plugin repository.
+ ///
+ public class AddDefaultPluginRepository : IMigrationRoutine
+ {
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+
+ private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
+ {
+ Name = "Jellyfin Stable",
+ Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
+ };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ public AddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
+ {
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
+ ///
+ public Guid Id => Guid.Parse("EB58EBEE-9514-4B9B-8225-12E1A40020DF");
+
+ ///
+ public string Name => "AddDefaultPluginRepository";
+
+ ///
+ public void Perform()
+ {
+ _serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
+ _serverConfigurationManager.SaveConfiguration();
+ }
+ }
+}
diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs
index cc250b06e2..a26114e778 100644
--- a/Jellyfin.Server/StartupOptions.cs
+++ b/Jellyfin.Server/StartupOptions.cs
@@ -79,10 +79,6 @@ namespace Jellyfin.Server
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
public string? RestartArgs { get; set; }
- ///
- [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
- public string? PluginManifestUrl { get; set; }
-
///
[Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")]
public Uri? PublishedServerUrl { get; set; }
@@ -95,11 +91,6 @@ namespace Jellyfin.Server
{
var config = new Dictionary();
- if (PluginManifestUrl != null)
- {
- config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
- }
-
if (NoWebClient)
{
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index a63d06ad5f..a84556fcc4 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -13,6 +13,18 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
{
+ [Route("/Repositories", "GET", Summary = "Gets all package repositories")]
+ [Authenticated]
+ public class GetRepositories : IReturnVoid
+ {
+ }
+
+ [Route("/Repositories", "POST", Summary = "Sets the enabled and existing package repositories")]
+ [Authenticated]
+ public class SetRepositories : List, IReturnVoid
+ {
+ }
+
///
/// Class GetPackage.
///
@@ -94,6 +106,7 @@ namespace MediaBrowser.Api
public class PackageService : BaseApiService
{
private readonly IInstallationManager _installationManager;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
public PackageService(
ILogger logger,
@@ -103,6 +116,19 @@ namespace MediaBrowser.Api
: base(logger, serverConfigurationManager, httpResultFactory)
{
_installationManager = installationManager;
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
+ public object Get(GetRepositories request)
+ {
+ var result = _serverConfigurationManager.Configuration.PluginRepositories;
+ return ToOptimizedResult(result);
+ }
+
+ public void Post(SetRepositories request)
+ {
+ _serverConfigurationManager.Configuration.PluginRepositories = request;
+ _serverConfigurationManager.SaveConfiguration();
}
///
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index 965ffe0ec2..a5025aee96 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -40,6 +40,14 @@ namespace MediaBrowser.Common.Updates
///
IEnumerable CompletedInstallations { get; }
+ ///
+ /// Parses a plugin manifest at the supplied URL.
+ ///
+ /// The URL to query.
+ /// The cancellation token.
+ /// Task{IReadOnlyList{PackageInfo}}.
+ Task> GetPackages(string manifest, CancellationToken cancellationToken = default);
+
///
/// Gets all available packages.
///
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 7428876204..b87c8fbab2 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -2,7 +2,9 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Generic;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Updates;
namespace MediaBrowser.Model.Configuration
{
@@ -229,6 +231,8 @@ namespace MediaBrowser.Model.Configuration
public string[] CodecsUsed { get; set; }
+ public List PluginRepositories { get; set; }
+
public bool IgnoreVirtualInterfaces { get; set; }
public bool EnableExternalContentInSuggestions { get; set; }
diff --git a/MediaBrowser.Model/Updates/RepositoryInfo.cs b/MediaBrowser.Model/Updates/RepositoryInfo.cs
new file mode 100644
index 0000000000..905327c368
--- /dev/null
+++ b/MediaBrowser.Model/Updates/RepositoryInfo.cs
@@ -0,0 +1,23 @@
+#nullable disable
+using System;
+
+namespace MediaBrowser.Model.Updates
+{
+ ///
+ /// Class RepositoryInfo.
+ ///
+ public class RepositoryInfo
+ {
+ ///
+ /// Gets or sets the name.
+ ///
+ /// The name.
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the URL.
+ ///
+ /// The URL.
+ public string Url { get; set; }
+ }
+}