diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0dba92f60c7..55d0c836d47 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -589,6 +589,7 @@ ./services/networking/autossh.nix ./services/networking/bird.nix ./services/networking/bitlbee.nix + ./services/networking/blockbook-frontend.nix ./services/networking/charybdis.nix ./services/networking/cjdns.nix ./services/networking/cntlm.nix diff --git a/nixos/modules/services/networking/blockbook-frontend.nix b/nixos/modules/services/networking/blockbook-frontend.nix new file mode 100644 index 00000000000..61938e51e06 --- /dev/null +++ b/nixos/modules/services/networking/blockbook-frontend.nix @@ -0,0 +1,272 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + eachBlockbook = config.services.blockbook-frontend; + + blockbookOpts = { config, lib, name, ...}: { + + options = { + + enable = mkEnableOption "blockbook-frontend application."; + + package = mkOption { + type = types.package; + default = pkgs.blockbook; + description = "Which blockbook package to use."; + }; + + user = mkOption { + type = types.str; + default = "blockbook-frontend-${name}"; + description = "The user as which to run blockbook-frontend-${name}."; + }; + + group = mkOption { + type = types.str; + default = "${config.user}"; + description = "The group as which to run blockbook-frontend-${name}."; + }; + + certFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/etc/secrets/blockbook-frontend-${name}/certFile"; + description = '' + To enable SSL, specify path to the name of certificate files without extension. + Expecting certFile.crt and certFile.key. + ''; + }; + + configFile = mkOption { + type = with types; nullOr path; + default = null; + example = "${config.dataDir}/config.json"; + description = "Location of the blockbook configuration file."; + }; + + coinName = mkOption { + type = types.str; + default = "Bitcoin"; + example = "Bitcoin"; + description = '' + See + for current of coins supported in master (Note: may differ from release). + ''; + }; + + cssDir = mkOption { + type = types.path; + default = "${config.package}/share/css/"; + example = "${config.dataDir}/static/css/"; + description = '' + Location of the dir with main.css CSS file. + By default, the one shipped with the package is used. + ''; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/blockbook-frontend-${name}"; + description = "Location of blockbook-frontend-${name} data directory."; + }; + + debug = mkOption { + type = types.bool; + default = false; + description = "Debug mode, return more verbose errors, reload templates on each request."; + }; + + internal = mkOption { + type = types.nullOr types.str; + default = ":9030"; + example = ":9030"; + description = "Internal http server binding [address]:port."; + }; + + messageQueueBinding = mkOption { + type = types.str; + default = "tcp://127.0.0.1:38330"; + example = "tcp://127.0.0.1:38330"; + description = "Message Queue Binding address:port."; + }; + + public = mkOption { + type = types.nullOr types.str; + default = ":9130"; + example = ":9130"; + description = "Public http server binding [address]:port."; + }; + + rpc = { + url = mkOption { + type = types.str; + default = "http://127.0.0.1"; + description = "URL for JSON-RPC connections."; + }; + + port = mkOption { + type = types.port; + default = 8030; + description = "Port for JSON-RPC connections."; + }; + + user = mkOption { + type = types.str; + default = "rpc"; + example = "rpc"; + description = "Username for JSON-RPC connections."; + }; + + password = mkOption { + type = types.str; + default = "rpc"; + example = "rpc"; + description = '' + RPC password for JSON-RPC connections. + Warning: this is stored in cleartext in the Nix store!!! + Use configFile or passwordFile if needed. + ''; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing password of the RPC user. + Note: This options is ignored when configFile is used. + ''; + }; + }; + + sync = mkOption { + type = types.bool; + default = true; + description = "Synchronizes until tip, if together with zeromq, keeps index synchronized."; + }; + + templateDir = mkOption { + type = types.path; + default = "${config.package}/share/templates/"; + example = "${config.dataDir}/templates/static/"; + description = "Location of the HTML templates. By default, ones shipped with the package are used."; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' { + alternative_estimate_fee = "whatthefee-disabled"; + alternative_estimate_fee_params = "{\"url\": \"https://whatthefee.io/data.json\", \"periodSeconds\": 60}"; + fiat_rates = "coingecko"; + fiat_rates_params = "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"bitcoin\", \"periodSeconds\": 60}"; + coin_shortcut = "BTC"; + coin_label = "Bitcoin"; + xpub_magic = 76067358; + xpub_magic_segwit_p2sh = 77429938; + xpub_magic_segwit_native = 78792518; + }''; + description = '' + Additional configurations to be appended to coin.conf. + Overrides any already defined configuration options. + See + for current configuration options supported in master (Note: may differ from release). + ''; + }; + + extraCmdLineOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "-workers=1" "-dbcache=0" "-logtosderr" ]; + description = '' + Extra command line options to pass to Blockbook. + Run blockbook --help to list all available options. + ''; + }; + }; + }; +in +{ + # interface + + options = { + services.blockbook-frontend = mkOption { + type = types.attrsOf (types.submodule blockbookOpts); + default = {}; + description = "Specification of one or more blockbook-frontend instances."; + }; + }; + + # implementation + + config = mkIf (eachBlockbook != {}) { + + systemd.services = mapAttrs' (blockbookName: cfg: ( + nameValuePair "blockbook-frontend-${blockbookName}" ( + let + configFile = if cfg.configFile != null then cfg.configFile else + pkgs.writeText "config.conf" (builtins.toJSON ( { + coin_name = "${cfg.coinName}"; + rpc_user = "${cfg.rpc.user}"; + rpc_pass = "${cfg.rpc.password}"; + rpc_url = "${cfg.rpc.url}:${toString cfg.rpc.port}"; + message_queue_binding = "${cfg.messageQueueBinding}"; + } // cfg.extraConfig) + ); + in { + description = "blockbook-frontend-${blockbookName} daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + ln -sf ${cfg.templateDir} ${cfg.dataDir}/static/ + ln -sf ${cfg.cssDir} ${cfg.dataDir}/static/ + ${optionalString (cfg.rpc.passwordFile != null && cfg.configFile == null) '' + CONFIGTMP=$(mktemp) + ${pkgs.jq}/bin/jq ".rpc_pass = \"$(cat ${cfg.rpc.passwordFile})\"" ${configFile} > $CONFIGTMP + mv $CONFIGTMP ${cfg.dataDir}/${blockbookName}-config.json + ''} + ''; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = '' + ${cfg.package}/bin/blockbook \ + ${if (cfg.rpc.passwordFile != null && cfg.configFile == null) then + "-blockchaincfg=${cfg.dataDir}/${blockbookName}-config.json" + else + "-blockchaincfg=${configFile}" + } \ + -datadir=${cfg.dataDir} \ + ${optionalString (cfg.sync != false) "-sync"} \ + ${optionalString (cfg.certFile != null) "-certfile=${toString cfg.certFile}"} \ + ${optionalString (cfg.debug != false) "-debug"} \ + ${optionalString (cfg.internal != null) "-internal=${toString cfg.internal}"} \ + ${optionalString (cfg.public != null) "-public=${toString cfg.public}"} \ + ${toString cfg.extraCmdLineOptions} + ''; + Restart = "on-failure"; + WorkingDirectory = cfg.dataDir; + LimitNOFILE = 65536; + }; + } + ) )) eachBlockbook; + + systemd.tmpfiles.rules = flatten (mapAttrsToList (blockbookName: cfg: [ + "d ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -" + "d ${cfg.dataDir}/static 0750 ${cfg.user} ${cfg.group} - -" + ]) eachBlockbook); + + users.users = mapAttrs' (blockbookName: cfg: ( + nameValuePair "blockbook-frontend-${blockbookName}" { + name = cfg.user; + group = cfg.group; + home = cfg.dataDir; + isSystemUser = true; + })) eachBlockbook; + + users.groups = mapAttrs' (instanceName: cfg: ( + nameValuePair "${cfg.group}" { })) eachBlockbook; + }; +}