From 20025f254c8a69777febd50d06acb6844df6d96e Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Mon, 21 Nov 2022 10:23:25 +0900 Subject: [PATCH] Use the plugin builder website instead of docker to fetch plugins (#4285) --- .../Configuration/BTCPayServerOptions.cs | 5 +- .../Configuration/DefaultConfiguration.cs | 2 +- ...ightningNetworkPaymentMethodsController.cs | 2 - .../Controllers/UIServerController.Plugins.cs | 4 +- BTCPayServer/Hosting/BTCPayServerServices.cs | 12 ++- BTCPayServer/Plugins/PluginBuilderClient.cs | 31 ++++++++ BTCPayServer/Plugins/PluginService.cs | 75 +++++-------------- BTCPayServer/Services/PoliciesSettings.cs | 7 ++ .../Views/UIServer/ListPlugins.cshtml | 4 +- BTCPayServer/Views/UIServer/Policies.cshtml | 15 ++++ 10 files changed, 90 insertions(+), 67 deletions(-) create mode 100644 BTCPayServer/Plugins/PluginBuilderClient.cs diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 200c578e4..0ebdeae0e 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -140,14 +140,15 @@ namespace BTCPayServer.Configuration } DisableRegistration = conf.GetOrDefault("disable-registration", true); - PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins"); + var pluginRemote = conf.GetOrDefault("plugin-remote", null); + if (pluginRemote != null) + Logs.Configuration.LogWarning("plugin-remote is an obsolete configuration setting, please remove it from configuration"); RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r', '\n', '\t', ' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray(); CheatMode = conf.GetOrDefault("cheatmode", false); if (CheatMode && this.NetworkType == ChainName.Mainnet) throw new ConfigException($"cheatmode can't be used on mainnet"); } - public string PluginRemote { get; set; } public string[] RecommendedPlugins { get; set; } public bool CheatMode { get; set; } diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index 37f472df5..c9bae9170 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -45,7 +45,7 @@ namespace BTCPayServer.Configuration app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue); app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue); app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue); - app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue); + app.Option("--plugin-remote", "Obsolete, do not use", CommandOptionType.SingleValue); app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue); app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue); app.Option("--cheatmode", "Add some helper UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue); diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreLightningNetworkPaymentMethodsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreLightningNetworkPaymentMethodsController.cs index 5e8a3ca1a..172931cb3 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreLightningNetworkPaymentMethodsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreLightningNetworkPaymentMethodsController.cs @@ -35,7 +35,6 @@ namespace BTCPayServer.Controllers.Greenfield private readonly StoreRepository _storeRepository; private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly IAuthorizationService _authorizationService; - private readonly ISettingsRepository _settingsRepository; public GreenfieldStoreLightningNetworkPaymentMethodsController( StoreRepository storeRepository, @@ -47,7 +46,6 @@ namespace BTCPayServer.Controllers.Greenfield _storeRepository = storeRepository; _btcPayNetworkProvider = btcPayNetworkProvider; _authorizationService = authorizationService; - _settingsRepository = settingsRepository; PoliciesSettings = policiesSettings; } diff --git a/BTCPayServer/Controllers/UIServerController.Plugins.cs b/BTCPayServer/Controllers/UIServerController.Plugins.cs index d4fb40118..7597225ad 100644 --- a/BTCPayServer/Controllers/UIServerController.Plugins.cs +++ b/BTCPayServer/Controllers/UIServerController.Plugins.cs @@ -83,11 +83,11 @@ namespace BTCPayServer.Controllers [HttpPost("server/plugins/install")] public async Task InstallPlugin( - [FromServices] PluginService pluginService, string plugin, bool update = false, string path ="") + [FromServices] PluginService pluginService, string plugin, bool update = false, string version = null) { try { - await pluginService.DownloadRemotePlugin(plugin, path); + await pluginService.DownloadRemotePlugin(plugin, version); if (update) { pluginService.UpdatePlugin(plugin); diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 62e96ee84..33b7cac17 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -87,6 +87,16 @@ namespace BTCPayServer.Hosting { httpClient.Timeout = Timeout.InfiniteTimeSpan; }); + services.AddHttpClient((prov, httpClient) => + { + var p = prov.GetRequiredService(); + var pluginSource = p.PluginSource ?? PoliciesSettings.DefaultPluginSource; + if (pluginSource.EndsWith('/')) + pluginSource = pluginSource.Substring(0, pluginSource.Length - 1); + if (!Uri.TryCreate(pluginSource, UriKind.Absolute, out var r) || (r.Scheme != "https" && r.Scheme != "http")) + r = new Uri(PoliciesSettings.DefaultPluginSource, UriKind.Absolute); + httpClient.BaseAddress = r; + }); services.AddSingleton(logs); services.AddSingleton(); @@ -263,7 +273,7 @@ namespace BTCPayServer.Hosting services.TryAddSingleton(o => configuration.ConfigureNetworkProvider(logs)); services.TryAddSingleton(); - services.AddSingleton(); + services.AddTransient(); services.AddSingleton(); services.TryAddTransient(); services.TryAddSingleton(o => diff --git a/BTCPayServer/Plugins/PluginBuilderClient.cs b/BTCPayServer/Plugins/PluginBuilderClient.cs new file mode 100644 index 000000000..20d27166b --- /dev/null +++ b/BTCPayServer/Plugins/PluginBuilderClient.cs @@ -0,0 +1,31 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Plugins +{ + public class PublishedVersion + { + public string ProjectSlug { get; set; } + public long BuildId { get; set; } + public JObject BuildInfo { get; set; } + public JObject ManifestInfo { get; set; } + } + public class PluginBuilderClient + { + HttpClient httpClient; + public HttpClient HttpClient => httpClient; + public PluginBuilderClient(HttpClient httpClient) + { + this.httpClient = httpClient; + } + static JsonSerializerSettings serializerSettings = new JsonSerializerSettings() { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() }; + public async Task GetPublishedVersions(string btcpayVersion, bool includePreRelease) + { + var result = await httpClient.GetStringAsync($"api/v1/plugins?btcpayVersion={btcpayVersion}&includePreRelease={includePreRelease}"); + return JsonConvert.DeserializeObject(result, serializerSettings) ?? throw new InvalidOperationException(); + } + } +} diff --git a/BTCPayServer/Plugins/PluginService.cs b/BTCPayServer/Plugins/PluginService.cs index 33590cbf4..0b90b1bd0 100644 --- a/BTCPayServer/Plugins/PluginService.cs +++ b/BTCPayServer/Plugins/PluginService.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading.Tasks; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Configuration; +using BTCPayServer.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; @@ -22,79 +23,40 @@ namespace BTCPayServer.Plugins public class PluginService { private readonly IOptions _dataDirectories; - private readonly IMemoryCache _memoryCache; + private readonly PoliciesSettings _policiesSettings; private readonly ISettingsRepository _settingsRepository; - private readonly BTCPayServerOptions _btcPayServerOptions; - private readonly HttpClient _githubClient; + private readonly PluginBuilderClient _pluginBuilderClient; public PluginService( ISettingsRepository settingsRepository, IEnumerable btcPayServerPlugins, - IHttpClientFactory httpClientFactory, BTCPayServerOptions btcPayServerOptions, - IOptions dataDirectories, IMemoryCache memoryCache) + PluginBuilderClient pluginBuilderClient, + IOptions dataDirectories, + PoliciesSettings policiesSettings, + BTCPayServerEnvironment env) { LoadedPlugins = btcPayServerPlugins; - _githubClient = httpClientFactory.CreateClient(); - _githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1")); + _pluginBuilderClient = pluginBuilderClient; _settingsRepository = settingsRepository; - _btcPayServerOptions = btcPayServerOptions; _dataDirectories = dataDirectories; - _memoryCache = memoryCache; - } - - private async Task CallHttpAndCache(string uri) - { - var cacheTime = TimeSpan.FromMinutes(30); - return await _memoryCache.GetOrCreateAsync(nameof(PluginService) + uri, async entry => - { - entry.AbsoluteExpiration = DateTimeOffset.UtcNow + cacheTime; - return await _githubClient.GetStringAsync(uri); - }); + _policiesSettings = policiesSettings; + Env = env; } public IEnumerable LoadedPlugins { get; } + public BTCPayServerEnvironment Env { get; } public async Task GetRemotePlugins() { - var resp = await CallHttpAndCache($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/git/trees/master?recursive=1"); - - var respObj = JObject.Parse(resp)["tree"] as JArray; - - var detectedPlugins = respObj.Where(token => token["path"].ToString().EndsWith(".btcpay", StringComparison.OrdinalIgnoreCase)); - - List> result = new List>(); - foreach (JToken detectedPlugin in detectedPlugins) - { - var pluginName = detectedPlugin["path"].ToString(); - - var metadata = respObj.SingleOrDefault(token => (pluginName + ".json")== token["path"].ToString()); - if (metadata is null) - { - continue; - } - result.Add( CallHttpAndCache(metadata["url"].ToString()) - .ContinueWith( - task => - { - var d = JObject.Parse(task.Result); - - var content = Encoders.Base64.DecodeData(d["content"].Value()); - - var r = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(content)); - r.Path = $"https://raw.githubusercontent.com/{_btcPayServerOptions.PluginRemote}/master/{pluginName}"; - return r; - }, TaskScheduler.Current)); - - } - - return await Task.WhenAll(result); + var versions = await _pluginBuilderClient.GetPublishedVersions(Env.Version, _policiesSettings.PluginPreReleases); + return versions.Select(v => v.ManifestInfo.ToObject()).ToArray(); } - public async Task DownloadRemotePlugin(string plugin, string path) + public async Task DownloadRemotePlugin(string pluginIdentifier, string version) { var dest = _dataDirectories.Value.PluginDir; - - var filedest = Path.Join(dest, plugin+".btcpay"); + var filedest = Path.Join(dest, pluginIdentifier + ".btcpay"); Directory.CreateDirectory(Path.GetDirectoryName(filedest)); - using var resp2 = await _githubClient.GetAsync(path); + var url = $"api/v1/plugins/[{Uri.EscapeDataString(pluginIdentifier)}]/versions/{Uri.EscapeDataString(version)}/download"; + using var resp2 = await _pluginBuilderClient.HttpClient.GetAsync(url); using var fs = new FileStream(filedest, FileMode.Create, FileAccess.ReadWrite); await resp2.Content.CopyToAsync(fs); await fs.FlushAsync(); @@ -130,7 +92,7 @@ namespace BTCPayServer.Plugins PluginManager.QueueCommands(dest, ("delete", plugin)); } - public class AvailablePlugin : IBTCPayServerPlugin + public class AvailablePlugin { public string Identifier { get; set; } public string Name { get; set; } @@ -139,7 +101,6 @@ namespace BTCPayServer.Plugins public bool SystemPlugin { get; set; } = false; public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty(); - public string Path { get; set; } public void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices) diff --git a/BTCPayServer/Services/PoliciesSettings.cs b/BTCPayServer/Services/PoliciesSettings.cs index c8f2bbd1b..f0f9d3619 100644 --- a/BTCPayServer/Services/PoliciesSettings.cs +++ b/BTCPayServer/Services/PoliciesSettings.cs @@ -33,6 +33,13 @@ namespace BTCPayServer.Services [Display(Name = "Disable non-admins access to the user creation API endpoint")] public bool DisableNonAdminCreateUserApi { get; set; } + public const string DefaultPluginSource = "https://plugin-builder.btcpayserver.org"; + [UriAttribute] + [Display(Name = "Plugin server")] + public string PluginSource { get; set; } + [Display(Name = "Show plugins in pre-release")] + public bool PluginPreReleases { get; set; } + public bool DisableSSHService { get; set; } [Display(Name = "Display app on website root")] diff --git a/BTCPayServer/Views/UIServer/ListPlugins.cshtml b/BTCPayServer/Views/UIServer/ListPlugins.cshtml index 8781c37e1..12abea3be 100644 --- a/BTCPayServer/Views/UIServer/ListPlugins.cshtml +++ b/BTCPayServer/Views/UIServer/ListPlugins.cshtml @@ -226,7 +226,7 @@ { @if (updateAvailable && DependenciesMet(x.Dependencies)) { -
+
} @@ -304,7 +304,7 @@ @* Don't show the "Install" button if plugin has been disabled *@ @if (!disabled) { -
+
} diff --git a/BTCPayServer/Views/UIServer/Policies.cshtml b/BTCPayServer/Views/UIServer/Policies.cshtml index cbb656637..aa6a634ac 100644 --- a/BTCPayServer/Views/UIServer/Policies.cshtml +++ b/BTCPayServer/Views/UIServer/Policies.cshtml @@ -118,6 +118,21 @@ +

Plugins

+
+
+
+ + + +
+
+ + +
+
+
+

Customization Settings