mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-20 10:40:29 +01:00
Merge pull request #1788 from btcpayserver/feat/new-version-check
Adding HostedService that checks for new BTCPayServer version on GitHub once a day
This commit is contained in:
commit
e399815427
@ -3103,5 +3103,61 @@ namespace BTCPayServer.Tests
|
||||
.GetResult())
|
||||
.Where(i => i.GetAddress() == h).Any();
|
||||
}
|
||||
|
||||
|
||||
class MockVersionFetcher : IVersionFetcher
|
||||
{
|
||||
public const string MOCK_NEW_VERSION = "9.9.9.9";
|
||||
public Task<string> Fetch(CancellationToken cancellation)
|
||||
{
|
||||
return Task.FromResult(MOCK_NEW_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCheckForNewVersion()
|
||||
{
|
||||
using (var tester = ServerTester.Create(newDb: true))
|
||||
{
|
||||
await tester.StartAsync();
|
||||
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess(true);
|
||||
|
||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||
await settings.UpdateSetting<PoliciesSettings>(new PoliciesSettings() { CheckForNewVersions = true });
|
||||
|
||||
var mockEnv = tester.PayTester.GetService<BTCPayServerEnvironment>();
|
||||
var mockSender = tester.PayTester.GetService<Services.Notifications.NotificationSender>();
|
||||
|
||||
var svc = new NewVersionCheckerHostedService(settings, mockEnv, mockSender, new MockVersionFetcher());
|
||||
await svc.ProcessVersionCheck();
|
||||
|
||||
// since last version present in database was null, it should've been updated with version mock returned
|
||||
var lastVersion = await settings.GetSettingAsync<NewVersionCheckerDataHolder>();
|
||||
Assert.Equal(MockVersionFetcher.MOCK_NEW_VERSION, lastVersion.LastVersion);
|
||||
|
||||
// we should also have notification in UI
|
||||
var ctrl = acc.GetController<NotificationsController>();
|
||||
var newVersion = MockVersionFetcher.MOCK_NEW_VERSION;
|
||||
|
||||
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
||||
Assert.IsType<ViewResult>(ctrl.Index()).Model);
|
||||
|
||||
Assert.True(vm.Skip == 0);
|
||||
Assert.True(vm.Count == 50);
|
||||
Assert.True(vm.Total == 1);
|
||||
Assert.True(vm.Items.Count == 1);
|
||||
|
||||
var fn = vm.Items.First();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
Assert.True(fn.Created >= now.AddSeconds(-3));
|
||||
Assert.True(fn.Created <= now);
|
||||
Assert.Equal($"New version {newVersion} released!", fn.Body);
|
||||
Assert.Equal($"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}", fn.ActionLink);
|
||||
Assert.False(fn.Seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +176,7 @@ namespace BTCPayServer.Configuration
|
||||
SocksEndpoint = endpoint;
|
||||
}
|
||||
|
||||
UpdateUrl = conf.GetOrDefault<Uri>("updateurl", null);
|
||||
|
||||
var sshSettings = ParseSSHConfiguration(conf);
|
||||
if ((!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile)) && !string.IsNullOrEmpty(sshSettings.Server))
|
||||
@ -301,5 +302,6 @@ namespace BTCPayServer.Configuration
|
||||
set;
|
||||
}
|
||||
public string TorrcFile { get; set; }
|
||||
public Uri UpdateUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
|
||||
app.Option("--torrcfile", "Path to torrc file containing hidden services directories (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--socksendpoint", "Socks endpoint to connect to onion urls (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--updateurl", $"Url used for once a day new release version check. Check performed only if value is not empty (default: empty)", 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("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
|
||||
|
@ -443,13 +443,8 @@ namespace BTCPayServer.Controllers
|
||||
var settings = await _SettingsRepository.GetSettingAsync<ThemeSettings>();
|
||||
settings.FirstRun = false;
|
||||
await _SettingsRepository.UpdateSetting<ThemeSettings>(settings);
|
||||
if (_Options.DisableRegistration)
|
||||
{
|
||||
// Once the admin user has been created lock subsequent user registrations (needs to be disabled for unit tests that require multiple users).
|
||||
Logs.PayServer.LogInformation("First admin created, disabling subscription (disable-registration is set to true)");
|
||||
policies.LockSubscription = true;
|
||||
await _SettingsRepository.UpdateSetting(policies);
|
||||
}
|
||||
|
||||
await _SettingsRepository.FirstAdminRegistered(policies, _Options.UpdateUrl != null, _Options.DisableRegistration);
|
||||
RegisteredAdmin = true;
|
||||
}
|
||||
|
||||
@ -626,7 +621,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private bool CanLoginOrRegister()
|
||||
{
|
||||
return _btcPayServerEnvironment.IsDevelopping || _btcPayServerEnvironment.IsSecure;
|
||||
return _btcPayServerEnvironment.IsDeveloping || _btcPayServerEnvironment.IsSecure;
|
||||
}
|
||||
|
||||
private void SetInsecureFlags()
|
||||
|
@ -288,7 +288,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
protected bool CanUseInternalLightning(bool doingAdminThings)
|
||||
{
|
||||
return (_btcPayServerEnvironment.IsDevelopping || User.IsInRole(Roles.ServerAdmin) ||
|
||||
return (_btcPayServerEnvironment.IsDeveloping || User.IsInRole(Roles.ServerAdmin) ||
|
||||
(_cssThemeManager.AllowLightningInternalNodeForAll && !doingAdminThings));
|
||||
}
|
||||
|
||||
|
@ -148,13 +148,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||
if (!anyAdmin)
|
||||
{
|
||||
if (_options.DisableRegistration)
|
||||
{
|
||||
// automatically lock subscriptions now that we have our first admin
|
||||
Logs.PayServer.LogInformation("First admin created, disabling subscription (disable-registration is set to true)");
|
||||
policies.LockSubscription = true;
|
||||
await _settingsRepository.UpdateSetting(policies);
|
||||
}
|
||||
await _settingsRepository.FirstAdminRegistered(policies, _options.UpdateUrl != null, _options.DisableRegistration);
|
||||
}
|
||||
}
|
||||
_eventAggregator.Publish(new UserRegisteredEvent() { RequestUri = Request.GetAbsoluteRootUri(), User = user, Admin = request.IsAdministrator is true });
|
||||
|
@ -172,7 +172,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private bool CanUseInternalLightning()
|
||||
{
|
||||
return (_BTCPayEnv.IsDevelopping || User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll);
|
||||
return (_BTCPayEnv.IsDeveloping || User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
BTCPayServer/Extensions/ActionLogicExtensions.cs
Normal file
39
BTCPayServer/Extensions/ActionLogicExtensions.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
// All logic that would otherwise be duplicated across solution goes into this utility class
|
||||
// ~If~ Once this starts growing out of control, begin extracting action logic classes out of here
|
||||
// Also some of logic in here may be result of parallel development of Greenfield API
|
||||
// It's much better that we extract those common methods then copy paste and maintain same code across codebase
|
||||
internal static class ActionLogicExtensions
|
||||
{
|
||||
internal static async Task FirstAdminRegistered(this SettingsRepository settingsRepository, PoliciesSettings policies,
|
||||
bool updateCheck, bool disableRegistrations)
|
||||
{
|
||||
if (updateCheck)
|
||||
{
|
||||
Logs.PayServer.LogInformation("First admin created, enabling checks for new versions");
|
||||
policies.CheckForNewVersions = updateCheck;
|
||||
}
|
||||
|
||||
if (disableRegistrations)
|
||||
{
|
||||
// Once the admin user has been created lock subsequent user registrations (needs to be disabled for unit tests that require multiple users).
|
||||
Logs.PayServer.LogInformation("First admin created, disabling subscription (disable-registration is set to true)");
|
||||
policies.LockSubscription = true;
|
||||
}
|
||||
|
||||
if (updateCheck || disableRegistrations)
|
||||
await settingsRepository.UpdateSetting(policies);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,12 +10,11 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public abstract class BaseAsyncService : IHostedService
|
||||
{
|
||||
private CancellationTokenSource _Cts;
|
||||
private CancellationTokenSource _Cts = new CancellationTokenSource();
|
||||
protected Task[] _Tasks;
|
||||
|
||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Cts = new CancellationTokenSource();
|
||||
_Tasks = InitializeTasks();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
121
BTCPayServer/HostedServices/NewVersionCheckerHostedService.cs
Normal file
121
BTCPayServer/HostedServices/NewVersionCheckerHostedService.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class NewVersionCheckerHostedService : BaseAsyncService
|
||||
{
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly BTCPayServerEnvironment _env;
|
||||
private readonly NotificationSender _notificationSender;
|
||||
private readonly IVersionFetcher _versionFetcher;
|
||||
|
||||
public NewVersionCheckerHostedService(SettingsRepository settingsRepository, BTCPayServerEnvironment env,
|
||||
NotificationSender notificationSender, IVersionFetcher versionFetcher)
|
||||
{
|
||||
_settingsRepository = settingsRepository;
|
||||
_env = env;
|
||||
_notificationSender = notificationSender;
|
||||
_versionFetcher = versionFetcher;
|
||||
}
|
||||
|
||||
internal override Task[] InitializeTasks()
|
||||
{
|
||||
return new Task[] { CreateLoopTask(LoopVersionCheck) };
|
||||
}
|
||||
|
||||
protected async Task LoopVersionCheck()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessVersionCheck();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.Events.LogError(ex, "Error while performing new version check");
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromDays(1), Cancellation);
|
||||
}
|
||||
|
||||
public async Task ProcessVersionCheck()
|
||||
{
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.CheckForNewVersions)
|
||||
{
|
||||
var tag = await _versionFetcher.Fetch(Cancellation);
|
||||
if (tag != null && tag != _env.Version)
|
||||
{
|
||||
var dh = await _settingsRepository.GetSettingAsync<NewVersionCheckerDataHolder>() ?? new NewVersionCheckerDataHolder();
|
||||
if (dh.LastVersion != tag)
|
||||
{
|
||||
await _notificationSender.SendNotification(new AdminScope(), new NewVersionNotification(tag));
|
||||
|
||||
dh.LastVersion = tag;
|
||||
await _settingsRepository.UpdateSetting(dh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NewVersionCheckerDataHolder
|
||||
{
|
||||
public string LastVersion { get; set; }
|
||||
}
|
||||
|
||||
public interface IVersionFetcher
|
||||
{
|
||||
Task<string> Fetch(CancellationToken cancellation);
|
||||
}
|
||||
|
||||
public class GithubVersionFetcher : IVersionFetcher
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly Uri _updateurl;
|
||||
public GithubVersionFetcher(IHttpClientFactory httpClientFactory, BTCPayServerOptions options)
|
||||
{
|
||||
_httpClient = httpClientFactory.CreateClient(nameof(GithubVersionFetcher));
|
||||
_httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent", "BTCPayServer/NewVersionChecker");
|
||||
|
||||
_updateurl = options.UpdateUrl;
|
||||
}
|
||||
|
||||
private static readonly Regex _releaseVersionTag = new Regex("^(v[1-9]+(\\.[0-9]+)*(-[0-9]+)?)$");
|
||||
public async Task<string> Fetch(CancellationToken cancellation)
|
||||
{
|
||||
if (_updateurl == null)
|
||||
return null;
|
||||
|
||||
using (var resp = await _httpClient.GetAsync(_updateurl, cancellation))
|
||||
{
|
||||
var strResp = await resp.Content.ReadAsStringAsync();
|
||||
if (resp.IsSuccessStatusCode)
|
||||
{
|
||||
var jobj = JObject.Parse(strResp);
|
||||
var tag = jobj["tag_name"].ToString();
|
||||
|
||||
var isReleaseVersionTag = _releaseVersionTag.IsMatch(tag);
|
||||
return isReleaseVersionTag ? tag : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logs.Events.LogWarning($"Unsuccessful status code returned during new version check. " +
|
||||
$"Url: {_updateurl}, HTTP Code: {resp.StatusCode}, Response Body: {strResp}");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -239,7 +239,11 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
||||
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
|
||||
|
||||
services.AddSingleton<IVersionFetcher, GithubVersionFetcher>();
|
||||
services.AddSingleton<IHostedService, NewVersionCheckerHostedService>();
|
||||
services.AddSingleton<INotificationHandler, NewVersionNotification.Handler>();
|
||||
|
||||
services.AddSingleton<INotificationHandler, InvoiceEventNotification.Handler>();
|
||||
services.AddSingleton<INotificationHandler, PayoutNotification.Handler>();
|
||||
|
||||
@ -284,7 +288,7 @@ namespace BTCPayServer.Hosting
|
||||
{
|
||||
var btcPayEnv = provider.GetService<BTCPayServerEnvironment>();
|
||||
var rateLimits = new RateLimitService();
|
||||
if (btcPayEnv.IsDevelopping)
|
||||
if (btcPayEnv.IsDeveloping)
|
||||
{
|
||||
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=1000r/min burst=100 nodelay");
|
||||
rateLimits.SetZone($"zone={ZoneLimits.Register} rate=1000r/min burst=100 nodelay");
|
||||
|
@ -502,7 +502,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
{
|
||||
var o = new JObject();
|
||||
o.Add(new JProperty("errorCode", PayjoinReceiverHelper.GetErrorCode(error)));
|
||||
if (string.IsNullOrEmpty(debug) || !_env.IsDevelopping)
|
||||
if (string.IsNullOrEmpty(debug) || !_env.IsDeveloping)
|
||||
{
|
||||
o.Add(new JProperty("message", PayjoinReceiverHelper.GetMessage(error)));
|
||||
}
|
||||
|
@ -19,7 +19,8 @@
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||
"BTCPAY_DEBUGLOG": "debug.log",
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050"
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_UPDATEURL": ""
|
||||
},
|
||||
"applicationUrl": "http://127.0.0.1:14142/"
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
await daemonRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, SyncInfoResponse>("sync_info",
|
||||
JsonRpcClient.NoRequestModel.Instance);
|
||||
summary.TargetHeight = daemonResult.TargetHeight ?? daemonResult.Height;
|
||||
summary.Synced = daemonResult.Height >= summary.TargetHeight && (summary.TargetHeight > 0 || _btcPayServerEnvironment.IsDevelopping);
|
||||
summary.Synced = daemonResult.Height >= summary.TargetHeight && (summary.TargetHeight > 0 || _btcPayServerEnvironment.IsDeveloping);
|
||||
summary.CurrentHeight = daemonResult.Height;
|
||||
summary.UpdatedAt = DateTime.Now;
|
||||
summary.DaemonAvailable = true;
|
||||
|
@ -54,7 +54,7 @@ namespace BTCPayServer.Services
|
||||
}
|
||||
public bool AltcoinsVersion { get; set; }
|
||||
|
||||
public bool IsDevelopping
|
||||
public bool IsDeveloping
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -24,6 +24,8 @@ namespace BTCPayServer.Services
|
||||
public bool AllowHotWalletForAll { get; set; }
|
||||
[Display(Name = "Allow non-admins to import their hot wallets to the node wallet")]
|
||||
public bool AllowHotWalletRPCImportForAll { get; set; }
|
||||
[Display(Name = "Check releases on GitHub and alert when new BTCPayServer version is available")]
|
||||
public bool CheckForNewVersions { get; set; }
|
||||
|
||||
[Display(Name = "Display app on website root")]
|
||||
public string RootAppId { get; set; }
|
||||
|
@ -42,6 +42,11 @@
|
||||
<label asp-for="AllowHotWalletRPCImportForAll" class="form-check-label"></label>
|
||||
<span asp-validation-for="AllowHotWalletRPCImportForAll" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input asp-for="CheckForNewVersions" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="CheckForNewVersions" class="form-check-label"></label>
|
||||
<span asp-validation-for="CheckForNewVersions" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="RootAppId"></label>
|
||||
|
Loading…
Reference in New Issue
Block a user