joinmarket-jam: init module

This commit is contained in:
Otto Sabart 2024-05-08 21:00:00 +02:00
parent 60119b1b74
commit 7551b6df82
No known key found for this signature in database
GPG Key ID: 823BAE99F8BE1E3C
11 changed files with 237 additions and 0 deletions

View File

@ -94,6 +94,7 @@ NixOS modules ([src](modules/modules.nix))
* [liquid](https://github.com/elementsproject/elements): federated sidechain
* [JoinMarket](https://github.com/joinmarket-org/joinmarket-clientserver)
* [JoinMarket Orderbook Watcher](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/orderbook.md)
* [Jam](https://github.com/joinmarket-webui/jam): simplified user-friendly JoinMarket web interface
* [bitcoin-core-hwi](https://github.com/bitcoin-core/HWI)
* Helper
* [netns-isolation](modules/netns-isolation.nix): isolates applications on the network-level via network namespaces

View File

@ -557,6 +557,16 @@ See [here](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master
3. Profit
## Run the Jam, the JoinMarket web interface
The [Jam](https://github.com/joinmarket-webui/jam) is a static web interface
which is served by nginx. It connects to the jmwalled daemon which gets started
in the background.
TODO
- How to access the web interface using e.g. ssh proxy: `ssh -L 61851:localhost:61851 root@<nix-bitcoin-node-host>`
- how to access interface over onion?
# clightning
## Plugins

View File

@ -249,6 +249,13 @@
#
# Set this to enable the JoinMarket order book watcher.
# services.joinmarket-ob-watcher.enable = true;
#
# Set this to enable Jam, the JoinMarket web interface.
# services.joinmarket-jam.enable = true;
#
# Set this to create an onion service to make the Jam web interface
# available via Tor:
# nix-bitcoin.onionServices.joinmarket-jam.enable = true;
### Nodeinfo
# Set this to add command `nodeinfo` to the system environment.

180
modules/joinmarket-jam.nix Normal file
View File

@ -0,0 +1,180 @@
{ config, lib, pkgs, ... }:
with lib;
let
options.services.joinmarket-jam = {
enable = mkEnableOption "Enable the JoinMarket Jam web interface.";
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "HTTP server address.";
};
port = mkOption {
type = types.port;
default = 61851;
description = mdDoc "HTTP server port.";
};
staticContentRoot = mkOption {
type = types.path;
default = nbPkgs.joinmarket-jam;
defaultText = "config.nix-bitcoin.pkgs.joinmarket-jam";
description = mdDoc "Path of the static content root.";
};
nginxConfig = mkOption {
readOnly = true;
default = nginxConfig;
defaultText = "(See source)";
description = mdDoc ''
An attrset of nginx config snippets for assembling a custom
joinmarket's jam nginx config.
'';
};
package = mkOption {
type = types.package;
default = nbPkgs.joinmarket-jam;
defaultText = "config.nix-bitcoin.pkgs.joinmarket-jam";
description = mdDoc "The package providing joinmarket's jam files.";
};
user = mkOption {
type = types.str;
default = "joinmarket-jam";
description = mdDoc "The user as which to run Jam.";
};
group = mkOption {
type = types.str;
default = cfg.user;
description = mdDoc "The group as which to run Jam.";
};
tor.enforce = nbLib.tor.enforce;
#settings = mkOption {
#};
};
cfg = config.services.joinmarket-jam;
nbLib = config.nix-bitcoin.lib;
nbPkgs = config.nix-bitcoin.pkgs;
inherit (config.services) joinmarket-ob-watcher joinmarket-jmwalletd;
# Nginx configuration is highgly inspired by official jam-docker ui-only container.
# https://github.com/joinmarket-webui/jam-docker/tree/master/ui-only/nginx
nginxConfig = {
staticContent = ''
index index.html;
add_header Cache-Control "public, no-transform";
add_header Vary Accept-Language;
add_header Vary Cookie;
'';
proxyApi = let
jmwalletd_api_backend = "https://${nbLib.addressWithPort joinmarket-jmwalletd.address joinmarket-jmwalletd.port}";
jmwalletd_wss_backend = "https://${nbLib.addressWithPort joinmarket-jmwalletd.address joinmarket-jmwalletd.wssPort}/";
ob_watcher_backend = "http://${nbLib.addressWithPort joinmarket-ob-watcher.address joinmarket-ob-watcher.port}";
in ''
location / {
#include /etc/nginx/snippets/proxy-params.conf;
try_files $uri $uri/ /index.html;
add_header Cache-Control no-cache;
}
location /api/ {
proxy_pass ${jmwalletd_api_backend};
#include /etc/nginx/snippets/proxy-params.conf;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Authorization $http_x_jm_authorization;
proxy_set_header x-jm-authorization "";
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
}
location = /jmws {
proxy_pass ${jmwalletd_wss_backend};
#include /etc/nginx/snippets/proxy-params.conf;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Authorization "";
# allow 10m without socket activity (default is 60 sec)
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
location /obwatch/ {
proxy_pass ${ob_watcher_backend};
#include /etc/nginx/snippets/proxy-params.conf;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
}
location = /jam/internal/auth {
internal;
proxy_pass http://$server_addr:$server_port/api/v1/session;
#if ($jm_auth_present != 1) {
# return 401;
#}
#include /etc/nginx/snippets/proxy-params.conf;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location = /jam/api/v0/features {
auth_request /jam/internal/auth;
default_type application/json;
return 200 '{ "features": { "logs": false } }';
}
location /jam/api/v0/log/ {
auth_request /jam/internal/auth;
return 501; # Not Implemented
}
'';
};
in {
inherit options;
config = mkIf cfg.enable {
services = {
joinmarket-ob-watcher.enable = true;
joinmarket-jmwalletd.enable = true;
nginx = {
enable = true;
enableReload = true;
recommendedBrotliSettings = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
# TODO: Use this to define "map"? See: https://github.com/joinmarket-webui/jam-docker/blob/master/ui-only/nginx/templates/default.conf.template#L20
#commonHttpConfig = nginxConfig.httpConfig;
virtualHosts."joinmarket-jam" = {
serverName = "_";
listen = [ { addr = cfg.address; port = cfg.port; } ];
root = cfg.staticContentRoot;
extraConfig = nginxConfig.staticContent + nginxConfig.proxyApi;
};
};
};
users = {
users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
};
groups.${cfg.group} = {};
};
};
}

View File

@ -40,6 +40,24 @@ let
default = "/var/lib/joinmarket";
description = mdDoc "The data directory for JoinMarket.";
};
max_cj_fee_abs = mkOption {
type = types.ints.unsigned;
default = 30000; # in sats
description = mdDoc ''
Maximum absolute coinjoin fee in satoshi to pay to a single market
maker for a transaction.
'';
};
max_cj_fee_rel = mkOption {
type = types.float;
default = 0.0003; # 0.03 %
description = mdDoc ''
Maximum relative coinjoin fee, in fractions of the coinjoin value e.g.
if your coinjoin amount is 2 btc (200 million satoshi) and
max_cj_fee_rel = 0.001 (0.1%), the maximum fee allowed would be 0.002
btc (200 thousand satoshi).
'';
};
rpcWalletFile = mkOption {
type = types.nullOr types.str;
default = "jm_wallet";
@ -217,6 +235,8 @@ let
tx_fees_factor = 0.2
absurd_fee_per_kb = 350000
max_sweep_fee_change = 0.8
max_cj_fee_abs = ${toString cfg.max_cj_fee_abs}
max_cj_fee_rel = ${toString cfg.max_cj_fee_rel}
tx_broadcast = self
minimum_makers = 4
max_sats_freeze_reuse = -1

View File

@ -28,6 +28,7 @@
./joinmarket.nix
./joinmarket-ob-watcher.nix
./joinmarket-jmwalletd.nix
./joinmarket-jam.nix
./hardware-wallets.nix
# Support features

View File

@ -356,6 +356,7 @@ in {
};
services.joinmarket-ob-watcher.address = netns.joinmarket-ob-watcher.address;
services.joinmarket-jam.address = netns.nginx.address;
services.lightning-pool.rpcAddress = netns.lightning-pool.address;

View File

@ -149,6 +149,11 @@ in {
liquidd = mkInfo "";
joinmarket-ob-watcher = mkInfo "";
joinmarket-jmwalletd = mkInfo "";
joinmarket-jam = name: cfg: mkInfoLong {
inherit name cfg;
systemdServiceName = "nginx";
extraCode = "";
};
rtl = mkInfo "";
mempool = mkInfo "";
mempool-frontend = name: cfg: mkInfoLong {

View File

@ -110,6 +110,9 @@ in {
joinmarket-ob-watcher = {
externalPort = 80;
};
joinmarket-jam = {
externalPort = 80;
};
rtl = {
externalPort = 80;
};

View File

@ -133,6 +133,7 @@ let
cjfee_r = 0.00003;
txfee = 200;
};
tests.joinmarket-jam = cfg.joinmarket-jam.enable;
tests.nodeinfo = config.nix-bitcoin.nodeinfo.enable;
tests.backups = cfg.backups.enable;
@ -207,6 +208,7 @@ let
services.joinmarket.enable = true;
services.joinmarket-ob-watcher.enable = true;
services.joinmarket-jmwalletd.enable = true;
services.joinmarket-jam.enable = true;
services.backups.enable = true;
nix-bitcoin.nodeinfo.enable = true;
@ -255,6 +257,7 @@ let
services.btcpayserver.enable = true;
services.joinmarket.enable = true;
services.joinmarket-jmwalletd.enable = true;
services.joinmarket-jam.enable = true;
};
# netns and regtest, without secure-node.nix

View File

@ -292,6 +292,12 @@ def _():
# Test web server response
assert_full_match(f"curl -fsSL --insecure https://{ip('joinmarket')}:28183/api/v1/getinfo | jq -jr keys[0]", "version")
@test("joinmarket-jam")
def _():
assert_running("nginx")
wait_for_open_port(ip("nginx"), 61851)
assert_matches(f"curl -L {ip('nginx')}:61851", "Jam for JoinMarket")
@test("nodeinfo")
def _():
status, _ = machine.execute("systemctl is-enabled --quiet onion-addresses 2> /dev/null")