Use the plugin builder website instead of docker to fetch plugins (#4285)

This commit is contained in:
Nicolas Dorier
2022-11-21 10:23:25 +09:00
committed by GitHub
parent ec76acd3a6
commit 20025f254c
10 changed files with 90 additions and 67 deletions

View File

@@ -140,14 +140,15 @@ namespace BTCPayServer.Configuration
} }
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true); DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins"); var pluginRemote = conf.GetOrDefault<string>("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(); RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r', '\n', '\t', ' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
CheatMode = conf.GetOrDefault("cheatmode", false); CheatMode = conf.GetOrDefault("cheatmode", false);
if (CheatMode && this.NetworkType == ChainName.Mainnet) if (CheatMode && this.NetworkType == ChainName.Mainnet)
throw new ConfigException($"cheatmode can't be used on mainnet"); throw new ConfigException($"cheatmode can't be used on mainnet");
} }
public string PluginRemote { get; set; }
public string[] RecommendedPlugins { get; set; } public string[] RecommendedPlugins { get; set; }
public bool CheatMode { get; set; } public bool CheatMode { get; set; }

View File

@@ -45,7 +45,7 @@ namespace BTCPayServer.Configuration
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue); 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("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
app.Option("--disable-registration", "Disables new user registrations (default:true)", 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("--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("--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); app.Option("--cheatmode", "Add some helper UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue);

View File

@@ -35,7 +35,6 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly ISettingsRepository _settingsRepository;
public GreenfieldStoreLightningNetworkPaymentMethodsController( public GreenfieldStoreLightningNetworkPaymentMethodsController(
StoreRepository storeRepository, StoreRepository storeRepository,
@@ -47,7 +46,6 @@ namespace BTCPayServer.Controllers.Greenfield
_storeRepository = storeRepository; _storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_settingsRepository = settingsRepository;
PoliciesSettings = policiesSettings; PoliciesSettings = policiesSettings;
} }

View File

@@ -83,11 +83,11 @@ namespace BTCPayServer.Controllers
[HttpPost("server/plugins/install")] [HttpPost("server/plugins/install")]
public async Task<IActionResult> InstallPlugin( public async Task<IActionResult> InstallPlugin(
[FromServices] PluginService pluginService, string plugin, bool update = false, string path ="") [FromServices] PluginService pluginService, string plugin, bool update = false, string version = null)
{ {
try try
{ {
await pluginService.DownloadRemotePlugin(plugin, path); await pluginService.DownloadRemotePlugin(plugin, version);
if (update) if (update)
{ {
pluginService.UpdatePlugin(plugin); pluginService.UpdatePlugin(plugin);

View File

@@ -87,6 +87,16 @@ namespace BTCPayServer.Hosting
{ {
httpClient.Timeout = Timeout.InfiniteTimeSpan; httpClient.Timeout = Timeout.InfiniteTimeSpan;
}); });
services.AddHttpClient<PluginBuilderClient>((prov, httpClient) =>
{
var p = prov.GetRequiredService<PoliciesSettings>();
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>(logs); services.AddSingleton<Logs>(logs);
services.AddSingleton<BTCPayNetworkJsonSerializerSettings>(); services.AddSingleton<BTCPayNetworkJsonSerializerSettings>();
@@ -263,7 +273,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton(o => configuration.ConfigureNetworkProvider(logs)); services.TryAddSingleton(o => configuration.ConfigureNetworkProvider(logs));
services.TryAddSingleton<AppService>(); services.TryAddSingleton<AppService>();
services.AddSingleton<PluginService>(); services.AddTransient<PluginService>();
services.AddSingleton<IPluginHookService, PluginHookService>(); services.AddSingleton<IPluginHookService, PluginHookService>();
services.TryAddTransient<Safe>(); services.TryAddTransient<Safe>();
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o => services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>

View File

@@ -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<PublishedVersion[]> GetPublishedVersions(string btcpayVersion, bool includePreRelease)
{
var result = await httpClient.GetStringAsync($"api/v1/plugins?btcpayVersion={btcpayVersion}&includePreRelease={includePreRelease}");
return JsonConvert.DeserializeObject<PublishedVersion[]>(result, serializerSettings) ?? throw new InvalidOperationException();
}
}
}

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
@@ -22,79 +23,40 @@ namespace BTCPayServer.Plugins
public class PluginService public class PluginService
{ {
private readonly IOptions<DataDirectories> _dataDirectories; private readonly IOptions<DataDirectories> _dataDirectories;
private readonly IMemoryCache _memoryCache; private readonly PoliciesSettings _policiesSettings;
private readonly ISettingsRepository _settingsRepository; private readonly ISettingsRepository _settingsRepository;
private readonly BTCPayServerOptions _btcPayServerOptions; private readonly PluginBuilderClient _pluginBuilderClient;
private readonly HttpClient _githubClient;
public PluginService( public PluginService(
ISettingsRepository settingsRepository, ISettingsRepository settingsRepository,
IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins, IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
IHttpClientFactory httpClientFactory, BTCPayServerOptions btcPayServerOptions, PluginBuilderClient pluginBuilderClient,
IOptions<DataDirectories> dataDirectories, IMemoryCache memoryCache) IOptions<DataDirectories> dataDirectories,
PoliciesSettings policiesSettings,
BTCPayServerEnvironment env)
{ {
LoadedPlugins = btcPayServerPlugins; LoadedPlugins = btcPayServerPlugins;
_githubClient = httpClientFactory.CreateClient(); _pluginBuilderClient = pluginBuilderClient;
_githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1"));
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
_btcPayServerOptions = btcPayServerOptions;
_dataDirectories = dataDirectories; _dataDirectories = dataDirectories;
_memoryCache = memoryCache; _policiesSettings = policiesSettings;
} Env = env;
private async Task<string> 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);
});
} }
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; } public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
public BTCPayServerEnvironment Env { get; }
public async Task<AvailablePlugin[]> GetRemotePlugins() public async Task<AvailablePlugin[]> GetRemotePlugins()
{ {
var resp = await CallHttpAndCache($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/git/trees/master?recursive=1"); var versions = await _pluginBuilderClient.GetPublishedVersions(Env.Version, _policiesSettings.PluginPreReleases);
return versions.Select(v => v.ManifestInfo.ToObject<AvailablePlugin>()).ToArray();
var respObj = JObject.Parse(resp)["tree"] as JArray;
var detectedPlugins = respObj.Where(token => token["path"].ToString().EndsWith(".btcpay", StringComparison.OrdinalIgnoreCase));
List<Task<AvailablePlugin>> result = new List<Task<AvailablePlugin>>();
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<string>());
var r = JsonConvert.DeserializeObject<AvailablePlugin>(Encoding.UTF8.GetString(content));
r.Path = $"https://raw.githubusercontent.com/{_btcPayServerOptions.PluginRemote}/master/{pluginName}";
return r;
}, TaskScheduler.Current));
}
return await Task.WhenAll(result);
} }
public async Task DownloadRemotePlugin(string plugin, string path) public async Task DownloadRemotePlugin(string pluginIdentifier, string version)
{ {
var dest = _dataDirectories.Value.PluginDir; var dest = _dataDirectories.Value.PluginDir;
var filedest = Path.Join(dest, pluginIdentifier + ".btcpay");
var filedest = Path.Join(dest, plugin+".btcpay");
Directory.CreateDirectory(Path.GetDirectoryName(filedest)); 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); using var fs = new FileStream(filedest, FileMode.Create, FileAccess.ReadWrite);
await resp2.Content.CopyToAsync(fs); await resp2.Content.CopyToAsync(fs);
await fs.FlushAsync(); await fs.FlushAsync();
@@ -130,7 +92,7 @@ namespace BTCPayServer.Plugins
PluginManager.QueueCommands(dest, ("delete", plugin)); PluginManager.QueueCommands(dest, ("delete", plugin));
} }
public class AvailablePlugin : IBTCPayServerPlugin public class AvailablePlugin
{ {
public string Identifier { get; set; } public string Identifier { get; set; }
public string Name { get; set; } public string Name { get; set; }
@@ -139,7 +101,6 @@ namespace BTCPayServer.Plugins
public bool SystemPlugin { get; set; } = false; public bool SystemPlugin { get; set; } = false;
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>(); public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
public string Path { get; set; }
public void Execute(IApplicationBuilder applicationBuilder, public void Execute(IApplicationBuilder applicationBuilder,
IServiceProvider applicationBuilderApplicationServices) IServiceProvider applicationBuilderApplicationServices)

View File

@@ -33,6 +33,13 @@ namespace BTCPayServer.Services
[Display(Name = "Disable non-admins access to the user creation API endpoint")] [Display(Name = "Disable non-admins access to the user creation API endpoint")]
public bool DisableNonAdminCreateUserApi { get; set; } 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; } public bool DisableSSHService { get; set; }
[Display(Name = "Display app on website root")] [Display(Name = "Display app on website root")]

View File

@@ -226,7 +226,7 @@
{ {
@if (updateAvailable && DependenciesMet(x.Dependencies)) @if (updateAvailable && DependenciesMet(x.Dependencies))
{ {
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-path="@x.Path" asp-route-update="true" class="me-3"> <form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@x.Version" asp-route-update="true" class="me-3">
<button type="submit" class="btn btn-secondary">Update</button> <button type="submit" class="btn btn-secondary">Update</button>
</form> </form>
} }
@@ -304,7 +304,7 @@
@* Don't show the "Install" button if plugin has been disabled *@ @* Don't show the "Install" button if plugin has been disabled *@
@if (!disabled) @if (!disabled)
{ {
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-path="@plugin.Path"> <form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version">
<button type="submit" class="btn btn-primary">Install</button> <button type="submit" class="btn btn-primary">Install</button>
</form> </form>
} }

View File

@@ -118,6 +118,21 @@
</div> </div>
</div> </div>
<h4 class="mb-3">Plugins</h4>
<div class="row">
<div class="col-12 col-lg-8">
<div class="form-group">
<label asp-for="PluginSource" class="form-label"></label>
<input asp-for="PluginSource" placeholder="@PoliciesSettings.DefaultPluginSource" class="form-control"/>
<span asp-validation-for="PluginSource" class="text-danger"></span>
</div>
<div class="form-check my-3">
<input asp-for="PluginPreReleases" type="checkbox" class="form-check-input"/>
<label asp-for="PluginPreReleases" class="form-check-label"></label>
</div>
</div>
</div>
<h4 class="mb-3">Customization Settings</h4> <h4 class="mb-3">Customization Settings</h4>
<div class="form-group"> <div class="form-group">