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