github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/cmd/puppeth/wizard.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of Spectrum. 3 // 4 // Spectrum 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 // Spectrum 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 Spectrum. 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 "os" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 "sync" 32 33 "github.com/SmartMeshFoundation/Spectrum/common" 34 "github.com/SmartMeshFoundation/Spectrum/core" 35 "github.com/SmartMeshFoundation/Spectrum/log" 36 "golang.org/x/crypto/ssh/terminal" 37 ) 38 39 // config contains all the configurations needed by puppeth that should be saved 40 // between sessions. 41 type config struct { 42 path string // File containing the configuration values 43 bootFull []string // Bootnodes to always connect to by full nodes 44 bootLight []string // Bootnodes to always connect to by light 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 // readInt reads a single line from stdin, trimming if from spaces, enforcing it 123 // to parse into an integer. 124 func (w *wizard) readInt() int { 125 for { 126 fmt.Printf("> ") 127 text, err := w.in.ReadString('\n') 128 if err != nil { 129 log.Crit("Failed to read user input", "err", err) 130 } 131 if text = strings.TrimSpace(text); text == "" { 132 continue 133 } 134 val, err := strconv.Atoi(strings.TrimSpace(text)) 135 if err != nil { 136 log.Error("Invalid input, expected integer", "err", err) 137 continue 138 } 139 return val 140 } 141 } 142 143 // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing 144 // it to parse into an integer. If an empty line is entered, the default value is 145 // returned. 146 func (w *wizard) readDefaultInt(def int) int { 147 for { 148 fmt.Printf("> ") 149 text, err := w.in.ReadString('\n') 150 if err != nil { 151 log.Crit("Failed to read user input", "err", err) 152 } 153 if text = strings.TrimSpace(text); text == "" { 154 return def 155 } 156 val, err := strconv.Atoi(strings.TrimSpace(text)) 157 if err != nil { 158 log.Error("Invalid input, expected integer", "err", err) 159 continue 160 } 161 return val 162 } 163 } 164 165 // readDefaultBigInt reads a single line from stdin, trimming if from spaces, 166 // enforcing it to parse into a big integer. If an empty line is entered, the 167 // default value is returned. 168 func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int { 169 for { 170 fmt.Printf("> ") 171 text, err := w.in.ReadString('\n') 172 if err != nil { 173 log.Crit("Failed to read user input", "err", err) 174 } 175 if text = strings.TrimSpace(text); text == "" { 176 return def 177 } 178 val, ok := new(big.Int).SetString(text, 0) 179 if !ok { 180 log.Error("Invalid input, expected big integer") 181 continue 182 } 183 return val 184 } 185 } 186 187 /* 188 // readFloat reads a single line from stdin, trimming if from spaces, enforcing it 189 // to parse into a float. 190 func (w *wizard) readFloat() float64 { 191 for { 192 fmt.Printf("> ") 193 text, err := w.in.ReadString('\n') 194 if err != nil { 195 log.Crit("Failed to read user input", "err", err) 196 } 197 if text = strings.TrimSpace(text); text == "" { 198 continue 199 } 200 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 201 if err != nil { 202 log.Error("Invalid input, expected float", "err", err) 203 continue 204 } 205 return val 206 } 207 } 208 */ 209 210 // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing 211 // it to parse into a float. If an empty line is entered, the default value is returned. 212 func (w *wizard) readDefaultFloat(def float64) float64 { 213 for { 214 fmt.Printf("> ") 215 text, err := w.in.ReadString('\n') 216 if err != nil { 217 log.Crit("Failed to read user input", "err", err) 218 } 219 if text = strings.TrimSpace(text); text == "" { 220 return def 221 } 222 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 223 if err != nil { 224 log.Error("Invalid input, expected float", "err", err) 225 continue 226 } 227 return val 228 } 229 } 230 231 // readPassword reads a single line from stdin, trimming it from the trailing new 232 // line and returns it. The input will not be echoed. 233 func (w *wizard) readPassword() string { 234 fmt.Printf("> ") 235 text, err := terminal.ReadPassword(int(os.Stdin.Fd())) 236 if err != nil { 237 log.Crit("Failed to read password", "err", err) 238 } 239 fmt.Println() 240 return string(text) 241 } 242 243 // readAddress reads a single line from stdin, trimming if from spaces and converts 244 // it to an Ethereum address. 245 func (w *wizard) readAddress() *common.Address { 246 for { 247 // Read the address from the user 248 fmt.Printf("> 0x") 249 text, err := w.in.ReadString('\n') 250 if err != nil { 251 log.Crit("Failed to read user input", "err", err) 252 } 253 if text = strings.TrimSpace(text); text == "" { 254 return nil 255 } 256 // Make sure it looks ok and return it if so 257 if len(text) != 40 { 258 log.Error("Invalid address length, please retry") 259 continue 260 } 261 bigaddr, _ := new(big.Int).SetString(text, 16) 262 address := common.BigToAddress(bigaddr) 263 return &address 264 } 265 } 266 267 // readDefaultAddress reads a single line from stdin, trimming if from spaces and 268 // converts it to an Ethereum address. If an empty line is entered, the default 269 // value is returned. 270 func (w *wizard) readDefaultAddress(def common.Address) common.Address { 271 for { 272 // Read the address from the user 273 fmt.Printf("> 0x") 274 text, err := w.in.ReadString('\n') 275 if err != nil { 276 log.Crit("Failed to read user input", "err", err) 277 } 278 if text = strings.TrimSpace(text); text == "" { 279 return def 280 } 281 // Make sure it looks ok and return it if so 282 if len(text) != 40 { 283 log.Error("Invalid address length, please retry") 284 continue 285 } 286 bigaddr, _ := new(big.Int).SetString(text, 16) 287 return common.BigToAddress(bigaddr) 288 } 289 } 290 291 // readJSON reads a raw JSON message and returns it. 292 func (w *wizard) readJSON() string { 293 var blob json.RawMessage 294 295 for { 296 fmt.Printf("> ") 297 if err := json.NewDecoder(w.in).Decode(&blob); err != nil { 298 log.Error("Invalid JSON, please try again", "err", err) 299 continue 300 } 301 return string(blob) 302 } 303 } 304 305 // readIPAddress reads a single line from stdin, trimming if from spaces and 306 // returning it if it's convertible to an IP address. The reason for keeping 307 // the user input format instead of returning a Go net.IP is to match with 308 // weird formats used by ethstats, which compares IPs textually, not by value. 309 func (w *wizard) readIPAddress() string { 310 for { 311 // Read the IP address from the user 312 fmt.Printf("> ") 313 text, err := w.in.ReadString('\n') 314 if err != nil { 315 log.Crit("Failed to read user input", "err", err) 316 } 317 if text = strings.TrimSpace(text); text == "" { 318 return "" 319 } 320 // Make sure it looks ok and return it if so 321 if ip := net.ParseIP(text); ip == nil { 322 log.Error("Invalid IP address, please retry") 323 continue 324 } 325 return text 326 } 327 }