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