From 27cea81cb2e3b3c75c714f3f7776398d79b373d1 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 24 Jul 2018 17:04:57 +0900 Subject: [PATCH] Add ability to change domain from server settings --- BTCPayServer/BTCPayServer.csproj | 6 +- BTCPayServer/Controllers/HomeController.cs | 1 + BTCPayServer/Controllers/ServerController.cs | 138 +++++++++++++++++- .../ServerViewModels/MaintainanceViewModel.cs | 24 +++ BTCPayServer/Views/Server/Maintenance.cshtml | 52 +++++++ BTCPayServer/Views/Server/ServerNavPages.cs | 2 +- BTCPayServer/Views/Server/_Nav.cshtml | 1 + 7 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 BTCPayServer/Models/ServerViewModels/MaintainanceViewModel.cs create mode 100644 BTCPayServer/Views/Server/Maintenance.cshtml diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 42668cf76..3c209132f 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.62 + 1.0.2.63 NU1701,CA1816,CA1308,CA1810,CA2208 @@ -48,6 +48,7 @@ + @@ -118,6 +119,9 @@ $(IncludeRazorContentInPack) + + $(IncludeRazorContentInPack) + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/HomeController.cs b/BTCPayServer/Controllers/HomeController.cs index 49e7036f1..80c1328c7 100644 --- a/BTCPayServer/Controllers/HomeController.cs +++ b/BTCPayServer/Controllers/HomeController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using BTCPayServer.Models; +using NBitcoin.DataEncoders; namespace BTCPayServer.Controllers { diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 805c37668..7721f04e3 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -22,6 +22,8 @@ using System.Net; using System.Net.Http; using System.Net.Mail; using System.Threading.Tasks; +using Renci.SshNet; +using BTCPayServer.Logging; namespace BTCPayServer.Controllers { @@ -149,6 +151,138 @@ namespace BTCPayServer.Controllers return View(userVM); } + [Route("server/maintenance")] + public IActionResult Maintenance() + { + MaintenanceViewModel vm = new MaintenanceViewModel(); + vm.UserName = "btcpayserver"; + vm.DNSDomain = this.Request.Host.Host; + if (IPAddress.TryParse(vm.DNSDomain, out var unused)) + vm.DNSDomain = null; + return View(vm); + } + [Route("server/maintenance")] + [HttpPost] + public async Task Maintenance(MaintenanceViewModel vm, string command) + { + if (!ModelState.IsValid) + return View(vm); + if (command == "changedomain") + { + if (string.IsNullOrWhiteSpace(vm.DNSDomain)) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"Required field"); + return View(vm); + } + vm.DNSDomain = vm.DNSDomain.Trim().ToLowerInvariant(); + if (IPAddress.TryParse(vm.DNSDomain, out var unused)) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"This should be a domain name"); + return View(vm); + } + if (vm.DNSDomain.Equals(this.Request.Host.Host, StringComparison.InvariantCultureIgnoreCase)) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"The server is already set to use this domain"); + return View(vm); + } + var builder = new UriBuilder(); + using (var client = new HttpClient(new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + })) + { + try + { + builder.Scheme = this.Request.Scheme; + builder.Host = vm.DNSDomain; + if (this.Request.Host.Port != null) + builder.Port = this.Request.Host.Port.Value; + builder.Path = "runid"; + builder.Query = $"expected={RunId}"; + var response = await client.GetAsync(builder.Uri); + if (!response.IsSuccessStatusCode) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"Invalid host ({vm.DNSDomain} is not pointing to this BTCPay instance)"); + return View(vm); + } + } + catch (Exception ex) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"Invalid domain ({ex.Message})"); + return View(vm); + } + } + + var error = RunSSH(vm, command, $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && . changedomain.sh {vm.DNSDomain}'"); + if (error != null) + return error; + + builder.Path = null; + builder.Query = null; + StatusMessage = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\""; + } + else + { + return NotFound(); + } + return RedirectToAction(nameof(Maintenance)); + } + + public static string RunId = Encoders.Hex.EncodeData(NBitcoin.RandomUtils.GetBytes(32)); + [HttpGet] + [Route("runid")] + [AllowAnonymous] + public IActionResult SeeRunId(string expected = null) + { + if (expected == RunId) + return Ok(); + return BadRequest(); + } + + private IActionResult RunSSH(MaintenanceViewModel vm, string command, string ssh) + { + var sshClient = vm.CreateSSHClient(this.Request.Host.Host); + try + { + sshClient.Connect(); + } + catch (Renci.SshNet.Common.SshAuthenticationException) + { + ModelState.AddModelError(nameof(vm.Password), "Invalid credentials"); + sshClient.Dispose(); + return View(vm); + } + catch (Exception ex) + { + var message = ex.Message; + if (ex is AggregateException aggrEx && aggrEx.InnerException?.Message != null) + { + message = aggrEx.InnerException.Message; + } + ModelState.AddModelError(nameof(vm.UserName), $"Connection problem ({message})"); + sshClient.Dispose(); + return View(vm); + } + + var sshCommand = sshClient.CreateCommand(ssh); + sshCommand.CommandTimeout = TimeSpan.FromMinutes(1.0); + sshCommand.BeginExecute(ar => + { + try + { + Logs.PayServer.LogInformation("Running SSH command: " + command); + 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; + } + private static bool IsAdmin(IList roles) { return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal); @@ -336,7 +470,7 @@ namespace BTCPayServer.Controllers if (connectionString == null) return null; connectionString = connectionString.Clone(); - if(connectionString.MacaroonFilePath != null) + if (connectionString.MacaroonFilePath != null) { try { @@ -351,7 +485,7 @@ namespace BTCPayServer.Controllers } return connectionString; } - + [Route("server/theme")] public async Task Theme() { diff --git a/BTCPayServer/Models/ServerViewModels/MaintainanceViewModel.cs b/BTCPayServer/Models/ServerViewModels/MaintainanceViewModel.cs new file mode 100644 index 000000000..e4393ac51 --- /dev/null +++ b/BTCPayServer/Models/ServerViewModels/MaintainanceViewModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Renci.SshNet; + +namespace BTCPayServer.Models.ServerViewModels +{ + public class MaintenanceViewModel + { + [Required] + public string UserName { get; set; } + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + [Display(Name = "Change domain")] + public string DNSDomain { get; set; } + public SshClient CreateSSHClient(string host) + { + return new SshClient(host, UserName, Password); + } + } +} diff --git a/BTCPayServer/Views/Server/Maintenance.cshtml b/BTCPayServer/Views/Server/Maintenance.cshtml new file mode 100644 index 000000000..388b22d1b --- /dev/null +++ b/BTCPayServer/Views/Server/Maintenance.cshtml @@ -0,0 +1,52 @@ +@model BTCPayServer.Models.ServerViewModels.MaintenanceViewModel +@{ + ViewData.SetActivePageAndTitle(ServerNavPages.Maintainance); +} + + +

@ViewData["Title"]

+ + +
+ +
+
+
+
SSH Settings
+ You need SSH credentials to run any of those actions +
+
+ + + +
+ +
+ + + +
+ +
+
Actions
+ Run any of those administrative functions +
+ +
+
+ + + + +
+ +
+
+
+
+ +@section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") +} diff --git a/BTCPayServer/Views/Server/ServerNavPages.cs b/BTCPayServer/Views/Server/ServerNavPages.cs index fb08dc50f..83a151e0e 100644 --- a/BTCPayServer/Views/Server/ServerNavPages.cs +++ b/BTCPayServer/Views/Server/ServerNavPages.cs @@ -7,6 +7,6 @@ namespace BTCPayServer.Views.Server { public enum ServerNavPages { - Index, Users, Rates, Emails, Policies, Theme, Hangfire, Services + Index, Users, Rates, Emails, Policies, Theme, Hangfire, Services, Maintainance } } diff --git a/BTCPayServer/Views/Server/_Nav.cshtml b/BTCPayServer/Views/Server/_Nav.cshtml index 5ec54ccf7..fcd7d7086 100644 --- a/BTCPayServer/Views/Server/_Nav.cshtml +++ b/BTCPayServer/Views/Server/_Nav.cshtml @@ -5,6 +5,7 @@ Policies Services Theme + Maintenance Hangfire