2018-11-22 16:46:56 -08:00
|
|
|
{ config, pkgs, lib, ... }:
|
2018-11-13 15:44:54 -08:00
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
2018-11-22 16:46:56 -08:00
|
|
|
cfg = config.services.bitcoind;
|
|
|
|
pidFile = "${cfg.dataDir}/bitcoind.pid";
|
2018-11-13 16:33:34 -08:00
|
|
|
configFile = pkgs.writeText "bitcoin.conf" ''
|
2018-11-22 16:46:56 -08:00
|
|
|
${optionalString cfg.testnet "testnet=1"}
|
|
|
|
${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"}
|
|
|
|
${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"}
|
2019-03-21 03:27:28 -07:00
|
|
|
${optionalString (cfg.sysperms != null) "sysperms=${if cfg.sysperms then "1" else "0"}"}
|
|
|
|
${optionalString (cfg.disablewallet != null) "disablewallet=${if cfg.disablewallet then "1" else "0"}"}
|
2018-11-22 16:46:56 -08:00
|
|
|
|
|
|
|
# Connection options
|
2018-11-22 10:49:53 -08:00
|
|
|
${optionalString (cfg.port != null) "port=${toString cfg.port}"}
|
2018-11-22 16:46:56 -08:00
|
|
|
${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"}
|
|
|
|
listen=${if cfg.listen then "1" else "0"}
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
}
|
2018-11-22 15:06:41 -08:00
|
|
|
${optionalString (cfg.rpcuser != null) "rpcuser=${cfg.rpcuser}"}
|
2018-11-22 15:51:16 -08:00
|
|
|
${optionalString (cfg.rpcpassword != null) "rpcpassword=${cfg.rpcpassword}"}
|
2018-11-22 16:46:56 -08:00
|
|
|
|
|
|
|
# Extra config options (from bitcoind nixos service)
|
|
|
|
${cfg.extraConfig}
|
|
|
|
'';
|
|
|
|
cmdlineOptions = concatMapStringsSep " " (arg: "'${arg}'") [
|
|
|
|
"-datadir=${cfg.dataDir}"
|
|
|
|
"-pid=${pidFile}"
|
|
|
|
];
|
|
|
|
hexStr = types.strMatching "[0-9a-f]+";
|
|
|
|
rpcUserOpts = { name, ... }: {
|
|
|
|
options = {
|
|
|
|
name = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
example = "alice";
|
|
|
|
description = ''
|
|
|
|
Username for JSON-RPC connections.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
passwordHMAC = mkOption {
|
|
|
|
type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
|
|
|
|
example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
|
|
|
|
description = ''
|
|
|
|
Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
|
|
|
|
format <SALT-HEX>$<HMAC-HEX>.
|
|
|
|
'';
|
|
|
|
};
|
2018-11-19 15:09:57 -08:00
|
|
|
};
|
2018-11-22 16:46:56 -08:00
|
|
|
config = {
|
|
|
|
name = mkDefault name;
|
2018-11-19 15:09:57 -08:00
|
|
|
};
|
2018-11-22 16:46:56 -08:00
|
|
|
};
|
|
|
|
in {
|
|
|
|
options = {
|
|
|
|
|
|
|
|
services.bitcoind = {
|
|
|
|
enable = mkEnableOption "Bitcoin daemon";
|
|
|
|
|
|
|
|
package = mkOption {
|
|
|
|
type = types.package;
|
|
|
|
default = pkgs.altcoins.bitcoind;
|
|
|
|
defaultText = "pkgs.altcoins.bitcoind";
|
|
|
|
description = "The package providing bitcoin binaries.";
|
|
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
|
|
|
example = ''
|
|
|
|
par=16
|
|
|
|
rpcthreads=16
|
|
|
|
logips=1
|
2018-11-24 14:34:39 -08:00
|
|
|
|
2018-11-22 16:46:56 -08:00
|
|
|
'';
|
|
|
|
description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
|
|
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
|
|
type = types.path;
|
|
|
|
default = "/var/lib/bitcoind";
|
|
|
|
description = "The data directory for bitcoind.";
|
|
|
|
};
|
2018-12-06 07:59:41 -08:00
|
|
|
configFileOption = mkOption {
|
|
|
|
type = types.path;
|
|
|
|
default = configFile;
|
|
|
|
description = "The data directory for bitcoind.";
|
|
|
|
};
|
2018-11-22 16:46:56 -08:00
|
|
|
|
|
|
|
user = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "bitcoin";
|
|
|
|
description = "The user as which to run bitcoind.";
|
|
|
|
};
|
|
|
|
group = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = cfg.user;
|
|
|
|
description = "The group as which to run bitcoind.";
|
|
|
|
};
|
|
|
|
|
|
|
|
rpc = {
|
|
|
|
port = mkOption {
|
|
|
|
type = types.nullOr types.ints.u16;
|
|
|
|
default = null;
|
|
|
|
description = "Override the default port on which to listen for JSON-RPC connections.";
|
|
|
|
};
|
|
|
|
users = mkOption {
|
|
|
|
default = {};
|
|
|
|
example = {
|
|
|
|
alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
|
|
|
|
bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
|
|
|
|
};
|
|
|
|
type = with types; loaOf (submodule rpcUserOpts);
|
|
|
|
description = ''
|
|
|
|
RPC user information for JSON-RPC connnections.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
rpcuser = mkOption {
|
|
|
|
type = types.nullOr types.string;
|
|
|
|
default = null;
|
|
|
|
description = "Username for JSON-RPC connections";
|
|
|
|
};
|
|
|
|
rpcpassword = mkOption {
|
|
|
|
type = types.nullOr types.string;
|
|
|
|
default = null;
|
|
|
|
description = "Password for JSON-RPC connections";
|
|
|
|
};
|
|
|
|
|
|
|
|
testnet = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = "Whether to use the test chain.";
|
|
|
|
};
|
|
|
|
port = mkOption {
|
2018-11-19 15:09:57 -08:00
|
|
|
type = types.nullOr types.ints.u16;
|
|
|
|
default = null;
|
|
|
|
description = "Override the default port on which to listen for connections.";
|
2018-11-22 16:46:56 -08:00
|
|
|
};
|
|
|
|
proxy = mkOption {
|
2018-11-22 15:06:41 -08:00
|
|
|
type = types.nullOr types.string;
|
|
|
|
default = null;
|
2018-11-22 16:46:56 -08:00
|
|
|
description = "Connect through SOCKS5 proxy";
|
|
|
|
};
|
|
|
|
listen = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = ''
|
|
|
|
If enabled, the bitcoin service will listen.
|
|
|
|
'';
|
|
|
|
};
|
2019-02-25 08:00:50 -08:00
|
|
|
sysperms = mkOption {
|
2019-03-21 03:27:28 -07:00
|
|
|
type = types.nullOr types.bool;
|
|
|
|
default = null;
|
2019-02-25 08:00:50 -08:00
|
|
|
description = ''
|
|
|
|
Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
disablewallet = mkOption {
|
2019-03-21 03:27:28 -07:00
|
|
|
type = types.nullOr types.bool;
|
|
|
|
default = null;
|
2019-02-25 08:00:50 -08:00
|
|
|
description = ''
|
|
|
|
Do not load the wallet and disable wallet RPC calls
|
|
|
|
'';
|
|
|
|
};
|
2018-11-22 16:46:56 -08:00
|
|
|
dbCache = mkOption {
|
|
|
|
type = types.nullOr (types.ints.between 4 16384);
|
|
|
|
default = null;
|
|
|
|
example = 4000;
|
|
|
|
description = "Override the default database cache size in megabytes.";
|
|
|
|
};
|
|
|
|
prune = mkOption {
|
|
|
|
type = types.nullOr (types.coercedTo
|
|
|
|
(types.enum [ "disable" "manual" ])
|
|
|
|
(x: if x == "disable" then 0 else 1)
|
|
|
|
types.ints.unsigned
|
|
|
|
);
|
2018-11-22 15:06:41 -08:00
|
|
|
default = null;
|
2018-11-22 16:46:56 -08:00
|
|
|
example = 10000;
|
|
|
|
description = ''
|
|
|
|
Reduce storage requirements by enabling pruning (deleting) of old
|
|
|
|
blocks. This allows the pruneblockchain RPC to be called to delete
|
|
|
|
specific blocks, and enables automatic pruning of old blocks if a
|
|
|
|
target size in MiB is provided. This mode is incompatible with -txindex
|
|
|
|
and -rescan. Warning: Reverting this setting requires re-downloading
|
|
|
|
the entire blockchain. ("disable" = disable pruning blocks, "manual"
|
|
|
|
= allow manual pruning via RPC, >=550 = automatically prune block files
|
|
|
|
to stay under the specified target size in MiB)
|
|
|
|
'';
|
|
|
|
};
|
2018-11-22 15:06:41 -08:00
|
|
|
};
|
2018-11-13 15:44:54 -08:00
|
|
|
};
|
2018-11-22 16:46:56 -08:00
|
|
|
|
2018-11-13 15:44:54 -08:00
|
|
|
config = mkIf cfg.enable {
|
2018-11-22 16:46:56 -08:00
|
|
|
environment.systemPackages = [ cfg.package ];
|
2018-11-22 10:49:53 -08:00
|
|
|
systemd.services.bitcoind = {
|
2018-11-22 16:46:56 -08:00
|
|
|
description = "Bitcoin daemon";
|
2018-11-28 14:58:36 -08:00
|
|
|
requires = [ "bitcoin-rpcpassword-key.service" ];
|
|
|
|
after = [ "network.target" "bitcoin-rpcpassword-key.service" ];
|
2018-11-22 10:49:53 -08:00
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
preStart = ''
|
2018-11-22 16:46:56 -08:00
|
|
|
if ! test -e ${cfg.dataDir}; then
|
|
|
|
mkdir -m 0770 -p '${cfg.dataDir}'
|
2019-03-25 06:40:12 -07:00
|
|
|
chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}'
|
|
|
|
fi
|
|
|
|
if ! test -e ${cfg.dataDir}/blocks; then
|
|
|
|
mkdir -m 0770 -p '${cfg.dataDir}/blocks'
|
|
|
|
chown -R '${cfg.user}:${cfg.group}' '${cfg.dataDir}/blocks'
|
2018-11-22 16:46:56 -08:00
|
|
|
fi
|
2018-12-06 07:59:41 -08:00
|
|
|
cp '${cfg.configFileOption}' '${cfg.dataDir}/bitcoin.conf'
|
2018-11-28 15:54:19 -08:00
|
|
|
chmod o-rw '${cfg.dataDir}/bitcoin.conf'
|
2018-12-03 07:31:44 -08:00
|
|
|
chown '${cfg.user}:${cfg.group}' '${cfg.dataDir}/bitcoin.conf'
|
2018-11-28 14:58:36 -08:00
|
|
|
echo "rpcpassword=$(cat /secrets/bitcoin-rpcpassword)" >> '${cfg.dataDir}/bitcoin.conf'
|
2019-02-25 08:00:50 -08:00
|
|
|
chmod -R g+rX '${cfg.dataDir}/blocks'
|
2018-11-22 16:46:56 -08:00
|
|
|
'';
|
2018-11-22 10:49:53 -08:00
|
|
|
serviceConfig = {
|
2018-11-22 16:46:56 -08:00
|
|
|
Type = "simple";
|
|
|
|
User = "${cfg.user}";
|
|
|
|
Group = "${cfg.group}";
|
|
|
|
ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}";
|
|
|
|
StateDirectory = "bitcoind";
|
|
|
|
PIDFile = "${pidFile}";
|
2018-11-22 10:49:53 -08:00
|
|
|
Restart = "on-failure";
|
|
|
|
|
2018-11-22 16:46:56 -08:00
|
|
|
# Hardening measures
|
2018-11-22 10:49:53 -08:00
|
|
|
PrivateTmp = "true";
|
|
|
|
ProtectSystem = "full";
|
|
|
|
NoNewPrivileges = "true";
|
|
|
|
PrivateDevices = "true";
|
|
|
|
MemoryDenyWriteExecute = "true";
|
2018-11-22 16:46:56 -08:00
|
|
|
|
|
|
|
# Permission for preStart
|
|
|
|
PermissionsStartOnly = "true";
|
2018-11-13 15:44:54 -08:00
|
|
|
};
|
2018-11-19 15:09:57 -08:00
|
|
|
};
|
2019-03-29 02:44:30 -07:00
|
|
|
systemd.services.bitcoind-import-banlist = {
|
|
|
|
description = "Bitcoin daemon banlist importer";
|
2019-03-26 03:39:49 -07:00
|
|
|
requires = [ "bitcoind.service" ];
|
|
|
|
after = [ "bitcoind.service" ];
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
preStart = ''
|
2019-03-29 02:44:30 -07:00
|
|
|
set +e
|
2019-03-26 03:39:49 -07:00
|
|
|
echo "Checking that bitcoind is up"
|
|
|
|
# Give bitcoind time to create pid file
|
|
|
|
sleep 2
|
|
|
|
while true
|
|
|
|
do
|
|
|
|
pid=$(cat ${pidFile})
|
|
|
|
${pkgs.ps}/bin/ps -p "$pid" > /dev/null
|
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
echo "bitcoind already exited"
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
'${cfg.package}'/bin/bitcoin-cli -datadir='${cfg.dataDir}' getnetworkinfo > /dev/null
|
|
|
|
if [ "$?" -eq 0 ]; then
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
sleep 1
|
|
|
|
done
|
|
|
|
'';
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
User = "${cfg.user}";
|
|
|
|
Group = "${cfg.group}";
|
|
|
|
ExecStart = "${pkgs.bash}/bin/bash ${pkgs.banlist}/bin/banlist ${pkgs.altcoins.bitcoind}";
|
|
|
|
StateDirectory = "bitcoind";
|
|
|
|
|
|
|
|
# Hardening measures
|
|
|
|
PrivateTmp = "true";
|
|
|
|
ProtectSystem = "full";
|
|
|
|
NoNewPrivileges = "true";
|
|
|
|
PrivateDevices = "true";
|
|
|
|
MemoryDenyWriteExecute = "true";
|
|
|
|
|
|
|
|
# Permission for preStart
|
|
|
|
PermissionsStartOnly = "true";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2018-11-22 16:46:56 -08:00
|
|
|
users.users.${cfg.user} = {
|
|
|
|
name = cfg.user;
|
|
|
|
group = cfg.group;
|
2018-11-28 14:58:36 -08:00
|
|
|
extraGroups = [ "keys" ];
|
2018-11-22 16:46:56 -08:00
|
|
|
description = "Bitcoin daemon user";
|
|
|
|
home = cfg.dataDir;
|
|
|
|
};
|
|
|
|
users.groups.${cfg.group} = {
|
|
|
|
name = cfg.group;
|
|
|
|
};
|
2018-11-22 10:49:53 -08:00
|
|
|
};
|
2018-11-13 15:44:54 -08:00
|
|
|
}
|