| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | // Copyright 2017 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of go-ethereum. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"math/rand" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 	"text/template" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ethstatsDockerfile is the Dockerfile required to build an ethstats backend | 
					
						
							|  |  |  | // and associated monitoring site. | 
					
						
							|  |  |  | var ethstatsDockerfile = ` | 
					
						
							| 
									
										
										
										
											2017-10-27 14:36:49 +03:00
										 |  |  | FROM puppeth/ethstats:latest | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-12 11:35:35 +03:00
										 |  |  | RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: [{{.Banned}}], reserved: ["yournode"]};' > lib/utils/config.js | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | ` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ethstatsComposefile is the docker-compose.yml file required to deploy and | 
					
						
							|  |  |  | // maintain an ethstats monitoring site. | 
					
						
							|  |  |  | var ethstatsComposefile = ` | 
					
						
							|  |  |  | version: '2' | 
					
						
							|  |  |  | services: | 
					
						
							|  |  |  |   ethstats: | 
					
						
							|  |  |  |     build: . | 
					
						
							| 
									
										
										
										
											2018-12-11 13:41:41 +02:00
										 |  |  |     image: {{.Network}}/ethstats | 
					
						
							|  |  |  |     container_name: {{.Network}}_ethstats_1{{if not .VHost}} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  |     ports: | 
					
						
							|  |  |  |       - "{{.Port}}:3000"{{end}} | 
					
						
							|  |  |  |     environment: | 
					
						
							|  |  |  |       - WS_SECRET={{.Secret}}{{if .VHost}} | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  |       - VIRTUAL_HOST={{.VHost}}{{end}}{{if .Banned}} | 
					
						
							|  |  |  |       - BANNED={{.Banned}}{{end}} | 
					
						
							| 
									
										
										
										
											2017-07-17 20:38:40 +03:00
										 |  |  |     logging: | 
					
						
							|  |  |  |       driver: "json-file" | 
					
						
							|  |  |  |       options: | 
					
						
							|  |  |  |         max-size: "1m" | 
					
						
							|  |  |  |         max-file: "10" | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  |     restart: always | 
					
						
							|  |  |  | ` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // deployEthstats deploys a new ethstats container to a remote machine via SSH, | 
					
						
							|  |  |  | // docker and docker-compose. If an instance with the specified network name | 
					
						
							|  |  |  | // already exists there, it will be overwritten! | 
					
						
							| 
									
										
										
										
											2017-10-19 16:00:55 +03:00
										 |  |  | func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string, banned []string, nocache bool) ([]byte, error) { | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	// Generate the content to upload to the server | 
					
						
							|  |  |  | 	workdir := fmt.Sprintf("%d", rand.Int63()) | 
					
						
							|  |  |  | 	files := make(map[string][]byte) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 	trustedLabels := make([]string, len(trusted)) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	for i, address := range trusted { | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 		trustedLabels[i] = fmt.Sprintf("\"%s\"", address) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	bannedLabels := make([]string, len(banned)) | 
					
						
							|  |  |  | 	for i, address := range banned { | 
					
						
							|  |  |  | 		bannedLabels[i] = fmt.Sprintf("\"%s\"", address) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dockerfile := new(bytes.Buffer) | 
					
						
							|  |  |  | 	template.Must(template.New("").Parse(ethstatsDockerfile)).Execute(dockerfile, map[string]interface{}{ | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 		"Trusted": strings.Join(trustedLabels, ", "), | 
					
						
							|  |  |  | 		"Banned":  strings.Join(bannedLabels, ", "), | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	composefile := new(bytes.Buffer) | 
					
						
							|  |  |  | 	template.Must(template.New("").Parse(ethstatsComposefile)).Execute(composefile, map[string]interface{}{ | 
					
						
							|  |  |  | 		"Network": network, | 
					
						
							|  |  |  | 		"Port":    port, | 
					
						
							|  |  |  | 		"Secret":  secret, | 
					
						
							|  |  |  | 		"VHost":   vhost, | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 		"Banned":  strings.Join(banned, ","), | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Upload the deployment files to the remote server (and clean up afterwards) | 
					
						
							|  |  |  | 	if out, err := client.Upload(files); err != nil { | 
					
						
							|  |  |  | 		return out, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer client.Run("rm -rf " + workdir) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Build and deploy the ethstats service | 
					
						
							| 
									
										
										
										
											2017-10-19 16:00:55 +03:00
										 |  |  | 	if nocache { | 
					
						
							| 
									
										
										
										
											2018-08-03 12:08:19 +03:00
										 |  |  | 		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)) | 
					
						
							| 
									
										
										
										
											2017-10-19 16:00:55 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-03 12:08:19 +03:00
										 |  |  | 	return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network)) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ethstatsInfos is returned from an ethstats status check to allow reporting | 
					
						
							|  |  |  | // various configuration parameters. | 
					
						
							|  |  |  | type ethstatsInfos struct { | 
					
						
							|  |  |  | 	host   string | 
					
						
							|  |  |  | 	port   int | 
					
						
							|  |  |  | 	secret string | 
					
						
							|  |  |  | 	config string | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 	banned []string | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-23 14:22:59 +02:00
										 |  |  | // Report converts the typed struct into a plain string->string map, containing | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | // most - but not all - fields for reporting to the user. | 
					
						
							|  |  |  | func (info *ethstatsInfos) Report() map[string]string { | 
					
						
							|  |  |  | 	return map[string]string{ | 
					
						
							|  |  |  | 		"Website address":       info.host, | 
					
						
							|  |  |  | 		"Website listener port": strconv.Itoa(info.port), | 
					
						
							|  |  |  | 		"Login secret":          info.secret, | 
					
						
							| 
									
										
										
										
											2018-07-30 15:39:35 +03:00
										 |  |  | 		"Banned addresses":      strings.Join(info.banned, "\n"), | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // checkEthstats does a health-check against an ethstats server to verify whether | 
					
						
							|  |  |  | // it's running, and if yes, gathering a collection of useful infos about it. | 
					
						
							|  |  |  | func checkEthstats(client *sshClient, network string) (*ethstatsInfos, error) { | 
					
						
							|  |  |  | 	// Inspect a possible ethstats container on the host | 
					
						
							|  |  |  | 	infos, err := inspectContainer(client, fmt.Sprintf("%s_ethstats_1", network)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if !infos.running { | 
					
						
							|  |  |  | 		return nil, ErrServiceOffline | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Resolve the port from the host, or the reverse proxy | 
					
						
							|  |  |  | 	port := infos.portmap["3000/tcp"] | 
					
						
							|  |  |  | 	if port == 0 { | 
					
						
							|  |  |  | 		if proxy, _ := checkNginx(client, network); proxy != nil { | 
					
						
							|  |  |  | 			port = proxy.port | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if port == 0 { | 
					
						
							|  |  |  | 		return nil, ErrNotExposed | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Resolve the host from the reverse-proxy and configure the connection string | 
					
						
							|  |  |  | 	host := infos.envvars["VIRTUAL_HOST"] | 
					
						
							|  |  |  | 	if host == "" { | 
					
						
							|  |  |  | 		host = client.server | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	secret := infos.envvars["WS_SECRET"] | 
					
						
							|  |  |  | 	config := fmt.Sprintf("%s@%s", secret, host) | 
					
						
							|  |  |  | 	if port != 80 && port != 443 { | 
					
						
							|  |  |  | 		config += fmt.Sprintf(":%d", port) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 	// Retrieve the IP blacklist | 
					
						
							|  |  |  | 	banned := strings.Split(infos.envvars["BANNED"], ",") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	// Run a sanity check to see if the port is reachable | 
					
						
							|  |  |  | 	if err = checkPort(host, port); err != nil { | 
					
						
							|  |  |  | 		log.Warn("Ethstats service seems unreachable", "server", host, "port", port, "err", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Container available, assemble and return the useful infos | 
					
						
							|  |  |  | 	return ðstatsInfos{ | 
					
						
							|  |  |  | 		host:   host, | 
					
						
							|  |  |  | 		port:   port, | 
					
						
							|  |  |  | 		secret: secret, | 
					
						
							|  |  |  | 		config: config, | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 		banned: banned, | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	}, nil | 
					
						
							|  |  |  | } |