Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add liana support #708

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions modules/liana.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{ config, lib, pkgs, ... }:

with lib;
let
options.services.lianad = {
enable = mkEnableOption "lianad bitcoin wallet";
daemon = mkOption {
type = types.bool;
default = false;
description = mdDoc "Whether to run the process as a UNIX daemon (double fork magic).";
};
data_dir = mkOption {
type = types.path;
default = "/var/lib/lianad";
description = mdDoc "Path to the folder where we should store the application data.";
};
main_descriptor = mkOption {
type = types.str;
default = "wsh(or_d(pk([0dd8c6f0/48'/1'/0'/2']tpubDFMbZ7U5k5hEfsttnZTKMmwrGMHnqUGxhShsvBjHimXBpmAp5KmxpyGsLx2toCaQgYq5TipBLhTUtA2pRSB9b14m5KwSohTDoCHkk1EnqtZ/<0;1>/*),and_v(v:pkh([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<0;1>/*),older(65535))))#7nvn6ssc";
description = mdDoc "The wallet descriptor.";
Comment on lines +19 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to move these hardcoded defaults to example instead of default.
I can see easily an user shooting himself in the foot.

Copy link
Author

@plebhash plebhash Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally this should come from options.services.lianad.*, and it definitely makes sense to remove this default value, but with the tradeoff that options.services.lianad.main_descriptor must become a mandatory option.

unfortunately lianad's CLI is still very limited, so everything needs to be written indo a config.toml, and lianad will not start unless there is a valid descriptor inside config.toml

so the challenge here is:

  • declare all options from this sample lianad_config_example.toml as options.services.lianad.* inside modules/liana.nix so they're available as systemd configurations (which seems to be the most common pattern across nix-bitcoin modules)
  • make sure these configurations are taken from options.services.lianad.* and used to generate a config.toml on the module's output (ideally placed somewhere like /var/lib/lianad)

I started doing a heredoc inside the preStart but still unsure whether that is the best approach. It feels a bit dirty tbh.

Also, while the variables are still hardcoded on the heredoc, the idea is to take their actual values from options.services.lianad.*. There heredoc includes some comments. But again: heredoc might not be the best approach.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but still in a footgun for the user.
Check my comment below on how to interpolate TOML file contents into a string.

Copy link
Author

@plebhash plebhash Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really, this is a proposal to mitigate the footgun

  • the user MUST provide a options.services.lianad.main_descriptor on their configuration.nix, which in turn is used to dynamically generate a config.toml file on disk of the output's datadir
  • failing to do so blocks the module from ever launching a systemd service, because there's no valid config.toml in disk

the heredoc is still hardcoding things, but hopefully something along these lines will be a viable approach:

        cat << EOF > lianad_config.toml
# these should come from options.services.lianad
- daemon = false
- data_dir = "/var/lib/lianad"
- log_level = "debug"
- main_descriptor = "wsh(or_d(pk([0dd8c6f0/48'/1'/0'/2']tpubDFMbZ7U5k5hEfsttnZTKMmwrGMHnqUGxhShsvBjHimXBpmAp5KmxpyGsLx2toCaQgYq5TipBLhTUtA2pRSB9b14m5KwSohTDoCHkk1EnqtZ/<0;1>/*),and_v(v:pkh([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<0;1>/*),older(65535))))#7nvn6ssc"
+ daemon = ${options.services.lianad.daemon}
+ data_dir = "${options.services.lianad.data_dir}"
+ log_level = "${options.services.lianad.log_level}"
+ main_descriptor = "${options.services.lianad.main_descriptor}"

I'll give it a try at some time later.

};
network = mkOption {
type = types.str;
default = "bitcoin";
description = mdDoc "bitcoin, testnet, signet, or regtest";
};
bitcoind_addr = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "bitcoind address.";
};
bitcoind_port = mkOption {
type = types.port;
default = 8332;
description = mdDoc "bitcoind port.";
};
};

