1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 eachGeth = config.services.geth; 7 8 gethOpts = { config, lib, name, ...}: { 9 10 options = { 11 12 enable = lib.mkEnableOption "Go Ethereum Node"; 13 14 port = mkOption { 15 type = types.port; 16 default = 30303; 17 description = "Port number Go Ethereum will be listening on, both TCP and UDP."; 18 }; 19 20 http = { 21 enable = lib.mkEnableOption "Go Ethereum HTTP API"; 22 address = mkOption { 23 type = types.str; 24 default = "127.0.0.1"; 25 description = "Listen address of Go Ethereum HTTP API."; 26 }; 27 28 port = mkOption { 29 type = types.port; 30 default = 8545; 31 description = "Port number of Go Ethereum HTTP API."; 32 }; 33 34 apis = mkOption { 35 type = types.nullOr (types.listOf types.str); 36 default = null; 37 description = "APIs to enable over WebSocket"; 38 example = ["net" "eth"]; 39 }; 40 }; 41 42 websocket = { 43 enable = lib.mkEnableOption "Go Ethereum WebSocket API"; 44 address = mkOption { 45 type = types.str; 46 default = "127.0.0.1"; 47 description = "Listen address of Go Ethereum WebSocket API."; 48 }; 49 50 port = mkOption { 51 type = types.port; 52 default = 8546; 53 description = "Port number of Go Ethereum WebSocket API."; 54 }; 55 56 apis = mkOption { 57 type = types.nullOr (types.listOf types.str); 58 default = null; 59 description = "APIs to enable over WebSocket"; 60 example = ["net" "eth"]; 61 }; 62 }; 63 64 metrics = { 65 enable = lib.mkEnableOption "Go Ethereum prometheus metrics"; 66 address = mkOption { 67 type = types.str; 68 default = "127.0.0.1"; 69 description = "Listen address of Go Ethereum metrics service."; 70 }; 71 72 port = mkOption { 73 type = types.port; 74 default = 6060; 75 description = "Port number of Go Ethereum metrics service."; 76 }; 77 }; 78 79 network = mkOption { 80 type = types.nullOr (types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]); 81 default = null; 82 description = "The network to connect to. Mainnet (null) is the default ethereum network."; 83 }; 84 85 syncmode = mkOption { 86 type = types.enum [ "fast" "full" "light" ]; 87 default = "fast"; 88 description = "Blockchain sync mode."; 89 }; 90 91 gcmode = mkOption { 92 type = types.enum [ "full" "archive" ]; 93 default = "full"; 94 description = "Blockchain garbage collection mode."; 95 }; 96 97 maxpeers = mkOption { 98 type = types.int; 99 default = 50; 100 description = "Maximum peers to connect to."; 101 }; 102 103 extraArgs = mkOption { 104 type = types.listOf types.str; 105 description = "Additional arguments passed to Go Ethereum."; 106 default = []; 107 }; 108 109 package = mkOption { 110 default = pkgs.go-ethereum.geth; 111 type = types.package; 112 description = "Package to use as Go Ethereum node."; 113 }; 114 }; 115 }; 116in 117 118{ 119 120 ###### interface 121 122 options = { 123 services.geth = mkOption { 124 type = types.attrsOf (types.submodule gethOpts); 125 default = {}; 126 description = "Specification of one or more geth instances."; 127 }; 128 }; 129 130 ###### implementation 131 132 config = mkIf (eachGeth != {}) { 133 134 environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [ 135 cfg.package 136 ]) eachGeth); 137 138 systemd.services = mapAttrs' (gethName: cfg: ( 139 nameValuePair "geth-${gethName}" (mkIf cfg.enable { 140 description = "Go Ethereum node (${gethName})"; 141 wantedBy = [ "multi-user.target" ]; 142 after = [ "network.target" ]; 143 144 serviceConfig = { 145 DynamicUser = true; 146 Restart = "always"; 147 StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}"; 148 149 # Hardening measures 150 PrivateTmp = "true"; 151 ProtectSystem = "full"; 152 NoNewPrivileges = "true"; 153 PrivateDevices = "true"; 154 MemoryDenyWriteExecute = "true"; 155 }; 156 157 script = '' 158 ${cfg.package}/bin/geth \ 159 --nousb \ 160 --ipcdisable \ 161 ${optionalString (cfg.network != null) ''--${cfg.network}''} \ 162 --syncmode ${cfg.syncmode} \ 163 --gcmode ${cfg.gcmode} \ 164 --port ${toString cfg.port} \ 165 --maxpeers ${toString cfg.maxpeers} \ 166 ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \ 167 ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \ 168 ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \ 169 ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \ 170 ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \ 171 ${lib.escapeShellArgs cfg.extraArgs} \ 172 --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network} 173 ''; 174 }))) eachGeth; 175 176 }; 177 178}