github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/cmd/puppeth/module_wallet.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  package main
    13  
    14  import (
    15  	"bytes"
    16  	"fmt"
    17  	"html/template"
    18  	"math/rand"
    19  	"path/filepath"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/Sberex/go-sberex/log"
    24  )
    25  
    26  // walletDockerfile is the Dockerfile required to run a web wallet.
    27  var walletDockerfile = `
    28  FROM puppeth/wallet:latest
    29  
    30  ADD genesis.json /genesis.json
    31  
    32  RUN \
    33    echo 'node server.js &'                     > wallet.sh && \
    34  	echo 'geth --cache 512 init /genesis.json' >> wallet.sh && \
    35  	echo $'geth --networkid {{.NetworkID}} --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcaddr=0.0.0.0 --rpccorsdomain "*"' >> wallet.sh
    36  
    37  RUN \
    38  	sed -i 's/PuppethNetworkID/{{.NetworkID}}/g' dist/js/etherwallet-master.js && \
    39  	sed -i 's/PuppethNetwork/{{.Network}}/g'     dist/js/etherwallet-master.js && \
    40  	sed -i 's/PuppethDenom/{{.Denom}}/g'         dist/js/etherwallet-master.js && \
    41  	sed -i 's/PuppethHost/{{.Host}}/g'           dist/js/etherwallet-master.js && \
    42  	sed -i 's/PuppethRPCPort/{{.RPCPort}}/g'     dist/js/etherwallet-master.js
    43  
    44  ENTRYPOINT ["/bin/sh", "wallet.sh"]
    45  `
    46  
    47  // walletComposefile is the docker-compose.yml file required to deploy and
    48  // maintain a web wallet.
    49  var walletComposefile = `
    50  version: '2'
    51  services:
    52    wallet:
    53      build: .
    54      image: {{.Network}}/wallet
    55      ports:
    56        - "{{.NodePort}}:{{.NodePort}}"
    57        - "{{.NodePort}}:{{.NodePort}}/udp"
    58        - "{{.RPCPort}}:8545"{{if not .VHost}}
    59        - "{{.WebPort}}:80"{{end}}
    60      volumes:
    61        - {{.Datadir}}:/root/.sberex
    62      environment:
    63        - NODE_PORT={{.NodePort}}/tcp
    64        - STATS={{.Ethstats}}{{if .VHost}}
    65        - VIRTUAL_HOST={{.VHost}}
    66        - VIRTUAL_PORT=80{{end}}
    67      logging:
    68        driver: "json-file"
    69        options:
    70          max-size: "1m"
    71          max-file: "10"
    72      restart: always
    73  `
    74  
    75  // deployWallet deploys a new web wallet container to a remote machine via SSH,
    76  // docker and docker-compose. If an instance with the specified network name
    77  // already exists there, it will be overwritten!
    78  func deployWallet(client *sshClient, network string, bootnodes []string, config *walletInfos, nocache bool) ([]byte, error) {
    79  	// Generate the content to upload to the server
    80  	workdir := fmt.Sprintf("%d", rand.Int63())
    81  	files := make(map[string][]byte)
    82  
    83  	dockerfile := new(bytes.Buffer)
    84  	template.Must(template.New("").Parse(walletDockerfile)).Execute(dockerfile, map[string]interface{}{
    85  		"Network":   strings.ToTitle(network),
    86  		"Denom":     strings.ToUpper(network),
    87  		"NetworkID": config.network,
    88  		"NodePort":  config.nodePort,
    89  		"RPCPort":   config.rpcPort,
    90  		"Bootnodes": strings.Join(bootnodes, ","),
    91  		"Ethstats":  config.ethstats,
    92  		"Host":      client.address,
    93  	})
    94  	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
    95  
    96  	composefile := new(bytes.Buffer)
    97  	template.Must(template.New("").Parse(walletComposefile)).Execute(composefile, map[string]interface{}{
    98  		"Datadir":  config.datadir,
    99  		"Network":  network,
   100  		"NodePort": config.nodePort,
   101  		"RPCPort":  config.rpcPort,
   102  		"VHost":    config.webHost,
   103  		"WebPort":  config.webPort,
   104  		"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
   105  	})
   106  	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
   107  
   108  	files[filepath.Join(workdir, "genesis.json")] = config.genesis
   109  
   110  	// Upload the deployment files to the remote server (and clean up afterwards)
   111  	if out, err := client.Upload(files); err != nil {
   112  		return out, err
   113  	}
   114  	defer client.Run("rm -rf " + workdir)
   115  
   116  	// Build and deploy the boot or seal node service
   117  	if nocache {
   118  		return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
   119  	}
   120  	return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
   121  }
   122  
   123  // walletInfos is returned from a web wallet status check to allow reporting
   124  // various configuration parameters.
   125  type walletInfos struct {
   126  	genesis  []byte
   127  	network  int64
   128  	datadir  string
   129  	ethstats string
   130  	nodePort int
   131  	rpcPort  int
   132  	webHost  string
   133  	webPort  int
   134  }
   135  
   136  // Report converts the typed struct into a plain string->string map, containing
   137  // most - but not all - fields for reporting to the user.
   138  func (info *walletInfos) Report() map[string]string {
   139  	report := map[string]string{
   140  		"Data directory":         info.datadir,
   141  		"Ethstats username":      info.ethstats,
   142  		"Node listener port ":    strconv.Itoa(info.nodePort),
   143  		"RPC listener port ":     strconv.Itoa(info.rpcPort),
   144  		"Website address ":       info.webHost,
   145  		"Website listener port ": strconv.Itoa(info.webPort),
   146  	}
   147  	return report
   148  }
   149  
   150  // checkWallet does a health-check against web wallet server to verify whether
   151  // it's running, and if yes, whether it's responsive.
   152  func checkWallet(client *sshClient, network string) (*walletInfos, error) {
   153  	// Inspect a possible web wallet container on the host
   154  	infos, err := inspectContainer(client, fmt.Sprintf("%s_wallet_1", network))
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	if !infos.running {
   159  		return nil, ErrServiceOffline
   160  	}
   161  	// Resolve the port from the host, or the reverse proxy
   162  	webPort := infos.portmap["80/tcp"]
   163  	if webPort == 0 {
   164  		if proxy, _ := checkNginx(client, network); proxy != nil {
   165  			webPort = proxy.port
   166  		}
   167  	}
   168  	if webPort == 0 {
   169  		return nil, ErrNotExposed
   170  	}
   171  	// Resolve the host from the reverse-proxy and the config values
   172  	host := infos.envvars["VIRTUAL_HOST"]
   173  	if host == "" {
   174  		host = client.server
   175  	}
   176  	// Run a sanity check to see if the devp2p and RPC ports are reachable
   177  	nodePort := infos.portmap[infos.envvars["NODE_PORT"]]
   178  	if err = checkPort(client.server, nodePort); err != nil {
   179  		log.Warn(fmt.Sprintf("Wallet devp2p port seems unreachable"), "server", client.server, "port", nodePort, "err", err)
   180  	}
   181  	rpcPort := infos.portmap["8545/tcp"]
   182  	if err = checkPort(client.server, rpcPort); err != nil {
   183  		log.Warn(fmt.Sprintf("Wallet RPC port seems unreachable"), "server", client.server, "port", rpcPort, "err", err)
   184  	}
   185  	// Assemble and return the useful infos
   186  	stats := &walletInfos{
   187  		datadir:  infos.volumes["/root/.sberex"],
   188  		nodePort: nodePort,
   189  		rpcPort:  rpcPort,
   190  		webHost:  host,
   191  		webPort:  webPort,
   192  		ethstats: infos.envvars["STATS"],
   193  	}
   194  	return stats, nil
   195  }