wizard.go 9.0 KB

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