github.com/myafeier/go-ethereum@v1.6.8-0.20170719123245-3e0dbe0eaa72/cmd/puppeth/module_ethstats.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 "fmt" 22 "math/rand" 23 "path/filepath" 24 "strings" 25 "text/template" 26 27 "github.com/ethereum/go-ethereum/log" 28 ) 29 30 // ethstatsDockerfile is the Dockerfile required to build an ethstats backend 31 // and associated monitoring site. 32 var ethstatsDockerfile = ` 33 FROM mhart/alpine-node:latest 34 35 RUN \ 36 apk add --update git && \ 37 git clone --depth=1 https://github.com/karalabe/eth-netstats && \ 38 apk del git && rm -rf /var/cache/apk/* && \ 39 \ 40 cd /eth-netstats && npm install && npm install -g grunt-cli && grunt 41 42 WORKDIR /eth-netstats 43 EXPOSE 3000 44 45 RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: []};' > lib/utils/config.js 46 47 CMD ["npm", "start"] 48 ` 49 50 // ethstatsComposefile is the docker-compose.yml file required to deploy and 51 // maintain an ethstats monitoring site. 52 var ethstatsComposefile = ` 53 version: '2' 54 services: 55 ethstats: 56 build: . 57 image: {{.Network}}/ethstats{{if not .VHost}} 58 ports: 59 - "{{.Port}}:3000"{{end}} 60 environment: 61 - WS_SECRET={{.Secret}}{{if .VHost}} 62 - VIRTUAL_HOST={{.VHost}}{{end}} 63 logging: 64 driver: "json-file" 65 options: 66 max-size: "1m" 67 max-file: "10" 68 restart: always 69 ` 70 71 // deployEthstats deploys a new ethstats container to a remote machine via SSH, 72 // docker and docker-compose. If an instance with the specified network name 73 // already exists there, it will be overwritten! 74 func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string) ([]byte, error) { 75 // Generate the content to upload to the server 76 workdir := fmt.Sprintf("%d", rand.Int63()) 77 files := make(map[string][]byte) 78 79 for i, address := range trusted { 80 trusted[i] = fmt.Sprintf("\"%s\"", address) 81 } 82 83 dockerfile := new(bytes.Buffer) 84 template.Must(template.New("").Parse(ethstatsDockerfile)).Execute(dockerfile, map[string]interface{}{ 85 "Trusted": strings.Join(trusted, ", "), 86 }) 87 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 88 89 composefile := new(bytes.Buffer) 90 template.Must(template.New("").Parse(ethstatsComposefile)).Execute(composefile, map[string]interface{}{ 91 "Network": network, 92 "Port": port, 93 "Secret": secret, 94 "VHost": vhost, 95 }) 96 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 97 98 // Upload the deployment files to the remote server (and clean up afterwards) 99 if out, err := client.Upload(files); err != nil { 100 return out, err 101 } 102 defer client.Run("rm -rf " + workdir) 103 104 // Build and deploy the ethstats service 105 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build", workdir, network)) 106 } 107 108 // ethstatsInfos is returned from an ethstats status check to allow reporting 109 // various configuration parameters. 110 type ethstatsInfos struct { 111 host string 112 port int 113 secret string 114 config string 115 } 116 117 // String implements the stringer interface. 118 func (info *ethstatsInfos) String() string { 119 return fmt.Sprintf("host=%s, port=%d, secret=%s", info.host, info.port, info.secret) 120 } 121 122 // checkEthstats does a health-check against an ethstats server to verify whether 123 // it's running, and if yes, gathering a collection of useful infos about it. 124 func checkEthstats(client *sshClient, network string) (*ethstatsInfos, error) { 125 // Inspect a possible ethstats container on the host 126 infos, err := inspectContainer(client, fmt.Sprintf("%s_ethstats_1", network)) 127 if err != nil { 128 return nil, err 129 } 130 if !infos.running { 131 return nil, ErrServiceOffline 132 } 133 // Resolve the port from the host, or the reverse proxy 134 port := infos.portmap["3000/tcp"] 135 if port == 0 { 136 if proxy, _ := checkNginx(client, network); proxy != nil { 137 port = proxy.port 138 } 139 } 140 if port == 0 { 141 return nil, ErrNotExposed 142 } 143 // Resolve the host from the reverse-proxy and configure the connection string 144 host := infos.envvars["VIRTUAL_HOST"] 145 if host == "" { 146 host = client.server 147 } 148 secret := infos.envvars["WS_SECRET"] 149 config := fmt.Sprintf("%s@%s", secret, host) 150 if port != 80 && port != 443 { 151 config += fmt.Sprintf(":%d", port) 152 } 153 // Run a sanity check to see if the port is reachable 154 if err = checkPort(host, port); err != nil { 155 log.Warn("Ethstats service seems unreachable", "server", host, "port", port, "err", err) 156 } 157 // Container available, assemble and return the useful infos 158 return ðstatsInfos{ 159 host: host, 160 port: port, 161 secret: secret, 162 config: config, 163 }, nil 164 }