github.com/tyler-smith/go-ethereum@v1.9.7/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 "bufio" 21 "encoding/json" 22 "fmt" 23 "io/ioutil" 24 "math/big" 25 "net" 26 "net/url" 27 "os" 28 "path/filepath" 29 "sort" 30 "strconv" 31 "strings" 32 "sync" 33 34 "github.com/ethereum/go-ethereum/common" 35 "github.com/ethereum/go-ethereum/core" 36 "github.com/ethereum/go-ethereum/log" 37 "golang.org/x/crypto/ssh/terminal" 38 ) 39 40 // config contains all the configurations needed by puppeth that should be saved 41 // between sessions. 42 type config struct { 43 path string // File containing the configuration values 44 bootnodes []string // Bootnodes to always connect to by all nodes 45 ethstats string // Ethstats settings to cache for node deploys 46 47 Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys 48 Servers map[string][]byte `json:"servers,omitempty"` 49 } 50 51 // servers retrieves an alphabetically sorted list of servers. 52 func (c config) servers() []string { 53 servers := make([]string, 0, len(c.Servers)) 54 for server := range c.Servers { 55 servers = append(servers, server) 56 } 57 sort.Strings(servers) 58 59 return servers 60 } 61 62 // flush dumps the contents of config to disk. 63 func (c config) flush() { 64 os.MkdirAll(filepath.Dir(c.path), 0755) 65 66 out, _ := json.MarshalIndent(c, "", " ") 67 if err := ioutil.WriteFile(c.path, out, 0644); err != nil { 68 log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) 69 } 70 } 71 72 type wizard struct { 73 network string // Network name to manage 74 conf config // Configurations from previous runs 75 76 servers map[string]*sshClient // SSH connections to servers to administer 77 services map[string][]string // Ethereum services known to be running on servers 78 79 in *bufio.Reader // Wrapper around stdin to allow reading user input 80 lock sync.Mutex // Lock to protect configs during concurrent service discovery 81 } 82 83 // read reads a single line from stdin, trimming if from spaces. 84 func (w *wizard) read() string { 85 fmt.Printf("> ") 86 text, err := w.in.ReadString('\n') 87 if err != nil { 88 log.Crit("Failed to read user input", "err", err) 89 } 90 return strings.TrimSpace(text) 91 } 92 93 // readString reads a single line from stdin, trimming if from spaces, enforcing 94 // non-emptyness. 95 func (w *wizard) readString() string { 96 for { 97 fmt.Printf("> ") 98 text, err := w.in.ReadString('\n') 99 if err != nil { 100 log.Crit("Failed to read user input", "err", err) 101 } 102 if text = strings.TrimSpace(text); text != "" { 103 return text 104 } 105 } 106 } 107 108 // readDefaultString reads a single line from stdin, trimming if from spaces. If 109 // an empty line is entered, the default value is returned. 110 func (w *wizard) readDefaultString(def string) string { 111 fmt.Printf("> ") 112 text, err := w.in.ReadString('\n') 113 if err != nil { 114 log.Crit("Failed to read user input", "err", err) 115 } 116 if text = strings.TrimSpace(text); text != "" { 117 return text 118 } 119 return def 120 } 121 122 // readDefaultYesNo reads a single line from stdin, trimming if from spaces and 123 // interpreting it as a 'yes' or a 'no'. If an empty line is entered, the default 124 // value is returned. 125 func (w *wizard) readDefaultYesNo(def bool) bool { 126 for { 127 fmt.Printf("> ") 128 text, err := w.in.ReadString('\n') 129 if err != nil { 130 log.Crit("Failed to read user input", "err", err) 131 } 132 if text = strings.ToLower(strings.TrimSpace(text)); text == "" { 133 return def 134 } 135 if text == "y" || text == "yes" { 136 return true 137 } 138 if text == "n" || text == "no" { 139 return false 140 } 141 log.Error("Invalid input, expected 'y', 'yes', 'n', 'no' or empty") 142 } 143 } 144 145 // readURL reads a single line from stdin, trimming if from spaces and trying to 146 // interpret it as a URL (http, https or file). 147 func (w *wizard) readURL() *url.URL { 148 for { 149 fmt.Printf("> ") 150 text, err := w.in.ReadString('\n') 151 if err != nil { 152 log.Crit("Failed to read user input", "err", err) 153 } 154 uri, err := url.Parse(strings.TrimSpace(text)) 155 if err != nil { 156 log.Error("Invalid input, expected URL", "err", err) 157 continue 158 } 159 return uri 160 } 161 } 162 163 // readInt reads a single line from stdin, trimming if from spaces, enforcing it 164 // to parse into an integer. 165 func (w *wizard) readInt() int { 166 for { 167 fmt.Printf("> ") 168 text, err := w.in.ReadString('\n') 169 if err != nil { 170 log.Crit("Failed to read user input", "err", err) 171 } 172 if text = strings.TrimSpace(text); text == "" { 173 continue 174 } 175 val, err := strconv.Atoi(strings.TrimSpace(text)) 176 if err != nil { 177 log.Error("Invalid input, expected integer", "err", err) 178 continue 179 } 180 return val 181 } 182 } 183 184 // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing 185 // it to parse into an integer. If an empty line is entered, the default value is 186 // returned. 187 func (w *wizard) readDefaultInt(def int) int { 188 for { 189 fmt.Printf("> ") 190 text, err := w.in.ReadString('\n') 191 if err != nil { 192 log.Crit("Failed to read user input", "err", err) 193 } 194 if text = strings.TrimSpace(text); text == "" { 195 return def 196 } 197 val, err := strconv.Atoi(strings.TrimSpace(text)) 198 if err != nil { 199 log.Error("Invalid input, expected integer", "err", err) 200 continue 201 } 202 return val 203 } 204 } 205 206 // readDefaultBigInt reads a single line from stdin, trimming if from spaces, 207 // enforcing it to parse into a big integer. If an empty line is entered, the 208 // default value is returned. 209 func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int { 210 for { 211 fmt.Printf("> ") 212 text, err := w.in.ReadString('\n') 213 if err != nil { 214 log.Crit("Failed to read user input", "err", err) 215 } 216 if text = strings.TrimSpace(text); text == "" { 217 return def 218 } 219 val, ok := new(big.Int).SetString(text, 0) 220 if !ok { 221 log.Error("Invalid input, expected big integer") 222 continue 223 } 224 return val 225 } 226 } 227 228 /* 229 // readFloat reads a single line from stdin, trimming if from spaces, enforcing it 230 // to parse into a float. 231 func (w *wizard) readFloat() float64 { 232 for { 233 fmt.Printf("> ") 234 text, err := w.in.ReadString('\n') 235 if err != nil { 236 log.Crit("Failed to read user input", "err", err) 237 } 238 if text = strings.TrimSpace(text); text == "" { 239 continue 240 } 241 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 242 if err != nil { 243 log.Error("Invalid input, expected float", "err", err) 244 continue 245 } 246 return val 247 } 248 } 249 */ 250 251 // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing 252 // it to parse into a float. If an empty line is entered, the default value is returned. 253 func (w *wizard) readDefaultFloat(def float64) float64 { 254 for { 255 fmt.Printf("> ") 256 text, err := w.in.ReadString('\n') 257 if err != nil { 258 log.Crit("Failed to read user input", "err", err) 259 } 260 if text = strings.TrimSpace(text); text == "" { 261 return def 262 } 263 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 264 if err != nil { 265 log.Error("Invalid input, expected float", "err", err) 266 continue 267 } 268 return val 269 } 270 } 271 272 // readPassword reads a single line from stdin, trimming it from the trailing new 273 // line and returns it. The input will not be echoed. 274 func (w *wizard) readPassword() string { 275 fmt.Printf("> ") 276 text, err := terminal.ReadPassword(int(os.Stdin.Fd())) 277 if err != nil { 278 log.Crit("Failed to read password", "err", err) 279 } 280 fmt.Println() 281 return string(text) 282 } 283 284 // readAddress reads a single line from stdin, trimming if from spaces and converts 285 // it to an Ethereum address. 286 func (w *wizard) readAddress() *common.Address { 287 for { 288 // Read the address from the user 289 fmt.Printf("> 0x") 290 text, err := w.in.ReadString('\n') 291 if err != nil { 292 log.Crit("Failed to read user input", "err", err) 293 } 294 if text = strings.TrimSpace(text); text == "" { 295 return nil 296 } 297 // Make sure it looks ok and return it if so 298 if len(text) != 40 { 299 log.Error("Invalid address length, please retry") 300 continue 301 } 302 bigaddr, _ := new(big.Int).SetString(text, 16) 303 address := common.BigToAddress(bigaddr) 304 return &address 305 } 306 } 307 308 // readDefaultAddress reads a single line from stdin, trimming if from spaces and 309 // converts it to an Ethereum address. If an empty line is entered, the default 310 // value is returned. 311 func (w *wizard) readDefaultAddress(def common.Address) common.Address { 312 for { 313 // Read the address from the user 314 fmt.Printf("> 0x") 315 text, err := w.in.ReadString('\n') 316 if err != nil { 317 log.Crit("Failed to read user input", "err", err) 318 } 319 if text = strings.TrimSpace(text); text == "" { 320 return def 321 } 322 // Make sure it looks ok and return it if so 323 if len(text) != 40 { 324 log.Error("Invalid address length, please retry") 325 continue 326 } 327 bigaddr, _ := new(big.Int).SetString(text, 16) 328 return common.BigToAddress(bigaddr) 329 } 330 } 331 332 // readJSON reads a raw JSON message and returns it. 333 func (w *wizard) readJSON() string { 334 var blob json.RawMessage 335 336 for { 337 fmt.Printf("> ") 338 if err := json.NewDecoder(w.in).Decode(&blob); err != nil { 339 log.Error("Invalid JSON, please try again", "err", err) 340 continue 341 } 342 return string(blob) 343 } 344 } 345 346 // readIPAddress reads a single line from stdin, trimming if from spaces and 347 // returning it if it's convertible to an IP address. The reason for keeping 348 // the user input format instead of returning a Go net.IP is to match with 349 // weird formats used by ethstats, which compares IPs textually, not by value. 350 func (w *wizard) readIPAddress() string { 351 for { 352 // Read the IP address from the user 353 fmt.Printf("> ") 354 text, err := w.in.ReadString('\n') 355 if err != nil { 356 log.Crit("Failed to read user input", "err", err) 357 } 358 if text = strings.TrimSpace(text); text == "" { 359 return "" 360 } 361 // Make sure it looks ok and return it if so 362 if ip := net.ParseIP(text); ip == nil { 363 log.Error("Invalid IP address, please retry") 364 continue 365 } 366 return text 367 } 368 }