github.com/pixichain/go-pixicoin@v0.0.0-20220708132717-27ba739265ff/cmd/puppeth/module_faucet.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 "encoding/json" 22 "fmt" 23 "html/template" 24 "math/rand" 25 "path/filepath" 26 "strconv" 27 "strings" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/log" 31 ) 32 33 // faucetDockerfile is the Dockerfile required to build an faucet container to 34 // grant crypto tokens based on GitHub authentications. 35 var faucetDockerfile = ` 36 FROM ethereum/client-go:alltools-latest 37 38 ADD genesis.json /genesis.json 39 ADD account.json /account.json 40 ADD account.pass /account.pass 41 42 ENTRYPOINT [ \ 43 "faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \ 44 "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ 45 "--account.json", "/account.json", "--account.pass", "/account.pass" \ 46 {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ 47 ]` 48 49 // faucetComposefile is the docker-compose.yml file required to deploy and maintain 50 // a crypto faucet. 51 var faucetComposefile = ` 52 version: '2' 53 services: 54 faucet: 55 build: . 56 image: {{.Network}}/faucet 57 ports: 58 - "{{.EthPort}}:{{.EthPort}}"{{if not .VHost}} 59 - "{{.ApiPort}}:8080"{{end}} 60 volumes: 61 - {{.Datadir}}:/root/.faucet 62 environment: 63 - ETH_PORT={{.EthPort}} 64 - ETH_NAME={{.EthName}} 65 - FAUCET_AMOUNT={{.FaucetAmount}} 66 - FAUCET_MINUTES={{.FaucetMinutes}} 67 - FAUCET_TIERS={{.FaucetTiers}} 68 - CAPTCHA_TOKEN={{.CaptchaToken}} 69 - CAPTCHA_SECRET={{.CaptchaSecret}} 70 - NO_AUTH={{.NoAuth}}{{if .VHost}} 71 - VIRTUAL_HOST={{.VHost}} 72 - VIRTUAL_PORT=8080{{end}} 73 logging: 74 driver: "json-file" 75 options: 76 max-size: "1m" 77 max-file: "10" 78 restart: always 79 ` 80 81 // deployFaucet deploys a new faucet 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 deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos, 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(faucetDockerfile)).Execute(dockerfile, map[string]interface{}{ 91 "NetworkID": config.node.network, 92 "Bootnodes": strings.Join(bootnodes, ","), 93 "Ethstats": config.node.ethstats, 94 "EthPort": config.node.portFull, 95 "CaptchaToken": config.captchaToken, 96 "CaptchaSecret": config.captchaSecret, 97 "FaucetName": strings.Title(network), 98 "FaucetAmount": config.amount, 99 "FaucetMinutes": config.minutes, 100 "FaucetTiers": config.tiers, 101 "NoAuth": config.noauth, 102 }) 103 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 104 105 composefile := new(bytes.Buffer) 106 template.Must(template.New("").Parse(faucetComposefile)).Execute(composefile, map[string]interface{}{ 107 "Network": network, 108 "Datadir": config.node.datadir, 109 "VHost": config.host, 110 "ApiPort": config.port, 111 "EthPort": config.node.portFull, 112 "EthName": config.node.ethstats[:strings.Index(config.node.ethstats, ":")], 113 "CaptchaToken": config.captchaToken, 114 "CaptchaSecret": config.captchaSecret, 115 "FaucetAmount": config.amount, 116 "FaucetMinutes": config.minutes, 117 "FaucetTiers": config.tiers, 118 "NoAuth": config.noauth, 119 }) 120 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 121 122 files[filepath.Join(workdir, "genesis.json")] = config.node.genesis 123 files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON) 124 files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass) 125 126 // Upload the deployment files to the remote server (and clean up afterwards) 127 if out, err := client.Upload(files); err != nil { 128 return out, err 129 } 130 defer client.Run("rm -rf " + workdir) 131 132 // Build and deploy the faucet service 133 if nocache { 134 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)) 135 } 136 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network)) 137 } 138 139 // faucetInfos is returned from an faucet status check to allow reporting various 140 // configuration parameters. 141 type faucetInfos struct { 142 node *nodeInfos 143 host string 144 port int 145 amount int 146 minutes int 147 tiers int 148 noauth bool 149 captchaToken string 150 captchaSecret string 151 } 152 153 // Report converts the typed struct into a plain string->string map, containing 154 // most - but not all - fields for reporting to the user. 155 func (info *faucetInfos) Report() map[string]string { 156 report := map[string]string{ 157 "Website address": info.host, 158 "Website listener port": strconv.Itoa(info.port), 159 "pixicoin listener port": strconv.Itoa(info.node.portFull), 160 "Funding amount (base tier)": fmt.Sprintf("%d Ethers", info.amount), 161 "Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes), 162 "Funding tiers": strconv.Itoa(info.tiers), 163 "Captha protection": fmt.Sprintf("%v", info.captchaToken != ""), 164 "Ethstats username": info.node.ethstats, 165 } 166 if info.noauth { 167 report["Debug mode (no auth)"] = "enabled" 168 } 169 if info.node.keyJSON != "" { 170 var key struct { 171 Address string `json:"address"` 172 } 173 if err := json.Unmarshal([]byte(info.node.keyJSON), &key); err == nil { 174 report["Funding account"] = common.HexToAddress(key.Address).Hex() 175 } else { 176 log.Error("Failed to retrieve signer address", "err", err) 177 } 178 } 179 return report 180 } 181 182 // checkFaucet does a health-check against an faucet server to verify whether 183 // it's running, and if yes, gathering a collection of useful infos about it. 184 func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { 185 // Inspect a possible faucet container on the host 186 infos, err := inspectContainer(client, fmt.Sprintf("%s_faucet_1", network)) 187 if err != nil { 188 return nil, err 189 } 190 if !infos.running { 191 return nil, ErrServiceOffline 192 } 193 // Resolve the port from the host, or the reverse proxy 194 port := infos.portmap["8080/tcp"] 195 if port == 0 { 196 if proxy, _ := checkNginx(client, network); proxy != nil { 197 port = proxy.port 198 } 199 } 200 if port == 0 { 201 return nil, ErrNotExposed 202 } 203 // Resolve the host from the reverse-proxy and the config values 204 host := infos.envvars["VIRTUAL_HOST"] 205 if host == "" { 206 host = client.server 207 } 208 amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"]) 209 minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"]) 210 tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"]) 211 212 // Retrieve the funding account informations 213 var out []byte 214 keyJSON, keyPass := "", "" 215 if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.json", network)); err == nil { 216 keyJSON = string(bytes.TrimSpace(out)) 217 } 218 if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.pass", network)); err == nil { 219 keyPass = string(bytes.TrimSpace(out)) 220 } 221 // Run a sanity check to see if the port is reachable 222 if err = checkPort(host, port); err != nil { 223 log.Warn("Faucet service seems unreachable", "server", host, "port", port, "err", err) 224 } 225 // Container available, assemble and return the useful infos 226 return &faucetInfos{ 227 node: &nodeInfos{ 228 datadir: infos.volumes["/root/.faucet"], 229 portFull: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"], 230 ethstats: infos.envvars["ETH_NAME"], 231 keyJSON: keyJSON, 232 keyPass: keyPass, 233 }, 234 host: host, 235 port: port, 236 amount: amount, 237 minutes: minutes, 238 tiers: tiers, 239 captchaToken: infos.envvars["CAPTCHA_TOKEN"], 240 captchaSecret: infos.envvars["CAPTCHA_SECRET"], 241 noauth: infos.envvars["NO_AUTH"] == "true", 242 }, nil 243 }