nix-decred/modules/dcrd.nix

124 lines
4.1 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.services.dcrd;
settingsFormat = pkgs.formats.ini {
listsAsDuplicateKeys = true;
};
networkPorts = {
mainnet = 9108;
testnet = 19108;
simnet = 18555;
regnet = 18655;
};
effectivePort = if cfg.port == null then networkPorts.${cfg.network} else cfg.port;
# dcrd requires settings to be lowercase
lowercaseKeys = attrs: lib.mapAttrs' (k: v: { name = lib.strings.toLower k; value = v; }) attrs;
baseAppOptions =
{}
// (if cfg.network == "testnet" then { testnet = true; }
else if cfg.network == "simnet" then { simnet = true; }
else if cfg.network == "regnet" then { regnet = true; }
else {})
// lib.optionalAttrs (cfg.port != null || cfg.listenAddress != "0.0.0.0") {
listen = "${cfg.listenAddress}:${toString effectivePort}";
};
combinedAppOptions = lib.recursiveUpdate baseAppOptions (lowercaseKeys cfg.settings);
finalSettings = { "Application Options" = combinedAppOptions; };
generatedConfig = settingsFormat.generate "dcrd.conf" finalSettings;
in
{
options.services.dcrd = with lib; {
enable = mkEnableOption "Decred daemon";
package = mkOption {
type = types.package;
default = pkgs.dcrd;
description = "Package providing the dcrd binary";
};
network = mkOption {
type = types.enum [ "mainnet" "testnet" "simnet" "regnet" ];
default = "mainnet";
description = "Select which network to run: mainnet, testnet, simnet, or regnet";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
description = "P2P port override. Null uses the default for the selected network";
};
listenAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = "IP address to bind to listen for connections";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Open P2P port in the firewall";
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Absolute path to a dcrd.conf to use instead of the generated config. When set, the generated settings are ignored.";
};
user = mkOption {
type = types.str;
default = "dcrd";
description = "User to run dcrd as";
};
group = mkOption {
type = types.str;
default = cfg.user;
description = "Group to run dcrd as";
};
settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf settingsFormat.lib.types.atom;
};
default = {};
example = {
externalip = [ "203.0.113.42" ];
rpclisten = [ "127.0.0.1:9109" "192.168.1.100:9109" ];
rpcuser = "dcrd";
rpcpass = "hunter2";
txindex = true;
proxy = "127.0.0.1:9050";
};
description = ''
Options that accept multiple values (like rpclisten, externalip,
listen, addpeer, miningaddr, altdnsnames, whitelist) should be
specified as lists.
Single-value options (like rpcuser, rpcpass, proxy, txindex)
should be specified as strings or booleans.
'';
};
};
config = lib.mkIf cfg.enable {
users.groups.${cfg.group} = {};
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
description = "Decred daemon user";
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ effectivePort ];
systemd.services.dcrd = {
description = "Decred full node";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
StateDirectory = "dcrd";
StateDirectoryMode = "0750";
ExecStart = ''${lib.getExe cfg.package} --appdata=/var/lib/dcrd --configfile=${if cfg.configFile != null then cfg.configFile else generatedConfig}'';
Restart = "on-failure";
RestartSec = 5;
};
restartTriggers = [ generatedConfig ] ++ lib.optional (cfg.configFile != null) cfg.configFile;
};
};
}