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