diff --git a/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs b/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs index e4d0786b2..55f24bed8 100644 --- a/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs +++ b/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs @@ -91,10 +91,10 @@ namespace BTCPayServer.Data builder.UseSqlite(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data")); else if (_Type == DatabaseType.Postgres) builder - .UseNpgsql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data")) + .UseNpgsql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10)) .ReplaceService(); else if (_Type == DatabaseType.MySQL) - builder.UseMySql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data")); + builder.UseMySql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10)); } } } diff --git a/BTCPayServer.Rating/Providers/ByllsRateProvider.cs b/BTCPayServer.Rating/Providers/ByllsRateProvider.cs index e47aac21f..23a822ed2 100644 --- a/BTCPayServer.Rating/Providers/ByllsRateProvider.cs +++ b/BTCPayServer.Rating/Providers/ByllsRateProvider.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; diff --git a/BTCPayServer.Rating/Providers/NdaxRateProvider.cs b/BTCPayServer.Rating/Providers/NdaxRateProvider.cs new file mode 100644 index 000000000..1b8e27a62 --- /dev/null +++ b/BTCPayServer.Rating/Providers/NdaxRateProvider.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Rating; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Services.Rates +{ + public class NdaxRateProvider : IRateProvider, IHasExchangeName + { + private readonly HttpClient _httpClient; + + public NdaxRateProvider(HttpClient httpClient) + { + _httpClient = httpClient ?? new HttpClient(); + } + + public string ExchangeName => "ndax"; + + public async Task GetRatesAsync(CancellationToken cancellationToken) + { + var response = await _httpClient.GetAsync("https://ndax.io/api/returnTicker", cancellationToken); + var jobj = await response.Content.ReadAsAsync>(cancellationToken); + return new ExchangeRates(jobj.Select(pair => new ExchangeRate(ExchangeName, CurrencyPair.Parse(pair.Key), + new BidAsk(GetValue(pair.Value["highestBid"]), GetValue(pair.Value["lowestAsk"]))))); + } + + private static decimal GetValue(JToken jobj) + { + return string.IsNullOrEmpty(jobj.ToString()) ? 0 : jobj.Value(); + } + + } +} diff --git a/BTCPayServer.Rating/Services/RateProviderFactory.cs b/BTCPayServer.Rating/Services/RateProviderFactory.cs index b58a1df88..a693d7046 100644 --- a/BTCPayServer.Rating/Services/RateProviderFactory.cs +++ b/BTCPayServer.Rating/Services/RateProviderFactory.cs @@ -113,6 +113,7 @@ namespace BTCPayServer.Services.Rates Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_COINAVERAGE"), Authenticator = _CoinAverageSettings }); Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") }); Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS"))); + Providers.Add("ndax", new NdaxRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_NDAX"))); Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK"))); Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY"))); @@ -171,6 +172,7 @@ namespace BTCPayServer.Services.Rates // Add other exchanges supported here exchanges.Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average", $"https://apiv2.bitcoinaverage.com/indices/global/ticker/short")); exchanges.Add(new CoinAverageExchange("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD")); + exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker")); exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices")); return exchanges; diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 2c4984ac2..79bbc6538 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -118,6 +118,12 @@ namespace BTCPayServer.Tests config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}"); config.AppendLine($"ltc.explorer.cookiefile=0"); config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}"); + if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile)) + config.AppendLine($"sshpassword={SSHPassword}"); + if (!string.IsNullOrEmpty(SSHKeyFile)) + config.AppendLine($"sshkeyfile={SSHKeyFile}"); + if (!string.IsNullOrEmpty(SSHConnection)) + config.AppendLine($"sshconnection={SSHConnection}"); if (TestDatabase == TestDatabases.MySQL && !String.IsNullOrEmpty(MySQL)) config.AppendLine($"mysql=" + MySQL); @@ -280,8 +286,11 @@ namespace BTCPayServer.Tests return _Host.Services.GetRequiredService(); } - public IServiceProvider ServiceProvider => _Host.Services; + public IServiceProvider ServiceProvider => _Host.Services; + public string SSHPassword { get; internal set; } + public string SSHKeyFile { get; internal set; } + public string SSHConnection { get; set; } public T GetController(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller { var context = new DefaultHttpContext(); diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 03ff81652..4d3f2dcbb 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -7,6 +7,7 @@ using Xunit.Abstractions; using OpenQA.Selenium.Interactions; using System.Linq; using NBitcoin; +using System.Threading.Tasks; namespace BTCPayServer.Tests { @@ -94,6 +95,27 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("LoginButton")).Click(); s.Driver.AssertNoError(); } + [Fact] + public async Task CanUseSSHService() + { + using (var s = SeleniumTester.Create()) + { + s.Start(); + var alice = s.RegisterNewUser(isAdmin: true); + s.Driver.Navigate().GoToUrl(s.Link("/server/services")); + Assert.Contains("server/services/ssh", s.Driver.PageSource); + using (var client = await s.Server.PayTester.GetService().SSHSettings.ConnectAsync()) + { + var result = await client.RunBash("echo hello"); + Assert.Equal(string.Empty, result.Error); + Assert.Equal("hello\n", result.Output); + Assert.Equal(0, result.ExitStatus); + } + s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh")); + s.Driver.AssertNoError(); + } + } + [Fact] public void CanUseDynamicDns() { @@ -269,7 +291,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC"); s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY"); s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700"); - s.Driver.FindElement(By.Id("SaveSettings")).Submit(); + s.Driver.FindElement(By.Id("SaveSettings")).ForceClick(); s.Driver.FindElement(By.Id("ViewApp")).ForceClick(); s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last()); Assert.True(s.Driver.PageSource.Contains("Currently Active!"), "Unable to create CF"); @@ -292,7 +314,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123"); s.Driver.FindElement(By.Id("Amount")).SendKeys("700"); s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC"); - s.Driver.FindElement(By.Id("SaveButton")).Submit(); + s.Driver.FindElement(By.Id("SaveButton")).ForceClick(); s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return); s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last()); Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request"); diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 433625dcb..f0c987133 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -71,6 +71,10 @@ namespace BTCPayServer.Tests PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture); PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1"); PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false")); + + PayTester.SSHPassword = GetEnvironment("TESTS_SSHPASSWORD", "opD3i2282D"); + PayTester.SSHKeyFile = GetEnvironment("TESTS_SSHKEYFILE", ""); + PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622"); } public bool Dockerized diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 737a5e26a..2bfa0b939 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -16,7 +16,6 @@ services: TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/ TESTS_DB: "Postgres" TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver - TESTS_MYSQL: User ID=root;Host=mysql;Port=3306;Database=btcpayserver TESTS_PORT: 80 TESTS_HOSTNAME: tests TESTS_RUN_EXTERNAL_INTEGRATION: ${TESTS_RUN_EXTERNAL_INTEGRATION:-false} @@ -26,6 +25,9 @@ services: TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify" TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/" TESTS_INCONTAINER: "true" + TESTS_SSHCONNECTION: "root@sshd:22" + TESTS_SSHPASSWORD: "" + TESTS_SSHKEYFILE: "" expose: - "80" links: @@ -33,26 +35,33 @@ services: extra_hosts: - "tests:127.0.0.1" volumes: + - "sshd_datadir:/root/.ssh" - "customer_lightningd_datadir:/etc/customer_lightningd_datadir" - "merchant_lightningd_datadir:/etc/merchant_lightningd_datadir" # The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services dev: - image: btcpayserver/bitcoin:0.18.0 - environment: - BITCOIN_NETWORK: regtest - BITCOIN_EXTRA_ARGS: | - deprecatedrpc=signrawtransaction - connect=bitcoind:39388 + image: coscale/docker-sleep links: - nbxplorer - postgres - - mysql - customer_lightningd - merchant_lightningd - lightning-charged - customer_lnd - merchant_lnd + - sshd + + sshd: + build: + context: . + dockerfile: sshd.Dockerfile + ports: + - "21622:22" + expose: + - 22 + volumes: + - "sshd_datadir:/root/.ssh" devlnd: image: btcpayserver/bitcoin:0.18.0 @@ -64,7 +73,6 @@ services: links: - nbxplorer - postgres - - mysql - customer_lnd - merchant_lnd nbxplorer: @@ -213,15 +221,6 @@ services: - "39372:5432" expose: - "5432" - - mysql: - image: mysql:8.0.12 - expose: - - "3306" - ports: - - "33036:3306" - environment: - - MYSQL_ALLOW_EMPTY_PASSWORD=yes merchant_lnd: image: btcpayserver/lnd:v0.7.0-beta @@ -287,6 +286,7 @@ services: - bitcoind volumes: + sshd_datadir: bitcoin_datadir: customer_lightningd_datadir: merchant_lightningd_datadir: diff --git a/BTCPayServer.Tests/sshd.Dockerfile b/BTCPayServer.Tests/sshd.Dockerfile new file mode 100644 index 000000000..9c672f52f --- /dev/null +++ b/BTCPayServer.Tests/sshd.Dockerfile @@ -0,0 +1,12 @@ +FROM alpine:3.8 + +RUN apk add --no-cache openssh sudo bash +RUN ssh-keygen -f /root/.ssh/id_rsa -t rsa -q -P "" -m PEM +RUN echo 'root:opD3i2282D' | chpasswd +RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config +RUN ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa && \ + ssh-keygen -f /etc/ssh/ssh_host_dsa_key -N '' -t dsa && \ + ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa && \ + ssh-keygen -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519 + +CMD ["/usr/sbin/sshd", "-D"] diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs index 9651d225d..0bc3000ff 100644 --- a/BTCPayServer/Controllers/WalletsController.cs +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -531,7 +531,7 @@ namespace BTCPayServer.Controllers var balanceChange = psbt.GetBalance(settings.AccountDerivation, signingKey, rootedKeyPath); if (balanceChange == Money.Zero) { - ModelState.AddModelError(nameof(viewModel.SeedOrKey), "This seed does not seem to be able to sign this transaction. Either this is the wrong key, or Wallet Settings have not the correct account path in the wallet settings."); + ModelState.AddModelError(nameof(viewModel.SeedOrKey), "This seed is unable to sign this transaction. Either the seed is incorrect, or the account path has not been properly configured in the Wallet Settings."); return View(viewModel); } psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath); diff --git a/BTCPayServer/Currencies.txt b/BTCPayServer/Currencies.txt index d1835e7d1..8179b2fdc 100644 --- a/BTCPayServer/Currencies.txt +++ b/BTCPayServer/Currencies.txt @@ -266,4 +266,5 @@ CFP Franc XPF 0 Moroccan Dirham MAD 2 Yemeni Rial YER 2 Zambian Kwacha ZMW 2 -Zimbabwe Dollar ZWL 2 \ No newline at end of file +Zimbabwe Dollar ZWL 2 +Indonesian Rupiah RP 2 diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 471ee67a8..25788849a 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -106,12 +106,14 @@ namespace BTCPayServer.Hosting else if(!String.IsNullOrEmpty(opts.MySQLConnectionString)) { Logs.Configuration.LogInformation($"MySQL DB used ({opts.MySQLConnectionString})"); + Logs.Configuration.LogWarning("MySQL is not widely tested and should be considered experimental, we advise you to use postgres instead."); dbContext = new ApplicationDbContextFactory(DatabaseType.MySQL, opts.MySQLConnectionString); } else { var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db"); Logs.Configuration.LogInformation($"SQLite DB used ({connStr})"); + Logs.Configuration.LogWarning("MySQL is not widely tested and should be considered experimental, we advise you to use postgres instead."); dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr); } diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json index ec1458aca..2f70b636b 100644 --- a/BTCPayServer/Properties/launchSettings.json +++ b/BTCPayServer/Properties/launchSettings.json @@ -41,7 +41,9 @@ "ASPNETCORE_ENVIRONMENT": "Development", "BTCPAY_CHAINS": "btc,ltc", "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver", - "BTCPAY_EXTERNALSERVICES": "totoservice:totolink;" + "BTCPAY_EXTERNALSERVICES": "totoservice:totolink;", + "BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622", + "BTCPAY_SSHPASSWORD": "opD3i2282D" }, "applicationUrl": "https://localhost:14142/" } diff --git a/BTCPayServer/Services/DynamicDnsSettings.cs b/BTCPayServer/Services/DynamicDnsSettings.cs index 71360cee6..d840fb034 100644 --- a/BTCPayServer/Services/DynamicDnsSettings.cs +++ b/BTCPayServer/Services/DynamicDnsSettings.cs @@ -16,6 +16,10 @@ namespace BTCPayServer.Services public class DynamicDnsSettings { public List Services { get; set; } = new List(); + public override string ToString() + { + return String.Empty; + } } public class DynamicDnsService { diff --git a/BTCPayServer/Views/Stores/PayButton.cshtml b/BTCPayServer/Views/Stores/PayButton.cshtml index b68edc8e6..57709da2d 100644 --- a/BTCPayServer/Views/Stores/PayButton.cshtml +++ b/BTCPayServer/Views/Stores/PayButton.cshtml @@ -151,9 +151,9 @@

These parameters allow you to influence process after purchase. Server IPN is location we'll query with details. - We can also deliver email notification to specified addres. + We can also deliver email notification to specified address.

- Finally Browser Redirect defines where BtcPayServer will redirect customer after puchase is completed. + Finally Browser Redirect defines where BTCPayServer will redirect customer after purchase is completed.

Advanced

diff --git a/BTCPayServer/Views/Wallets/WalletTransactions.cshtml b/BTCPayServer/Views/Wallets/WalletTransactions.cshtml index 174dcdd2d..2317fa3c1 100644 --- a/BTCPayServer/Views/Wallets/WalletTransactions.cshtml +++ b/BTCPayServer/Views/Wallets/WalletTransactions.cshtml @@ -5,8 +5,6 @@ ViewData.SetActivePageAndTitle(WalletsNavPages.Transactions); } @if (TempData.ContainsKey("TempDataProperty-StatusMessage")) { @@ -73,7 +75,17 @@ @foreach (var label in transaction.Labels) { - @label.Value + + @label.Value + }