diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 43842f9de..522e4cbc6 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -77,10 +77,10 @@ namespace BTCPayServer.Tests ServerUri = new Uri("http://127.0.0.1:" + port + "/"); - BTCPayServerOptions options = new BTCPayServerOptions(); - options.LoadArgs(new TextFileConfiguration(new string[] { "-datadir", _Directory })); + var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory }); _Host = new WebHostBuilder() + .UseConfiguration(conf) .ConfigureServices(s => { s.AddSingleton(new MockRateProvider(new Rate("USD", 5000m))); @@ -91,7 +91,6 @@ namespace BTCPayServer.Tests .AddProvider(Logs.LogProvider); }); }) - .AddPayServer(options) .UseKestrel() .UseStartup() .Build(); diff --git a/BTCPayServer.Tests/NBXplorerTester.cs b/BTCPayServer.Tests/NBXplorerTester.cs index e2cc6117d..7a7cfaa50 100644 --- a/BTCPayServer.Tests/NBXplorerTester.cs +++ b/BTCPayServer.Tests/NBXplorerTester.cs @@ -71,7 +71,7 @@ namespace BTCPayServer.Tests config.AppendLine($"rpc.auth={Node.AuthenticationString}"); config.AppendLine($"node.endpoint={Node.NodeEndpoint.Address}:{Node.NodeEndpoint.Port}"); File.WriteAllText(Path.Combine(launcher2.CurrentDirectory, "settings.config"), config.ToString()); - _Process = launcher.Start("dotnet", $"NBXplorer.dll -datadir \"{launcher2.CurrentDirectory}\""); + _Process = launcher.Start("dotnet", $"NBXplorer.dll --datadir \"{launcher2.CurrentDirectory}\""); ExplorerClient = new NBXplorer.ExplorerClient(Node.Network, new Uri($"http://127.0.0.1:{port}/")); CookieFile = Path.Combine(launcher2.CurrentDirectory, ".cookie"); File.Create(CookieFile).Close(); //Will be wipedout when the client starts diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 6cc37989e..c18f15e95 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -15,6 +15,9 @@ + + + diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 30eb07ebf..133e4cccf 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Text; +using StandardConfiguration; +using Microsoft.Extensions.Configuration; namespace BTCPayServer.Configuration { @@ -41,75 +43,20 @@ namespace BTCPayServer.Configuration set; } - public void LoadArgs(TextFileConfiguration consoleConfig) + public void LoadArgs(IConfiguration conf) { - ConfigurationFile = consoleConfig.GetOrDefault("conf", null); - DataDir = consoleConfig.GetOrDefault("datadir", null); - if(DataDir != null && ConfigurationFile != null) - { - var isRelativePath = Path.GetFullPath(ConfigurationFile).Length > ConfigurationFile.Length; - if(isRelativePath) - { - ConfigurationFile = Path.Combine(DataDir, ConfigurationFile); - } - } - - Network = consoleConfig.GetOrDefault("testnet", false) ? Network.TestNet : - consoleConfig.GetOrDefault("regtest", false) ? Network.RegTest : - null; - - if(DataDir != null && ConfigurationFile == null) - { - ConfigurationFile = GetDefaultConfigurationFile(Network != null); - } - - if(ConfigurationFile != null) - { - AssetConfigFileExists(); - var configTemp = TextFileConfiguration.Parse(File.ReadAllText(ConfigurationFile)); - Network = Network ?? (configTemp.GetOrDefault("testnet", false) ? Network.TestNet : - configTemp.GetOrDefault("regtest", false) ? Network.RegTest : - null); - } - - Network = Network ?? Network.Main; - if(DataDir == null) - { - DataDir = DefaultDataDirectory.GetDefaultDirectory("BTCPayServer", Network, true); - ConfigurationFile = GetDefaultConfigurationFile(true); - } - - if(!Directory.Exists(DataDir)) - throw new ConfigurationException("Data directory does not exists"); - - var config = TextFileConfiguration.Parse(File.ReadAllText(ConfigurationFile)); - consoleConfig.MergeInto(config, true); + var networkInfo = DefaultConfiguration.GetNetwork(conf); + Network = networkInfo?.Network; + if(Network == null) + throw new ConfigException("Invalid network"); + DataDir = conf.GetOrDefault("datadir", networkInfo.DefaultDataDirectory); Logs.Configuration.LogInformation("Network: " + Network); - Logs.Configuration.LogInformation("Data directory set to " + DataDir); - Logs.Configuration.LogInformation("Configuration file set to " + ConfigurationFile); - var defaultPort = config.GetOrDefault("port", GetDefaultPort(Network)); - Listen = config - .GetAll("bind") - .Select(p => ConvertToEndpoint(p, defaultPort)) - .ToList(); - if(Listen.Count == 0) - { - Listen.Add(new IPEndPoint(IPAddress.Parse("127.0.0.1"), defaultPort)); - } - - Explorer = config.GetOrDefault("explorer.url", GetDefaultNXplorerUri()); - CookieFile = config.GetOrDefault("explorer.cookiefile", GetExplorerDefaultCookiePath()); - ExternalUrl = config.GetOrDefault("externalurl", null); - if(ExternalUrl == null) - { - var ip = Listen.Where(u => !u.Address.ToString().Equals("0.0.0.0", StringComparison.OrdinalIgnoreCase)).FirstOrDefault() - ?? new IPEndPoint(IPAddress.Parse("127.0.0.1"), defaultPort); - ExternalUrl = new Uri($"http://{ip.Address}:{ip.Port}/"); - } - - RequireHttps = config.GetOrDefault("requirehttps", false); + Explorer = conf.GetOrDefault("explorer.url", networkInfo.DefaultExplorerUrl); + CookieFile = conf.GetOrDefault("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile); + ExternalUrl = conf.GetOrDefault("externalurl", null); + RequireHttps = conf.GetOrDefault("requirehttps", false); } public bool RequireHttps @@ -121,92 +68,5 @@ namespace BTCPayServer.Configuration { get; set; } - - private Uri GetDefaultNXplorerUri() - { - return new Uri("http://localhost:" + GetNXplorerDefaultPort(Network)); - } - - - public string[] GetUrls() - { - return Listen.Select(b => "http://" + b + "/").ToArray(); - } - - private void AssetConfigFileExists() - { - if(!File.Exists(ConfigurationFile)) - throw new ConfigurationException("Configuration file does not exists"); - } - - public static IPEndPoint ConvertToEndpoint(string str, int defaultPort) - { - var portOut = defaultPort; - var hostOut = ""; - int colon = str.LastIndexOf(':'); - // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator - bool fHaveColon = colon != -1; - bool fBracketed = fHaveColon && (str[0] == '[' && str[colon - 1] == ']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe - bool fMultiColon = fHaveColon && (str.LastIndexOf(':', colon - 1) != -1); - if(fHaveColon && (colon == 0 || fBracketed || !fMultiColon)) - { - int n; - if(int.TryParse(str.Substring(colon + 1), out n) && n > 0 && n < 0x10000) - { - str = str.Substring(0, colon); - portOut = n; - } - } - if(str.Length > 0 && str[0] == '[' && str[str.Length - 1] == ']') - hostOut = str.Substring(1, str.Length - 2); - else - hostOut = str; - return new IPEndPoint(IPAddress.Parse(hostOut), portOut); - } - - const string DefaultConfigFile = "settings.config"; - private string GetDefaultConfigurationFile(bool createIfNotExist) - { - var config = Path.Combine(DataDir, DefaultConfigFile); - Logs.Configuration.LogInformation("Configuration file set to " + config); - if(createIfNotExist && !File.Exists(config)) - { - Logs.Configuration.LogInformation("Creating configuration file"); - StringBuilder builder = new StringBuilder(); - builder.AppendLine("### Global settings ###"); - builder.AppendLine("#testnet=0"); - builder.AppendLine("#regtest=0"); - builder.AppendLine("#Put here the xpub key of your hardware wallet"); - builder.AppendLine("#hdpubkey=xpub..."); - builder.AppendLine(); - builder.AppendLine("### Server settings ###"); - builder.AppendLine("#port=" + GetDefaultPort(Network)); - builder.AppendLine("#bind=127.0.0.1"); - builder.AppendLine("#externalurl=http://127.0.0.1/"); - builder.AppendLine(); - builder.AppendLine("### NBXplorer settings ###"); - builder.AppendLine("#explorer.url=" + GetDefaultNXplorerUri()); - builder.AppendLine("#explorer.cookiefile=" + GetExplorerDefaultCookiePath()); - File.WriteAllText(config, builder.ToString()); - } - return config; - } - - private string GetExplorerDefaultCookiePath() - { - return Path.Combine(DefaultDataDirectory.GetDefaultDirectory("NBXplorer", Network, false), ".cookie"); - } - - private int GetNXplorerDefaultPort(Network network) - { - return network == Network.Main ? 24444 : - network == Network.TestNet ? 24445 : 24446; - } - - private int GetDefaultPort(Network network) - { - return network == Network.Main ? 23000 : - network == Network.TestNet ? 23001 : 23002; - } } } diff --git a/BTCPayServer/Configuration/BTCPayServerRuntime.cs b/BTCPayServer/Configuration/BTCPayServerRuntime.cs index 903b6520f..293e36a36 100644 --- a/BTCPayServer/Configuration/BTCPayServerRuntime.cs +++ b/BTCPayServer/Configuration/BTCPayServerRuntime.cs @@ -47,7 +47,7 @@ namespace BTCPayServer.Configuration } catch(Exception ex) { - throw new ConfigurationException($"Could not connect to NBXplorer, {ex.Message}"); + throw new ConfigException($"Could not connect to NBXplorer, {ex.Message}"); } DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB")); _Resources.Add(db); diff --git a/BTCPayServer/Configuration/ConfigException.cs b/BTCPayServer/Configuration/ConfigException.cs new file mode 100644 index 000000000..116dc2c61 --- /dev/null +++ b/BTCPayServer/Configuration/ConfigException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Configuration +{ + public class ConfigException : Exception + { + public ConfigException(string message) : base(message) + { + + } + } +} diff --git a/BTCPayServer/Configuration/ConfigurationExtensions.cs b/BTCPayServer/Configuration/ConfigurationExtensions.cs new file mode 100644 index 000000000..c509c0c27 --- /dev/null +++ b/BTCPayServer/Configuration/ConfigurationExtensions.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.Primitives; + +namespace BTCPayServer.Configuration +{ + public static class ConfigurationExtensions + { + public static T GetOrDefault(this IConfiguration configuration, string key, T defaultValue) + { + var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)]; + if(str == null) + return defaultValue; + if(typeof(T) == typeof(bool)) + { + var trueValues = new[] { "1", "true" }; + var falseValues = new[] { "0", "false" }; + if(trueValues.Contains(str, StringComparer.OrdinalIgnoreCase)) + return (T)(object)true; + if(falseValues.Contains(str, StringComparer.OrdinalIgnoreCase)) + return (T)(object)false; + throw new FormatException(); + } + else if(typeof(T) == typeof(Uri)) + return (T)(object)new Uri(str, UriKind.Absolute); + else if(typeof(T) == typeof(string)) + return (T)(object)str; + else if(typeof(T) == typeof(IPEndPoint)) + { + var separator = str.LastIndexOf(":"); + if(separator == -1) + throw new FormatException(); + var ip = str.Substring(0, separator); + var port = str.Substring(separator + 1); + return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port)); + } + else if(typeof(T) == typeof(int)) + { + return (T)(object)int.Parse(str, CultureInfo.InvariantCulture); + } + else + { + throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name); + } + } + } +} diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs new file mode 100644 index 000000000..e17745120 --- /dev/null +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -0,0 +1,96 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Logging; +using System.Net; +using System.Threading.Tasks; +using NBitcoin; +using System.Text; +using CommandLine; + +namespace BTCPayServer.Configuration +{ + public class DefaultConfiguration : StandardConfiguration.DefaultConfiguration + { + protected override CommandLineApplication CreateCommandLineApplicationCore() + { + CommandLineApplication app = new CommandLineApplication(true) + { + FullName = "NBXplorer\r\nLightweight block explorer for tracking HD wallets", + Name = "NBXplorer" + }; + app.HelpOption("-? | -h | --help"); + app.Option("-n | --network", $"Set the network among ({NetworkInformation.ToStringAll()}) (default: {Network.Main.ToString()})", CommandOptionType.SingleValue); + app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue); + app.Option("--regtest | -regtest", $"Use regtest", CommandOptionType.BoolValue); + app.Option("--requirehttps", $"Will redirect to https version of the website (default: false)", CommandOptionType.BoolValue); + app.Option("--externalurl", $"The external url of the website", CommandOptionType.SingleValue); + app.Option("--explorerurl", $"Url of the NBxplorer (default: : Default setting of NBXplorer for the network)", CommandOptionType.SingleValue); + app.Option("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue); + + return app; + } + + protected override string GetDefaultDataDir(IConfiguration conf) + { + return GetNetwork(conf).DefaultDataDirectory; + } + + protected override string GetDefaultConfigurationFile(IConfiguration conf) + { + var network = GetNetwork(conf); + var dataDir = conf["datadir"]; + if(dataDir == null) + return network.DefaultConfigurationFile; + var fileName = Path.GetFileName(network.DefaultConfigurationFile); + return Path.Combine(dataDir, fileName); + } + + public static NetworkInformation GetNetwork(IConfiguration conf) + { + var network = conf.GetOrDefault("network", null); + if(network != null) + { + var info = NetworkInformation.GetNetworkByName(network); + if(info == null) + throw new ConfigException($"Invalid network name {network}"); + return info; + } + + var net = conf.GetOrDefault("regtest", false) ? Network.RegTest : + conf.GetOrDefault("testnet", false) ? Network.TestNet : Network.Main; + + return NetworkInformation.GetNetworkByName(net.Name); + } + + protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf) + { + var network = GetNetwork(conf); + StringBuilder builder = new StringBuilder(); + builder.AppendLine("### Global settings ###"); + builder.AppendLine("#testnet=0"); + builder.AppendLine("#regtest=0"); + builder.AppendLine(); + builder.AppendLine("### Server settings ###"); + builder.AppendLine("#requirehttps=0"); + builder.AppendLine("#port=" + network.DefaultPort); + builder.AppendLine("#bind=127.0.0.1"); + builder.AppendLine("#externalurl=http://127.0.0.1/"); + builder.AppendLine(); + builder.AppendLine("### NBXplorer settings ###"); + builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri); + builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile); + return builder.ToString(); + } + + + + protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf) + { + return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort); + } + } +} diff --git a/BTCPayServer/Configuration/DefaultDataDirectory.cs b/BTCPayServer/Configuration/DefaultDataDirectory.cs deleted file mode 100644 index 83fd4c37a..000000000 --- a/BTCPayServer/Configuration/DefaultDataDirectory.cs +++ /dev/null @@ -1,52 +0,0 @@ -using BTCPayServer.Logging; -using Microsoft.Extensions.Logging; -using NBitcoin; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace BTCPayServer.Configuration -{ - public class DefaultDataDirectory - { - public static string GetDefaultDirectory(string appName, Network network, bool createDirectory) - { - string directory = null; - var home = Environment.GetEnvironmentVariable("HOME"); - if(!string.IsNullOrEmpty(home)) - { - if(createDirectory) - Logs.Configuration.LogInformation("Using HOME environment variable for initializing application data"); - directory = home; - directory = Path.Combine(directory, "." + appName.ToLowerInvariant()); - } - else - { - var localAppData = Environment.GetEnvironmentVariable("APPDATA"); - if(!string.IsNullOrEmpty(localAppData)) - { - if(createDirectory) - Logs.Configuration.LogInformation("Using APPDATA environment variable for initializing application data"); - directory = localAppData; - directory = Path.Combine(directory, appName); - } - else - { - throw new DirectoryNotFoundException("Could not find suitable datadir"); - } - } - if(!Directory.Exists(directory) && createDirectory) - { - Directory.CreateDirectory(directory); - } - directory = Path.Combine(directory, network.Name); - if(!Directory.Exists(directory) && createDirectory) - { - Logs.Configuration.LogInformation("Creating data directory"); - Directory.CreateDirectory(directory); - } - return directory; - } - } -} diff --git a/BTCPayServer/Configuration/NetworkInformation.cs b/BTCPayServer/Configuration/NetworkInformation.cs new file mode 100644 index 000000000..13f8bd5e6 --- /dev/null +++ b/BTCPayServer/Configuration/NetworkInformation.cs @@ -0,0 +1,103 @@ +using Microsoft.Extensions.Configuration; +using NBitcoin; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Configuration +{ + public class NetworkInformation + { + static NetworkInformation() + { + _Networks = new Dictionary(); + foreach(var network in Network.GetNetworks()) + { + NetworkInformation info = new NetworkInformation(); + info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name); + info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config"); + info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie"); + info.Network = network; + info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute); + info.DefaultPort = 23002; + _Networks.Add(network.Name, info); + if(network == Network.Main) + { + info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute); + Main = info; + info.DefaultPort = 23000; + } + if(network == Network.TestNet) + { + info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute); + info.DefaultPort = 23001; + } + } + } + + static Dictionary _Networks; + public static NetworkInformation GetNetworkByName(string name) + { + var value = _Networks.TryGet(name); + if(value != null) + return value; + + //Maybe alias ? + var network = Network.GetNetwork(name); + if(network != null) + { + value = _Networks.TryGet(network.Name); + if(value != null) + return value; + } + return null; + } + + public static NetworkInformation Main + { + get; + set; + } + public Network Network + { + get; set; + } + public string DefaultConfigurationFile + { + get; + set; + } + public string DefaultDataDirectory + { + get; + set; + } + public Uri DefaultExplorerUrl + { + get; + internal set; + } + public int DefaultPort + { + get; + private set; + } + public string DefaultExplorerCookieFile + { + get; + internal set; + } + + public override string ToString() + { + return Network.ToString(); + } + + public static string ToStringAll() + { + return string.Join(", ", _Networks.Select(n => n.Key).ToArray()); + } + } +} diff --git a/BTCPayServer/Configuration/TextFileConfiguration.cs b/BTCPayServer/Configuration/TextFileConfiguration.cs deleted file mode 100644 index 939a126fc..000000000 --- a/BTCPayServer/Configuration/TextFileConfiguration.cs +++ /dev/null @@ -1,221 +0,0 @@ -using NBitcoin; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; - -namespace BTCPayServer -{ - public class ConfigurationException : Exception - { - public ConfigurationException(string message) : base(message) - { - - } - } - - public class TextFileConfiguration - { - private Dictionary> _Args; - - public TextFileConfiguration(string[] args) - { - _Args = new Dictionary>(); - string noValueParam = null; - Action flushNoValueParam = () => - { - if(noValueParam != null) - { - Add(noValueParam, "1", false); - noValueParam = null; - } - }; - - foreach(var arg in args) - { - bool isParamName = arg.StartsWith("-", StringComparison.Ordinal); - if(isParamName) - { - var splitted = arg.Split('='); - if(splitted.Length > 1) - { - var value = String.Join("=", splitted.Skip(1).ToArray()); - flushNoValueParam(); - Add(splitted[0], value, false); - } - else - { - flushNoValueParam(); - noValueParam = splitted[0]; - } - } - else - { - if(noValueParam != null) - { - Add(noValueParam, arg, false); - noValueParam = null; - } - } - } - flushNoValueParam(); - } - - private void Add(string key, string value, bool sourcePriority) - { - key = NormalizeKey(key); - List list; - if(!_Args.TryGetValue(key, out list)) - { - list = new List(); - _Args.Add(key, list); - } - if(sourcePriority) - list.Insert(0, value); - else - list.Add(value); - } - - private static string NormalizeKey(string key) - { - key = key.ToLowerInvariant(); - while(key.Length > 0 && key[0] == '-') - { - key = key.Substring(1); - } - key = key.Replace(".", ""); - return key; - } - - public void MergeInto(TextFileConfiguration destination, bool sourcePriority) - { - foreach(var kv in _Args) - { - foreach(var v in kv.Value) - destination.Add(kv.Key, v, sourcePriority); - } - } - - public TextFileConfiguration(Dictionary> args) - { - _Args = args; - } - - public static TextFileConfiguration Parse(string data) - { - Dictionary> result = new Dictionary>(); - var lines = data.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); - int lineCount = -1; - foreach(var l in lines) - { - lineCount++; - var line = l.Trim(); - if(line.StartsWith("#", StringComparison.Ordinal)) - continue; - var split = line.Split('='); - if(split.Length == 0) - continue; - if(split.Length == 1) - throw new FormatException("Line " + lineCount + ": No value are set"); - - var key = split[0]; - key = NormalizeKey(key); - List values; - if(!result.TryGetValue(key, out values)) - { - values = new List(); - result.Add(key, values); - } - var value = String.Join("=", split.Skip(1).ToArray()); - values.Add(value); - } - return new TextFileConfiguration(result); - } - - public bool Contains(string key) - { - List values; - return _Args.TryGetValue(key, out values); - } - public string[] GetAll(string key) - { - List values; - if(!_Args.TryGetValue(key, out values)) - return new string[0]; - return values.ToArray(); - } - - private List> _Aliases = new List>(); - - public void AddAlias(string from, string to) - { - from = NormalizeKey(from); - to = NormalizeKey(to); - _Aliases.Add(Tuple.Create(from, to)); - } - public T GetOrDefault(string key, T defaultValue) - { - key = NormalizeKey(key); - - var aliases = _Aliases - .Where(a => a.Item1 == key || a.Item2 == key) - .Select(a => a.Item1 == key ? a.Item2 : a.Item1) - .ToList(); - aliases.Insert(0, key); - - foreach(var alias in aliases) - { - List values; - if(!_Args.TryGetValue(alias, out values)) - continue; - if(values.Count == 0) - continue; - try - { - return ConvertValue(values[0]); - } - catch(FormatException) { throw new ConfigurationException("Key " + key + " should be of type " + typeof(T).Name); } - } - return defaultValue; - } - - private T ConvertValue(string str) - { - if(typeof(T) == typeof(bool)) - { - var trueValues = new[] { "1", "true" }; - var falseValues = new[] { "0", "false" }; - if(trueValues.Contains(str, StringComparer.OrdinalIgnoreCase)) - return (T)(object)true; - if(falseValues.Contains(str, StringComparer.OrdinalIgnoreCase)) - return (T)(object)false; - throw new FormatException(); - } - else if(typeof(T) == typeof(Uri)) - return (T)(object)new Uri(str, UriKind.Absolute); - else if(typeof(T) == typeof(string)) - return (T)(object)str; - else if(typeof(T) == typeof(IPEndPoint)) - { - var separator = str.LastIndexOf(":"); - if(separator == -1) - throw new FormatException(); - var ip = str.Substring(0, separator); - var port = str.Substring(separator + 1); - return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port)); - } - else if(typeof(T) == typeof(int)) - { - return (T)(object)int.Parse(str, CultureInfo.InvariantCulture); - } - else - { - throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name); - } - } - } -} diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 065d992b0..2c1dcf649 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -1,5 +1,8 @@ using BTCPayServer.Authentication; +using BTCPayServer.Configuration; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Text; @@ -8,6 +11,17 @@ namespace BTCPayServer { public static class Extensions { + + public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf) + { + services.Configure(o => + { + o.LoadArgs(conf); + }); + return services; + } + + public static BitIdentity GetBitIdentity(this Controller controller) { if(!(controller.User.Identity is BitIdentity)) diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index d0e33ed1c..377c055f3 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -22,70 +22,142 @@ using BTCPayServer.Services.Stores; using BTCPayServer.Services.Fees; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Rewrite; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Authorization; +using BTCPayServer.Controllers; +using BTCPayServer.Services.Mails; +using Microsoft.AspNetCore.Identity; +using BTCPayServer.Models; +using System.Threading.Tasks; namespace BTCPayServer.Hosting { public static class BTCPayServerServices { - public static IWebHostBuilder AddPayServer(this IWebHostBuilder builder, BTCPayServerOptions options) + public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement { - return - builder - .ConfigureServices(c => + public OwnStoreAuthorizationRequirement() + { + } + + public OwnStoreAuthorizationRequirement(string role) + { + Role = role; + } + + public string Role + { + get; set; + } + } + + public class OwnStoreHandler : AuthorizationHandler + { + StoreRepository _StoreRepository; + UserManager _UserManager; + public OwnStoreHandler(StoreRepository storeRepository, UserManager userManager) + { + _StoreRepository = storeRepository; + _UserManager = userManager; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement) + { + object storeId = null; + if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId)) + context.Succeed(requirement); + else { - c.AddDbContext(o => - { - var path = Path.Combine(options.DataDir, "sqllite.db"); - o.UseSqlite("Data Source=" + path); - }); - c.AddSingleton(options); - c.AddSingleton(o => - { - var runtime = new BTCPayServerRuntime(); - runtime.Configure(options); - return runtime; - }); + var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User)); + if(store != null) + if(requirement.Role == null || requirement.Role == store.Role) + context.Succeed(requirement); + } + } + } + class BTCPayServerConfigureOptions : IConfigureOptions + { + BTCPayServerOptions _Options; + public BTCPayServerConfigureOptions(BTCPayServerOptions options) + { + _Options = options; + } + public void Configure(MvcOptions options) + { + if(_Options.RequireHttps) + options.Filters.Add(new RequireHttpsAttribute()); + } + } + public static IServiceCollection AddBTCPayServer(this IServiceCollection services) + { + services.AddDbContext((provider, o) => + { + var path = Path.Combine(provider.GetRequiredService().DataDir, "sqllite.db"); + o.UseSqlite("Data Source=" + path); + }); + services.TryAddSingleton(o => o.GetRequiredService>().Value); + services.TryAddSingleton, BTCPayServerConfigureOptions>(); + services.TryAddSingleton(o => + { + var runtime = new BTCPayServerRuntime(); + runtime.Configure(o.GetRequiredService()); + return runtime; + }); + services.TryAddSingleton(o => o.GetRequiredService().TokenRepository); + services.TryAddSingleton(o => o.GetRequiredService().InvoiceRepository); + services.TryAddSingleton(o => o.GetRequiredService().Network); + services.TryAddSingleton(o => o.GetRequiredService().DBFactory); + services.TryAddSingleton(); + services.TryAddSingleton(o => o.GetRequiredService().Wallet); + services.TryAddSingleton(); + services.TryAddSingleton(o => new NBXplorerFeeProvider() + { + Fallback = new FeeRate(100, 1), + BlockTarget = 20, + ExplorerClient = o.GetRequiredService() + }); + services.TryAddSingleton(o => + { + var runtime = o.GetRequiredService(); + return runtime.Explorer; + }); + services.TryAddSingleton(o => + { + if(o.GetRequiredService().Network == Network.Main) + return new Bitpay(new Key(), new Uri("https://bitpay.com/")); + else + return new Bitpay(new Key(), new Uri("https://test.bitpay.com/")); + }); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(o => o.GetRequiredService()); + services.TryAddScoped(); + services.TryAddSingleton(o => + { + var op = o.GetRequiredService(); + if(op.ExternalUrl != null) + return new FixedExternalUrlProvider(op.ExternalUrl, o.GetRequiredService()); + return new DefaultExternalUrlProvider(o.GetRequiredService()); + }); + services.TryAddSingleton(); + services.AddTransient(); + // Add application services. + services.AddTransient(); - if(options.RequireHttps) - { - c.Configure(o => - { - o.Filters.Add(new RequireHttpsAttribute()); - }); - } + services.AddAuthorization(o => + { + o.AddPolicy("CanAccessStore", builder => + { + builder.AddRequirements(new OwnStoreAuthorizationRequirement()); + }); - c.AddSingleton(options.Network); - c.AddSingleton(o => o.GetRequiredService().TokenRepository); - c.AddSingleton(o => o.GetRequiredService().InvoiceRepository); - c.AddSingleton(o => o.GetRequiredService().DBFactory); - c.AddSingleton(); - c.AddSingleton(o => o.GetRequiredService().Wallet); - c.AddSingleton(); - c.AddSingleton(o => new NBXplorerFeeProvider() - { - Fallback = new FeeRate(100, 1), - BlockTarget = 20, - ExplorerClient = o.GetRequiredService() - }); - c.AddSingleton(o => - { - var runtime = o.GetRequiredService(); - return runtime.Explorer; - }); - c.AddSingleton(o => - { - if(options.Network == Network.Main) - return new Bitpay(new Key(), new Uri("https://bitpay.com/")); - else - return new Bitpay(new Key(), new Uri("https://test.bitpay.com/")); - }); - c.TryAddSingleton(); - c.AddSingleton(); - c.AddSingleton(o => o.GetRequiredService()); - c.AddScoped(); - c.AddSingleton(o => new FixedExternalUrlProvider(options.ExternalUrl, o.GetRequiredService())); - }) - .UseUrls(options.GetUrls()); + o.AddPolicy("OwnStore", builder => + { + builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner")); + }); + }); + + return services; } public static IApplicationBuilder UsePayServer(this IApplicationBuilder app) diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index 115825198..b65d8ca50 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -23,45 +23,30 @@ using System.Threading.Tasks; using BTCPayServer.Controllers; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Mails; +using Microsoft.Extensions.Configuration; namespace BTCPayServer.Hosting { public class Startup { + public Startup(IConfiguration conf) + { + Configuration = conf; + } + + public IConfiguration Configuration + { + get; set; + } public void ConfigureServices(IServiceCollection services) { + services.ConfigureBTCPayServer(Configuration); services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); - services.AddAuthorization(o => - { - o.AddPolicy("CanAccessStore", builder => - { - builder.AddRequirements(new OwnStoreAuthorizationRequirement()); - }); - - o.AddPolicy("OwnStore", builder => - { - builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner")); - }); - }); - services.AddSingleton(); - services.AddTransient(); - // Add application services. - services.AddTransient(); - - //services.AddSingleton(); - services.AddMvcCore(o => - { - //o.Filters.Add(new NBXplorerExceptionFilter()); - o.OutputFormatters.Clear(); - o.InputFormatters.Clear(); - }) - .AddJsonFormatters() - .AddFormatterMappings(); - + services.AddBTCPayServer(); services.AddMvc(); } public void Configure( @@ -74,6 +59,8 @@ namespace BTCPayServer.Hosting app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } + + Logs.Configure(loggerFactory); app.UsePayServer(); app.UseStaticFiles(); @@ -87,45 +74,4 @@ namespace BTCPayServer.Hosting }); } } - - public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement - { - public OwnStoreAuthorizationRequirement() - { - } - - public OwnStoreAuthorizationRequirement(string role) - { - Role = role; - } - - public string Role - { - get; set; - } - } - - public class OwnStoreHandler : AuthorizationHandler - { - StoreRepository _StoreRepository; - UserManager _UserManager; - public OwnStoreHandler(StoreRepository storeRepository, UserManager userManager) - { - _StoreRepository = storeRepository; - _UserManager = userManager; - } - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement) - { - object storeId = null; - if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId)) - context.Succeed(requirement); - else - { - var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User)); - if(store != null) - if(requirement.Role == null || requirement.Role == store.Role) - context.Succeed(requirement); - } - } - } } diff --git a/BTCPayServer/Program.cs b/BTCPayServer/Program.cs index c47aceb56..9309865fd 100644 --- a/BTCPayServer/Program.cs +++ b/BTCPayServer/Program.cs @@ -13,6 +13,8 @@ using System.IO; using System.Net; using System.Collections.Generic; using System.Collections; +using Microsoft.AspNetCore.Hosting.Server.Features; +using System.Threading; namespace BTCPayServer { @@ -22,18 +24,22 @@ namespace BTCPayServer { ServicePointManager.DefaultConnectionLimit = 100; IWebHost host = null; + CustomConsoleLogProvider loggerProvider = new CustomConsoleLogProvider(); + + var loggerFactory = new LoggerFactory(); + loggerFactory.AddProvider(loggerProvider); + var logger = loggerFactory.CreateLogger("Configuration"); try { - var conf = new BTCPayServerOptions(); - var arguments = new TextFileConfiguration(args); - arguments = LoadEnvironmentVariables(arguments); - conf.LoadArgs(arguments); + var conf = new DefaultConfiguration() { Logger = logger }.CreateConfiguration(args); + if(conf == null) + return; host = new WebHostBuilder() - .AddPayServer(conf) .UseKestrel() .UseIISIntegration() .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(conf) .ConfigureServices(services => { services.AddLogging(l => @@ -44,11 +50,19 @@ namespace BTCPayServer }) .UseStartup() .Build(); - var running = host.RunAsync(); - OpenBrowser(conf.GetUrls().Select(url => url.Replace("0.0.0.0", "127.0.0.1")).First()); - running.GetAwaiter().GetResult(); - } - catch(ConfigurationException ex) + host.StartAsync().GetAwaiter().GetResult(); + var urls = host.ServerFeatures.Get().Addresses; + if(urls.Count != 0) + { + OpenBrowser(urls.Select(url => url.Replace("0.0.0.0", "127.0.0.1")).First()); + } + foreach(var url in urls) + { + logger.LogInformation("Listening on " + url); + } + host.WaitForShutdown(); + } + catch(ConfigException ex) { if(!string.IsNullOrEmpty(ex.Message)) Logs.Configuration.LogError(ex.Message); @@ -62,30 +76,10 @@ namespace BTCPayServer { if(host != null) host.Dispose(); + loggerProvider.Dispose(); } } - private static TextFileConfiguration LoadEnvironmentVariables(TextFileConfiguration args) - { - var variables = Environment.GetEnvironmentVariables(); - List values = new List(); - foreach(DictionaryEntry variable in variables) - { - var key = (string)variable.Key; - var value = (string)variable.Value; - if(key.StartsWith("APPSETTING_", StringComparison.Ordinal)) - { - key = key.Substring("APPSETTING_".Length); - values.Add("-" + key); - values.Add(value); - } - } - - TextFileConfiguration envConfig = new TextFileConfiguration(values.ToArray()); - args.MergeInto(envConfig, true); - return envConfig; - } - public static void OpenBrowser(string url) { try diff --git a/BTCPayServer/wwwroot/img/ibuki.png b/BTCPayServer/wwwroot/img/ibuki.png index 6bc6b1e79..8c85da96d 100644 Binary files a/BTCPayServer/wwwroot/img/ibuki.png and b/BTCPayServer/wwwroot/img/ibuki.png differ