mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Can configure BTCPay SSH connection at startup
This commit is contained in:
@@ -116,6 +116,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Server\LNDGRPCServices.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
||||
@@ -11,6 +11,7 @@ using StandardConfiguration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using Renci.SshNet;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
@@ -21,6 +22,69 @@ namespace BTCPayServer.Configuration
|
||||
public string CookieFile { get; internal set; }
|
||||
}
|
||||
|
||||
public class SSHSettings
|
||||
{
|
||||
public string Server { get; set; }
|
||||
public int Port { get; set; } = 22;
|
||||
public string KeyFile { get; set; }
|
||||
public string KeyFilePassword { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
|
||||
public ConnectionInfo CreateConnectionInfo()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(KeyFile))
|
||||
{
|
||||
return new ConnectionInfo(Server, Port, Username, new[] { new PrivateKeyAuthenticationMethod(Username, new PrivateKeyFile(KeyFile, KeyFilePassword)) });
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ConnectionInfo(Server, Port, Username, new[] { new PasswordAuthenticationMethod(Username, Password) });
|
||||
}
|
||||
}
|
||||
|
||||
public static SSHSettings ParseConfiguration(IConfiguration conf)
|
||||
{
|
||||
var externalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
var settings = new SSHSettings();
|
||||
settings.Server = conf.GetOrDefault<string>("sshconnection", null);
|
||||
if (settings.Server != null)
|
||||
{
|
||||
var parts = settings.Server.Split(':');
|
||||
if (parts.Length == 2 && int.TryParse(parts[1], out int port))
|
||||
{
|
||||
settings.Port = port;
|
||||
settings.Server = parts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.Port = 22;
|
||||
}
|
||||
|
||||
parts = settings.Server.Split('@');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
settings.Username = parts[0];
|
||||
settings.Server = parts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.Username = "root";
|
||||
}
|
||||
}
|
||||
else if(externalUrl != null)
|
||||
{
|
||||
settings.Port = 22;
|
||||
settings.Username = "root";
|
||||
settings.Server = externalUrl.DnsSafeHost;
|
||||
}
|
||||
settings.Password = conf.GetOrDefault<string>("sshpassword", "");
|
||||
settings.KeyFile = conf.GetOrDefault<string>("sshkeyfile", "");
|
||||
settings.KeyFilePassword = conf.GetOrDefault<string>("sshkeyfilepassword", "");
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
public class BTCPayServerOptions
|
||||
{
|
||||
public NetworkType NetworkType
|
||||
@@ -117,6 +181,28 @@ namespace BTCPayServer.Configuration
|
||||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
|
||||
var sshSettings = SSHSettings.ParseConfiguration(conf);
|
||||
if (!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sshSettings.KeyFile) && !File.Exists(sshSettings.KeyFile))
|
||||
throw new ConfigException($"sshkeyfile does not exist");
|
||||
if (sshSettings.Port > ushort.MaxValue ||
|
||||
sshSettings.Port < ushort.MinValue)
|
||||
throw new ConfigException($"ssh port is invalid");
|
||||
if (!string.IsNullOrEmpty(sshSettings.Password) && !string.IsNullOrEmpty(sshSettings.KeyFile))
|
||||
throw new ConfigException($"sshpassword or sshkeyfile should be provided, but not both");
|
||||
try
|
||||
{
|
||||
sshSettings.CreateConnectionInfo();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ConfigException($"sshkeyfilepassword is invalid");
|
||||
}
|
||||
|
||||
SSHSettings = sshSettings;
|
||||
}
|
||||
|
||||
RootPath = conf.GetOrDefault<string>("rootpath", "/");
|
||||
if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase))
|
||||
RootPath = "/" + RootPath;
|
||||
@@ -144,6 +230,11 @@ namespace BTCPayServer.Configuration
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public SSHSettings SSHSettings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal string GetRootUri()
|
||||
{
|
||||
|
||||
@@ -34,6 +34,10 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--externalurl", $"The expected external URL of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
|
||||
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshconnection", "SSH server to manage BTCPay under the form user@server:port (default: root@externalhost or empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshpassword", "SSH password to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshkeyfile", "SSH private key file to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue);
|
||||
foreach (var network in provider.GetAll())
|
||||
{
|
||||
var crypto = network.CryptoCode.ToLowerInvariant();
|
||||
|
||||
@@ -160,6 +160,7 @@ namespace BTCPayServer.Controllers
|
||||
MaintenanceViewModel vm = new MaintenanceViewModel();
|
||||
vm.UserName = "btcpayserver";
|
||||
vm.DNSDomain = this.Request.Host.Host;
|
||||
vm.SetConfiguredSSH(_Options.SSHSettings);
|
||||
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
|
||||
vm.DNSDomain = null;
|
||||
return View(vm);
|
||||
@@ -170,6 +171,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
vm.SetConfiguredSSH(_Options.SSHSettings);
|
||||
if (command == "changedomain")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(vm.DNSDomain))
|
||||
@@ -256,7 +258,8 @@ namespace BTCPayServer.Controllers
|
||||
private IActionResult RunSSH(MaintenanceViewModel vm, string ssh)
|
||||
{
|
||||
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'";
|
||||
var sshClient = vm.CreateSSHClient(this.Request.Host.Host);
|
||||
var sshClient = _Options.SSHSettings == null ? vm.CreateSSHClient(this.Request.Host.Host)
|
||||
: new SshClient(_Options.SSHSettings.CreateConnectionInfo());
|
||||
try
|
||||
{
|
||||
sshClient.Connect();
|
||||
@@ -404,13 +407,14 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
result.HasSSH = _Options.SSHSettings != null;
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
public IActionResult LNDGRPCServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if(!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
@@ -507,6 +511,27 @@ namespace BTCPayServer.Controllers
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
public IActionResult SSHService(bool downloadKeyFile = false)
|
||||
{
|
||||
var settings = _Options.SSHSettings;
|
||||
if (settings == null)
|
||||
return NotFound();
|
||||
if (downloadKeyFile)
|
||||
{
|
||||
if (!System.IO.File.Exists(settings.KeyFile))
|
||||
return NotFound();
|
||||
return File(System.IO.File.ReadAllBytes(settings.KeyFile), "application/octet-stream", "id_rsa");
|
||||
}
|
||||
SSHServiceViewModel vm = new SSHServiceViewModel();
|
||||
string port = settings.Port == 22 ? "" : $" -p {settings.Port}";
|
||||
vm.CommandLine = $"ssh {settings.Username}@{settings.Server}{port}";
|
||||
vm.Password = settings.Password;
|
||||
vm.KeyFilePassword = settings.KeyFilePassword;
|
||||
vm.HasKeyFile = !string.IsNullOrEmpty(settings.KeyFile);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Route("server/theme")]
|
||||
public async Task<IActionResult> Theme()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class CheckConfigurationHostedService : IHostedService
|
||||
{
|
||||
private readonly BTCPayServerOptions _options;
|
||||
|
||||
public CheckConfigurationHostedService(BTCPayServerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
new Thread(() =>
|
||||
{
|
||||
if (_options.SSHSettings != null)
|
||||
{
|
||||
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
|
||||
var connection = new Renci.SshNet.SshClient(_options.SSHSettings.CreateConnectionInfo());
|
||||
try
|
||||
{
|
||||
connection.Connect();
|
||||
connection.Disconnect();
|
||||
Logs.Configuration.LogInformation($"SSH connection succeeded");
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
})
|
||||
{ IsBackground = true }.Start();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,8 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<Payments.IPaymentMethodHandler<DerivationStrategy>, Payments.Bitcoin.BitcoinLikePaymentHandler>();
|
||||
services.AddSingleton<IHostedService, Payments.Bitcoin.NBXplorerListener>();
|
||||
|
||||
services.AddSingleton<IHostedService, HostedServices.CheckConfigurationHostedService>();
|
||||
|
||||
services.AddSingleton<Payments.IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>, Payments.Lightning.LightningLikePaymentHandler>();
|
||||
services.AddSingleton<IHostedService, Payments.Lightning.LightningListener>();
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using Renci.SshNet;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class MaintenanceViewModel
|
||||
{
|
||||
public bool ExposedSSH { get; set; }
|
||||
[Required]
|
||||
public string UserName { get; set; }
|
||||
[Required]
|
||||
@@ -20,5 +22,15 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
return new SshClient(host, UserName, Password);
|
||||
}
|
||||
|
||||
internal void SetConfiguredSSH(SSHSettings settings)
|
||||
{
|
||||
if(settings != null)
|
||||
{
|
||||
ExposedSSH = true;
|
||||
UserName = "unknown";
|
||||
Password = "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs
Normal file
15
BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class SSHServiceViewModel
|
||||
{
|
||||
public string CommandLine { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string KeyFilePassword { get; set; }
|
||||
public bool HasKeyFile { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,6 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public int Index { get; set; }
|
||||
}
|
||||
public List<LNDServiceViewModel> LNDServices { get; set; } = new List<LNDServiceViewModel>();
|
||||
public bool HasSSH { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"BTCPAY_BUNDLEJSCSS": "false",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||
"BTCPAY_BUNDLEJSCSS": "false"
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
},
|
||||
"applicationUrl": "http://127.0.0.1:14142/"
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<div class="col-md-8">
|
||||
<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>
|
||||
@@ -26,7 +28,14 @@
|
||||
<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">
|
||||
<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>
|
||||
|
||||
50
BTCPayServer/Views/Server/SSHService.cshtml
Normal file
50
BTCPayServer/Views/Server/SSHService.cshtml
Normal file
@@ -0,0 +1,50 @@
|
||||
@model BTCPayServer.Models.ServerViewModels.SSHServiceViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>SSH settings</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>SSH servies are used by the maintenance operations<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="CommandLine"></label>
|
||||
<input asp-for="CommandLine" readonly class="form-control" />
|
||||
</div>
|
||||
@if(!string.IsNullOrEmpty(Model.Password))
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Password"></label>
|
||||
<input asp-for="Password" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if(!string.IsNullOrEmpty(Model.KeyFilePassword))
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="KeyFilePassword"></label>
|
||||
<input asp-for="KeyFilePassword" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if(Model.HasKeyFile)
|
||||
{
|
||||
<a class="btn btn-primary form-control" asp-action="SSHService" asp-route-downloadKeyFile="true">Download Key File</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,6 +41,16 @@
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if(Model.HasSSH)
|
||||
{
|
||||
<tr>
|
||||
<td>None</td>
|
||||
<td>SSH</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="SSHService">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user