2017-09-13 08:47:34 +02:00
using BTCPayServer.Logging ;
using System.Linq ;
using Microsoft.Extensions.Logging ;
using NBitcoin ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Net ;
using System.Text ;
2017-09-22 18:31:29 +02:00
using StandardConfiguration ;
using Microsoft.Extensions.Configuration ;
2018-01-07 18:36:41 +01:00
using NBXplorer ;
2018-03-20 18:09:25 +01:00
using BTCPayServer.Payments.Lightning ;
2018-08-12 14:38:45 +02:00
using Renci.SshNet ;
2018-08-12 16:23:26 +02:00
using NBitcoin.DataEncoders ;
2018-08-13 02:43:59 +02:00
using BTCPayServer.SSH ;
2018-08-30 04:50:39 +02:00
using BTCPayServer.Lightning ;
2018-10-27 15:49:39 +02:00
using BTCPayServer.Configuration.External ;
2018-11-07 14:29:35 +01:00
using Serilog.Events ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Configuration
{
2018-01-12 08:00:31 +01:00
public class NBXplorerConnectionSetting
{
public string CryptoCode { get ; internal set ; }
public Uri ExplorerUri { get ; internal set ; }
public string CookieFile { get ; internal set ; }
}
2017-10-27 10:53:04 +02:00
public class BTCPayServerOptions
{
2018-04-19 09:54:25 +02:00
public NetworkType NetworkType
2017-10-27 10:53:04 +02:00
{
get ; set ;
}
public string ConfigurationFile
2018-11-07 14:29:35 +01:00
{
get ;
private set ;
}
public string LogFile
2017-10-27 10:53:04 +02:00
{
get ;
private set ;
}
public string DataDir
{
get ;
private set ;
}
public List < IPEndPoint > Listen
{
get ;
set ;
}
2017-09-13 08:47:34 +02:00
2018-01-12 08:00:31 +01:00
public List < NBXplorerConnectionSetting > NBXplorerConnectionSettings
{
get ;
set ;
} = new List < NBXplorerConnectionSetting > ( ) ;
2018-11-07 14:29:35 +01:00
public static string GetDebugLog ( IConfiguration configuration )
{
return configuration . GetValue < string > ( "debuglog" , null ) ;
}
public static LogEventLevel GetDebugLogLevel ( IConfiguration configuration )
{
var raw = configuration . GetValue ( "debugloglevel" , nameof ( LogEventLevel . Debug ) ) ;
return ( LogEventLevel ) Enum . Parse ( typeof ( LogEventLevel ) , raw , true ) ;
}
2017-10-27 10:53:04 +02:00
public void LoadArgs ( IConfiguration conf )
{
2018-04-19 09:54:25 +02:00
NetworkType = DefaultConfiguration . GetNetworkType ( conf ) ;
var defaultSettings = BTCPayDefaultSettings . GetDefaultSettings ( NetworkType ) ;
2018-01-11 14:52:28 +01:00
DataDir = conf . GetOrDefault < string > ( "datadir" , defaultSettings . DefaultDataDirectory ) ;
2018-04-19 09:54:25 +02:00
Logs . Configuration . LogInformation ( "Network: " + NetworkType . ToString ( ) ) ;
2017-09-13 08:47:34 +02:00
2018-01-11 14:52:28 +01:00
var supportedChains = conf . GetOrDefault < string > ( "chains" , "btc" )
. Split ( ',' , StringSplitOptions . RemoveEmptyEntries )
2018-01-12 03:54:57 +01:00
. Select ( t = > t . ToUpperInvariant ( ) ) ;
2018-04-19 09:54:25 +02:00
NetworkProvider = new BTCPayNetworkProvider ( NetworkType ) . Filter ( supportedChains . ToArray ( ) ) ;
2018-02-25 16:48:12 +01:00
foreach ( var chain in supportedChains )
{
if ( NetworkProvider . GetNetwork ( chain ) = = null )
throw new ConfigException ( $"Invalid chains \" { chain } \ "" ) ;
}
2018-01-11 14:52:28 +01:00
var validChains = new List < string > ( ) ;
2018-02-25 16:48:12 +01:00
foreach ( var net in NetworkProvider . GetAll ( ) )
2018-01-07 18:36:41 +01:00
{
2018-02-25 16:48:12 +01:00
NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting ( ) ;
setting . CryptoCode = net . CryptoCode ;
setting . ExplorerUri = conf . GetOrDefault < Uri > ( $"{net.CryptoCode}.explorer.url" , net . NBXplorerNetwork . DefaultSettings . DefaultUrl ) ;
setting . CookieFile = conf . GetOrDefault < string > ( $"{net.CryptoCode}.explorer.cookiefile" , net . NBXplorerNetwork . DefaultSettings . DefaultCookieFile ) ;
NBXplorerConnectionSettings . Add ( setting ) ;
2018-10-27 15:49:39 +02:00
2018-03-20 18:09:25 +01:00
{
2018-07-23 04:53:39 +02:00
var lightning = conf . GetOrDefault < string > ( $"{net.CryptoCode}.lightning" , string . Empty ) ;
if ( lightning . Length ! = 0 )
2018-03-20 18:09:25 +01:00
{
2018-07-23 04:53:39 +02:00
if ( ! LightningConnectionString . TryParse ( lightning , true , out var connectionString , out var error ) )
{
throw new ConfigException ( $"Invalid setting {net.CryptoCode}.lightning, " + Environment . NewLine +
$"If you have a lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment . NewLine +
$"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" + Environment . NewLine +
$"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment . NewLine +
$" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment . NewLine +
error ) ;
}
if ( connectionString . IsLegacy )
{
Logs . Configuration . LogWarning ( $"Setting {net.CryptoCode}.lightning will work but use an deprecated format, please replace it by '{connectionString.ToString()}'" ) ;
}
InternalLightningByCryptoCode . Add ( net . CryptoCode , connectionString ) ;
2018-07-01 08:45:08 +02:00
}
2018-07-23 04:53:39 +02:00
}
2018-10-27 15:49:39 +02:00
void externalLnd < T > ( string code , string lndType )
2018-07-23 04:53:39 +02:00
{
2018-10-27 15:49:39 +02:00
var lightning = conf . GetOrDefault < string > ( code , string . Empty ) ;
2018-07-23 04:53:39 +02:00
if ( lightning . Length ! = 0 )
2018-07-01 08:45:08 +02:00
{
2018-07-23 04:53:39 +02:00
if ( ! LightningConnectionString . TryParse ( lightning , false , out var connectionString , out var error ) )
{
2018-10-27 15:49:39 +02:00
throw new ConfigException ( $"Invalid setting {code}, " + Environment . NewLine +
$"lnd server: 'type={lndType};server=https://lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment . NewLine +
$"lnd server: 'type={lndType};server=https://lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment . NewLine +
2018-07-23 04:53:39 +02:00
error ) ;
}
2018-10-27 15:49:39 +02:00
var instanceType = typeof ( T ) ;
ExternalServicesByCryptoCode . Add ( net . CryptoCode , ( ExternalService ) Activator . CreateInstance ( instanceType , connectionString ) ) ;
2018-03-20 18:09:25 +01:00
}
2018-10-27 15:49:39 +02:00
} ;
externalLnd < ExternalLndGrpc > ( $"{net.CryptoCode}.external.lnd.grpc" , "lnd-grpc" ) ;
externalLnd < ExternalLndRest > ( $"{net.CryptoCode}.external.lnd.rest" , "lnd-rest" ) ;
2018-12-12 10:19:13 +01:00
var spark = conf . GetOrDefault < string > ( $"{net.CryptoCode}.external.spark" , string . Empty ) ;
if ( spark . Length ! = 0 )
{
if ( ! SparkConnectionString . TryParse ( spark , out var connectionString ) )
{
throw new ConfigException ( $"Invalid setting {net.CryptoCode}.external.spark, " + Environment . NewLine +
$"Valid example: 'server=https://btcpay.example.com/spark/btc/;cookiefile=/etc/clightning_bitcoin_spark/.cookie'" ) ;
}
ExternalServicesByCryptoCode . Add ( net . CryptoCode , new ExternalSpark ( connectionString ) ) ;
}
2018-01-07 18:36:41 +01:00
}
2018-01-12 03:54:57 +01:00
Logs . Configuration . LogInformation ( "Supported chains: " + String . Join ( ',' , supportedChains . ToArray ( ) ) ) ;
2018-02-25 16:48:12 +01:00
2018-12-07 10:42:39 +01:00
var services = conf . GetOrDefault < string > ( "externalservices" , null ) ;
if ( services ! = null )
{
foreach ( var service in services . Split ( new [ ] { ';' , ',' } )
. Select ( p = > p . Split ( ':' ) )
. Where ( p = > p . Length = = 2 )
. Select ( p = > ( Name : p [ 0 ] , Link : p [ 1 ] ) ) )
{
ExternalServices . AddOrReplace ( service . Name , service . Link ) ;
}
}
2017-10-27 10:53:04 +02:00
PostgresConnectionString = conf . GetOrDefault < string > ( "postgres" , null ) ;
2018-10-27 16:15:21 +02:00
MySQLConnectionString = conf . GetOrDefault < string > ( "mysql" , null ) ;
2018-02-21 07:48:25 +01:00
BundleJsCss = conf . GetOrDefault < bool > ( "bundlejscss" , true ) ;
2017-12-02 15:22:23 +01:00
ExternalUrl = conf . GetOrDefault < Uri > ( "externalurl" , null ) ;
2018-04-05 08:50:23 +02:00
2018-08-13 02:43:59 +02:00
var sshSettings = ParseSSHConfiguration ( conf ) ;
2018-08-12 16:23:26 +02:00
if ( ( ! string . IsNullOrEmpty ( sshSettings . Password ) | | ! string . IsNullOrEmpty ( sshSettings . KeyFile ) ) & & ! string . IsNullOrEmpty ( sshSettings . Server ) )
2018-08-12 14:38:45 +02:00
{
2018-08-13 02:43:59 +02:00
int waitTime = 0 ;
while ( ! string . IsNullOrEmpty ( sshSettings . KeyFile ) & & ! File . Exists ( sshSettings . KeyFile ) )
{
2018-10-27 15:49:39 +02:00
if ( waitTime + + < 5 )
2018-08-13 02:43:59 +02:00
System . Threading . Thread . Sleep ( 1000 ) ;
else
throw new ConfigException ( $"sshkeyfile does not exist" ) ;
}
2018-10-27 15:49:39 +02:00
2018-08-12 14:38:45 +02:00
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 ;
}
2018-08-12 16:23:26 +02:00
var fingerPrints = conf . GetOrDefault < string > ( "sshtrustedfingerprints" , "" ) ;
if ( ! string . IsNullOrEmpty ( fingerPrints ) )
{
2018-08-13 02:43:59 +02:00
foreach ( var fingerprint in fingerPrints . Split ( ';' , StringSplitOptions . RemoveEmptyEntries ) )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
if ( ! SSHFingerprint . TryParse ( fingerprint , out var f ) )
throw new ConfigException ( $"Invalid ssh fingerprint format {fingerprint}" ) ;
TrustedFingerprints . Add ( f ) ;
2018-08-12 16:23:26 +02:00
}
}
2018-04-05 08:50:23 +02:00
RootPath = conf . GetOrDefault < string > ( "rootpath" , "/" ) ;
2018-04-09 07:31:39 +02:00
if ( ! RootPath . StartsWith ( "/" , StringComparison . InvariantCultureIgnoreCase ) )
RootPath = "/" + RootPath ;
2018-03-20 18:09:25 +01:00
var old = conf . GetOrDefault < Uri > ( "internallightningnode" , null ) ;
2018-07-23 04:53:39 +02:00
if ( old ! = null )
2018-03-20 18:09:25 +01:00
throw new ConfigException ( $"internallightningnode should not be used anymore, use btclightning instead" ) ;
2018-11-07 14:29:35 +01:00
LogFile = GetDebugLog ( conf ) ;
if ( ! string . IsNullOrEmpty ( LogFile ) )
{
Logs . Configuration . LogInformation ( "LogFile: " + LogFile ) ;
Logs . Configuration . LogInformation ( "Log Level: " + GetDebugLogLevel ( conf ) ) ;
}
2017-10-27 10:53:04 +02:00
}
2018-08-12 16:23:26 +02:00
2018-08-13 02:43:59 +02:00
private SSHSettings ParseSSHConfiguration ( IConfiguration conf )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
var externalUrl = conf . GetOrDefault < Uri > ( "externalurl" , null ) ;
var settings = new SSHSettings ( ) ;
settings . Server = conf . GetOrDefault < string > ( "sshconnection" , null ) ;
if ( settings . Server ! = null )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
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 ;
}
2018-08-12 16:23:26 +02:00
2018-08-13 02:43:59 +02:00
parts = settings . Server . Split ( '@' ) ;
if ( parts . Length = = 2 )
{
settings . Username = parts [ 0 ] ;
settings . Server = parts [ 1 ] ;
}
else
{
settings . Username = "root" ;
}
2018-08-12 16:23:26 +02:00
}
2018-08-13 02:43:59 +02:00
else if ( externalUrl ! = null )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
settings . Port = 22 ;
settings . Username = "root" ;
settings . Server = externalUrl . DnsSafeHost ;
2018-08-12 16:23:26 +02:00
}
2018-08-13 02:43:59 +02:00
settings . Password = conf . GetOrDefault < string > ( "sshpassword" , "" ) ;
settings . KeyFile = conf . GetOrDefault < string > ( "sshkeyfile" , "" ) ;
settings . KeyFilePassword = conf . GetOrDefault < string > ( "sshkeyfilepassword" , "" ) ;
return settings ;
2018-08-12 16:23:26 +02:00
}
2018-08-13 02:43:59 +02:00
internal bool IsTrustedFingerprint ( byte [ ] fingerPrint , byte [ ] hostKey )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
return TrustedFingerprints . Any ( f = > f . Match ( fingerPrint , hostKey ) ) ;
2018-08-12 16:23:26 +02:00
}
2018-04-05 08:50:23 +02:00
public string RootPath { get ; set ; }
2018-03-20 18:09:25 +01:00
public Dictionary < string , LightningConnectionString > InternalLightningByCryptoCode { get ; set ; } = new Dictionary < string , LightningConnectionString > ( ) ;
2018-12-07 10:42:39 +01:00
public Dictionary < string , string > ExternalServices { get ; set ; } = new Dictionary < string , string > ( ) ;
2018-07-23 04:53:39 +02:00
public ExternalServices ExternalServicesByCryptoCode { get ; set ; } = new ExternalServices ( ) ;
2018-02-26 10:58:02 +01:00
2018-02-25 16:48:12 +01:00
public BTCPayNetworkProvider NetworkProvider { get ; set ; }
2017-10-27 10:53:04 +02:00
public string PostgresConnectionString
{
get ;
set ;
}
2018-10-27 16:15:21 +02:00
public string MySQLConnectionString
{
get ;
set ;
}
2017-12-02 15:22:23 +01:00
public Uri ExternalUrl
{
get ;
set ;
}
2018-02-21 07:48:25 +01:00
public bool BundleJsCss
{
get ;
set ;
}
2018-08-13 02:43:59 +02:00
public List < SSHFingerprint > TrustedFingerprints { get ; set ; } = new List < SSHFingerprint > ( ) ;
2018-08-12 14:38:45 +02:00
public SSHSettings SSHSettings
{
get ;
set ;
}
2018-04-09 07:41:52 +02:00
internal string GetRootUri ( )
{
if ( ExternalUrl = = null )
return null ;
UriBuilder builder = new UriBuilder ( ExternalUrl ) ;
builder . Path = RootPath ;
return builder . ToString ( ) ;
}
2017-10-27 10:53:04 +02:00
}
2017-09-13 08:47:34 +02:00
}