github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/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 12:09:31</date>
    10  //</624342603621470208>
    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      ports:
    56        - "{{.EthPort}}:{{.EthPort}}"{{if not .VHost}}
    57        - "{{.ApiPort}}:8080"{{end}}
    58      volumes:
    59        - {{.Datadir}}:/root/.faucet
    60      environment:
    61        - ETH_PORT={{.EthPort}}
    62        - ETH_NAME={{.EthName}}
    63        - FAUCET_AMOUNT={{.FaucetAmount}}
    64        - FAUCET_MINUTES={{.FaucetMinutes}}
    65        - FAUCET_TIERS={{.FaucetTiers}}
    66        - CAPTCHA_TOKEN={{.CaptchaToken}}
    67        - CAPTCHA_SECRET={{.CaptchaSecret}}
    68        - NO_AUTH={{.NoAuth}}{{if .VHost}}
    69        - VIRTUAL_HOST={{.VHost}}
    70        - VIRTUAL_PORT=8080{{end}}
    71      logging:
    72        driver: "json-file"
    73        options:
    74          max-size: "1m"
    75          max-file: "10"
    76      restart: always
    77  `
    78  
    79  //
    80  //Docker和Docker组合。如果具有指定网络名称的实例
    81  //已经存在,将被覆盖!
    82  func deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos, nocache bool) ([]byte, error) {
    83  //生成要上载到服务器的内容
    84  	workdir := fmt.Sprintf("%d", rand.Int63())
    85  	files := make(map[string][]byte)
    86  
    87  	dockerfile := new(bytes.Buffer)
    88  	template.Must(template.New("").Parse(faucetDockerfile)).Execute(dockerfile, map[string]interface{}{
    89  		"NetworkID":     config.node.network,
    90  		"Bootnodes":     strings.Join(bootnodes, ","),
    91  		"Ethstats":      config.node.ethstats,
    92  		"EthPort":       config.node.port,
    93  		"CaptchaToken":  config.captchaToken,
    94  		"CaptchaSecret": config.captchaSecret,
    95  		"FaucetName":    strings.Title(network),
    96  		"FaucetAmount":  config.amount,
    97  		"FaucetMinutes": config.minutes,
    98  		"FaucetTiers":   config.tiers,
    99  		"NoAuth":        config.noauth,
   100  	})
   101  	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
   102  
   103  	composefile := new(bytes.Buffer)
   104  	template.Must(template.New("").Parse(faucetComposefile)).Execute(composefile, map[string]interface{}{
   105  		"Network":       network,
   106  		"Datadir":       config.node.datadir,
   107  		"VHost":         config.host,
   108  		"ApiPort":       config.port,
   109  		"EthPort":       config.node.port,
   110  		"EthName":       config.node.ethstats[:strings.Index(config.node.ethstats, ":")],
   111  		"CaptchaToken":  config.captchaToken,
   112  		"CaptchaSecret": config.captchaSecret,
   113  		"FaucetAmount":  config.amount,
   114  		"FaucetMinutes": config.minutes,
   115  		"FaucetTiers":   config.tiers,
   116  		"NoAuth":        config.noauth,
   117  	})
   118  	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
   119  
   120  	files[filepath.Join(workdir, "genesis.json")] = config.node.genesis
   121  	files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON)
   122  	files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass)
   123  
   124  //将部署文件上载到远程服务器(然后清理)
   125  	if out, err := client.Upload(files); err != nil {
   126  		return out, err
   127  	}
   128  	defer client.Run("rm -rf " + workdir)
   129  
   130  //
   131  	if nocache {
   132  		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))
   133  	}
   134  	return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
   135  }
   136  
   137  //水龙头信息从水龙头状态检查返回,以允许报告各种
   138  //配置参数。
   139  type faucetInfos struct {
   140  	node          *nodeInfos
   141  	host          string
   142  	port          int
   143  	amount        int
   144  	minutes       int
   145  	tiers         int
   146  	noauth        bool
   147  	captchaToken  string
   148  	captchaSecret string
   149  }
   150  
   151  //报表将类型化结构转换为纯字符串->字符串映射,其中包含
   152  //大多数(但不是全部)字段用于向用户报告。
   153  func (info *faucetInfos) Report() map[string]string {
   154  	report := map[string]string{
   155  		"Website address":              info.host,
   156  		"Website listener port":        strconv.Itoa(info.port),
   157  		"Ethereum listener port":       strconv.Itoa(info.node.port),
   158  		"Funding amount (base tier)":   fmt.Sprintf("%d Ethers", info.amount),
   159  		"Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes),
   160  		"Funding tiers":                strconv.Itoa(info.tiers),
   161  		"Captha protection":            fmt.Sprintf("%v", info.captchaToken != ""),
   162  		"Ethstats username":            info.node.ethstats,
   163  	}
   164  	if info.noauth {
   165  		report["Debug mode (no auth)"] = "enabled"
   166  	}
   167  	if info.node.keyJSON != "" {
   168  		var key struct {
   169  			Address string `json:"address"`
   170  		}
   171  		if err := json.Unmarshal([]byte(info.node.keyJSON), &key); err == nil {
   172  			report["Funding account"] = common.HexToAddress(key.Address).Hex()
   173  		} else {
   174  			log.Error("Failed to retrieve signer address", "err", err)
   175  		}
   176  	}
   177  	return report
   178  }
   179  
   180  //检查水龙头是否对水龙头服务器进行健康检查以验证
   181  //它正在运行,如果是,收集有关它的有用信息。
   182  func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
   183  //
   184  	infos, err := inspectContainer(client, fmt.Sprintf("%s_faucet_1", network))
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	if !infos.running {
   189  		return nil, ErrServiceOffline
   190  	}
   191  //从主机或反向代理解析端口
   192  	port := infos.portmap["8080/tcp"]
   193  	if port == 0 {
   194  		if proxy, _ := checkNginx(client, network); proxy != nil {
   195  			port = proxy.port
   196  		}
   197  	}
   198  	if port == 0 {
   199  		return nil, ErrNotExposed
   200  	}
   201  //
   202  	host := infos.envvars["VIRTUAL_HOST"]
   203  	if host == "" {
   204  		host = client.server
   205  	}
   206  	amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"])
   207  	minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
   208  	tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"])
   209  
   210  //
   211  	var out []byte
   212  	keyJSON, keyPass := "", ""
   213  	if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.json", network)); err == nil {
   214  		keyJSON = string(bytes.TrimSpace(out))
   215  	}
   216  	if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.pass", network)); err == nil {
   217  		keyPass = string(bytes.TrimSpace(out))
   218  	}
   219  //运行健全检查以查看端口是否可访问
   220  	if err = checkPort(host, port); err != nil {
   221  		log.Warn("Faucet service seems unreachable", "server", host, "port", port, "err", err)
   222  	}
   223  //容器可用,组装并返回有用的信息
   224  	return &faucetInfos{
   225  		node: &nodeInfos{
   226  			datadir:  infos.volumes["/root/.faucet"],
   227  			port:     infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
   228  			ethstats: infos.envvars["ETH_NAME"],
   229  			keyJSON:  keyJSON,
   230  			keyPass:  keyPass,
   231  		},
   232  		host:          host,
   233  		port:          port,
   234  		amount:        amount,
   235  		minutes:       minutes,
   236  		tiers:         tiers,
   237  		captchaToken:  infos.envvars["CAPTCHA_TOKEN"],
   238  		captchaSecret: infos.envvars["CAPTCHA_SECRET"],
   239  		noauth:        infos.envvars["NO_AUTH"] == "true",
   240  	}, nil
   241  }
   242