mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Asyncify SSH access, do not show SSH service if ssh is not well configured
This commit is contained in:
@@ -194,7 +194,7 @@ namespace BTCPayServer.Configuration
|
|||||||
{
|
{
|
||||||
if (!SSHFingerprint.TryParse(fingerprint, out var f))
|
if (!SSHFingerprint.TryParse(fingerprint, out var f))
|
||||||
throw new ConfigException($"Invalid ssh fingerprint format {fingerprint}");
|
throw new ConfigException($"Invalid ssh fingerprint format {fingerprint}");
|
||||||
TrustedFingerprints.Add(f);
|
SSHSettings?.TrustedFingerprints.Add(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +249,6 @@ namespace BTCPayServer.Configuration
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsTrustedFingerprint(byte[] fingerPrint, byte[] hostKey)
|
|
||||||
{
|
|
||||||
return TrustedFingerprints.Any(f => f.Match(fingerPrint, hostKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string RootPath { get; set; }
|
public string RootPath { get; set; }
|
||||||
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
||||||
|
|
||||||
@@ -277,7 +272,6 @@ namespace BTCPayServer.Configuration
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public bool AllowAdminRegistration { get; set; }
|
public bool AllowAdminRegistration { get; set; }
|
||||||
public List<SSHFingerprint> TrustedFingerprints { get; set; } = new List<SSHFingerprint>();
|
|
||||||
public SSHSettings SSHSettings
|
public SSHSettings SSHSettings
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ namespace BTCPayServer.Controllers
|
|||||||
private readonly TorServices _torServices;
|
private readonly TorServices _torServices;
|
||||||
private BTCPayServerOptions _Options;
|
private BTCPayServerOptions _Options;
|
||||||
private readonly AppService _AppService;
|
private readonly AppService _AppService;
|
||||||
|
private readonly CheckConfigurationHostedService _sshState;
|
||||||
private readonly StoredFileRepository _StoredFileRepository;
|
private readonly StoredFileRepository _StoredFileRepository;
|
||||||
private readonly FileService _FileService;
|
private readonly FileService _FileService;
|
||||||
private readonly IEnumerable<IStorageProviderService> _StorageProviderServices;
|
private readonly IEnumerable<IStorageProviderService> _StorageProviderServices;
|
||||||
@@ -64,7 +65,8 @@ namespace BTCPayServer.Controllers
|
|||||||
LightningConfigurationProvider lnConfigProvider,
|
LightningConfigurationProvider lnConfigProvider,
|
||||||
TorServices torServices,
|
TorServices torServices,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
AppService appService)
|
AppService appService,
|
||||||
|
CheckConfigurationHostedService sshState)
|
||||||
{
|
{
|
||||||
_Options = options;
|
_Options = options;
|
||||||
_StoredFileRepository = storedFileRepository;
|
_StoredFileRepository = storedFileRepository;
|
||||||
@@ -78,6 +80,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_LnConfigProvider = lnConfigProvider;
|
_LnConfigProvider = lnConfigProvider;
|
||||||
_torServices = torServices;
|
_torServices = torServices;
|
||||||
_AppService = appService;
|
_AppService = appService;
|
||||||
|
_sshState = sshState;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/rates")]
|
[Route("server/rates")]
|
||||||
@@ -186,9 +189,8 @@ namespace BTCPayServer.Controllers
|
|||||||
public IActionResult Maintenance()
|
public IActionResult Maintenance()
|
||||||
{
|
{
|
||||||
MaintenanceViewModel vm = new MaintenanceViewModel();
|
MaintenanceViewModel vm = new MaintenanceViewModel();
|
||||||
vm.UserName = "btcpayserver";
|
vm.CanUseSSH = _sshState.CanUseSSH;
|
||||||
vm.DNSDomain = this.Request.Host.Host;
|
vm.DNSDomain = this.Request.Host.Host;
|
||||||
vm.SetConfiguredSSH(_Options.SSHSettings);
|
|
||||||
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
|
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
|
||||||
vm.DNSDomain = null;
|
vm.DNSDomain = null;
|
||||||
return View(vm);
|
return View(vm);
|
||||||
@@ -198,9 +200,9 @@ namespace BTCPayServer.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Maintenance(MaintenanceViewModel vm, string command)
|
public async Task<IActionResult> Maintenance(MaintenanceViewModel vm, string command)
|
||||||
{
|
{
|
||||||
|
vm.CanUseSSH = _sshState.CanUseSSH;
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return View(vm);
|
return View(vm);
|
||||||
vm.SetConfiguredSSH(_Options.SSHSettings);
|
|
||||||
if (command == "changedomain")
|
if (command == "changedomain")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(vm.DNSDomain))
|
if (string.IsNullOrWhiteSpace(vm.DNSDomain))
|
||||||
@@ -254,7 +256,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = RunSSH(vm, $"changedomain.sh {vm.DNSDomain}");
|
var error = await RunSSH(vm, $"changedomain.sh {vm.DNSDomain}");
|
||||||
if (error != null)
|
if (error != null)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@@ -264,14 +266,14 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
else if (command == "update")
|
else if (command == "update")
|
||||||
{
|
{
|
||||||
var error = RunSSH(vm, $"btcpay-update.sh");
|
var error = await RunSSH(vm, $"btcpay-update.sh");
|
||||||
if (error != null)
|
if (error != null)
|
||||||
return error;
|
return error;
|
||||||
StatusMessage = $"The server might restart soon if an update is available...";
|
StatusMessage = $"The server might restart soon if an update is available...";
|
||||||
}
|
}
|
||||||
else if (command == "clean")
|
else if (command == "clean")
|
||||||
{
|
{
|
||||||
var error = RunSSH(vm, $"btcpay-clean.sh");
|
var error = await RunSSH(vm, $"btcpay-clean.sh");
|
||||||
if (error != null)
|
if (error != null)
|
||||||
return error;
|
return error;
|
||||||
StatusMessage = $"The old docker images will be cleaned soon...";
|
StatusMessage = $"The old docker images will be cleaned soon...";
|
||||||
@@ -301,43 +303,13 @@ namespace BTCPayServer.Controllers
|
|||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IActionResult RunSSH(MaintenanceViewModel vm, string ssh)
|
private async Task<IActionResult> RunSSH(MaintenanceViewModel vm, string command)
|
||||||
{
|
{
|
||||||
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'";
|
SshClient sshClient = null;
|
||||||
var sshClient = _Options.SSHSettings == null ? vm.CreateSSHClient(this.Request.Host.Host)
|
|
||||||
: new SshClient(_Options.SSHSettings.CreateConnectionInfo());
|
|
||||||
|
|
||||||
if (_Options.TrustedFingerprints.Count != 0)
|
|
||||||
{
|
|
||||||
sshClient.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
|
|
||||||
{
|
|
||||||
if (_Options.TrustedFingerprints.Count == 0)
|
|
||||||
{
|
|
||||||
Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
|
||||||
e.CanTrust = true; // Not a typo, we want the connection to succeed with a warning
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
e.CanTrust = _Options.IsTrustedFingerprint(e.FingerPrint, e.HostKey);
|
|
||||||
if (!e.CanTrust)
|
|
||||||
Logs.Configuration.LogError($"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sshClient.Connect();
|
sshClient = await _Options.SSHSettings.ConnectAsync();
|
||||||
}
|
|
||||||
catch (Renci.SshNet.Common.SshAuthenticationException)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(nameof(vm.Password), "Invalid credentials");
|
|
||||||
sshClient.Dispose();
|
|
||||||
return View(vm);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -346,30 +318,31 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
message = aggrEx.InnerException.Message;
|
message = aggrEx.InnerException.Message;
|
||||||
}
|
}
|
||||||
ModelState.AddModelError(nameof(vm.UserName), $"Connection problem ({message})");
|
ModelState.AddModelError(string.Empty, $"Connection problem ({message})");
|
||||||
sshClient.Dispose();
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
_ = RunSSHCore(sshClient, $". /etc/profile.d/btcpay-env.sh && nohup {command} > /dev/null 2>&1 & disown");
|
||||||
var sshCommand = sshClient.CreateCommand(ssh);
|
|
||||||
sshCommand.CommandTimeout = TimeSpan.FromMinutes(1.0);
|
|
||||||
sshCommand.BeginExecute(ar =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logs.PayServer.LogInformation("Running SSH command: " + ssh);
|
|
||||||
var result = sshCommand.EndExecute(ar);
|
|
||||||
Logs.PayServer.LogInformation("SSH command executed: " + result);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logs.PayServer.LogWarning("Error while executing SSH command: " + ex.Message);
|
|
||||||
}
|
|
||||||
sshClient.Dispose();
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task RunSSHCore(SshClient sshClient, string ssh)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogInformation("Running SSH command: " + ssh);
|
||||||
|
var result = await sshClient.RunBash(ssh, TimeSpan.FromMinutes(1.0));
|
||||||
|
Logs.PayServer.LogInformation($"SSH command executed with exit status {result.ExitStatus}. Output: {result.Output}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogWarning("Error while executing SSH command: " + ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
sshClient.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsAdmin(IList<string> roles)
|
private static bool IsAdmin(IList<string> roles)
|
||||||
{
|
{
|
||||||
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
||||||
@@ -531,7 +504,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri
|
Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (_Options.SSHSettings != null)
|
if (_sshState.CanUseSSH)
|
||||||
{
|
{
|
||||||
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
|
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,5 +3,9 @@ namespace BTCPayServer.Events
|
|||||||
public class SettingsChanged<T>
|
public class SettingsChanged<T>
|
||||||
{
|
{
|
||||||
public T Settings { get; set; }
|
public T Settings { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Settings?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
BTCPayServer/Extensions/SSHClientExtensions.cs
Normal file
118
BTCPayServer/Extensions/SSHClientExtensions.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.SSH;
|
||||||
|
using Renci.SshNet;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public static class SSHClientExtensions
|
||||||
|
{
|
||||||
|
public static Task<SshClient> ConnectAsync(this SSHSettings sshSettings)
|
||||||
|
{
|
||||||
|
if (sshSettings == null)
|
||||||
|
throw new ArgumentNullException(nameof(sshSettings));
|
||||||
|
|
||||||
|
TaskCompletionSource<SshClient> tcs = new TaskCompletionSource<SshClient>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
new Thread(() =>
|
||||||
|
{
|
||||||
|
var sshClient = new SshClient(sshSettings.CreateConnectionInfo());
|
||||||
|
sshClient.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
|
||||||
|
{
|
||||||
|
if (sshSettings.TrustedFingerprints.Count == 0)
|
||||||
|
{
|
||||||
|
e.CanTrust = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
e.CanTrust = sshSettings.IsTrustedFingerprint(e.FingerPrint, e.HostKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sshClient.Connect();
|
||||||
|
tcs.TrySetResult(sshClient);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
tcs.TrySetException(ex);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sshClient.Dispose();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{ IsBackground = true }.Start();
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<SSHCommandResult> RunBash(this SshClient sshClient, string command, TimeSpan? timeout = null)
|
||||||
|
{
|
||||||
|
if (sshClient == null)
|
||||||
|
throw new ArgumentNullException(nameof(sshClient));
|
||||||
|
if (command == null)
|
||||||
|
throw new ArgumentNullException(nameof(command));
|
||||||
|
command = $"sudo bash -c '{command}'";
|
||||||
|
var sshCommand = sshClient.CreateCommand(command);
|
||||||
|
if (timeout is TimeSpan v)
|
||||||
|
sshCommand.CommandTimeout = v;
|
||||||
|
var tcs = new TaskCompletionSource<SSHCommandResult>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
new Thread(() =>
|
||||||
|
{
|
||||||
|
sshCommand.BeginExecute(ar =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sshCommand.EndExecute(ar);
|
||||||
|
tcs.TrySetResult(CreateSSHCommandResult(sshCommand));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
tcs.TrySetException(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
sshCommand.Dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
{ IsBackground = true }.Start();
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SSHCommandResult CreateSSHCommandResult(SshCommand sshCommand)
|
||||||
|
{
|
||||||
|
return new SSHCommandResult()
|
||||||
|
{
|
||||||
|
Output = sshCommand.Result,
|
||||||
|
Error = sshCommand.Error,
|
||||||
|
ExitStatus = sshCommand.ExitStatus
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task DisconnectAsync(this SshClient sshClient)
|
||||||
|
{
|
||||||
|
if (sshClient == null)
|
||||||
|
throw new ArgumentNullException(nameof(sshClient));
|
||||||
|
|
||||||
|
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sshClient.Disconnect();
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(true); // We don't care about exception
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{ IsBackground = true }.Start();
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,49 +23,44 @@ namespace BTCPayServer.HostedServices
|
|||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanUseSSH { get; private set; }
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
new Thread(() =>
|
_ = TestConnection();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task TestConnection()
|
||||||
|
{
|
||||||
|
var canUseSSH = false;
|
||||||
|
if (_options.SSHSettings != null)
|
||||||
{
|
{
|
||||||
if (_options.SSHSettings != null)
|
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
|
using (var connection = await _options.SSHSettings.ConnectAsync())
|
||||||
var connection = new Renci.SshNet.SshClient(_options.SSHSettings.CreateConnectionInfo());
|
|
||||||
connection.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
|
|
||||||
{
|
{
|
||||||
e.CanTrust = true;
|
await connection.DisconnectAsync();
|
||||||
if (!_options.IsTrustedFingerprint(e.FingerPrint, e.HostKey))
|
|
||||||
{
|
|
||||||
Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connection.Connect();
|
|
||||||
connection.Disconnect();
|
|
||||||
Logs.Configuration.LogInformation($"SSH connection succeeded");
|
Logs.Configuration.LogInformation($"SSH connection succeeded");
|
||||||
}
|
canUseSSH = true;
|
||||||
catch (Renci.SshNet.Common.SshAuthenticationException)
|
|
||||||
{
|
|
||||||
Logs.Configuration.LogWarning($"SSH invalid credentials");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.Message;
|
|
||||||
if (ex is AggregateException aggrEx && aggrEx.InnerException?.Message != null)
|
|
||||||
{
|
|
||||||
message = aggrEx.InnerException.Message;
|
|
||||||
}
|
|
||||||
Logs.Configuration.LogWarning($"SSH connection issue: {message}");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connection.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
catch (Renci.SshNet.Common.SshAuthenticationException)
|
||||||
{ IsBackground = true }.Start();
|
{
|
||||||
return Task.CompletedTask;
|
Logs.Configuration.LogWarning($"SSH invalid credentials");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var message = ex.Message;
|
||||||
|
if (ex is AggregateException aggrEx && aggrEx.InnerException?.Message != null)
|
||||||
|
{
|
||||||
|
message = aggrEx.InnerException.Message;
|
||||||
|
}
|
||||||
|
Logs.Configuration.LogWarning($"SSH connection issue of type {ex.GetType().Name}: {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CanUseSSH = canUseSSH;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -188,7 +188,8 @@ namespace BTCPayServer.Hosting
|
|||||||
});
|
});
|
||||||
services.AddSingleton<IHostedService, CssThemeManagerHostedService>();
|
services.AddSingleton<IHostedService, CssThemeManagerHostedService>();
|
||||||
|
|
||||||
services.AddSingleton<IHostedService, HostedServices.CheckConfigurationHostedService>();
|
services.AddSingleton<HostedServices.CheckConfigurationHostedService>();
|
||||||
|
services.AddSingleton<IHostedService, HostedServices.CheckConfigurationHostedService>(o => o.GetRequiredService<CheckConfigurationHostedService>());
|
||||||
|
|
||||||
services.AddSingleton<BitcoinLikePaymentHandler>();
|
services.AddSingleton<BitcoinLikePaymentHandler>();
|
||||||
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<BitcoinLikePaymentHandler>());
|
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<BitcoinLikePaymentHandler>());
|
||||||
|
|||||||
@@ -11,27 +11,8 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||||||
{
|
{
|
||||||
public class MaintenanceViewModel
|
public class MaintenanceViewModel
|
||||||
{
|
{
|
||||||
public bool ExposedSSH { get; set; }
|
|
||||||
[Required]
|
|
||||||
public string UserName { get; set; }
|
|
||||||
[Required]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
public string Password { get; set; }
|
|
||||||
[Display(Name = "Change domain")]
|
[Display(Name = "Change domain")]
|
||||||
public string DNSDomain { get; set; }
|
public string DNSDomain { get; set; }
|
||||||
public SshClient CreateSSHClient(string host)
|
public bool CanUseSSH { get; internal set; }
|
||||||
{
|
|
||||||
return new SshClient(host, UserName, Password);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SetConfiguredSSH(SSHSettings settings)
|
|
||||||
{
|
|
||||||
if(settings != null)
|
|
||||||
{
|
|
||||||
ExposedSSH = true;
|
|
||||||
UserName = "unknown";
|
|
||||||
Password = "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
BTCPayServer/SSH/SSHCommandResult.cs
Normal file
14
BTCPayServer/SSH/SSHCommandResult.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.SSH
|
||||||
|
{
|
||||||
|
public class SSHCommandResult
|
||||||
|
{
|
||||||
|
public int ExitStatus { get; internal set; }
|
||||||
|
public string Output { get; internal set; }
|
||||||
|
public string Error { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,11 @@ namespace BTCPayServer.SSH
|
|||||||
public string KeyFilePassword { get; set; }
|
public string KeyFilePassword { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
public List<SSHFingerprint> TrustedFingerprints { get; set; } = new List<SSHFingerprint>();
|
||||||
|
internal bool IsTrustedFingerprint(byte[] fingerPrint, byte[] hostKey)
|
||||||
|
{
|
||||||
|
return TrustedFingerprints.Any(f => f.Match(fingerPrint, hostKey));
|
||||||
|
}
|
||||||
|
|
||||||
public ConnectionInfo CreateConnectionInfo()
|
public ConnectionInfo CreateConnectionInfo()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,5 +13,9 @@ namespace BTCPayServer.Services
|
|||||||
public bool ConvertNetworkFeeProperty { get; set; }
|
public bool ConvertNetworkFeeProperty { get; set; }
|
||||||
public bool ConvertCrowdfundOldSettings { get; set; }
|
public bool ConvertCrowdfundOldSettings { get; set; }
|
||||||
public bool ConvertWalletKeyPathRoots { get; set; }
|
public bool ConvertWalletKeyPathRoots { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,14 @@
|
|||||||
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||||
|
@if (!Model.CanUseSSH)
|
||||||
|
{
|
||||||
|
<partial name="_StatusMessage" model="@("Error: Maintenance feature requires access to SSH properly configured in BTCPayServer configuration")" />
|
||||||
|
}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@if (!Model.ExposedSSH)
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<h5>SSH Settings</h5>
|
|
||||||
<span>For changing any settings, you need to enter your SSH credentials:</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label asp-for="UserName"></label>
|
|
||||||
<input asp-for="UserName" class="form-control" />
|
|
||||||
<span asp-validation-for="UserName" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label asp-for="Password"></label>
|
|
||||||
<input asp-for="Password" class="form-control" />
|
|
||||||
<span asp-validation-for="Password" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input asp-for="Password" type="hidden" class="form-control" />
|
|
||||||
<span asp-validation-for="Password" class="text-danger"></span>
|
|
||||||
<input asp-for="UserName" type="hidden" class="form-control" />
|
|
||||||
<span asp-validation-for="UserName" class="text-danger"></span>
|
|
||||||
}
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<h5>Change domain name</h5>
|
<h5>Change domain name</h5>
|
||||||
<span>You can change the domain name of your server by following <a href="https://github.com/btcpayserver/btcpayserver-doc/blob/master/ChangeDomain.md">this guide</a></span>
|
<span>You can change the domain name of your server by following <a href="https://github.com/btcpayserver/btcpayserver-doc/blob/master/ChangeDomain.md">this guide</a></span>
|
||||||
@@ -44,7 +22,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input asp-for="DNSDomain" class="form-control" />
|
<input asp-for="DNSDomain" class="form-control" />
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button name="command" type="submit" class="btn btn-primary" value="changedomain" title="Change domain">
|
<button name="command" type="submit" class="btn btn-primary" value="changedomain" title="Change domain" disabled="@(Model.CanUseSSH ? null : "disabled")">
|
||||||
<span class="fa fa-check"></span> Confirm
|
<span class="fa fa-check"></span> Confirm
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@@ -58,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button name="command" type="submit" class="btn btn-primary" value="update">Update</button>
|
<button name="command" type="submit" class="btn btn-primary" value="update" disabled="@(Model.CanUseSSH ? null : "disabled")">Update</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -67,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button name="command" type="submit" class="btn btn-primary" value="clean">Clean</button>
|
<button name="command" type="submit" class="btn btn-primary" value="clean" disabled="@(Model.CanUseSSH ? null : "disabled")">Clean</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user