Merge pull request #93700 from 1000101/bitcoind
nixos/bitcoind: change to multi-instance + add tests
This commit is contained in:
commit
b415ebae97
@ -539,6 +539,23 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
|
|||||||
to be used for every display-manager in NixOS.
|
to be used for every display-manager in NixOS.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The <literal>bitcoind</literal> module has changed to multi-instance, using submodules.
|
||||||
|
Therefore, it is now mandatory to name each instance, e.g.:
|
||||||
|
<programlisting>
|
||||||
|
services.bitcoind = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
</programlisting>
|
||||||
|
requires a name now:
|
||||||
|
<programlisting>
|
||||||
|
services.bitcoind."example-mainnet" = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -3,31 +3,8 @@
|
|||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.services.bitcoind;
|
|
||||||
pidFile = "${cfg.dataDir}/bitcoind.pid";
|
|
||||||
configFile = pkgs.writeText "bitcoin.conf" ''
|
|
||||||
${optionalString cfg.testnet "testnet=1"}
|
|
||||||
${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"}
|
|
||||||
${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"}
|
|
||||||
|
|
||||||
# Connection options
|
eachBitcoind = config.services.bitcoind;
|
||||||
${optionalString (cfg.port != null) "port=${toString cfg.port}"}
|
|
||||||
|
|
||||||
# RPC server options
|
|
||||||
${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"}
|
|
||||||
${concatMapStringsSep "\n"
|
|
||||||
(rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
|
|
||||||
(attrValues cfg.rpc.users)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extra config options (from bitcoind nixos service)
|
|
||||||
${cfg.extraConfig}
|
|
||||||
'';
|
|
||||||
cmdlineOptions = escapeShellArgs [
|
|
||||||
"-conf=${cfg.configFile}"
|
|
||||||
"-datadir=${cfg.dataDir}"
|
|
||||||
"-pid=${pidFile}"
|
|
||||||
];
|
|
||||||
|
|
||||||
rpcUserOpts = { name, ... }: {
|
rpcUserOpts = { name, ... }: {
|
||||||
options = {
|
options = {
|
||||||
@ -39,11 +16,14 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
passwordHMAC = mkOption {
|
passwordHMAC = mkOption {
|
||||||
type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
|
type = types.uniq (types.strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
|
||||||
example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
|
example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
|
||||||
description = ''
|
description = ''
|
||||||
Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
|
Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
|
||||||
format <SALT-HEX>$<HMAC-HEX>.
|
format <SALT-HEX>$<HMAC-HEX>.
|
||||||
|
|
||||||
|
Tool (Python script) for HMAC generation is available here:
|
||||||
|
<link xlink:href="https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py"/>
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -51,10 +31,10 @@ let
|
|||||||
name = mkDefault name;
|
name = mkDefault name;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in {
|
|
||||||
options = {
|
|
||||||
|
|
||||||
services.bitcoind = {
|
bitcoindOpts = { config, lib, name, ...}: {
|
||||||
|
options = {
|
||||||
|
|
||||||
enable = mkEnableOption "Bitcoin daemon";
|
enable = mkEnableOption "Bitcoin daemon";
|
||||||
|
|
||||||
package = mkOption {
|
package = mkOption {
|
||||||
@ -63,12 +43,14 @@ in {
|
|||||||
defaultText = "pkgs.bitcoind";
|
defaultText = "pkgs.bitcoind";
|
||||||
description = "The package providing bitcoin binaries.";
|
description = "The package providing bitcoin binaries.";
|
||||||
};
|
};
|
||||||
|
|
||||||
configFile = mkOption {
|
configFile = mkOption {
|
||||||
type = types.path;
|
type = types.nullOr types.path;
|
||||||
default = configFile;
|
default = null;
|
||||||
example = "/etc/bitcoind.conf";
|
example = "/var/lib/${name}/bitcoin.conf";
|
||||||
description = "The configuration file path to supply bitcoind.";
|
description = "The configuration file path to supply bitcoind.";
|
||||||
};
|
};
|
||||||
|
|
||||||
extraConfig = mkOption {
|
extraConfig = mkOption {
|
||||||
type = types.lines;
|
type = types.lines;
|
||||||
default = "";
|
default = "";
|
||||||
@ -79,20 +61,22 @@ in {
|
|||||||
'';
|
'';
|
||||||
description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
|
description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
|
||||||
};
|
};
|
||||||
|
|
||||||
dataDir = mkOption {
|
dataDir = mkOption {
|
||||||
type = types.path;
|
type = types.path;
|
||||||
default = "/var/lib/bitcoind";
|
default = "/var/lib/bitcoind-${name}";
|
||||||
description = "The data directory for bitcoind.";
|
description = "The data directory for bitcoind.";
|
||||||
};
|
};
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "bitcoin";
|
default = "bitcoind-${name}";
|
||||||
description = "The user as which to run bitcoind.";
|
description = "The user as which to run bitcoind.";
|
||||||
};
|
};
|
||||||
|
|
||||||
group = mkOption {
|
group = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = cfg.user;
|
default = config.user;
|
||||||
description = "The group as which to run bitcoind.";
|
description = "The group as which to run bitcoind.";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,29 +94,36 @@ in {
|
|||||||
bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
|
bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
type = with types; loaOf (submodule rpcUserOpts);
|
type = types.attrsOf (types.submodule rpcUserOpts);
|
||||||
description = ''
|
description = "RPC user information for JSON-RPC connnections.";
|
||||||
RPC user information for JSON-RPC connnections.
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pidFile = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "${config.dataDir}/bitcoind.pid";
|
||||||
|
description = "Location of bitcoind pid file.";
|
||||||
|
};
|
||||||
|
|
||||||
testnet = mkOption {
|
testnet = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = "Whether to use the test chain.";
|
description = "Whether to use the testnet instead of mainnet.";
|
||||||
};
|
};
|
||||||
|
|
||||||
port = mkOption {
|
port = mkOption {
|
||||||
type = types.nullOr types.port;
|
type = types.nullOr types.port;
|
||||||
default = null;
|
default = null;
|
||||||
description = "Override the default port on which to listen for connections.";
|
description = "Override the default port on which to listen for connections.";
|
||||||
};
|
};
|
||||||
|
|
||||||
dbCache = mkOption {
|
dbCache = mkOption {
|
||||||
type = types.nullOr (types.ints.between 4 16384);
|
type = types.nullOr (types.ints.between 4 16384);
|
||||||
default = null;
|
default = null;
|
||||||
example = 4000;
|
example = 4000;
|
||||||
description = "Override the default database cache size in megabytes.";
|
description = "Override the default database cache size in MiB.";
|
||||||
};
|
};
|
||||||
|
|
||||||
prune = mkOption {
|
prune = mkOption {
|
||||||
type = types.nullOr (types.coercedTo
|
type = types.nullOr (types.coercedTo
|
||||||
(types.enum [ "disable" "manual" ])
|
(types.enum [ "disable" "manual" ])
|
||||||
@ -149,45 +140,122 @@ in {
|
|||||||
and -rescan. Warning: Reverting this setting requires re-downloading
|
and -rescan. Warning: Reverting this setting requires re-downloading
|
||||||
the entire blockchain. ("disable" = disable pruning blocks, "manual"
|
the entire blockchain. ("disable" = disable pruning blocks, "manual"
|
||||||
= allow manual pruning via RPC, >=550 = automatically prune block files
|
= allow manual pruning via RPC, >=550 = automatically prune block files
|
||||||
to stay under the specified target size in MiB)
|
to stay under the specified target size in MiB).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraCmdlineOptions = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
Extra command line options to pass to bitcoind.
|
||||||
|
Run bitcoind --help to list all available options.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
options = {
|
||||||
environment.systemPackages = [ cfg.package ];
|
services.bitcoind = mkOption {
|
||||||
systemd.tmpfiles.rules = [
|
type = types.attrsOf (types.submodule bitcoindOpts);
|
||||||
"d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
|
default = {};
|
||||||
"L '${cfg.dataDir}/bitcoin.conf' - - - - '${cfg.configFile}'"
|
description = "Specification of one or more bitcoind instances.";
|
||||||
];
|
|
||||||
systemd.services.bitcoind = {
|
|
||||||
description = "Bitcoin daemon";
|
|
||||||
after = [ "network.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
User = cfg.user;
|
|
||||||
Group = cfg.group;
|
|
||||||
ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}";
|
|
||||||
Restart = "on-failure";
|
|
||||||
|
|
||||||
# Hardening measures
|
|
||||||
PrivateTmp = "true";
|
|
||||||
ProtectSystem = "full";
|
|
||||||
NoNewPrivileges = "true";
|
|
||||||
PrivateDevices = "true";
|
|
||||||
MemoryDenyWriteExecute = "true";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
users.users.${cfg.user} = {
|
};
|
||||||
|
|
||||||
|
config = mkIf (eachBitcoind != {}) {
|
||||||
|
|
||||||
|
assertions = flatten (mapAttrsToList (bitcoindName: cfg: [
|
||||||
|
{
|
||||||
|
assertion = (cfg.prune != null) -> (builtins.elem cfg.prune [ "disable" "manual" 0 1 ] || (builtins.isInt cfg.prune && cfg.prune >= 550));
|
||||||
|
message = ''
|
||||||
|
If set, services.bitcoind.${bitcoindName}.prune has to be "disable", "manual", 0 , 1 or >= 550.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = (cfg.rpc.users != {}) -> (cfg.configFile == null);
|
||||||
|
message = ''
|
||||||
|
You cannot set both services.bitcoind.${bitcoindName}.rpc.users and services.bitcoind.${bitcoindName}.configFile
|
||||||
|
as they are exclusive. RPC user setting would have no effect if custom configFile would be used.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
]) eachBitcoind);
|
||||||
|
|
||||||
|
environment.systemPackages = flatten (mapAttrsToList (bitcoindName: cfg: [
|
||||||
|
cfg.package
|
||||||
|
]) eachBitcoind);
|
||||||
|
|
||||||
|
systemd.services = mapAttrs' (bitcoindName: cfg: (
|
||||||
|
nameValuePair "bitcoind-${bitcoindName}" (
|
||||||
|
let
|
||||||
|
configFile = pkgs.writeText "bitcoin.conf" ''
|
||||||
|
# If Testnet is enabled, we need to add [test] section
|
||||||
|
# otherwise, some options (e.g.: custom RPC port) will not work
|
||||||
|
${optionalString cfg.testnet "[test]"}
|
||||||
|
# RPC users
|
||||||
|
${concatMapStringsSep "\n"
|
||||||
|
(rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
|
||||||
|
(attrValues cfg.rpc.users)
|
||||||
|
}
|
||||||
|
# Extra config options (from bitcoind nixos service)
|
||||||
|
${cfg.extraConfig}
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
description = "Bitcoin daemon";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
ExecStart = ''
|
||||||
|
${cfg.package}/bin/bitcoind \
|
||||||
|
${if (cfg.configFile != null) then
|
||||||
|
"-conf=${cfg.configFile}"
|
||||||
|
else
|
||||||
|
"-conf=${configFile}"
|
||||||
|
} \
|
||||||
|
-datadir=${cfg.dataDir} \
|
||||||
|
-pid=${cfg.pidFile} \
|
||||||
|
${optionalString cfg.testnet "-testnet"}\
|
||||||
|
${optionalString (cfg.port != null) "-port=${toString cfg.port}"}\
|
||||||
|
${optionalString (cfg.prune != null) "-prune=${toString cfg.prune}"}\
|
||||||
|
${optionalString (cfg.dbCache != null) "-dbcache=${toString cfg.dbCache}"}\
|
||||||
|
${optionalString (cfg.rpc.port != null) "-rpcport=${toString cfg.rpc.port}"}\
|
||||||
|
${toString cfg.extraCmdlineOptions}
|
||||||
|
'';
|
||||||
|
Restart = "on-failure";
|
||||||
|
|
||||||
|
# Hardening measures
|
||||||
|
PrivateTmp = "true";
|
||||||
|
ProtectSystem = "full";
|
||||||
|
NoNewPrivileges = "true";
|
||||||
|
PrivateDevices = "true";
|
||||||
|
MemoryDenyWriteExecute = "true";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
))) eachBitcoind;
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = flatten (mapAttrsToList (bitcoindName: cfg: [
|
||||||
|
"d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
|
||||||
|
]) eachBitcoind);
|
||||||
|
|
||||||
|
users.users = mapAttrs' (bitcoindName: cfg: (
|
||||||
|
nameValuePair "bitcoind-${bitcoindName}" {
|
||||||
name = cfg.user;
|
name = cfg.user;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
description = "Bitcoin daemon user";
|
description = "Bitcoin daemon user";
|
||||||
home = cfg.dataDir;
|
home = cfg.dataDir;
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
};
|
})) eachBitcoind;
|
||||||
users.groups.${cfg.group} = {
|
|
||||||
name = cfg.group;
|
users.groups = mapAttrs' (bitcoindName: cfg: (
|
||||||
};
|
nameValuePair "${cfg.group}" { }
|
||||||
|
)) eachBitcoind;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
meta.maintainers = with maintainers; [ maintainers."1000101" ];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ in
|
|||||||
beanstalkd = handleTest ./beanstalkd.nix {};
|
beanstalkd = handleTest ./beanstalkd.nix {};
|
||||||
bees = handleTest ./bees.nix {};
|
bees = handleTest ./bees.nix {};
|
||||||
bind = handleTest ./bind.nix {};
|
bind = handleTest ./bind.nix {};
|
||||||
|
bitcoind = handleTest ./bitcoind.nix {};
|
||||||
bittorrent = handleTest ./bittorrent.nix {};
|
bittorrent = handleTest ./bittorrent.nix {};
|
||||||
blockbook-frontend = handleTest ./blockbook-frontend.nix {};
|
blockbook-frontend = handleTest ./blockbook-frontend.nix {};
|
||||||
buildkite-agents = handleTest ./buildkite-agents.nix {};
|
buildkite-agents = handleTest ./buildkite-agents.nix {};
|
||||||
|
46
nixos/tests/bitcoind.nix
Normal file
46
nixos/tests/bitcoind.nix
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||||
|
name = "bitcoind";
|
||||||
|
meta = with pkgs.stdenv.lib; {
|
||||||
|
maintainers = with maintainers; [ maintainers."1000101" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
machine = { ... }: {
|
||||||
|
services.bitcoind."mainnet" = {
|
||||||
|
enable = true;
|
||||||
|
rpc = {
|
||||||
|
port = 8332;
|
||||||
|
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
|
||||||
|
users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.bitcoind."testnet" = {
|
||||||
|
enable = true;
|
||||||
|
configFile = "/test.blank";
|
||||||
|
testnet = true;
|
||||||
|
rpc = {
|
||||||
|
port = 18332;
|
||||||
|
};
|
||||||
|
extraCmdlineOptions = [ "-rpcuser=rpc" "-rpcpassword=rpc" "-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
machine.wait_for_unit("bitcoind-mainnet.service")
|
||||||
|
machine.wait_for_unit("bitcoind-testnet.service")
|
||||||
|
|
||||||
|
machine.wait_until_succeeds(
|
||||||
|
'curl --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
|
||||||
|
)
|
||||||
|
machine.wait_until_succeeds(
|
||||||
|
'curl --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
|
||||||
|
)
|
||||||
|
machine.wait_until_succeeds(
|
||||||
|
'curl --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
|
||||||
|
)
|
||||||
|
machine.wait_until_succeeds(
|
||||||
|
'curl --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user