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  }