2018-07-22 11:38:14 +02:00
using BTCPayServer.Configuration ;
2018-07-23 05:20:11 +02:00
using Microsoft.Extensions.Logging ;
2018-07-22 11:38:14 +02:00
using BTCPayServer.HostedServices ;
2018-04-13 23:15:03 +02:00
using BTCPayServer.Models ;
2017-09-15 18:15:17 +02:00
using BTCPayServer.Models.ServerViewModels ;
2018-07-22 11:38:14 +02:00
using BTCPayServer.Payments.Lightning ;
2017-09-27 07:18:09 +02:00
using BTCPayServer.Services ;
using BTCPayServer.Services.Mails ;
2018-04-14 15:35:52 +02:00
using BTCPayServer.Services.Rates ;
2018-07-20 08:24:19 +02:00
using BTCPayServer.Services.Stores ;
2018-11-06 07:38:07 +01:00
using BTCPayServer.Validation ;
2017-09-15 18:20:57 +02:00
using Microsoft.AspNetCore.Authorization ;
2017-09-15 18:15:17 +02:00
using Microsoft.AspNetCore.Identity ;
using Microsoft.AspNetCore.Mvc ;
2018-07-23 04:53:39 +02:00
using NBitcoin ;
2018-07-22 11:38:14 +02:00
using NBitcoin.DataEncoders ;
2017-09-15 18:15:17 +02:00
using System ;
using System.Collections.Generic ;
2017-09-27 07:18:09 +02:00
using System.ComponentModel.DataAnnotations ;
2018-11-07 14:29:35 +01:00
using System.IO ;
2017-09-15 18:15:17 +02:00
using System.Linq ;
2017-09-27 07:18:09 +02:00
using System.Net ;
2018-04-14 15:35:52 +02:00
using System.Net.Http ;
2017-09-27 07:18:09 +02:00
using System.Net.Mail ;
2017-09-15 18:15:17 +02:00
using System.Threading.Tasks ;
2018-07-24 10:04:57 +02:00
using Renci.SshNet ;
using BTCPayServer.Logging ;
2018-08-30 04:50:39 +02:00
using BTCPayServer.Lightning ;
2018-10-27 15:49:39 +02:00
using BTCPayServer.Configuration.External ;
2017-09-15 18:15:17 +02:00
namespace BTCPayServer.Controllers
{
2018-04-29 19:33:42 +02:00
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key)]
2017-10-27 10:53:04 +02:00
public class ServerController : Controller
{
private UserManager < ApplicationUser > _UserManager ;
SettingsRepository _SettingsRepository ;
2018-08-03 05:14:09 +02:00
private readonly NBXplorerDashboard _dashBoard ;
2018-08-22 09:53:40 +02:00
private RateFetcher _RateProviderFactory ;
2018-07-20 08:24:19 +02:00
private StoreRepository _StoreRepository ;
2018-07-22 11:38:14 +02:00
LightningConfigurationProvider _LnConfigProvider ;
BTCPayServerOptions _Options ;
2017-09-15 18:15:17 +02:00
2018-04-14 15:35:52 +02:00
public ServerController ( UserManager < ApplicationUser > userManager ,
2018-07-22 11:38:14 +02:00
Configuration . BTCPayServerOptions options ,
2018-08-22 09:53:40 +02:00
RateFetcher rateProviderFactory ,
2018-07-20 08:24:19 +02:00
SettingsRepository settingsRepository ,
2018-08-03 05:14:09 +02:00
NBXplorerDashboard dashBoard ,
2018-07-22 11:38:14 +02:00
LightningConfigurationProvider lnConfigProvider ,
2018-07-20 08:24:19 +02:00
Services . Stores . StoreRepository storeRepository )
2017-10-27 10:53:04 +02:00
{
2018-07-22 11:38:14 +02:00
_Options = options ;
2017-10-27 10:53:04 +02:00
_UserManager = userManager ;
_SettingsRepository = settingsRepository ;
2018-08-03 05:14:09 +02:00
_dashBoard = dashBoard ;
2018-04-14 15:35:52 +02:00
_RateProviderFactory = rateProviderFactory ;
2018-07-20 08:24:19 +02:00
_StoreRepository = storeRepository ;
2018-07-22 11:38:14 +02:00
_LnConfigProvider = lnConfigProvider ;
2017-10-27 10:53:04 +02:00
}
2017-09-15 18:15:17 +02:00
2018-04-14 15:35:52 +02:00
[Route("server/rates")]
public async Task < IActionResult > Rates ( )
{
var rates = ( await _SettingsRepository . GetSettingAsync < RatesSetting > ( ) ) ? ? new RatesSetting ( ) ;
2018-04-18 11:23:39 +02:00
var vm = new RatesViewModel ( )
2018-04-14 15:35:52 +02:00
{
CacheMinutes = rates . CacheInMinutes ,
PrivateKey = rates . PrivateKey ,
PublicKey = rates . PublicKey
2018-04-18 11:23:39 +02:00
} ;
await FetchRateLimits ( vm ) ;
return View ( vm ) ;
2018-04-14 15:35:52 +02:00
}
2018-04-18 11:23:39 +02:00
private static async Task FetchRateLimits ( RatesViewModel vm )
2018-04-14 15:35:52 +02:00
{
2018-04-18 11:23:39 +02:00
var coinAverage = GetCoinaverageService ( vm , false ) ;
if ( coinAverage ! = null )
2018-04-14 15:35:52 +02:00
{
2018-04-18 11:23:39 +02:00
try
{
vm . RateLimits = await coinAverage . GetRateLimitsAsync ( ) ;
}
catch { }
2018-04-14 15:35:52 +02:00
}
}
[Route("server/rates")]
[HttpPost]
public async Task < IActionResult > Rates ( RatesViewModel vm )
{
var rates = ( await _SettingsRepository . GetSettingAsync < RatesSetting > ( ) ) ? ? new RatesSetting ( ) ;
rates . PrivateKey = vm . PrivateKey ;
rates . PublicKey = vm . PublicKey ;
rates . CacheInMinutes = vm . CacheMinutes ;
try
{
2018-04-18 11:23:39 +02:00
var service = GetCoinaverageService ( vm , true ) ;
2018-07-22 11:38:14 +02:00
if ( service ! = null )
2018-04-18 11:23:39 +02:00
await service . TestAuthAsync ( ) ;
2018-04-14 15:35:52 +02:00
}
catch
{
ModelState . AddModelError ( nameof ( vm . PrivateKey ) , "Invalid API key pair" ) ;
}
if ( ! ModelState . IsValid )
2018-04-18 11:23:39 +02:00
{
await FetchRateLimits ( vm ) ;
2018-04-14 15:35:52 +02:00
return View ( vm ) ;
2018-04-18 11:23:39 +02:00
}
2018-04-14 15:35:52 +02:00
await _SettingsRepository . UpdateSetting ( rates ) ;
StatusMessage = "Rate settings successfully updated" ;
return RedirectToAction ( nameof ( Rates ) ) ;
2017-10-27 10:53:04 +02:00
}
2017-09-15 18:15:17 +02:00
2018-04-18 11:23:39 +02:00
private static CoinAverageRateProvider GetCoinaverageService ( RatesViewModel vm , bool withAuth )
{
var settings = new CoinAverageSettings ( )
{
KeyPair = ( vm . PublicKey , vm . PrivateKey )
} ;
if ( ! withAuth | | settings . GetCoinAverageSignature ( ) ! = null )
{
2018-05-02 20:32:42 +02:00
return new CoinAverageRateProvider ( )
2018-04-18 11:23:39 +02:00
{ Authenticator = settings } ;
}
return null ;
}
2017-10-27 10:53:04 +02:00
[Route("server/users")]
public IActionResult ListUsers ( )
{
var users = new UsersViewModel ( ) ;
2017-12-04 06:39:02 +01:00
users . StatusMessage = StatusMessage ;
2017-10-27 10:53:04 +02:00
users . Users
= _UserManager . Users . Select ( u = > new UsersViewModel . UserViewModel ( )
{
Name = u . UserName ,
2017-12-04 06:39:02 +01:00
Email = u . Email ,
Id = u . Id
2017-10-27 10:53:04 +02:00
} ) . ToList ( ) ;
return View ( users ) ;
}
2017-09-27 07:18:09 +02:00
2018-03-22 11:55:14 +01:00
[Route("server/users/{userId}")]
public new async Task < IActionResult > User ( string userId )
{
var user = await _UserManager . FindByIdAsync ( userId ) ;
if ( user = = null )
return NotFound ( ) ;
var roles = await _UserManager . GetRolesAsync ( user ) ;
var userVM = new UserViewModel ( ) ;
userVM . Id = user . Id ;
2018-04-19 18:44:24 +02:00
userVM . Email = user . Email ;
2018-03-22 11:55:14 +01:00
userVM . IsAdmin = IsAdmin ( roles ) ;
return View ( userVM ) ;
}
2018-07-24 10:04:57 +02:00
[Route("server/maintenance")]
public IActionResult Maintenance ( )
{
MaintenanceViewModel vm = new MaintenanceViewModel ( ) ;
vm . UserName = "btcpayserver" ;
vm . DNSDomain = this . Request . Host . Host ;
2018-08-12 14:38:45 +02:00
vm . SetConfiguredSSH ( _Options . SSHSettings ) ;
2018-07-24 10:04:57 +02:00
if ( IPAddress . TryParse ( vm . DNSDomain , out var unused ) )
vm . DNSDomain = null ;
return View ( vm ) ;
}
2018-11-07 14:29:35 +01:00
2018-07-24 10:04:57 +02:00
[Route("server/maintenance")]
[HttpPost]
public async Task < IActionResult > Maintenance ( MaintenanceViewModel vm , string command )
{
if ( ! ModelState . IsValid )
return View ( vm ) ;
2018-08-12 14:38:45 +02:00
vm . SetConfiguredSSH ( _Options . SSHSettings ) ;
2018-07-24 10:04:57 +02:00
if ( command = = "changedomain" )
{
if ( string . IsNullOrWhiteSpace ( vm . DNSDomain ) )
{
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"Required field" ) ;
return View ( vm ) ;
}
vm . DNSDomain = vm . DNSDomain . Trim ( ) . ToLowerInvariant ( ) ;
2018-08-13 09:48:10 +02:00
if ( vm . DNSDomain . Equals ( this . Request . Host . Host , StringComparison . OrdinalIgnoreCase ) )
return View ( vm ) ;
2018-07-24 10:04:57 +02:00
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
{
2018-08-13 10:04:37 +02:00
builder . Scheme = this . Request . Scheme ;
builder . Host = vm . DNSDomain ;
2018-08-13 09:48:10 +02:00
var addresses1 = Dns . GetHostAddressesAsync ( this . Request . Host . Host ) ;
var addresses2 = Dns . GetHostAddressesAsync ( vm . DNSDomain ) ;
await Task . WhenAll ( addresses1 , addresses2 ) ;
var addressesSet = addresses1 . GetAwaiter ( ) . GetResult ( ) . Select ( c = > c . ToString ( ) ) . ToHashSet ( ) ;
var hasCommonAddress = addresses2 . GetAwaiter ( ) . GetResult ( ) . Select ( c = > c . ToString ( ) ) . Any ( s = > addressesSet . Contains ( s ) ) ;
if ( ! hasCommonAddress )
2018-07-24 10:04:57 +02:00
{
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"Invalid host ({vm.DNSDomain} is not pointing to this BTCPay instance)" ) ;
return View ( vm ) ;
}
}
catch ( Exception ex )
{
2018-07-24 11:47:55 +02:00
var messages = new List < object > ( ) ;
messages . Add ( ex . Message ) ;
if ( ex . InnerException ! = null )
messages . Add ( ex . InnerException . Message ) ;
ModelState . AddModelError ( nameof ( vm . DNSDomain ) , $"Invalid domain ({string.Join(" , ", messages.ToArray())})" ) ;
2018-07-24 10:04:57 +02:00
return View ( vm ) ;
}
}
2018-07-24 17:51:45 +02:00
var error = RunSSH ( vm , $"changedomain.sh {vm.DNSDomain}" ) ;
2018-07-24 10:04:57 +02:00
if ( error ! = null )
return error ;
builder . Path = null ;
builder . Query = null ;
StatusMessage = $"Domain name changing... the server will restart, please use \" { builder . Uri . AbsoluteUri } \ "" ;
}
2018-07-24 15:10:37 +02:00
else if ( command = = "update" )
{
2018-07-24 18:01:05 +02:00
var error = RunSSH ( vm , $"btcpay-update.sh" ) ;
2018-07-24 15:10:37 +02:00
if ( error ! = null )
return error ;
StatusMessage = $"The server might restart soon if an update is available..." ;
}
2018-07-24 10:04:57 +02:00
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 ( ) ;
}
2018-07-24 17:51:45 +02:00
private IActionResult RunSSH ( MaintenanceViewModel vm , string ssh )
2018-07-24 10:04:57 +02:00
{
2018-07-24 17:51:45 +02:00
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'" ;
2018-08-12 14:38:45 +02:00
var sshClient = _Options . SSHSettings = = null ? vm . CreateSSHClient ( this . Request . Host . Host )
: new SshClient ( _Options . SSHSettings . CreateConnectionInfo ( ) ) ;
2018-08-12 16:23:26 +02:00
if ( _Options . TrustedFingerprints . Count ! = 0 )
{
sshClient . HostKeyReceived + = ( object sender , Renci . SshNet . Common . HostKeyEventArgs e ) = >
{
if ( _Options . TrustedFingerprints . Count = = 0 )
{
2018-08-13 02:43:59 +02:00
Logs . Configuration . LogWarning ( $"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \" { Encoders . Hex . EncodeData ( e . FingerPrint ) } \ "" ) ;
2018-08-12 16:23:26 +02:00
e . CanTrust = true ; // Not a typo, we want the connection to succeed with a warning
}
else
{
2018-08-13 02:43:59 +02:00
e . CanTrust = _Options . IsTrustedFingerprint ( e . FingerPrint , e . HostKey ) ;
2018-10-27 15:49:39 +02:00
if ( ! e . CanTrust )
2018-08-13 02:43:59 +02:00
Logs . Configuration . LogError ( $"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \" { Encoders . Hex . EncodeData ( e . FingerPrint ) } \ "" ) ;
2018-08-12 16:23:26 +02:00
}
} ;
}
else
{
}
2018-07-24 10:04:57 +02:00
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
{
2018-07-24 17:51:45 +02:00
Logs . PayServer . LogInformation ( "Running SSH command: " + ssh ) ;
2018-07-24 10:04:57 +02:00
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 ;
}
2018-03-22 11:55:14 +01:00
private static bool IsAdmin ( IList < string > roles )
{
return roles . Contains ( Roles . ServerAdmin , StringComparer . Ordinal ) ;
}
[Route("server/users/{userId}")]
[HttpPost]
public new async Task < IActionResult > User ( string userId , UserViewModel viewModel )
{
var user = await _UserManager . FindByIdAsync ( userId ) ;
if ( user = = null )
return NotFound ( ) ;
var roles = await _UserManager . GetRolesAsync ( user ) ;
var isAdmin = IsAdmin ( roles ) ;
bool updated = false ;
2018-04-14 15:35:52 +02:00
if ( isAdmin ! = viewModel . IsAdmin )
2018-03-22 11:55:14 +01:00
{
if ( viewModel . IsAdmin )
await _UserManager . AddToRoleAsync ( user , Roles . ServerAdmin ) ;
else
await _UserManager . RemoveFromRoleAsync ( user , Roles . ServerAdmin ) ;
updated = true ;
}
2018-04-14 15:35:52 +02:00
if ( updated )
2018-03-22 11:55:14 +01:00
{
viewModel . StatusMessage = "User successfully updated" ;
}
return View ( viewModel ) ;
}
2017-12-04 06:39:02 +01:00
[Route("server/users/{userId}/delete")]
public async Task < IActionResult > DeleteUser ( string userId )
{
var user = userId = = null ? null : await _UserManager . FindByIdAsync ( userId ) ;
if ( user = = null )
return NotFound ( ) ;
return View ( "Confirm" , new ConfirmModel ( )
{
Title = "Delete user " + user . Email ,
Description = "This user will be permanently deleted" ,
Action = "Delete"
} ) ;
}
[Route("server/users/{userId}/delete")]
[HttpPost]
public async Task < IActionResult > DeleteUserPost ( string userId )
{
var user = userId = = null ? null : await _UserManager . FindByIdAsync ( userId ) ;
if ( user = = null )
return NotFound ( ) ;
await _UserManager . DeleteAsync ( user ) ;
2018-07-20 08:24:19 +02:00
await _StoreRepository . CleanUnreachableStores ( ) ;
2017-12-04 06:39:02 +01:00
StatusMessage = "User deleted" ;
return RedirectToAction ( nameof ( ListUsers ) ) ;
}
[TempData]
public string StatusMessage
{
get ; set ;
}
2017-10-27 10:53:04 +02:00
[Route("server/emails")]
public async Task < IActionResult > Emails ( )
{
var data = ( await _SettingsRepository . GetSettingAsync < EmailSettings > ( ) ) ? ? new EmailSettings ( ) ;
return View ( new EmailsViewModel ( ) { Settings = data } ) ;
}
2017-09-27 07:18:09 +02:00
2017-10-27 10:53:04 +02:00
[Route("server/policies")]
public async Task < IActionResult > Policies ( )
{
var data = ( await _SettingsRepository . GetSettingAsync < PoliciesSettings > ( ) ) ? ? new PoliciesSettings ( ) ;
return View ( data ) ;
}
[Route("server/policies")]
[HttpPost]
public async Task < IActionResult > Policies ( PoliciesSettings settings )
2018-04-19 18:39:51 +02:00
{
await _SettingsRepository . UpdateSetting ( settings ) ;
TempData [ "StatusMessage" ] = "Policies updated successfully" ;
return View ( settings ) ;
}
2018-07-22 11:38:14 +02:00
[Route("server/services")]
public IActionResult Services ( )
{
var result = new ServicesViewModel ( ) ;
2018-07-23 04:53:39 +02:00
foreach ( var cryptoCode in _Options . ExternalServicesByCryptoCode . Keys )
2018-07-22 11:38:14 +02:00
{
2018-10-27 15:49:39 +02:00
int i = 0 ;
foreach ( var grpcService in _Options . ExternalServicesByCryptoCode . GetServices < ExternalLnd > ( cryptoCode ) )
2018-07-22 11:38:14 +02:00
{
2018-10-27 15:49:39 +02:00
result . LNDServices . Add ( new ServicesViewModel . LNDServiceViewModel ( )
2018-07-22 11:38:14 +02:00
{
2018-10-27 15:49:39 +02:00
Crypto = cryptoCode ,
Type = grpcService . Type ,
Index = i + + ,
} ) ;
2018-07-22 11:38:14 +02:00
}
}
2018-08-12 14:38:45 +02:00
result . HasSSH = _Options . SSHSettings ! = null ;
2018-07-22 11:38:14 +02:00
return View ( result ) ;
}
2018-07-23 04:53:39 +02:00
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
2018-10-27 15:49:39 +02:00
public IActionResult LndGrpcServices ( string cryptoCode , int index , uint? nonce )
2018-07-22 11:38:14 +02:00
{
2018-08-12 14:38:45 +02:00
if ( ! _dashBoard . IsFullySynched ( cryptoCode , out var unusud ) )
2018-08-03 05:14:09 +02:00
{
StatusMessage = $"Error: {cryptoCode} is not fully synched" ;
return RedirectToAction ( nameof ( Services ) ) ;
}
2018-10-27 15:49:39 +02:00
var external = GetExternalLndConnectionString ( cryptoCode , index ) ;
2018-07-23 04:53:39 +02:00
if ( external = = null )
2018-07-22 11:38:14 +02:00
return NotFound ( ) ;
2018-10-27 15:49:39 +02:00
var model = new LndGrpcServicesViewModel ( ) ;
2018-07-22 11:38:14 +02:00
2018-07-22 14:28:21 +02:00
model . Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}" ;
2018-07-22 11:38:14 +02:00
model . SSL = external . BaseUri . Scheme = = "https" ;
if ( external . CertificateThumbprint ! = null )
{
model . CertificateThumbprint = Encoders . Hex . EncodeData ( external . CertificateThumbprint ) ;
}
if ( external . Macaroon ! = null )
{
model . Macaroon = Encoders . Hex . EncodeData ( external . Macaroon ) ;
}
2018-07-23 04:53:39 +02:00
if ( nonce ! = null )
2018-07-22 11:38:14 +02:00
{
2018-07-23 04:53:39 +02:00
var configKey = GetConfigKey ( "lnd-grpc" , cryptoCode , index , nonce . Value ) ;
var lnConfig = _LnConfigProvider . GetConfig ( configKey ) ;
2018-07-22 11:38:14 +02:00
if ( lnConfig ! = null )
{
2018-07-23 04:53:39 +02:00
model . QRCodeLink = $"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}lnd-config/{configKey}/lnd.config" ;
2018-07-22 14:28:21 +02:00
model . QRCode = $"config={model.QRCodeLink}" ;
2018-07-22 11:38:14 +02:00
}
}
2018-07-23 04:53:39 +02:00
2018-07-22 11:38:14 +02:00
return View ( model ) ;
}
2018-07-23 04:53:39 +02:00
private static uint GetConfigKey ( string type , string cryptoCode , int index , uint nonce )
{
return ( uint ) HashCode . Combine ( type , cryptoCode , index , nonce ) ;
}
[Route("lnd-config/{configKey}/lnd.config")]
2018-07-22 11:43:11 +02:00
[AllowAnonymous]
2018-07-23 04:53:39 +02:00
public IActionResult GetLNDConfig ( uint configKey )
2018-07-22 11:38:14 +02:00
{
2018-07-23 04:53:39 +02:00
var conf = _LnConfigProvider . GetConfig ( configKey ) ;
2018-07-22 11:38:14 +02:00
if ( conf = = null )
return NotFound ( ) ;
return Json ( conf ) ;
}
2018-07-23 04:53:39 +02:00
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
2018-07-22 11:38:14 +02:00
[HttpPost]
2018-10-27 15:49:39 +02:00
public IActionResult LndGrpcServicesPost ( string cryptoCode , int index )
2018-07-22 11:38:14 +02:00
{
2018-10-27 15:49:39 +02:00
var external = GetExternalLndConnectionString ( cryptoCode , index ) ;
2018-07-23 04:53:39 +02:00
if ( external = = null )
2018-07-22 11:38:14 +02:00
return NotFound ( ) ;
LightningConfigurations confs = new LightningConfigurations ( ) ;
LightningConfiguration conf = new LightningConfiguration ( ) ;
conf . Type = "grpc" ;
2018-08-12 09:19:18 +02:00
conf . ChainType = _Options . NetworkType . ToString ( ) ;
2018-07-22 11:38:14 +02:00
conf . CryptoCode = cryptoCode ;
conf . Host = external . BaseUri . DnsSafeHost ;
conf . Port = external . BaseUri . Port ;
conf . SSL = external . BaseUri . Scheme = = "https" ;
conf . Macaroon = external . Macaroon = = null ? null : Encoders . Hex . EncodeData ( external . Macaroon ) ;
conf . CertificateThumbprint = external . CertificateThumbprint = = null ? null : Encoders . Hex . EncodeData ( external . CertificateThumbprint ) ;
confs . Configurations . Add ( conf ) ;
2018-07-23 04:53:39 +02:00
var nonce = RandomUtils . GetUInt32 ( ) ;
var configKey = GetConfigKey ( "lnd-grpc" , cryptoCode , index , nonce ) ;
_LnConfigProvider . KeepConfig ( configKey , confs ) ;
2018-10-27 15:49:39 +02:00
return RedirectToAction ( nameof ( LndGrpcServices ) , new { cryptoCode = cryptoCode , nonce = nonce } ) ;
2018-07-23 04:53:39 +02:00
}
2018-10-27 15:49:39 +02:00
private LightningConnectionString GetExternalLndConnectionString ( string cryptoCode , int index )
2018-07-23 04:53:39 +02:00
{
2018-10-27 15:49:39 +02:00
var connectionString = _Options . ExternalServicesByCryptoCode . GetServices < ExternalLnd > ( cryptoCode ) . Skip ( index ) . Select ( c = > c . ConnectionString ) . FirstOrDefault ( ) ;
2018-07-23 05:20:11 +02:00
if ( connectionString = = null )
return null ;
connectionString = connectionString . Clone ( ) ;
2018-07-24 10:04:57 +02:00
if ( connectionString . MacaroonFilePath ! = null )
2018-07-23 05:20:11 +02:00
{
try
{
connectionString . Macaroon = System . IO . File . ReadAllBytes ( connectionString . MacaroonFilePath ) ;
connectionString . MacaroonFilePath = null ;
}
catch
{
2018-10-27 15:49:39 +02:00
Logs . Configuration . LogWarning ( $"{cryptoCode}: The macaroon file path of the external LND grpc config was not found ({connectionString.MacaroonFilePath})" ) ;
2018-07-23 05:20:11 +02:00
return null ;
}
}
return connectionString ;
2018-07-22 11:38:14 +02:00
}
2018-07-24 10:04:57 +02:00
2018-10-27 15:49:39 +02:00
[Route("server/services/lnd-rest/{cryptoCode}/{index}")]
public IActionResult LndRestServices ( string cryptoCode , int index , uint? nonce )
{
if ( ! _dashBoard . IsFullySynched ( cryptoCode , out var unusud ) )
{
StatusMessage = $"Error: {cryptoCode} is not fully synched" ;
return RedirectToAction ( nameof ( Services ) ) ;
}
var external = GetExternalLndConnectionString ( cryptoCode , index ) ;
if ( external = = null )
return NotFound ( ) ;
var model = new LndRestServicesViewModel ( ) ;
model . BaseApiUrl = external . BaseUri . ToString ( ) ;
if ( external . CertificateThumbprint ! = null )
model . CertificateThumbprint = Encoders . Hex . EncodeData ( external . CertificateThumbprint ) ;
if ( external . Macaroon ! = null )
model . Macaroon = Encoders . Hex . EncodeData ( external . Macaroon ) ;
return View ( model ) ;
}
2018-08-12 14:38:45 +02:00
[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 ) ;
}
2018-04-19 18:39:51 +02:00
[Route("server/theme")]
public async Task < IActionResult > Theme ( )
{
var data = ( await _SettingsRepository . GetSettingAsync < ThemeSettings > ( ) ) ? ? new ThemeSettings ( ) ;
return View ( data ) ;
}
[Route("server/theme")]
[HttpPost]
public async Task < IActionResult > Theme ( ThemeSettings settings )
2017-10-27 10:53:04 +02:00
{
await _SettingsRepository . UpdateSetting ( settings ) ;
2018-04-19 18:39:51 +02:00
TempData [ "StatusMessage" ] = "Theme settings updated successfully" ;
2017-10-27 10:53:04 +02:00
return View ( settings ) ;
}
2017-09-27 07:18:09 +02:00
2017-10-27 10:53:04 +02:00
[Route("server/emails")]
[HttpPost]
public async Task < IActionResult > Emails ( EmailsViewModel model , string command )
{
if ( command = = "Test" )
{
try
{
2018-07-22 11:38:14 +02:00
if ( ! model . Settings . IsComplete ( ) )
2018-05-04 08:54:12 +02:00
{
model . StatusMessage = "Error: Required fields missing" ;
return View ( model ) ;
}
2017-10-27 10:53:04 +02:00
var client = model . Settings . CreateSmtpClient ( ) ;
await client . SendMailAsync ( model . Settings . From , model . TestEmail , "BTCPay test" , "BTCPay test" ) ;
model . StatusMessage = "Email sent to " + model . TestEmail + ", please, verify you received it" ;
}
catch ( Exception ex )
{
model . StatusMessage = "Error: " + ex . Message ;
}
return View ( model ) ;
}
2018-05-04 08:54:12 +02:00
else // if(command == "Save")
2017-10-27 10:53:04 +02:00
{
await _SettingsRepository . UpdateSetting ( model . Settings ) ;
model . StatusMessage = "Email settings saved" ;
return View ( model ) ;
}
}
2018-11-07 14:29:35 +01:00
[Route("server/logs/{file?}")]
public async Task < IActionResult > LogsView ( string file = null , int offset = 0 )
{
if ( offset < 0 )
{
offset = 0 ;
}
var vm = new LogsViewModel ( ) ;
if ( string . IsNullOrEmpty ( _Options . LogFile ) )
{
vm . StatusMessage = "Error: File Logging Option not specified. " +
"You need to set debuglog and optionally " +
"debugloglevel in the configuration or through runtime arguments" ;
}
else
{
var di = Directory . GetParent ( _Options . LogFile ) ;
if ( di = = null )
{
vm . StatusMessage = "Error: Could not load log files" ;
}
var fileNameWithoutExtension = Path . GetFileNameWithoutExtension ( _Options . LogFile ) ;
var fileExtension = Path . GetExtension ( _Options . LogFile ) ? ? string . Empty ;
var logFiles = di . GetFiles ( $"{fileNameWithoutExtension}*{fileExtension}" ) ;
vm . LogFileCount = logFiles . Length ;
vm . LogFiles = logFiles
. OrderBy ( info = > info . LastWriteTime )
. Skip ( offset )
. Take ( 5 )
. ToList ( ) ;
vm . LogFileOffset = offset ;
if ( string . IsNullOrEmpty ( file ) ) return View ( "Logs" , vm ) ;
vm . Log = "" ;
var path = Path . Combine ( di . FullName , file ) ;
2018-11-09 13:43:10 +01:00
try
2018-11-07 14:29:35 +01:00
{
2018-11-09 13:43:10 +01:00
using ( var fileStream = new FileStream (
path ,
FileMode . Open ,
FileAccess . Read ,
FileShare . ReadWrite ) )
2018-11-07 14:29:35 +01:00
{
2018-11-09 13:43:10 +01:00
using ( var reader = new StreamReader ( fileStream ) )
{
vm . Log = await reader . ReadToEndAsync ( ) ;
}
2018-11-07 14:29:35 +01:00
}
}
2018-11-09 13:43:10 +01:00
catch
{
return NotFound ( ) ;
}
2018-11-07 14:29:35 +01:00
}
return View ( "Logs" , vm ) ;
}
2017-10-27 10:53:04 +02:00
}
2017-09-15 18:15:17 +02:00
}