github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/cmd/puppeth/module_ethstats.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of Spectrum. 3 // 4 // Spectrum 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 // Spectrum 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 Spectrum. 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 "strconv" 25 "strings" 26 "text/template" 27 28 "github.com/SmartMeshFoundation/Spectrum/log" 29 ) 30 31 // ethstatsDockerfile is the Dockerfile required to build an ethstats backend 32 // and associated monitoring site. 33 var ethstatsDockerfile = ` 34 FROM puppeth/ethstats:latest 35 36 RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: [{{.Banned}}], reserved: ["yournode"]};' > lib/utils/config.js 37 ` 38 39 // ethstatsComposefile is the docker-compose.yml file required to deploy and 40 // maintain an ethstats monitoring site. 41 var ethstatsComposefile = ` 42 version: '2' 43 services: 44 ethstats: 45 build: . 46 image: {{.Network}}/ethstats{{if not .VHost}} 47 ports: 48 - "{{.Port}}:3000"{{end}} 49 environment: 50 - WS_SECRET={{.Secret}}{{if .VHost}} 51 - VIRTUAL_HOST={{.VHost}}{{end}}{{if .Banned}} 52 - BANNED={{.Banned}}{{end}} 53 logging: 54 driver: "json-file" 55 options: 56 max-size: "1m" 57 max-file: "10" 58 restart: always 59 ` 60 61 // deployEthstats deploys a new ethstats container to a remote machine via SSH, 62 // docker and docker-compose. If an instance with the specified network name 63 // already exists there, it will be overwritten! 64 func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string, banned []string, nocache bool) ([]byte, error) { 65 // Generate the content to upload to the server 66 workdir := fmt.Sprintf("%d", rand.Int63()) 67 files := make(map[string][]byte) 68 69 trustedLabels := make([]string, len(trusted)) 70 for i, address := range trusted { 71 trustedLabels[i] = fmt.Sprintf("\"%s\"", address) 72 } 73 bannedLabels := make([]string, len(banned)) 74 for i, address := range banned { 75 bannedLabels[i] = fmt.Sprintf("\"%s\"", address) 76 } 77 78 dockerfile := new(bytes.Buffer) 79 template.Must(template.New("").Parse(ethstatsDockerfile)).Execute(dockerfile, map[string]interface{}{ 80 "Trusted": strings.Join(trustedLabels, ", "), 81 "Banned": strings.Join(bannedLabels, ", "), 82 }) 83 files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() 84 85 composefile := new(bytes.Buffer) 86 template.Must(template.New("").Parse(ethstatsComposefile)).Execute(composefile, map[string]interface{}{ 87 "Network": network, 88 "Port": port, 89 "Secret": secret, 90 "VHost": vhost, 91 "Banned": strings.Join(banned, ","), 92 }) 93 files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() 94 95 // Upload the deployment files to the remote server (and clean up afterwards) 96 if out, err := client.Upload(files); err != nil { 97 return out, err 98 } 99 defer client.Run("rm -rf " + workdir) 100 101 // Build and deploy the ethstats service 102 if nocache { 103 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)) 104 } 105 return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", 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 banned []string 116 } 117 118 // Report converts the typed struct into a plain string->string map, containing 119 // most - but not all - fields for reporting to the user. 120 func (info *ethstatsInfos) Report() map[string]string { 121 return map[string]string{ 122 "Website address": info.host, 123 "Website listener port": strconv.Itoa(info.port), 124 "Login secret": info.secret, 125 "Banned addresses": fmt.Sprintf("%v", info.banned), 126 } 127 } 128 129 // checkEthstats does a health-check against an ethstats server to verify whether 130 // it's running, and if yes, gathering a collection of useful infos about it. 131 func checkEthstats(client *sshClient, network string) (*ethstatsInfos, error) { 132 // Inspect a possible ethstats container on the host 133 infos, err := inspectContainer(client, fmt.Sprintf("%s_ethstats_1", network)) 134 if err != nil { 135 return nil, err 136 } 137 if !infos.running { 138 return nil, ErrServiceOffline 139 } 140 // Resolve the port from the host, or the reverse proxy 141 port := infos.portmap["3000/tcp"] 142 if port == 0 { 143 if proxy, _ := checkNginx(client, network); proxy != nil { 144 port = proxy.port 145 } 146 } 147 if port == 0 { 148 return nil, ErrNotExposed 149 } 150 // Resolve the host from the reverse-proxy and configure the connection string 151 host := infos.envvars["VIRTUAL_HOST"] 152 if host == "" { 153 host = client.server 154 } 155 secret := infos.envvars["WS_SECRET"] 156 config := fmt.Sprintf("%s@%s", secret, host) 157 if port != 80 && port != 443 { 158 config += fmt.Sprintf(":%d", port) 159 } 160 // Retrieve the IP blacklist 161 banned := strings.Split(infos.envvars["BANNED"], ",") 162 163 // Run a sanity check to see if the port is reachable 164 if err = checkPort(host, port); err != nil { 165 log.Warn("Ethstats service seems unreachable", "server", host, "port", port, "err", err) 166 } 167 // Container available, assemble and return the useful infos 168 return ðstatsInfos{ 169 host: host, 170 port: port, 171 secret: secret, 172 config: config, 173 banned: banned, 174 }, nil 175 }