{
- !isDocker &&
-
- Install Latest
-
- }
-
- {
- isDocker &&
-
- An update is available. Please update your Docker image and re-create the container.
-
+ (updateMechanism === 'builtIn' || updateMechanism === 'script') && !isDocker ?
+
+ Install Latest
+ :
+
+
+
+
+
+ {externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external}
+
+
}
{
@@ -188,6 +200,7 @@ Updates.propTypes = {
items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
+ updateMechanism: PropTypes.string,
shortDateFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired
};
diff --git a/src/Lidarr.Api.V1/System/SystemModule.cs b/src/Lidarr.Api.V1/System/SystemModule.cs
index e157063b1..7b24f3e66 100644
--- a/src/Lidarr.Api.V1/System/SystemModule.cs
+++ b/src/Lidarr.Api.V1/System/SystemModule.cs
@@ -18,6 +18,7 @@ namespace Lidarr.Api.V1.System
private readonly IConfigFileProvider _configFileProvider;
private readonly IMainDatabase _database;
private readonly ILifecycleService _lifecycleService;
+ private readonly IDeploymentInfoProvider _deploymentInfoProvider;
public SystemModule(IAppFolderInfo appFolderInfo,
IRuntimeInfo runtimeInfo,
@@ -26,7 +27,8 @@ namespace Lidarr.Api.V1.System
IRouteCacheProvider routeCacheProvider,
IConfigFileProvider configFileProvider,
IMainDatabase database,
- ILifecycleService lifecycleService)
+ ILifecycleService lifecycleService,
+ IDeploymentInfoProvider deploymentInfoProvider)
: base("system")
{
_appFolderInfo = appFolderInfo;
@@ -37,6 +39,7 @@ namespace Lidarr.Api.V1.System
_configFileProvider = configFileProvider;
_database = database;
_lifecycleService = lifecycleService;
+ _deploymentInfoProvider = deploymentInfoProvider;
Get("/status", x => GetStatus());
Get("/routes", x => GetRoutes());
Post("/shutdown", x => Shutdown());
@@ -71,7 +74,10 @@ namespace Lidarr.Api.V1.System
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform,
- StartTime = _runtimeInfo.StartTime
+ StartTime = _runtimeInfo.StartTime,
+ PackageVersion = _deploymentInfoProvider.PackageVersion,
+ PackageAuthor = _deploymentInfoProvider.PackageAuthor,
+ PackageUpdateMechanism = _deploymentInfoProvider.PackageUpdateMechanism
};
}
diff --git a/src/NzbDrone.Core/Configuration/DeploymentInfoProvider.cs b/src/NzbDrone.Core/Configuration/DeploymentInfoProvider.cs
new file mode 100644
index 000000000..560699b4b
--- /dev/null
+++ b/src/NzbDrone.Core/Configuration/DeploymentInfoProvider.cs
@@ -0,0 +1,105 @@
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.Update;
+
+namespace NzbDrone.Core.Configuration
+{
+ public interface IDeploymentInfoProvider
+ {
+ string PackageVersion { get; }
+ string PackageAuthor { get; }
+ string PackageBranch { get; }
+ UpdateMechanism PackageUpdateMechanism { get; }
+
+ string ReleaseVersion { get; }
+ string ReleaseBranch { get; }
+
+ bool IsExternalUpdateMechanism { get; }
+ UpdateMechanism DefaultUpdateMechanism { get; }
+ string DefaultBranch { get; }
+ }
+
+ public class DeploymentInfoProvider : IDeploymentInfoProvider
+ {
+ public DeploymentInfoProvider(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
+ {
+ var bin = appFolderInfo.StartUpFolder;
+ var packageInfoPath = Path.Combine(bin, "..", "package_info");
+ var releaseInfoPath = Path.Combine(bin, "release_info");
+
+ PackageUpdateMechanism = UpdateMechanism.BuiltIn;
+ DefaultBranch = "aphrodite";
+
+ if (Path.GetFileName(bin) == "bin" && diskProvider.FileExists(packageInfoPath))
+ {
+ var data = diskProvider.ReadAllText(packageInfoPath);
+
+ PackageVersion = ReadValue(data, "PackageVersion");
+ PackageAuthor = ReadValue(data, "PackageAuthor");
+ PackageUpdateMechanism = ReadEnumValue(data, "UpdateMethod", UpdateMechanism.BuiltIn);
+ PackageBranch = ReadValue(data, "Branch");
+
+ ReleaseVersion = ReadValue(data, "ReleaseVersion");
+
+ if (PackageBranch.IsNotNullOrWhiteSpace())
+ {
+ DefaultBranch = PackageBranch;
+ }
+ }
+
+ if (diskProvider.FileExists(releaseInfoPath))
+ {
+ var data = diskProvider.ReadAllText(releaseInfoPath);
+
+ ReleaseVersion = ReadValue(data, "ReleaseVersion", ReleaseVersion);
+ ReleaseBranch = ReadValue(data, "Branch");
+
+ if (ReleaseBranch.IsNotNullOrWhiteSpace())
+ {
+ DefaultBranch = ReleaseBranch;
+ }
+ }
+
+ DefaultUpdateMechanism = PackageUpdateMechanism;
+ }
+
+ private static string ReadValue(string fileData, string key, string defaultValue = null)
+ {
+ var match = Regex.Match(fileData, "^" + key + "=(.*)$", RegexOptions.Multiline);
+ if (match.Success)
+ {
+ return match.Groups[1].Value.Trim();
+ }
+
+ return defaultValue;
+ }
+
+ private static T ReadEnumValue
(string fileData, string key, T defaultValue)
+ where T : struct
+ {
+ var value = ReadValue(fileData, key);
+ if (value != null && Enum.TryParse(value, true, out var result))
+ {
+ return result;
+ }
+
+ return defaultValue;
+ }
+
+ public string PackageVersion { get; private set; }
+ public string PackageAuthor { get; private set; }
+ public string PackageBranch { get; private set; }
+ public UpdateMechanism PackageUpdateMechanism { get; private set; }
+
+ public string ReleaseVersion { get; private set; }
+ public string ReleaseBranch { get; set; }
+
+ public bool IsExternalUpdateMechanism => PackageUpdateMechanism >= UpdateMechanism.External;
+ public UpdateMechanism DefaultUpdateMechanism { get; private set; }
+ public string DefaultBranch { get; private set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Update/ConfigureUpdateMechanism.cs b/src/NzbDrone.Core/Update/ConfigureUpdateMechanism.cs
new file mode 100644
index 000000000..79e91bce1
--- /dev/null
+++ b/src/NzbDrone.Core/Update/ConfigureUpdateMechanism.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NLog;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.Lifecycle;
+using NzbDrone.Core.Messaging.Events;
+
+namespace NzbDrone.Core.Update
+{
+ public interface IUpdaterConfigProvider
+ {
+ }
+
+ public class UpdaterConfigProvider : IUpdaterConfigProvider, IHandle
+ {
+ private readonly Logger _logger;
+ private readonly IConfigFileProvider _configFileProvider;
+ private readonly IDeploymentInfoProvider _deploymentInfoProvider;
+
+ public UpdaterConfigProvider(IDeploymentInfoProvider deploymentInfoProvider, IConfigFileProvider configFileProvider, Logger logger)
+ {
+ _deploymentInfoProvider = deploymentInfoProvider;
+ _configFileProvider = configFileProvider;
+ _logger = logger;
+ }
+
+ public void Handle(ApplicationStartedEvent message)
+ {
+ var updateMechanism = _configFileProvider.UpdateMechanism;
+ var packageUpdateMechanism = _deploymentInfoProvider.PackageUpdateMechanism;
+
+ var externalMechanisms = Enum.GetValues(typeof(UpdateMechanism))
+ .Cast()
+ .Where(v => v >= UpdateMechanism.External)
+ .ToArray();
+
+ foreach (var externalMechanism in externalMechanisms)
+ {
+ if ((packageUpdateMechanism != externalMechanism && updateMechanism == externalMechanism) ||
+ (packageUpdateMechanism == externalMechanism && updateMechanism == UpdateMechanism.BuiltIn))
+ {
+ _logger.Info("Update mechanism {0} not supported in the current configuration, changing to {1}.", updateMechanism, packageUpdateMechanism);
+ ChangeUpdateMechanism(packageUpdateMechanism);
+ break;
+ }
+ }
+
+ if (_deploymentInfoProvider.IsExternalUpdateMechanism)
+ {
+ var currentBranch = _configFileProvider.Branch;
+ var packageBranch = _deploymentInfoProvider.PackageBranch;
+ if (packageBranch.IsNotNullOrWhiteSpace() && packageBranch != currentBranch)
+ {
+ _logger.Info("External updater uses branch {0} instead of the currently selected {1}, changing to {0}.", packageBranch, currentBranch);
+ ChangeBranch(packageBranch);
+ }
+ }
+ }
+
+ private void ChangeUpdateMechanism(UpdateMechanism updateMechanism)
+ {
+ var config = new Dictionary
+ {
+ [nameof(_configFileProvider.UpdateMechanism)] = updateMechanism
+ };
+ _configFileProvider.SaveConfigDictionary(config);
+ }
+
+ private void ChangeBranch(string branch)
+ {
+ var config = new Dictionary
+ {
+ [nameof(_configFileProvider.Branch)] = branch
+ };
+ _configFileProvider.SaveConfigDictionary(config);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs
index 1746422fa..fbbf29346 100644
--- a/src/NzbDrone.Core/Update/InstallUpdateService.cs
+++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs
@@ -29,6 +29,7 @@ namespace NzbDrone.Core.Update
private readonly IProcessProvider _processProvider;
private readonly IVerifyUpdates _updateVerifier;
private readonly IStartupContext _startupContext;
+ private readonly IDeploymentInfoProvider _deploymentInfoProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IBackupService _backupService;
@@ -43,6 +44,7 @@ namespace NzbDrone.Core.Update
IProcessProvider processProvider,
IVerifyUpdates updateVerifier,
IStartupContext startupContext,
+ IDeploymentInfoProvider deploymentInfoProvider,
IConfigFileProvider configFileProvider,
IRuntimeInfo runtimeInfo,
IBackupService backupService,
@@ -63,6 +65,7 @@ namespace NzbDrone.Core.Update
_processProvider = processProvider;
_updateVerifier = updateVerifier;
_startupContext = startupContext;
+ _deploymentInfoProvider = deploymentInfoProvider;
_configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo;
_backupService = backupService;
@@ -237,6 +240,18 @@ namespace NzbDrone.Core.Update
return;
}
+ // Safety net, ConfigureUpdateMechanism should take care of invalid settings
+ if (_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _deploymentInfoProvider.IsExternalUpdateMechanism)
+ {
+ _logger.ProgressDebug("Built-In updater disabled, please use {0} to install", _deploymentInfoProvider.PackageUpdateMechanism);
+ return;
+ }
+ else if (_configFileProvider.UpdateMechanism != UpdateMechanism.Script && _deploymentInfoProvider.IsExternalUpdateMechanism)
+ {
+ _logger.ProgressDebug("Update available, please use {0} to install", _deploymentInfoProvider.PackageUpdateMechanism);
+ return;
+ }
+
try
{
InstallUpdate(latestAvailable);
diff --git a/src/NzbDrone.Core/Update/UpdateMechanism.cs b/src/NzbDrone.Core/Update/UpdateMechanism.cs
index 8b647a1e7..f87dca79c 100644
--- a/src/NzbDrone.Core/Update/UpdateMechanism.cs
+++ b/src/NzbDrone.Core/Update/UpdateMechanism.cs
@@ -1,8 +1,11 @@
-namespace NzbDrone.Core.Update
+namespace NzbDrone.Core.Update
{
public enum UpdateMechanism
{
BuiltIn = 0,
- Script = 1
+ Script = 1,
+ External = 10,
+ Apt = 11,
+ Docker = 12
}
}