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