github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/cmd/puppeth/wizard.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "math/big" 24 "net" 25 "net/url" 26 "os" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 "sync" 32 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/console/prompt" 35 "github.com/ethereum/go-ethereum/core" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/peterh/liner" 38 "golang.org/x/crypto/ssh/terminal" 39 ) 40 41 // config contains all the configurations needed by puppeth that should be saved 42 // between sessions. 43 type config struct { 44 path string // File containing the configuration values 45 bootnodes []string // Bootnodes to always connect to by all nodes 46 ethstats string // Ethstats settings to cache for node deploys 47 48 Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys 49 Servers map[string][]byte `json:"servers,omitempty"` 50 } 51 52 // servers retrieves an alphabetically sorted list of servers. 53 func (c config) servers() []string { 54 servers := make([]string, 0, len(c.Servers)) 55 for server := range c.Servers { 56 servers = append(servers, server) 57 } 58 sort.Strings(servers) 59 60 return servers 61 } 62 63 // flush dumps the contents of config to disk. 64 func (c config) flush() { 65 os.MkdirAll(filepath.Dir(c.path), 0755) 66 67 out, _ := json.MarshalIndent(c, "", " ") 68 if err := ioutil.WriteFile(c.path, out, 0644); err != nil { 69 log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) 70 } 71 } 72 73 type wizard struct { 74 network string // Network name to manage 75 conf config // Configurations from previous runs 76 77 servers map[string]*sshClient // SSH connections to servers to administer 78 services map[string][]string // Ethereum services known to be running on servers 79 80 lock sync.Mutex // Lock to protect configs during concurrent service discovery 81 } 82 83 // prompts the user for input with the given prompt string. Returns when a value is entered. 84 // Causes the wizard to exit if ctrl-d is pressed 85 func promptInput(p string) string { 86 for { 87 text, err := prompt.Stdin.PromptInput(p) 88 if err != nil { 89 if err != liner.ErrPromptAborted { 90 log.Crit("Failed to read user input", "err", err) 91 } 92 } else { 93 return text 94 } 95 } 96 } 97 98 // read reads a single line from stdin, trimming if from spaces. 99 func (w *wizard) read() string { 100 text := promptInput("> ") 101 return strings.TrimSpace(text) 102 } 103 104 // readString reads a single line from stdin, trimming if from spaces, enforcing 105 // non-emptyness. 106 func (w *wizard) readString() string { 107 for { 108 text := promptInput("> ") 109 if text = strings.TrimSpace(text); text != "" { 110 return text 111 } 112 } 113 } 114 115 // readDefaultString reads a single line from stdin, trimming if from spaces. If 116 // an empty line is entered, the default value is returned. 117 func (w *wizard) readDefaultString(def string) string { 118 text := promptInput("> ") 119 if text = strings.TrimSpace(text); text != "" { 120 return text 121 } 122 return def 123 } 124 125 // readDefaultYesNo reads a single line from stdin, trimming if from spaces and 126 // interpreting it as a 'yes' or a 'no'. If an empty line is entered, the default 127 // value is returned. 128 func (w *wizard) readDefaultYesNo(def bool) bool { 129 for { 130 text := promptInput("> ") 131 if text = strings.ToLower(strings.TrimSpace(text)); text == "" { 132 return def 133 } 134 if text == "y" || text == "yes" { 135 return true 136 } 137 if text == "n" || text == "no" { 138 return false 139 } 140 log.Error("Invalid input, expected 'y', 'yes', 'n', 'no' or empty") 141 } 142 } 143 144 // readURL reads a single line from stdin, trimming if from spaces and trying to 145 // interpret it as a URL (http, https or file). 146 func (w *wizard) readURL() *url.URL { 147 for { 148 text := promptInput("> ") 149 uri, err := url.Parse(strings.TrimSpace(text)) 150 if err != nil { 151 log.Error("Invalid input, expected URL", "err", err) 152 continue 153 } 154 return uri 155 } 156 } 157 158 // readInt reads a single line from stdin, trimming if from spaces, enforcing it 159 // to parse into an integer. 160 func (w *wizard) readInt() int { 161 for { 162 text := promptInput("> ") 163 if text = strings.TrimSpace(text); text == "" { 164 continue 165 } 166 val, err := strconv.Atoi(strings.TrimSpace(text)) 167 if err != nil { 168 log.Error("Invalid input, expected integer", "err", err) 169 continue 170 } 171 return val 172 } 173 } 174 175 // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing 176 // it to parse into an integer. If an empty line is entered, the default value is 177 // returned. 178 func (w *wizard) readDefaultInt(def int) int { 179 for { 180 text := promptInput("> ") 181 if text = strings.TrimSpace(text); text == "" { 182 return def 183 } 184 val, err := strconv.Atoi(strings.TrimSpace(text)) 185 if err != nil { 186 log.Error("Invalid input, expected integer", "err", err) 187 continue 188 } 189 return val 190 } 191 } 192 193 // readDefaultBigInt reads a single line from stdin, trimming if from spaces, 194 // enforcing it to parse into a big integer. If an empty line is entered, the 195 // default value is returned. 196 func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int { 197 for { 198 text := promptInput("> ") 199 if text = strings.TrimSpace(text); text == "" { 200 return def 201 } 202 val, ok := new(big.Int).SetString(text, 0) 203 if !ok { 204 log.Error("Invalid input, expected big integer") 205 continue 206 } 207 return val 208 } 209 } 210 211 // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing 212 // it to parse into a float. If an empty line is entered, the default value is returned. 213 func (w *wizard) readDefaultFloat(def float64) float64 { 214 for { 215 text := promptInput("> ") 216 if text = strings.TrimSpace(text); text == "" { 217 return def 218 } 219 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 220 if err != nil { 221 log.Error("Invalid input, expected float", "err", err) 222 continue 223 } 224 return val 225 } 226 } 227 228 // readPassword reads a single line from stdin, trimming it from the trailing new 229 // line and returns it. The input will not be echoed. 230 func (w *wizard) readPassword() string { 231 fmt.Printf("> ") 232 text, err := terminal.ReadPassword(int(os.Stdin.Fd())) 233 if err != nil { 234 log.Crit("Failed to read password", "err", err) 235 } 236 fmt.Println() 237 return string(text) 238 } 239 240 // readAddress reads a single line from stdin, trimming if from spaces and converts 241 // it to an Ethereum address. 242 func (w *wizard) readAddress() *common.Address { 243 for { 244 text := promptInput("> 0x") 245 if text = strings.TrimSpace(text); text == "" { 246 return nil 247 } 248 // Make sure it looks ok and return it if so 249 if len(text) != 40 { 250 log.Error("Invalid address length, please retry") 251 continue 252 } 253 bigaddr, _ := new(big.Int).SetString(text, 16) 254 address := common.BigToAddress(bigaddr) 255 return &address 256 } 257 } 258 259 // readDefaultAddress reads a single line from stdin, trimming if from spaces and 260 // converts it to an Ethereum address. If an empty line is entered, the default 261 // value is returned. 262 func (w *wizard) readDefaultAddress(def common.Address) common.Address { 263 for { 264 // Read the address from the user 265 text := promptInput("> 0x") 266 if text = strings.TrimSpace(text); text == "" { 267 return def 268 } 269 // Make sure it looks ok and return it if so 270 if len(text) != 40 { 271 log.Error("Invalid address length, please retry") 272 continue 273 } 274 bigaddr, _ := new(big.Int).SetString(text, 16) 275 return common.BigToAddress(bigaddr) 276 } 277 } 278 279 // readJSON reads a raw JSON message and returns it. 280 func (w *wizard) readJSON() string { 281 var blob json.RawMessage 282 283 for { 284 text := promptInput("> ") 285 reader := strings.NewReader(text) 286 if err := json.NewDecoder(reader).Decode(&blob); err != nil { 287 log.Error("Invalid JSON, please try again", "err", err) 288 continue 289 } 290 return string(blob) 291 } 292 } 293 294 // readIPAddress reads a single line from stdin, trimming if from spaces and 295 // returning it if it's convertible to an IP address. The reason for keeping 296 // the user input format instead of returning a Go net.IP is to match with 297 // weird formats used by ethstats, which compares IPs textually, not by value. 298 func (w *wizard) readIPAddress() string { 299 for { 300 // Read the IP address from the user 301 fmt.Printf("> ") 302 text := promptInput("> ") 303 if text = strings.TrimSpace(text); text == "" { 304 return "" 305 } 306 // Make sure it looks ok and return it if so 307 if ip := net.ParseIP(text); ip == nil { 308 log.Error("Invalid IP address, please retry") 309 continue 310 } 311 return text 312 } 313 }