2020-04-23 19:18:47 +03:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.services.joinmarket;
|
2021-02-04 00:44:41 +03:00
|
|
|
nbLib = config.nix-bitcoin.lib;
|
2020-11-10 00:09:09 +03:00
|
|
|
nbPkgs = config.nix-bitcoin.pkgs;
|
2020-04-23 19:18:47 +03:00
|
|
|
secretsDir = config.nix-bitcoin.secretsDir;
|
2021-01-31 01:08:43 +03:00
|
|
|
runAsUser = config.nix-bitcoin.runAsUserCmd;
|
2020-04-23 19:18:47 +03:00
|
|
|
|
2020-10-16 18:43:13 +03:00
|
|
|
inherit (config.services) bitcoind;
|
2020-04-23 19:18:47 +03:00
|
|
|
torAddress = builtins.head (builtins.split ":" config.services.tor.client.socksListenAddress);
|
2020-10-16 18:43:14 +03:00
|
|
|
# Based on https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/configure.py
|
2020-04-23 19:18:47 +03:00
|
|
|
configFile = builtins.toFile "config" ''
|
|
|
|
[DAEMON]
|
|
|
|
no_daemon = 0
|
|
|
|
daemon_port = 27183
|
|
|
|
daemon_host = localhost
|
|
|
|
use_ssl = false
|
|
|
|
|
|
|
|
[BLOCKCHAIN]
|
|
|
|
blockchain_source = bitcoin-rpc
|
2020-10-16 18:43:16 +03:00
|
|
|
network = ${bitcoind.network}
|
2021-01-14 15:24:01 +03:00
|
|
|
rpc_host = ${bitcoind.rpc.address}
|
2020-10-16 18:43:15 +03:00
|
|
|
rpc_port = ${toString bitcoind.rpc.port}
|
2020-10-16 18:43:13 +03:00
|
|
|
rpc_user = ${bitcoind.rpc.users.privileged.name}
|
2020-04-23 19:18:47 +03:00
|
|
|
@@RPC_PASSWORD@@
|
2021-01-10 22:27:45 +03:00
|
|
|
${optionalString (cfg.rpcWalletFile != null) "rpc_wallet_file=${cfg.rpcWalletFile}"}
|
2020-04-23 19:18:47 +03:00
|
|
|
|
|
|
|
[MESSAGING:server1]
|
|
|
|
host = darksci3bfoka7tw.onion
|
|
|
|
channel = joinmarket-pit
|
|
|
|
port = 6697
|
|
|
|
usessl = true
|
|
|
|
socks5 = true
|
|
|
|
socks5_host = ${torAddress}
|
|
|
|
socks5_port = 9050
|
|
|
|
|
|
|
|
[MESSAGING:server2]
|
|
|
|
host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion
|
|
|
|
channel = joinmarket-pit
|
|
|
|
port = 6667
|
|
|
|
usessl = false
|
|
|
|
socks5 = true
|
|
|
|
socks5_host = ${torAddress}
|
|
|
|
socks5_port = 9050
|
|
|
|
|
|
|
|
[LOGGING]
|
|
|
|
console_log_level = INFO
|
|
|
|
color = false
|
|
|
|
|
|
|
|
[POLICY]
|
|
|
|
segwit = true
|
2020-12-14 20:12:11 +03:00
|
|
|
native = true
|
2020-04-23 19:18:47 +03:00
|
|
|
merge_algorithm = default
|
|
|
|
tx_fees = 3
|
|
|
|
absurd_fee_per_kb = 350000
|
|
|
|
tx_broadcast = self
|
|
|
|
minimum_makers = 4
|
|
|
|
max_sats_freeze_reuse = -1
|
|
|
|
taker_utxo_retries = 3
|
|
|
|
taker_utxo_age = 5
|
|
|
|
taker_utxo_amtpercent = 20
|
|
|
|
accept_commitment_broadcasts = 1
|
|
|
|
commit_file_location = cmtdata/commitments.json
|
2020-10-29 15:46:36 +03:00
|
|
|
|
|
|
|
[PAYJOIN]
|
|
|
|
payjoin_version = 1
|
|
|
|
disable_output_substitution = 0
|
|
|
|
max_additional_fee_contribution = default
|
|
|
|
min_fee_rate = 1.1
|
|
|
|
onion_socks5_host = ${torAddress}
|
|
|
|
onion_socks5_port = 9050
|
|
|
|
tor_control_host = unix:/run/tor/control
|
|
|
|
hidden_service_ssl = false
|
2020-04-23 19:18:47 +03:00
|
|
|
'';
|
|
|
|
|
|
|
|
# The jm scripts create a 'logs' dir in the working dir,
|
|
|
|
# so run them inside dataDir.
|
|
|
|
cli = pkgs.runCommand "joinmarket-cli" {} ''
|
|
|
|
mkdir -p $out/bin
|
2020-11-10 00:09:09 +03:00
|
|
|
jm=${nbPkgs.joinmarket}/bin
|
2020-04-23 19:18:47 +03:00
|
|
|
cd $jm
|
|
|
|
for bin in jm-*; do
|
|
|
|
{
|
|
|
|
echo "#!${pkgs.bash}/bin/bash";
|
2021-01-31 01:08:43 +03:00
|
|
|
echo "cd '${cfg.dataDir}' && ${cfg.cliExec} ${runAsUser} ${cfg.user} $jm/$bin --datadir='${cfg.dataDir}' \"\$@\"";
|
2020-04-23 19:18:47 +03:00
|
|
|
} > $out/bin/$bin
|
|
|
|
done
|
|
|
|
chmod -R +x $out/bin
|
|
|
|
'';
|
|
|
|
in {
|
|
|
|
options.services.joinmarket = {
|
|
|
|
enable = mkEnableOption "JoinMarket";
|
|
|
|
yieldgenerator = {
|
|
|
|
enable = mkEnableOption "yield generator bot";
|
|
|
|
customParameters = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "";
|
|
|
|
example = ''
|
|
|
|
txfee = 200
|
|
|
|
cjfee_a = 300
|
|
|
|
'';
|
|
|
|
description = ''
|
|
|
|
Python code to define custom yield generator parameters, as described in
|
|
|
|
https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/YIELDGENERATOR.md
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
|
|
type = types.path;
|
|
|
|
default = "/var/lib/joinmarket";
|
|
|
|
description = "The data directory for JoinMarket.";
|
|
|
|
};
|
|
|
|
user = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "joinmarket";
|
|
|
|
description = "The user as which to run JoinMarket.";
|
|
|
|
};
|
|
|
|
group = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = cfg.user;
|
|
|
|
description = "The group as which to run JoinMarket.";
|
|
|
|
};
|
2021-01-10 22:27:45 +03:00
|
|
|
rpcWalletFile = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Name of the watch-only bitcoind wallet the JoinMarket addresses are imported to.
|
|
|
|
'';
|
|
|
|
};
|
2020-04-23 19:18:47 +03:00
|
|
|
cli = mkOption {
|
|
|
|
default = cli;
|
|
|
|
};
|
2021-01-04 19:09:20 +03:00
|
|
|
# This option is only used by netns-isolation
|
|
|
|
enforceTor = mkOption {
|
|
|
|
readOnly = true;
|
|
|
|
default = true;
|
|
|
|
};
|
2021-02-04 00:44:41 +03:00
|
|
|
inherit (nbLib) cliExec;
|
2020-04-23 19:18:47 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf cfg.enable (mkMerge [{
|
2021-02-02 00:53:22 +03:00
|
|
|
services.bitcoind = {
|
|
|
|
enable = true;
|
|
|
|
disablewallet = false;
|
2020-09-28 14:09:03 +03:00
|
|
|
};
|
2020-04-23 19:18:47 +03:00
|
|
|
|
2021-02-02 00:53:22 +03:00
|
|
|
# Joinmarket is Tor-only
|
2020-04-23 19:18:47 +03:00
|
|
|
services.tor = {
|
|
|
|
enable = true;
|
|
|
|
client.enable = true;
|
2021-02-02 00:53:18 +03:00
|
|
|
# Needed for payjoin onion service creation
|
2020-10-29 15:46:36 +03:00
|
|
|
controlSocket.enable = true;
|
2020-04-23 19:18:47 +03:00
|
|
|
};
|
|
|
|
|
2021-02-02 00:53:22 +03:00
|
|
|
environment.systemPackages = [
|
|
|
|
(hiPrio cfg.cli)
|
|
|
|
];
|
|
|
|
|
|
|
|
systemd.tmpfiles.rules = [
|
|
|
|
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
|
|
|
|
];
|
|
|
|
|
2020-04-23 19:18:47 +03:00
|
|
|
systemd.services.joinmarket = {
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
requires = [ "bitcoind.service" ];
|
|
|
|
after = [ "bitcoind.service" ];
|
2021-02-04 00:44:41 +03:00
|
|
|
serviceConfig = nbLib.defaultHardening // {
|
2021-02-02 00:53:23 +03:00
|
|
|
ExecStartPre = nbLib.privileged "joinmarket-create-config" ''
|
2020-04-23 19:18:47 +03:00
|
|
|
install -o '${cfg.user}' -g '${cfg.group}' -m 640 ${configFile} ${cfg.dataDir}/joinmarket.cfg
|
|
|
|
sed -i \
|
2020-09-11 14:53:12 +03:00
|
|
|
"s|@@RPC_PASSWORD@@|rpc_password = $(cat ${secretsDir}/bitcoin-rpcpassword-privileged)|" \
|
2020-04-23 19:18:47 +03:00
|
|
|
'${cfg.dataDir}/joinmarket.cfg'
|
|
|
|
'';
|
2020-10-16 18:43:16 +03:00
|
|
|
# Generating wallets (jmclient/wallet.py) is only supported for mainnet or testnet
|
2021-02-02 00:53:23 +03:00
|
|
|
ExecStartPost = mkIf (bitcoind.network == "mainnet")
|
|
|
|
(nbLib.privileged "joinmarket-create-wallet" ''
|
|
|
|
walletname=wallet.jmdat
|
|
|
|
wallet=${cfg.dataDir}/wallets/$walletname
|
|
|
|
if [[ ! -f $wallet ]]; then
|
|
|
|
echo "Create wallet"
|
|
|
|
pw=$(cat "${secretsDir}"/jm-wallet-password)
|
|
|
|
cd ${cfg.dataDir}
|
2021-01-31 01:08:42 +03:00
|
|
|
if ! ${pkgs.utillinux}/bin/runuser -u ${cfg.user} -- \
|
|
|
|
${nbPkgs.joinmarket}/bin/jm-genwallet --datadir=${cfg.dataDir} $walletname $pw \
|
2021-02-02 00:53:23 +03:00
|
|
|
| grep 'recovery_seed' \
|
|
|
|
| cut -d ':' -f2 \
|
|
|
|
| (umask u=r,go=; cat > "${secretsDir}/jm-wallet-seed"); then
|
|
|
|
echo "wallet creation failed"
|
|
|
|
rm -f "$wallet" "${secretsDir}/jm-wallet-seed"
|
|
|
|
exit 1
|
|
|
|
fi
|
2021-02-02 00:53:17 +03:00
|
|
|
fi
|
2021-02-02 00:53:23 +03:00
|
|
|
'');
|
2020-11-10 00:09:09 +03:00
|
|
|
ExecStart = "${nbPkgs.joinmarket}/bin/joinmarketd";
|
2021-02-02 00:53:12 +03:00
|
|
|
WorkingDirectory = cfg.dataDir; # The service creates 'commitmentlist' in the working dir
|
|
|
|
User = cfg.user;
|
2020-04-23 19:18:47 +03:00
|
|
|
Restart = "on-failure";
|
|
|
|
RestartSec = "10s";
|
2021-02-02 00:53:12 +03:00
|
|
|
ReadWritePaths = cfg.dataDir;
|
2021-02-04 00:44:41 +03:00
|
|
|
} // nbLib.allowTor;
|
2020-04-23 19:18:47 +03:00
|
|
|
};
|
|
|
|
|
2021-02-02 00:53:22 +03:00
|
|
|
users.users.${cfg.user} = {
|
|
|
|
group = cfg.group;
|
|
|
|
home = cfg.dataDir;
|
|
|
|
# Allow access to the tor control socket, needed for payjoin onion service creation
|
|
|
|
extraGroups = [ "tor" ];
|
|
|
|
};
|
|
|
|
users.groups.${cfg.group} = {};
|
|
|
|
nix-bitcoin.operator = {
|
|
|
|
groups = [ cfg.group ];
|
2021-01-31 01:08:43 +03:00
|
|
|
allowRunAsUsers = [ cfg.group ];
|
2021-02-02 00:53:22 +03:00
|
|
|
};
|
|
|
|
|
2020-04-23 19:18:47 +03:00
|
|
|
nix-bitcoin.secrets.jm-wallet-password.user = cfg.user;
|
2020-12-30 18:02:42 +03:00
|
|
|
}
|
2020-04-23 19:18:47 +03:00
|
|
|
|
2020-12-30 18:02:42 +03:00
|
|
|
(mkIf cfg.yieldgenerator.enable {
|
2020-04-23 19:18:47 +03:00
|
|
|
systemd.services.joinmarket-yieldgenerator = let
|
2020-11-10 00:09:09 +03:00
|
|
|
ygDefault = "${nbPkgs.joinmarket}/bin/jm-yg-privacyenhanced";
|
2020-04-23 19:18:47 +03:00
|
|
|
ygBinary = if cfg.yieldgenerator.customParameters == "" then
|
|
|
|
ygDefault
|
|
|
|
else
|
|
|
|
pkgs.runCommand "jm-yieldgenerator-custom" {
|
|
|
|
inherit (cfg.yieldgenerator) customParameters;
|
|
|
|
} ''
|
|
|
|
substitute ${ygDefault} $out \
|
|
|
|
--replace "# end of settings customization" "$customParameters"
|
|
|
|
chmod +x $out
|
|
|
|
'';
|
|
|
|
in {
|
|
|
|
wantedBy = [ "joinmarket.service" ];
|
|
|
|
requires = [ "joinmarket.service" ];
|
|
|
|
after = [ "joinmarket.service" ];
|
|
|
|
preStart = let
|
|
|
|
start = ''
|
|
|
|
exec ${ygBinary} --datadir='${cfg.dataDir}' --wallet-password-stdin wallet.jmdat
|
|
|
|
'';
|
|
|
|
in ''
|
|
|
|
pw=$(cat "${secretsDir}"/jm-wallet-password)
|
|
|
|
echo "echo -n $pw | ${start}" > $RUNTIME_DIRECTORY/start
|
|
|
|
'';
|
2021-02-04 00:44:41 +03:00
|
|
|
serviceConfig = nbLib.defaultHardening // rec {
|
2020-04-23 19:18:47 +03:00
|
|
|
RuntimeDirectory = "joinmarket-yieldgenerator"; # Only used to create start script
|
|
|
|
RuntimeDirectoryMode = "700";
|
2021-02-02 00:53:12 +03:00
|
|
|
WorkingDirectory = cfg.dataDir; # The service creates dir 'logs' in the working dir
|
2020-04-23 19:18:47 +03:00
|
|
|
ExecStart = "${pkgs.bash}/bin/bash /run/${RuntimeDirectory}/start";
|
2021-02-02 00:53:20 +03:00
|
|
|
# Show "joinmarket-yieldgenerator" instead of "bash" in the journal.
|
|
|
|
# The parent bash start process has to run alongside the main process
|
|
|
|
# because it provides the wallet password via stdin to the main process
|
|
|
|
SyslogIdentifier = "joinmarket-yieldgenerator";
|
2021-02-02 00:53:12 +03:00
|
|
|
User = cfg.user;
|
|
|
|
ReadWritePaths = cfg.dataDir;
|
2021-02-04 00:44:41 +03:00
|
|
|
} // nbLib.allowTor;
|
2020-04-23 19:18:47 +03:00
|
|
|
};
|
|
|
|
})
|
|
|
|
]);
|
|
|
|
}
|