github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/cmd/puppeth/module_faucet.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:33</date> 10 //</624450069432635392> 11 12 13 package main 14 15 import ( 16 "bytes" 17 "encoding/json" 18 "fmt" 19 "html/template" 20 "math/rand" 21 "path/filepath" 22 "strconv" 23 "strings" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/log" 27 ) 28 29 // 30 // 31 var faucetDockerfile = ` 32 FROM ethereum/client-go:alltools-latest 33 34 ADD genesis.json /genesis.json 35 ADD account.json /account.json 36 ADD account.pass /account.pass 37 38 EXPOSE 8080 30303 30303/udp 39 40 ENTRYPOINT [ \ 41 "faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \ 42 "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ 43 "--account.json", "/account.json", "--account.pass", "/account.pass" \ 44 {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ 45 ]` 46 47 //水龙头组合文件是部署和维护所需的docker-compose.yml文件。 48 //加密水龙头。 49 var faucetComposefile = ` 50 version: '2' 51 services: 52 faucet: 53 build: . 54 image: {{.Network}}/faucet 55 container_name: {{.Network}}_faucet_1 56 ports: 57 - "{{.EthPort}}:{{.EthPort}}" 58 - "{{.EthPort}}:{{.EthPort}}/udp"{{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 // 82 //Docker和Docker组合。如果具有指定网络名称的实例 83 //已经存在,将被覆盖! 84 func deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos, nocache bool) ([]byte, error) { 85 //生成要上载到服务器的内容 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.port, 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.port, 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 //将部署文件上载到远程服务器(然后清理) 127 if out, err := client.Upload(files); err != nil { 128 return out, err 129 } 130 defer client.Run("rm -rf " + workdir) 131 132 //建立和部署水龙头服务 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 --timeout 60", workdir, network, network)) 135 } 136 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network)) 137 } 138 139 //水龙头信息从水龙头状态检查返回,以允许报告各种 140 //配置参数。 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 //报表将类型化结构转换为纯字符串->字符串映射,其中包含 154 //大多数(但不是全部)字段用于向用户报告。 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 "Ethereum listener port": strconv.Itoa(info.node.port), 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 //检查水龙头是否对水龙头服务器进行健康检查以验证 183 //它正在运行,如果是,收集有关它的有用信息。 184 func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { 185 // 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 //从主机或反向代理解析端口 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 //从反向代理和配置值解析主机 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 //检索资金帐户信息 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 //运行健全检查以查看端口是否可访问 222 if err = checkPort(host, port); err != nil { 223 log.Warn("Faucet service seems unreachable", "server", host, "port", port, "err", err) 224 } 225 //容器可用,组装并返回有用的信息 226 return &faucetInfos{ 227 node: &nodeInfos{ 228 datadir: infos.volumes["/root/.faucet"], 229 port: 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 } 244