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 }