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 [ "snap" "fast" "full" "light" ]; 87 default = "snap"; 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 defaultText = literalExpression "pkgs.go-ethereum.geth"; 112 type = types.package; 113 description = "Package to use as Go Ethereum node."; 114 }; 115 }; 116 }; 117in 118 119{ 120 121 ###### interface 122 123 options = { 124 services.geth = mkOption { 125 type = types.attrsOf (types.submodule gethOpts); 126 default = {}; 127 description = "Specification of one or more geth instances."; 128 }; 129 }; 130 131 ###### implementation 132 133 config = mkIf (eachGeth != {}) { 134 135 environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [ 136 cfg.package 137 ]) eachGeth); 138 139 systemd.services = mapAttrs' (gethName: cfg: ( 140 nameValuePair "geth-${gethName}" (mkIf cfg.enable { 141 description = "Go Ethereum node (${gethName})"; 142 wantedBy = [ "multi-user.target" ]; 143 after = [ "network.target" ]; 144 145 serviceConfig = { 146 DynamicUser = true; 147 Restart = "always"; 148 StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}"; 149 150 # Hardening measures 151 PrivateTmp = "true"; 152 ProtectSystem = "full"; 153 NoNewPrivileges = "true"; 154 PrivateDevices = "true"; 155 MemoryDenyWriteExecute = "true"; 156 }; 157 158 script = '' 159 ${cfg.package}/bin/geth \ 160 --nousb \ 161 --ipcdisable \ 162 ${optionalString (cfg.network != null) ''--${cfg.network}''} \ 163 --syncmode ${cfg.syncmode} \ 164 --gcmode ${cfg.gcmode} \ 165 --port ${toString cfg.port} \ 166 --maxpeers ${toString cfg.maxpeers} \ 167 ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \ 168 ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \ 169 ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \ 170 ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \ 171 ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \ 172 ${lib.escapeShellArgs cfg.extraArgs} \ 173 --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network} 174 ''; 175 }))) eachGeth; 176 177 }; 178 179}