cfg = config.services.lianad;
nbLib = config.nix-bitcoin.lib;
secretsDir = config.nix-bitcoin.secretsDir;
bitcoind = config.services.bitcoind;
in {
inherit options;

config = mkIf cfg.enable {
services.bitcoind = {
enable = true;
listenWhitelisted = true;
};

systemd.tmpfiles.rules = [
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
];

systemd.services.lianad = {
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" "nix-bitcoin-secrets.target" ];
preStart = ''
cat << EOF > lianad_config.toml
# these should come from options.services.lianad
daemon = false
data_dir = "/var/lib/lianad"
log_level = "debug"
main_descriptor = "wsh(or_d(pk([0dd8c6f0/48'/1'/0'/2']tpubDFMbZ7U5k5hEfsttnZTKMmwrGMHnqUGxhShsvBjHimXBpmAp5KmxpyGsLx2toCaQgYq5TipBLhTUtA2pRSB9b14m5KwSohTDoCHkk1EnqtZ/<0;1>/*),and_v(v:pkh([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<0;1>/*),older(65535))))#7nvn6ssc"

# these should come from options.services.lianad
[bitcoin_config]
network = "signet"
poll_interval_secs = 30

# these should come from options.services.bitcoind
[bitcoind_config]
addr = "127.0.0.1:38332"
auth = "username:password"

EOF
'';
Comment on lines +60 to +79

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a builtins.fromTOML follwed by a toString, e.g.:

let
  tomlFile =./example.toml;
  tomlData = builtins.readFile tomlFile;
  tomlObj = builtins.fromTOML tomlData;
  tomlString = toString tomlObj;
in
  tomlString

Then you can interpolate the hardcoded nastiness with options.services.lianad.*

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

@plebhash plebhash Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this allow me to dynamically generate the TOML file and write it into the output's datadir?

it seems like a convenient tool for reading configs that already exist on disk, but our use case is the opposite direction:

we need to generate a TOML file with values coming from options.services.lianad.*, because lianad only launches with a valid config.toml coming from disk

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can generate a static toml file from a set using pkgs.formats.toml. E.g:

mySet = { foo = "bar""; };
myTomlFile = (pkgs.formats.toml {}).generate mySet;

But this of course cannot contain secrets such as bitcoind credentials that are not going into the nix store. So then the preStart rule could execute a command that reads the generated TOML, and appends the additional secrets, writing it to the dataDir for liana.

The lnd module actually already does this:

      preStart = ''
        install -m600 ${configFile} '${cfg.dataDir}/lnd.conf'
        {
          echo "bitcoind.rpcpass=$(cat ${secretsDir}/bitcoin-rpcpassword-${rpcUser})"
          ${optionalString (cfg.getPublicAddressCmd != "") ''
            echo "externalip=$(${cfg.getPublicAddressCmd})"
          ''}
        } >> '${cfg.dataDir}/lnd.conf'

serviceConfig = nbLib.defaultHardening // {
# lianad only uses the working directory for reading lianad_config.toml
WorkingDirectory = cfg.dataDir;
ExecStart = ''
${config.nix-bitcoin.pkgs.lianad}/bin/lianad \
--conf lianad_config.toml
'';
User = cfg.user;
Group = cfg.group;
Restart = "on-failure";
RestartSec = "10s";
ReadWritePaths = [ cfg.dataDir ];
} // nbLib.allowedIPAddresses cfg.tor.enforce;
};

users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
extraGroups = [ "bitcoinrpc-public" ];
};
users.groups.${cfg.group} = {};
};
}
22 changes: 22 additions & 0 deletions pkgs/liana/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{ stdenv, lib, fetchFromGitHub, rustPlatform }:

rustPlatform.buildRustPackage rec {
pname = "liana";
version = "5.0";

src = fetchFromGitHub {
owner = "wizardsardine";
repo = pname;
rev = version;
hash = "sha256-RkZ2HSN7IjwN3tD0UhpMeQeqkb+Y79kSWnjJZ5KPbQk=";
};

cargoHash = "sha256-v3tMz93mNsTy0k27IzgYk9bL2VfqtXImMlnvwgswp6U=";

meta = {
description = "The missing safety net for your coins";
homepage = "https://wizardsardine.com/liana/";
license = lib.licenses.bsd3;
};

}