wizard.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. "os"
  24. "path/filepath"
  25. "sort"
  26. "strconv"
  27. "strings"
  28. "syscall"
  29. "github.com/ethereum/go-ethereum/common"
  30. "github.com/ethereum/go-ethereum/core"
  31. "github.com/ethereum/go-ethereum/log"
  32. "golang.org/x/crypto/ssh/terminal"
  33. )
  34. // config contains all the configurations needed by puppeth that should be saved
  35. // between sessions.
  36. type config struct {
  37. path string // File containing the configuration values
  38. genesis *core.Genesis // Genesis block to cache for node deploys
  39. bootFull []string // Bootnodes to always connect to by full nodes
  40. bootLight []string // Bootnodes to always connect to by light nodes
  41. ethstats string // Ethstats settings 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. }
  68. // read reads a single line from stdin, trimming if from spaces.
  69. func (w *wizard) read() string {
  70. fmt.Printf("> ")
  71. text, err := w.in.ReadString('\n')
  72. if err != nil {
  73. log.Crit("Failed to read user input", "err", err)
  74. }
  75. return strings.TrimSpace(text)
  76. }
  77. // readString reads a single line from stdin, trimming if from spaces, enforcing
  78. // non-emptyness.
  79. func (w *wizard) readString() string {
  80. for {
  81. fmt.Printf("> ")
  82. text, err := w.in.ReadString('\n')
  83. if err != nil {
  84. log.Crit("Failed to read user input", "err", err)
  85. }
  86. if text = strings.TrimSpace(text); text != "" {
  87. return text
  88. }
  89. }
  90. }
  91. // readDefaultString reads a single line from stdin, trimming if from spaces. If
  92. // an empty line is entered, the default value is returned.
  93. func (w *wizard) readDefaultString(def string) string {
  94. fmt.Printf("> ")
  95. text, err := w.in.ReadString('\n')
  96. if err != nil {
  97. log.Crit("Failed to read user input", "err", err)
  98. }
  99. if text = strings.TrimSpace(text); text != "" {
  100. return text
  101. }
  102. return def
  103. }
  104. // readInt reads a single line from stdin, trimming if from spaces, enforcing it
  105. // to parse into an integer.
  106. func (w *wizard) readInt() int {
  107. for {
  108. fmt.Printf("> ")
  109. text, err := w.in.ReadString('\n')
  110. if err != nil {
  111. log.Crit("Failed to read user input", "err", err)
  112. }
  113. if text = strings.TrimSpace(text); text == "" {
  114. continue
  115. }
  116. val, err := strconv.Atoi(strings.TrimSpace(text))
  117. if err != nil {
  118. log.Error("Invalid input, expected integer", "err", err)
  119. continue
  120. }
  121. return val
  122. }
  123. }
  124. // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing
  125. // it to parse into an integer. If an empty line is entered, the default value is
  126. // returned.
  127. func (w *wizard) readDefaultInt(def int) int {
  128. for {
  129. fmt.Printf("> ")
  130. text, err := w.in.ReadString('\n')
  131. if err != nil {
  132. log.Crit("Failed to read user input", "err", err)
  133. }
  134. if text = strings.TrimSpace(text); text == "" {
  135. return def
  136. }
  137. val, err := strconv.Atoi(strings.TrimSpace(text))
  138. if err != nil {
  139. log.Error("Invalid input, expected integer", "err", err)
  140. continue
  141. }
  142. return val
  143. }
  144. }
  145. /*
  146. // readFloat reads a single line from stdin, trimming if from spaces, enforcing it
  147. // to parse into a float.
  148. func (w *wizard) readFloat() float64 {
  149. for {
  150. fmt.Printf("> ")
  151. text, err := w.in.ReadString('\n')
  152. if err != nil {
  153. log.Crit("Failed to read user input", "err", err)
  154. }
  155. if text = strings.TrimSpace(text); text == "" {
  156. continue
  157. }
  158. val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
  159. if err != nil {
  160. log.Error("Invalid input, expected float", "err", err)
  161. continue
  162. }
  163. return val
  164. }
  165. }
  166. */
  167. // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing
  168. // it to parse into a float. If an empty line is entered, the default value is returned.
  169. func (w *wizard) readDefaultFloat(def float64) float64 {
  170. for {
  171. fmt.Printf("> ")
  172. text, err := w.in.ReadString('\n')
  173. if err != nil {
  174. log.Crit("Failed to read user input", "err", err)
  175. }
  176. if text = strings.TrimSpace(text); text == "" {
  177. return def
  178. }
  179. val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
  180. if err != nil {
  181. log.Error("Invalid input, expected float", "err", err)
  182. continue
  183. }
  184. return val
  185. }
  186. }
  187. // readPassword reads a single line from stdin, trimming it from the trailing new
  188. // line and returns it. The input will not be echoed.
  189. func (w *wizard) readPassword() string {
  190. fmt.Printf("> ")
  191. text, err := terminal.ReadPassword(int(syscall.Stdin))
  192. if err != nil {
  193. log.Crit("Failed to read password", "err", err)
  194. }
  195. fmt.Println()
  196. return string(text)
  197. }
  198. // readAddress reads a single line from stdin, trimming if from spaces and converts
  199. // it to an Ethereum address.
  200. func (w *wizard) readAddress() *common.Address {
  201. for {
  202. // Read the address from the user
  203. fmt.Printf("> 0x")
  204. text, err := w.in.ReadString('\n')
  205. if err != nil {
  206. log.Crit("Failed to read user input", "err", err)
  207. }
  208. if text = strings.TrimSpace(text); text == "" {
  209. return nil
  210. }
  211. // Make sure it looks ok and return it if so
  212. if len(text) != 40 {
  213. log.Error("Invalid address length, please retry")
  214. continue
  215. }
  216. bigaddr, _ := new(big.Int).SetString(text, 16)
  217. address := common.BigToAddress(bigaddr)
  218. return &address
  219. }
  220. }
  221. // readDefaultAddress reads a single line from stdin, trimming if from spaces and
  222. // converts it to an Ethereum address. If an empty line is entered, the default
  223. // value is returned.
  224. func (w *wizard) readDefaultAddress(def common.Address) common.Address {
  225. for {
  226. // Read the address from the user
  227. fmt.Printf("> 0x")
  228. text, err := w.in.ReadString('\n')
  229. if err != nil {
  230. log.Crit("Failed to read user input", "err", err)
  231. }
  232. if text = strings.TrimSpace(text); text == "" {
  233. return def
  234. }
  235. // Make sure it looks ok and return it if so
  236. if len(text) != 40 {
  237. log.Error("Invalid address length, please retry")
  238. continue
  239. }
  240. bigaddr, _ := new(big.Int).SetString(text, 16)
  241. return common.BigToAddress(bigaddr)
  242. }
  243. }
  244. // readJSON reads a raw JSON message and returns it.
  245. func (w *wizard) readJSON() string {
  246. var blob json.RawMessage
  247. for {
  248. fmt.Printf("> ")
  249. if err := json.NewDecoder(w.in).Decode(&blob); err != nil {
  250. log.Error("Invalid JSON, please try again", "err", err)
  251. continue
  252. }
  253. return string(blob)
  254. }
  255. }