github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/cmd/puppeth/module_ethstats.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package main 19 20 import ( 21 "bytes" 22 "fmt" 23 "math/rand" 24 "path/filepath" 25 "strconv" 26 "strings" 27 "text/template" 28 29 "github.com/AigarNetwork/aigar/log" 30 ) 31 32 // ethstatsDockerfile is the Dockerfile required to build an ethstats backend 33 // and associated monitoring site. 34 var ethstatsDockerfile = ` 35 FROM puppeth/ethstats:latest 36 37 RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: [{{.Banned}}], reserved: ["yournode"]};' > lib/utils/config.js 38 ` 39 40 // ethstatsComposefile is the docker-compose.yml file required to deploy and 41 // maintain an ethstats monitoring site. 42 var ethstatsComposefile = ` 43 version: '2' 44 services: 45 ethstats: 46 build: . 47 image: {{.Network}}/ethstats 48 container_name: {{.Network}}_ethstats_1{{if not .VHost}} 49 ports: 50 - "{{.Port}}:3000"{{end}} 51 environment: 52 - WS_SECRET={{.Secret}}{{if .VHost}} 53 - VIRTUAL_HOST={{.VHost}}{{end}}{{if .Banned}} 54 - BANNED={{.Banned}}{{end}} 55 logging: 56 driver: "json-file" 57 options: 58 max-size: "1m" 59 max-file: "10" 60 restart: always 61 ` 62 63 // deployEthstats deploys a new ethstats container to a remote machine via SSH, 64 // docker and docker-compose. If an instance with the specified network name 65 // already exists there, it will be overwritten! 66 func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string, banned []string, nocache bool) ([]byte, error) { 67 // Generate the content to upload to the server 68 workdir := fmt.Sprintf("%d", rand.Int63()) 69 files := make(map[string][]byte) 70 71 trustedLabels := make([]string, len(trusted)) 72 for i, address := range trusted { 73 trustedLabels[i] = fmt.Sprintf("\"%s\"", address) 74 } 75 bannedLabels := make([]string, len(banned)) 76 for i, address := range banned { 77 bannedLabels[i] = fmt.Sprintf("\"%s\"", address) 78 } 79 80 dockerfile := new(bytes.Buffer) 81 template.Must(template.New("").Parse(ethstatsDockerfile)).Execute(dockerfile, map[string]interface{}{ 82 "Trusted": strings.Join(trustedLabels, ", "), 83 "Banned": strings.Join(bannedLabels, ", "), 84 }) 85 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 86 87 composefile := new(bytes.Buffer) 88 template.Must(template.New("").Parse(ethstatsComposefile)).Execute(composefile, map[string]interface{}{ 89 "Network": network, 90 "Port": port, 91 "Secret": secret, 92 "VHost": vhost, 93 "Banned": strings.Join(banned, ","), 94 }) 95 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 96 97 // Upload the deployment files to the remote server (and clean up afterwards) 98 if out, err := client.Upload(files); err != nil { 99 return out, err 100 } 101 defer client.Run("rm -rf " + workdir) 102 103 // Build and deploy the ethstats service 104 if nocache { 105 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)) 106 } 107 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network)) 108 } 109 110 // ethstatsInfos is returned from an ethstats status check to allow reporting 111 // various configuration parameters. 112 type ethstatsInfos struct { 113 host string 114 port int 115 secret string 116 config string 117 banned []string 118 } 119 120 // Report converts the typed struct into a plain string->string map, containing 121 // most - but not all - fields for reporting to the user. 122 func (info *ethstatsInfos) Report() map[string]string { 123 return map[string]string{ 124 "Website address": info.host, 125 "Website listener port": strconv.Itoa(info.port), 126 "Login secret": info.secret, 127 "Banned addresses": strings.Join(info.banned, "\n"), 128 } 129 } 130 131 // checkEthstats does a health-check against an ethstats server to verify whether 132 // it's running, and if yes, gathering a collection of useful infos about it. 133 func checkEthstats(client *sshClient, network string) (*ethstatsInfos, error) { 134 // Inspect a possible ethstats container on the host 135 infos, err := inspectContainer(client, fmt.Sprintf("%s_ethstats_1", network)) 136 if err != nil { 137 return nil, err 138 } 139 if !infos.running { 140 return nil, ErrServiceOffline 141 } 142 // Resolve the port from the host, or the reverse proxy 143 port := infos.portmap["3000/tcp"] 144 if port == 0 { 145 if proxy, _ := checkNginx(client, network); proxy != nil { 146 port = proxy.port 147 } 148 } 149 if port == 0 { 150 return nil, ErrNotExposed 151 } 152 // Resolve the host from the reverse-proxy and configure the connection string 153 host := infos.envvars["VIRTUAL_HOST"] 154 if host == "" { 155 host = client.server 156 } 157 secret := infos.envvars["WS_SECRET"] 158 config := fmt.Sprintf("%s@%s", secret, host) 159 if port != 80 && port != 443 { 160 config += fmt.Sprintf(":%d", port) 161 } 162 // Retrieve the IP blacklist 163 banned := strings.Split(infos.envvars["BANNED"], ",") 164 165 // Run a sanity check to see if the port is reachable 166 if err = checkPort(host, port); err != nil { 167 log.Warn("Ethstats service seems unreachable", "server", host, "port", port, "err", err) 168 } 169 // Container available, assemble and return the useful infos 170 return ðstatsInfos{ 171 host: host, 172 port: port, 173 secret: secret, 174 config: config, 175 banned: banned, 176 }, nil 177 }