wizard.go 10 KB

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