Compare commits
43 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c3d4f112b | ||
|
|
d6a3d9adb1 | ||
|
|
d284c4aec9 | ||
|
|
05a4730489 | ||
|
|
2dad3102e0 | ||
|
|
b33f14ddd9 | ||
|
|
1f6789cba3 | ||
|
|
073247e4e1 | ||
|
|
a3d83d23bd | ||
|
|
f318962371 | ||
|
|
db31722cfc | ||
|
|
d91de3d912 | ||
|
|
495ad4defd | ||
|
|
b03aa20c8d | ||
|
|
7d4a8d7f7e | ||
|
|
65b1b96b06 | ||
|
|
198a22f446 | ||
|
|
e1531802cf | ||
|
|
5c2686fc6d | ||
|
|
0ae286d617 | ||
|
|
62e5547c6d | ||
|
|
00a026c2a5 | ||
|
|
359564bd91 | ||
|
|
f8bffd8391 | ||
|
|
4b366ae19e | ||
|
|
82ffb9804d | ||
|
|
54510057bb | ||
|
|
6d7ab865d7 | ||
|
|
74097cc4ad | ||
|
|
bd063682dc | ||
|
|
c802a3397e | ||
|
|
276bd2be33 | ||
|
|
e83495a09e | ||
|
|
64cad5c35d | ||
|
|
911b809a4e | ||
|
|
3fe884670d | ||
|
|
804f3d5ca9 | ||
|
|
0bd28a4480 | ||
|
|
326b5b6554 | ||
|
|
cfca0d7f07 | ||
|
|
5e60cb2eb0 | ||
|
|
d178a0d96b | ||
|
|
fd83424ee2 |
32
Makefile
32
Makefile
@@ -1,28 +1,12 @@
|
|||||||
BINARY = "build/wings"
|
build:
|
||||||
OSARCHLIST = "darwin/386 darwin/amd64 linux/386 linux/amd64 linux/arm linux/arm64 windows/386 windows/amd64"
|
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=/Users/dane/Sites/development/code" -o build/wings_linux_amd64 -v wings.go
|
||||||
|
|
||||||
all: $(BINARY)
|
compress:
|
||||||
|
upx --brute build/wings_*
|
||||||
|
|
||||||
$(BINARY):
|
cross-build: clean build compress
|
||||||
go build -o $(BINARY)
|
|
||||||
|
|
||||||
cross-build:
|
clean:
|
||||||
gox -osarch $(OSARCHLIST) -output "build/{{.Dir}}_{{.OS}}_{{.Arch}}"
|
rm -rf build/wings_*
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: all build compress clean
|
||||||
install:
|
|
||||||
go install
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test `go list ./... | grep -v "/vendor/"`
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
goverage -coverprofile=coverage.out ./...
|
|
||||||
go tool cover -html=coverage.out
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
glide install
|
|
||||||
|
|
||||||
install-tools:
|
|
||||||
go get -u github.com/mitchellh/gox
|
|
||||||
go get -u github.com/haya14busa/goverage
|
|
||||||
38
api/api.go
38
api/api.go
@@ -4,9 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,6 +45,26 @@ func (r *PanelRequest) GetEndpoint(endpoint string) string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logs the request into the debug log with all of the important request bits.
|
||||||
|
// The authorization key will be cleaned up before being output.
|
||||||
|
func (r *PanelRequest) logDebug(req *http.Request) {
|
||||||
|
headers := make(map[string][]string)
|
||||||
|
for k, v := range req.Header {
|
||||||
|
if k != "Authorization" || len(v) == 0 {
|
||||||
|
headers[k] = v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
headers[k] = []string{v[0][0:15] + "(redacted)"}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"method": req.Method,
|
||||||
|
"endpoint": req.URL.String(),
|
||||||
|
"headers": headers,
|
||||||
|
}).Debug("making request to external HTTP endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *PanelRequest) Get(url string) (*http.Response, error) {
|
func (r *PanelRequest) Get(url string) (*http.Response, error) {
|
||||||
c := r.GetClient()
|
c := r.GetClient()
|
||||||
|
|
||||||
@@ -55,7 +75,7 @@ func (r *PanelRequest) Get(url string) (*http.Response, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("GET request to endpoint", zap.String("endpoint", r.GetEndpoint(url)), zap.Any("headers", req.Header))
|
r.logDebug(req)
|
||||||
|
|
||||||
return c.Do(req)
|
return c.Do(req)
|
||||||
}
|
}
|
||||||
@@ -70,7 +90,7 @@ func (r *PanelRequest) Post(url string, data []byte) (*http.Response, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("POST request to endpoint", zap.String("endpoint", r.GetEndpoint(url)), zap.Any("headers", req.Header))
|
r.logDebug(req)
|
||||||
|
|
||||||
return c.Do(req)
|
return c.Do(req)
|
||||||
}
|
}
|
||||||
@@ -110,6 +130,12 @@ func (r *PanelRequest) HttpResponseCode() int {
|
|||||||
return r.Response.StatusCode
|
return r.Response.StatusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsRequestError(err error) bool {
|
||||||
|
_, ok := err.(*RequestError)
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
type RequestError struct {
|
type RequestError struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@@ -117,10 +143,14 @@ type RequestError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the error response in a string form that can be more easily consumed.
|
// Returns the error response in a string form that can be more easily consumed.
|
||||||
func (re *RequestError) String() string {
|
func (re *RequestError) Error() string {
|
||||||
return fmt.Sprintf("%s: %s (HTTP/%s)", re.Code, re.Detail, re.Status)
|
return fmt.Sprintf("%s: %s (HTTP/%s)", re.Code, re.Detail, re.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (re *RequestError) String() string {
|
||||||
|
return re.Error()
|
||||||
|
}
|
||||||
|
|
||||||
type RequestErrorBag struct {
|
type RequestErrorBag struct {
|
||||||
Errors []RequestError `json:"errors"`
|
Errors []RequestError `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|||||||
60
cmd/config_finder.go
Normal file
60
cmd/config_finder.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We've gone through a couple of iterations of where the configuration is stored. This
|
||||||
|
// helpful little function will look through the three areas it might have ended up, and
|
||||||
|
// return it.
|
||||||
|
//
|
||||||
|
// We only run this if the configuration flag for the instance is not actually passed in
|
||||||
|
// via the command line. Once found, the configuration is moved into the expected default
|
||||||
|
// location. Only errors are returned from this function, you can safely assume that after
|
||||||
|
// running this the configuration can be found in the correct default location.
|
||||||
|
func RelocateConfiguration() error {
|
||||||
|
var match string
|
||||||
|
check := []string{
|
||||||
|
config.DefaultLocation,
|
||||||
|
"/var/lib/pterodactyl/config.yml",
|
||||||
|
"/etc/wings/config.yml",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all of the configuration paths, and return which one we found, if
|
||||||
|
// any.
|
||||||
|
for _, p := range check {
|
||||||
|
if s, err := os.Stat(p); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !s.IsDir() {
|
||||||
|
match = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just return a generic not exist error at this point if we didn't have a match, this
|
||||||
|
// will allow the caller to handle displaying a more friendly error to the user. If we
|
||||||
|
// did match in the default location, go ahead and return successfully.
|
||||||
|
if match == "" {
|
||||||
|
return os.ErrNotExist
|
||||||
|
} else if match == config.DefaultLocation {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of this function simply creates the new default location and moves the
|
||||||
|
// old configuration file over to the new location, then sets the permissions on the
|
||||||
|
// file correctly so that only the user running this process can read it.
|
||||||
|
p, _ := filepath.Split(config.DefaultLocation)
|
||||||
|
if err := os.MkdirAll(p, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(match, config.DefaultLocation); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Chmod(config.DefaultLocation, 0600)
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/AlecAivazis/survey/v2/terminal"
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
"github.com/creasty/defaults"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -147,8 +146,8 @@ func configureCmdRun(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
b, err := ioutil.ReadAll(res.Body)
|
b, err := ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
cfg := new(config.Configuration)
|
cfg, err := config.NewFromPath(configPath)
|
||||||
if err := defaults.Set(cfg); err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
119
cmd/root.go
119
cmd/root.go
@@ -3,6 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/pterodactyl/wings/loggers/cli"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -62,11 +65,22 @@ func readConfiguration() (*config.Configuration, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func rootCmdRun(*cobra.Command, []string) {
|
func rootCmdRun(*cobra.Command, []string) {
|
||||||
// Profile wings in production!!!!
|
|
||||||
if shouldRunProfiler {
|
if shouldRunProfiler {
|
||||||
defer profile.Start().Stop()
|
defer profile.Start().Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only attempt configuration file relocation if a custom location has not
|
||||||
|
// been specified in the command startup.
|
||||||
|
if configPath == config.DefaultLocation {
|
||||||
|
if err := RelocateConfiguration(); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
exitWithConfigurationNotice()
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c, err := readConfiguration()
|
c, err := readConfiguration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -81,10 +95,10 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infof("using configuration from path: %s", c.GetPath())
|
log.WithField("path", c.GetPath()).Info("loading configuration from path")
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
zap.S().Debugw("running in debug mode")
|
log.Debug("running in debug mode")
|
||||||
zap.S().Infow("certificate checking is disabled")
|
log.Info("certificate checking is disabled")
|
||||||
|
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@@ -95,42 +109,47 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
config.SetDebugViaFlag(debug)
|
config.SetDebugViaFlag(debug)
|
||||||
|
|
||||||
if err := c.System.ConfigureDirectories(); err != nil {
|
if err := c.System.ConfigureDirectories(); err != nil {
|
||||||
zap.S().Panicw("failed to configure system directories for pterodactyl", zap.Error(err))
|
log.Fatal("failed to configure system directories for pterodactyl")
|
||||||
return
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.Username)
|
log.WithField("username", c.System.Username).Info("checking for pterodactyl system user")
|
||||||
if su, err := c.EnsurePterodactylUser(); err != nil {
|
if su, err := c.EnsurePterodactylUser(); err != nil {
|
||||||
zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err))
|
log.Error("failed to create pterodactyl system user")
|
||||||
|
panic(err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
zap.S().Infow("configured system user", zap.String("username", su.Username), zap.String("uid", su.Uid), zap.String("gid", su.Gid))
|
log.WithFields(log.Fields{
|
||||||
|
"username": su.Username,
|
||||||
|
"uid": su.Uid,
|
||||||
|
"gid": su.Gid,
|
||||||
|
}).Info("configured system user successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infow("beginning file permission setting on server data directories")
|
log.Info("beginning file permission setting on server data directories")
|
||||||
if err := c.EnsureFilePermissions(); err != nil {
|
if err := c.EnsureFilePermissions(); err != nil {
|
||||||
zap.S().Errorw("failed to properly chown data directories", zap.Error(err))
|
log.WithField("error", err).Error("failed to properly chown data directories")
|
||||||
} else {
|
} else {
|
||||||
zap.S().Infow("finished ensuring file permissions")
|
log.Info("finished ensuring file permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := server.LoadDirectory(); err != nil {
|
if err := server.LoadDirectory(); err != nil {
|
||||||
zap.S().Fatalw("failed to load server configurations", zap.Error(errors.WithStack(err)))
|
log.WithField("error", err).Fatal("failed to load server configurations")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := environment.ConfigureDocker(&c.Docker); err != nil {
|
if err := environment.ConfigureDocker(&c.Docker); err != nil {
|
||||||
zap.S().Fatalw("failed to configure docker environment", zap.Error(errors.WithStack(err)))
|
log.WithField("error", err).Fatal("failed to configure docker environment")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.WriteToDisk(); err != nil {
|
if err := c.WriteToDisk(); err != nil {
|
||||||
zap.S().Errorw("failed to save configuration to disk", zap.Error(errors.WithStack(err)))
|
log.WithField("error", err).Error("failed to save configuration to disk")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just for some nice log output.
|
// Just for some nice log output.
|
||||||
for _, s := range server.GetServers().All() {
|
for _, s := range server.GetServers().All() {
|
||||||
zap.S().Infow("loaded configuration for server", zap.String("server", s.Uuid))
|
log.WithField("server", s.Uuid).Info("loaded configuration for server")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new WaitGroup that limits us to 4 servers being bootstrapped at a time
|
// Create a new WaitGroup that limits us to 4 servers being bootstrapped at a time
|
||||||
@@ -142,18 +161,23 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
wg.Add()
|
wg.Add()
|
||||||
|
|
||||||
go func(s *server.Server) {
|
go func(s *server.Server) {
|
||||||
defer wg.Done()
|
// Required for tracing purposes.
|
||||||
|
var err error
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
s.Log().Trace("ensuring server environment exists").Stop(&err)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
// Create a server environment if none exists currently. This allows us to recover from Docker
|
// Create a server environment if none exists currently. This allows us to recover from Docker
|
||||||
// being reinstalled on the host system for example.
|
// being reinstalled on the host system for example.
|
||||||
zap.S().Infow("ensuring environment exists", zap.String("server", s.Uuid))
|
if err = s.Environment.Create(); err != nil {
|
||||||
if err := s.Environment.Create(); err != nil {
|
s.Log().WithField("error", err).Error("failed to process environment")
|
||||||
zap.S().Errorw("failed to create an environment for server", zap.String("server", s.Uuid), zap.Error(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := s.Environment.IsRunning()
|
r, err := s.Environment.IsRunning()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw("error checking server environment status", zap.String("server", s.Uuid), zap.Error(err))
|
s.Log().WithField("error", err).Error("error checking server environment status")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the server is currently running on Docker, mark the process as being in that state.
|
// If the server is currently running on Docker, mark the process as being in that state.
|
||||||
@@ -163,13 +187,9 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
// This will also validate that a server process is running if the last tracked state we have
|
// This will also validate that a server process is running if the last tracked state we have
|
||||||
// is that it was running, but we see that the container process is not currently running.
|
// is that it was running, but we see that the container process is not currently running.
|
||||||
if r || (!r && s.IsRunning()) {
|
if r || (!r && s.IsRunning()) {
|
||||||
zap.S().Infow("detected server is running, re-attaching to process", zap.String("server", s.Uuid))
|
s.Log().Info("detected server is running, re-attaching to process...")
|
||||||
if err := s.Environment.Start(); err != nil {
|
if err := s.Environment.Start(); err != nil {
|
||||||
zap.S().Warnw(
|
s.Log().WithField("error", errors.WithStack(err)).Warn("failed to properly start server detected as already running")
|
||||||
"failed to properly start server detected as already running",
|
|
||||||
zap.String("server", s.Uuid),
|
|
||||||
zap.Error(errors.WithStack(err)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -184,33 +204,37 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
// Wait until all of the servers are ready to go before we fire up the HTTP server.
|
// Wait until all of the servers are ready to go before we fire up the HTTP server.
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
// If the SFTP subsystem should be started, do so now.
|
// Initalize SFTP.
|
||||||
if c.System.Sftp.UseInternalSystem {
|
sftp.Initialize(c)
|
||||||
sftp.Initialize(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the archive directory exists.
|
// Ensure the archive directory exists.
|
||||||
if err := os.MkdirAll(c.System.ArchiveDirectory, 0755); err != nil {
|
if err := os.MkdirAll(c.System.ArchiveDirectory, 0755); err != nil {
|
||||||
zap.S().Errorw("failed to create archive directory", zap.Error(err))
|
log.WithField("error", err).Error("failed to create archive directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the backup directory exists.
|
// Ensure the backup directory exists.
|
||||||
if err := os.MkdirAll(c.System.BackupDirectory, 0755); err != nil {
|
if err := os.MkdirAll(c.System.BackupDirectory, 0755); err != nil {
|
||||||
zap.S().Errorw("failed to create backup directory", zap.Error(err))
|
log.WithField("error", err).Error("failed to create backup directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infow("configuring webserver", zap.Bool("ssl", c.Api.Ssl.Enabled), zap.String("host", c.Api.Host), zap.Int("port", c.Api.Port))
|
log.WithFields(log.Fields{
|
||||||
|
"ssl": c.Api.Ssl.Enabled,
|
||||||
|
"host": c.Api.Host,
|
||||||
|
"port": c.Api.Port,
|
||||||
|
}).Info("configuring webserver...")
|
||||||
|
|
||||||
r := router.Configure()
|
r := router.Configure()
|
||||||
addr := fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port)
|
addr := fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port)
|
||||||
|
|
||||||
if c.Api.Ssl.Enabled {
|
if c.Api.Ssl.Enabled {
|
||||||
if err := r.RunTLS(addr, c.Api.Ssl.CertificateFile, c.Api.Ssl.KeyFile); err != nil {
|
if err := r.RunTLS(addr, c.Api.Ssl.CertificateFile, c.Api.Ssl.KeyFile); err != nil {
|
||||||
zap.S().Fatalw("failed to configure HTTPS server", zap.Error(err))
|
log.WithField("error", err).Fatal("failed to configure HTTPS server")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := r.Run(addr); err != nil {
|
if err := r.Run(addr); err != nil {
|
||||||
zap.S().Fatalw("failed to configure HTTP server", zap.Error(err))
|
log.WithField("error", err).Fatal("failed to configure HTTP server")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,6 +264,9 @@ func configureLogging(debug bool) error {
|
|||||||
|
|
||||||
zap.ReplaceGlobals(logger)
|
zap.ReplaceGlobals(logger)
|
||||||
|
|
||||||
|
log.SetHandler(cli.Default)
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,3 +286,23 @@ func printLogo() {
|
|||||||
fmt.Println(`Copyright © 2018 - 2020 Dane Everitt & Contributors`)
|
fmt.Println(`Copyright © 2018 - 2020 Dane Everitt & Contributors`)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exitWithConfigurationNotice() {
|
||||||
|
fmt.Print(colorstring.Color(`
|
||||||
|
[_red_][white][bold]Error: Configuration File Not Found[reset]
|
||||||
|
|
||||||
|
Wings was not able to locate your configuration file, and therefore is not
|
||||||
|
able to complete its boot process.
|
||||||
|
|
||||||
|
Please ensure you have copied your instance configuration file into
|
||||||
|
the default location, or have provided the --config flag to use a
|
||||||
|
custom location.
|
||||||
|
|
||||||
|
Default Location: /etc/pterodactyl/config.yml
|
||||||
|
|
||||||
|
[yellow]This is not a bug with this software. Please do not make a bug report
|
||||||
|
for this issue, it will be closed.[reset]
|
||||||
|
|
||||||
|
`))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package config
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/cobaugh/osrelease"
|
"github.com/cobaugh/osrelease"
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
"go.uber.org/zap"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultLocation = "/var/lib/pterodactyl/config.yml"
|
const DefaultLocation = "/etc/pterodactyl/config.yml"
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
sync.RWMutex `json:"-" yaml:"-"`
|
sync.RWMutex `json:"-" yaml:"-"`
|
||||||
@@ -46,9 +46,9 @@ type Configuration struct {
|
|||||||
// validate against it.
|
// validate against it.
|
||||||
AuthenticationToken string `json:"token" yaml:"token"`
|
AuthenticationToken string `json:"token" yaml:"token"`
|
||||||
|
|
||||||
Api ApiConfiguration
|
Api ApiConfiguration `json:"api" yaml:"api"`
|
||||||
System SystemConfiguration
|
System SystemConfiguration `json:"system" yaml:"system"`
|
||||||
Docker DockerConfiguration
|
Docker DockerConfiguration `json:"docker" yaml:"docker"`
|
||||||
|
|
||||||
// The amount of time in seconds that should elapse between disk usage checks
|
// The amount of time in seconds that should elapse between disk usage checks
|
||||||
// run by the daemon. Setting a higher number can result in better IO performance
|
// run by the daemon. Setting a higher number can result in better IO performance
|
||||||
@@ -84,9 +84,6 @@ type Configuration struct {
|
|||||||
|
|
||||||
// Defines the configuration of the internal SFTP server.
|
// Defines the configuration of the internal SFTP server.
|
||||||
type SftpConfiguration struct {
|
type SftpConfiguration struct {
|
||||||
// If set to false, the internal SFTP server will not be booted and you will need
|
|
||||||
// to run the SFTP server independent of this program.
|
|
||||||
UseInternalSystem bool `default:"true" json:"use_internal" yaml:"use_internal"`
|
|
||||||
// If set to true disk checking will not be performed. This will prevent the SFTP
|
// If set to true disk checking will not be performed. This will prevent the SFTP
|
||||||
// server from checking the total size of a directory when uploading files.
|
// server from checking the total size of a directory when uploading files.
|
||||||
DisableDiskChecking bool `default:"false" yaml:"disable_disk_checking"`
|
DisableDiskChecking bool `default:"false" yaml:"disable_disk_checking"`
|
||||||
@@ -135,7 +132,7 @@ func ReadConfiguration(path string) (*Configuration, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Track the location where we created this configuration.
|
// Track the location where we created this configuration.
|
||||||
c.path = path
|
c.unsafeSetPath(path)
|
||||||
|
|
||||||
// Replace environment variables within the configuration file with their
|
// Replace environment variables within the configuration file with their
|
||||||
// values from the host system.
|
// values from the host system.
|
||||||
@@ -189,8 +186,32 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
|
|||||||
return _jwtAlgo
|
return _jwtAlgo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new struct and set the path where it should be stored.
|
||||||
|
func NewFromPath(path string) (*Configuration, error) {
|
||||||
|
c := new(Configuration)
|
||||||
|
if err := defaults.Set(c); err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.unsafeSetPath(path)
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the path where the configuration file is located on the server. This function should
|
||||||
|
// not be called except by processes that are generating the configuration such as the configration
|
||||||
|
// command shipped with this software.
|
||||||
|
func (c *Configuration) unsafeSetPath(path string) {
|
||||||
|
c.Lock()
|
||||||
|
c.path = path
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the path for this configuration file.
|
// Returns the path for this configuration file.
|
||||||
func (c *Configuration) GetPath() string {
|
func (c *Configuration) GetPath() string {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
return c.path
|
return c.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,11 +269,10 @@ func (c *Configuration) setSystemUser(u *user.User) error {
|
|||||||
gid, _ := strconv.Atoi(u.Gid)
|
gid, _ := strconv.Atoi(u.Gid)
|
||||||
|
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
c.System.Username = u.Username
|
c.System.Username = u.Username
|
||||||
c.System.User.Uid = uid
|
c.System.User.Uid = uid
|
||||||
c.System.User.Gid = gid
|
c.System.User.Gid = gid
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
return c.WriteToDisk()
|
return c.WriteToDisk()
|
||||||
}
|
}
|
||||||
@@ -299,7 +319,7 @@ func (c *Configuration) EnsureFilePermissions() error {
|
|||||||
gid, _ := strconv.Atoi(su.Gid)
|
gid, _ := strconv.Atoi(su.Gid)
|
||||||
|
|
||||||
if err := os.Chown(path.Join(c.System.Data, f.Name()), uid, gid); err != nil {
|
if err := os.Chown(path.Join(c.System.Data, f.Name()), uid, gid); err != nil {
|
||||||
zap.S().Warnw("failed to chown server directory", zap.String("directory", f.Name()), zap.Error(err))
|
log.WithField("error", err).WithField("directory", f.Name()).Warn("failed to chown server directory")
|
||||||
}
|
}
|
||||||
}(file)
|
}(file)
|
||||||
}
|
}
|
||||||
@@ -313,6 +333,10 @@ func (c *Configuration) EnsureFilePermissions() error {
|
|||||||
// lock on the file. This prevents something else from writing at the exact same time and
|
// lock on the file. This prevents something else from writing at the exact same time and
|
||||||
// leading to bad data conditions.
|
// leading to bad data conditions.
|
||||||
func (c *Configuration) WriteToDisk() error {
|
func (c *Configuration) WriteToDisk() error {
|
||||||
|
// Obtain an exclusive write against the configuration file.
|
||||||
|
c.writeLock.Lock()
|
||||||
|
defer c.writeLock.Unlock()
|
||||||
|
|
||||||
ccopy := *c
|
ccopy := *c
|
||||||
// If debugging is set with the flag, don't save that to the configuration file, otherwise
|
// If debugging is set with the flag, don't save that to the configuration file, otherwise
|
||||||
// you'll always end up in debug mode.
|
// you'll always end up in debug mode.
|
||||||
@@ -329,10 +353,6 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain an exclusive write against the configuration file.
|
|
||||||
c.writeLock.Lock()
|
|
||||||
defer c.writeLock.Unlock()
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type dockerNetworkInterfaces struct {
|
|||||||
type DockerNetworkConfiguration struct {
|
type DockerNetworkConfiguration struct {
|
||||||
// The interface that should be used to create the network. Must not conflict
|
// The interface that should be used to create the network. Must not conflict
|
||||||
// with any other interfaces in use by Docker or on the system.
|
// with any other interfaces in use by Docker or on the system.
|
||||||
Interface string `default:"172.18.0.1"`
|
Interface string `default:"172.18.0.1" json:"interface" yaml:"interface"`
|
||||||
|
|
||||||
// The DNS settings for containers.
|
// The DNS settings for containers.
|
||||||
Dns []string `default:"[\"1.1.1.1\", \"1.0.0.1\"]"`
|
Dns []string `default:"[\"1.1.1.1\", \"1.0.0.1\"]"`
|
||||||
@@ -26,6 +26,7 @@ type DockerNetworkConfiguration struct {
|
|||||||
Name string `default:"pterodactyl_nw"`
|
Name string `default:"pterodactyl_nw"`
|
||||||
ISPN bool `default:"false" yaml:"ispn"`
|
ISPN bool `default:"false" yaml:"ispn"`
|
||||||
Driver string `default:"bridge"`
|
Driver string `default:"bridge"`
|
||||||
|
Mode string `default:"pterodactyl_nw" yaml:"network_mode"`
|
||||||
IsInternal bool `default:"false" yaml:"is_internal"`
|
IsInternal bool `default:"false" yaml:"is_internal"`
|
||||||
EnableICC bool `default:"true" yaml:"enable_icc"`
|
EnableICC bool `default:"true" yaml:"enable_icc"`
|
||||||
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
|
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
|
||||||
@@ -44,7 +45,7 @@ type DockerConfiguration struct {
|
|||||||
UpdateImages bool `default:"true" json:"update_images" yaml:"update_images"`
|
UpdateImages bool `default:"true" json:"update_images" yaml:"update_images"`
|
||||||
|
|
||||||
// The location of the Docker socket.
|
// The location of the Docker socket.
|
||||||
Socket string `default:"/var/run/docker.sock"`
|
Socket string `default:"/var/run/docker.sock" json:"socket" yaml:"socket"`
|
||||||
|
|
||||||
// Defines the location of the timezone file on the host system that should
|
// Defines the location of the timezone file on the host system that should
|
||||||
// be mounted into the created containers so that they all use the same time.
|
// be mounted into the created containers so that they all use the same time.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go.uber.org/zap"
|
"github.com/apex/log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
@@ -51,33 +51,33 @@ type SystemConfiguration struct {
|
|||||||
// the user did not press the stop button, but the process stopped cleanly.
|
// the user did not press the stop button, but the process stopped cleanly.
|
||||||
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
|
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
|
||||||
|
|
||||||
Sftp *SftpConfiguration `yaml:"sftp"`
|
Sftp SftpConfiguration `yaml:"sftp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that all of the system directories exist on the system. These directories are
|
// Ensures that all of the system directories exist on the system. These directories are
|
||||||
// created so that only the owner can read the data, and no other users.
|
// created so that only the owner can read the data, and no other users.
|
||||||
func (sc *SystemConfiguration) ConfigureDirectories() error {
|
func (sc *SystemConfiguration) ConfigureDirectories() error {
|
||||||
zap.S().Debugw("ensuring root data directory exists", zap.String("path", sc.RootDirectory))
|
log.WithField("path", sc.RootDirectory).Debug("ensuring root data directory exists")
|
||||||
if err := os.MkdirAll(sc.RootDirectory, 0700); err != nil {
|
if err := os.MkdirAll(sc.RootDirectory, 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("ensuring log directory exists", zap.String("path", sc.LogDirectory))
|
log.WithField("path", sc.LogDirectory).Debug("ensuring log directory exists")
|
||||||
if err := os.MkdirAll(path.Join(sc.LogDirectory, "/install"), 0700); err != nil {
|
if err := os.MkdirAll(path.Join(sc.LogDirectory, "/install"), 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("ensuring server data directory exists", zap.String("path", sc.Data))
|
log.WithField("path", sc.Data).Debug("ensuring server data directory exists")
|
||||||
if err := os.MkdirAll(sc.Data, 0700); err != nil {
|
if err := os.MkdirAll(sc.Data, 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("ensuring archive data directory exists", zap.String("path", sc.ArchiveDirectory))
|
log.WithField("path", sc.ArchiveDirectory).Debug("ensuring archive data directory exists")
|
||||||
if err := os.MkdirAll(sc.ArchiveDirectory, 0700); err != nil {
|
if err := os.MkdirAll(sc.ArchiveDirectory, 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("ensuring backup data directory exists", zap.String("path", sc.BackupDirectory))
|
log.WithField("path", sc.BackupDirectory).Debug("ensuring backup data directory exists")
|
||||||
if err := os.MkdirAll(sc.BackupDirectory, 0700); err != nil {
|
if err := os.MkdirAll(sc.BackupDirectory, 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package environment
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/apex/log"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configures the required network for the docker environment.
|
// Configures the required network for the docker environment.
|
||||||
@@ -20,10 +20,10 @@ func ConfigureDocker(c *config.DockerConfiguration) error {
|
|||||||
|
|
||||||
resource, err := cli.NetworkInspect(context.Background(), c.Network.Name, types.NetworkInspectOptions{})
|
resource, err := cli.NetworkInspect(context.Background(), c.Network.Name, types.NetworkInspectOptions{})
|
||||||
if err != nil && client.IsErrNotFound(err) {
|
if err != nil && client.IsErrNotFound(err) {
|
||||||
zap.S().Infow("creating missing pterodactyl0 interface, this could take a few seconds...")
|
log.Info("creating missing pterodactyl0 interface, this could take a few seconds...")
|
||||||
return createDockerNetwork(cli, c)
|
return createDockerNetwork(cli, c)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
zap.S().Fatalw("failed to create required docker network for containers", zap.Error(err))
|
log.WithField("error", err).Fatal("failed to create required docker network for containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch resource.Driver {
|
switch resource.Driver {
|
||||||
|
|||||||
21
go.mod
21
go.mod
@@ -16,6 +16,7 @@ require (
|
|||||||
github.com/Jeffail/gabs/v2 v2.2.0
|
github.com/Jeffail/gabs/v2 v2.2.0
|
||||||
github.com/Microsoft/go-winio v0.4.7 // indirect
|
github.com/Microsoft/go-winio v0.4.7 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
|
github.com/apex/log v1.3.0
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
||||||
github.com/aws/aws-sdk-go v1.30.14 // indirect
|
github.com/aws/aws-sdk-go v1.30.14 // indirect
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
@@ -27,6 +28,7 @@ require (
|
|||||||
github.com/docker/docker v0.0.0-20180422163414-57142e89befe
|
github.com/docker/docker v0.0.0-20180422163414-57142e89befe
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.3.3 // indirect
|
github.com/docker/go-units v0.3.3 // indirect
|
||||||
|
github.com/fatih/color v1.9.0
|
||||||
github.com/gabriel-vasile/mimetype v0.1.4
|
github.com/gabriel-vasile/mimetype v0.1.4
|
||||||
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.0
|
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.0
|
||||||
github.com/ghodss/yaml v1.0.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
@@ -36,9 +38,11 @@ require (
|
|||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
|
||||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
|
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
|
||||||
|
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835
|
||||||
github.com/imdario/mergo v0.3.8
|
github.com/imdario/mergo v0.3.8
|
||||||
github.com/klauspost/pgzip v1.2.3
|
github.com/klauspost/pgzip v1.2.3
|
||||||
github.com/magiconair/properties v1.8.1
|
github.com/magiconair/properties v1.8.1
|
||||||
|
github.com/mattn/go-colorable v0.1.4
|
||||||
github.com/mattn/go-shellwords v1.0.10 // indirect
|
github.com/mattn/go-shellwords v1.0.10 // indirect
|
||||||
github.com/mholt/archiver/v3 v3.3.0
|
github.com/mholt/archiver/v3 v3.3.0
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
||||||
@@ -50,25 +54,24 @@ require (
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/profile v1.4.0
|
github.com/pkg/profile v1.4.0
|
||||||
github.com/pkg/sftp v1.10.1 // indirect
|
github.com/pkg/sftp v1.11.0 // indirect
|
||||||
github.com/pterodactyl/sftp-server v1.1.1
|
github.com/pterodactyl/sftp-server v1.1.2
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
github.com/spf13/cobra v0.0.7
|
github.com/spf13/cobra v0.0.7
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
github.com/stretchr/objx v0.2.0 // indirect
|
||||||
github.com/yuin/goldmark v1.1.30 // indirect
|
github.com/yuin/goldmark v1.1.30 // indirect
|
||||||
go.uber.org/atomic v1.5.1 // indirect
|
go.uber.org/zap v1.15.0
|
||||||
go.uber.org/multierr v1.4.0 // indirect
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 // indirect
|
||||||
go.uber.org/zap v1.13.0
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 // indirect
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
|
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect
|
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
||||||
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b // indirect
|
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/ini.v1 v1.51.0
|
gopkg.in/ini.v1 v1.51.0
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
gotest.tools v2.2.0+incompatible // indirect
|
gotest.tools v2.2.0+incompatible // indirect
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
63
go.sum
63
go.sum
@@ -18,11 +18,19 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
|||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
|
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
|
||||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
||||||
|
github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=
|
||||||
|
github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
|
||||||
|
github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
|
||||||
|
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
|
||||||
|
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
|
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go v1.30.14 h1:vZfX2b/fknc9wKcytbLWykM7in5k6dbQ8iHTJDUP1Ng=
|
github.com/aws/aws-sdk-go v1.30.14 h1:vZfX2b/fknc9wKcytbLWykM7in5k6dbQ8iHTJDUP1Ng=
|
||||||
github.com/aws/aws-sdk-go v1.30.14/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.30.14/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
|
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo=
|
||||||
|
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
@@ -59,6 +67,10 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gabriel-vasile/mimetype v0.1.4 h1:5mcsq3+DXypREUkW+1juhjeKmE/XnWgs+paHMJn7lf8=
|
github.com/gabriel-vasile/mimetype v0.1.4 h1:5mcsq3+DXypREUkW+1juhjeKmE/XnWgs+paHMJn7lf8=
|
||||||
@@ -73,6 +85,7 @@ github.com/gin-gonic/gin v1.6.2 h1:88crIK23zO6TqlQBt+f9FrPJNKm9ZEr7qjp9vl/d5TM=
|
|||||||
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
@@ -121,15 +134,20 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
|||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
||||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||||
|
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835 h1:f1irK5f03uGGj+FjgQfZ5VhdKNVQVJ4skHsedzVohQ4=
|
||||||
|
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk=
|
||||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
@@ -155,6 +173,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
||||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@@ -167,9 +186,14 @@ github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQ
|
|||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
|
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
|
||||||
@@ -195,6 +219,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
|||||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
@@ -216,6 +242,8 @@ github.com/pkg/sftp v1.8.3 h1:9jSe2SxTM8/3bXZjtqnkgTBW+lA8db0knZJyns7gpBA=
|
|||||||
github.com/pkg/sftp v1.8.3/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
|
github.com/pkg/sftp v1.8.3/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
|
||||||
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
|
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
|
github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI=
|
||||||
|
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
@@ -229,20 +257,27 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
|||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/pterodactyl/sftp-server v1.1.1 h1:IjuOy21BNZxfejKnXG1RgLxXAYylDqBVpbKZ6+fG5FQ=
|
github.com/pterodactyl/sftp-server v1.1.1 h1:IjuOy21BNZxfejKnXG1RgLxXAYylDqBVpbKZ6+fG5FQ=
|
||||||
github.com/pterodactyl/sftp-server v1.1.1/go.mod h1:b1VVWYv0RF9rxSZQqaD/rYXriiRMNPsbV//CKMXR4ag=
|
github.com/pterodactyl/sftp-server v1.1.1/go.mod h1:b1VVWYv0RF9rxSZQqaD/rYXriiRMNPsbV//CKMXR4ag=
|
||||||
|
github.com/pterodactyl/sftp-server v1.1.2 h1:5bI9upe0kBRn9ALDabn9S2GVU5gkYvSErYgs32dAKjk=
|
||||||
|
github.com/pterodactyl/sftp-server v1.1.2/go.mod h1:KjSONrenRr1oCh94QIVAU6yEzMe+Hd7r/JHrh5/oQHs=
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY=
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY=
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||||
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
@@ -263,6 +298,11 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||||
|
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
||||||
|
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||||
|
github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
|
||||||
|
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/uber-go/zap v1.9.1/go.mod h1:GY+83l3yxBcBw2kmHu/sAWwItnTn+ynxHCRo+WiIQOY=
|
github.com/uber-go/zap v1.9.1/go.mod h1:GY+83l3yxBcBw2kmHu/sAWwItnTn+ynxHCRo+WiIQOY=
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
@@ -285,11 +325,15 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
|
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
|
||||||
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||||
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
|
go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
|
||||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
|
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||||
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||||
@@ -297,9 +341,12 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
|
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||||
|
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -309,6 +356,8 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEB
|
|||||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
|
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
|
||||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||||
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
@@ -316,10 +365,13 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA
|
|||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@@ -344,18 +396,22 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
|
|||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
||||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI=
|
||||||
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
@@ -374,10 +430,13 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64
|
|||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU=
|
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU=
|
||||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM=
|
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM=
|
||||||
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY=
|
golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -392,12 +451,15 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
@@ -409,3 +471,4 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
|
|||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package installer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
@@ -108,24 +108,27 @@ func (i *Installer) Server() *server.Server {
|
|||||||
// associated installation process based on the parameters passed through for
|
// associated installation process based on the parameters passed through for
|
||||||
// the server instance.
|
// the server instance.
|
||||||
func (i *Installer) Execute() {
|
func (i *Installer) Execute() {
|
||||||
zap.S().Debugw("creating required server data directory", zap.String("server", i.Uuid()))
|
p := path.Join(config.Get().System.Data, i.Uuid())
|
||||||
if err := os.MkdirAll(path.Join(config.Get().System.Data, i.Uuid()), 0755); err != nil {
|
l := log.WithFields(log.Fields{"server": i.Uuid(), "process": "installer"})
|
||||||
zap.S().Errorw("failed to create server data directory", zap.String("server", i.Uuid()), zap.Error(errors.WithStack(err)))
|
|
||||||
|
l.WithField("path", p).Debug("creating required server data directory")
|
||||||
|
if err := os.MkdirAll(p, 0755); err != nil {
|
||||||
|
l.WithFields(log.Fields{"path": p, "error": errors.WithStack(err)}).Error("failed to create server data directory")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Chown(path.Join(config.Get().System.Data, i.Uuid()), config.Get().System.User.Uid, config.Get().System.User.Gid); err != nil {
|
if err := os.Chown(p, config.Get().System.User.Uid, config.Get().System.User.Gid); err != nil {
|
||||||
zap.S().Errorw("failed to chown server data directory", zap.String("server", i.Uuid()), zap.Error(errors.WithStack(err)))
|
l.WithField("error", errors.WithStack(err)).Error("failed to chown server data directory")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("creating required environment for server instance", zap.String("server", i.Uuid()))
|
l.Debug("creating required environment for server instance")
|
||||||
if err := i.server.Environment.Create(); err != nil {
|
if err := i.server.Environment.Create(); err != nil {
|
||||||
zap.S().Errorw("failed to create environment for server", zap.String("server", i.Uuid()), zap.Error(err))
|
l.WithField("error", err).Error("failed to create environment for server")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("created environment for server during install process", zap.String("server", i.Uuid()))
|
l.Info("successfully created environment for server during install process")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a string value from the JSON data provided.
|
// Returns a string value from the JSON data provided.
|
||||||
|
|||||||
93
loggers/cli/cli.go
Normal file
93
loggers/cli/cli.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/apex/log/handlers/cli"
|
||||||
|
color2 "github.com/fatih/color"
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Default = New(os.Stderr)
|
||||||
|
|
||||||
|
var bold = color2.New(color2.Bold)
|
||||||
|
|
||||||
|
var Strings = [...]string{
|
||||||
|
log.DebugLevel: "DEBUG",
|
||||||
|
log.InfoLevel: " INFO",
|
||||||
|
log.WarnLevel: " WARN",
|
||||||
|
log.ErrorLevel: "ERROR",
|
||||||
|
log.FatalLevel: "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
Writer io.Writer
|
||||||
|
Padding int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(w io.Writer) *Handler {
|
||||||
|
if f, ok := w.(*os.File); ok {
|
||||||
|
return &Handler{Writer: colorable.NewColorable(f), Padding: 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Handler{Writer: w, Padding: 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tracer interface {
|
||||||
|
StackTrace() errors.StackTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleLog implements log.Handler.
|
||||||
|
func (h *Handler) HandleLog(e *log.Entry) error {
|
||||||
|
color := cli.Colors[e.Level]
|
||||||
|
level := Strings[e.Level]
|
||||||
|
names := e.Fields.Names()
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
color.Fprintf(h.Writer, "%s: [%s] %-25s", bold.Sprintf("%*s", h.Padding+1, level), time.Now().Format(time.StampMilli), e.Message)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
if name == "source" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(h.Writer, " %s=%v", color.Sprint(name), e.Fields.Get(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(h.Writer)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
if name != "error" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, ok := e.Fields.Get("error").(error); ok {
|
||||||
|
var br = color2.New(color2.Bold, color2.FgRed)
|
||||||
|
|
||||||
|
if e, ok := errors.Cause(err).(tracer); ok {
|
||||||
|
st := e.StackTrace()
|
||||||
|
|
||||||
|
l := len(st)
|
||||||
|
if l > 5 {
|
||||||
|
l = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Stacktrace:"), st[0:l])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(h.Writer, "\n%s\n%+v\n\n", br.Sprintf("Stacktrace:"), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\n\nINVALID ERROR\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,13 +3,12 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/Jeffail/gabs/v2"
|
"github.com/Jeffail/gabs/v2"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/iancoleman/strcase"
|
"github.com/iancoleman/strcase"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -48,13 +47,14 @@ func readFileBytes(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gets the value of a key based on the value type defined.
|
// Gets the value of a key based on the value type defined.
|
||||||
func getKeyValue(value []byte) interface{} {
|
func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} {
|
||||||
if reflect.ValueOf(value).Kind() == reflect.Bool {
|
if cfr.ReplaceWith.Type() == jsonparser.Boolean {
|
||||||
v, _ := strconv.ParseBool(string(value))
|
v, _ := strconv.ParseBool(string(value))
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to parse into an int, if this fails just ignore the error and
|
// Try to parse into an int, if this fails just ignore the error and continue
|
||||||
|
// through, returning the string.
|
||||||
if v, err := strconv.Atoi(string(value)); err == nil {
|
if v, err := strconv.Atoi(string(value)); err == nil {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,9 @@ func getKeyValue(value []byte) interface{} {
|
|||||||
// configurations per-world (such as Spigot and Bungeecord) where we'll need to make
|
// configurations per-world (such as Spigot and Bungeecord) where we'll need to make
|
||||||
// adjustments to the bind address for the user.
|
// adjustments to the bind address for the user.
|
||||||
//
|
//
|
||||||
// This does not currently support nested matches. container.*.foo.*.bar will not work.
|
// This does not currently support nested wildcard matches. For example, foo.*.bar
|
||||||
|
// will work, however foo.*.bar.*.baz will not, since we'll only be splitting at the
|
||||||
|
// first wildcard, and not subsequent ones.
|
||||||
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
||||||
parsed, err := gabs.ParseJSON(data)
|
parsed, err := gabs.ParseJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,12 +96,12 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
|||||||
// If the child is a null value, nothing will happen. Seems reasonable as of the
|
// If the child is a null value, nothing will happen. Seems reasonable as of the
|
||||||
// time this code is being written.
|
// time this code is being written.
|
||||||
for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() {
|
for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() {
|
||||||
if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), value); err != nil {
|
if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), []byte(value)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = v.SetAtPathway(parsed, v.Match, value); err != nil {
|
if err = v.SetAtPathway(parsed, v.Match, []byte(value)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,11 +120,9 @@ func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path st
|
|||||||
// We're doing some regex here.
|
// We're doing some regex here.
|
||||||
r, err := regexp.Compile(strings.TrimPrefix(cfr.IfValue, "regex:"))
|
r, err := regexp.Compile(strings.TrimPrefix(cfr.IfValue, "regex:"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Warnw(
|
log.WithFields(log.Fields{"if_value": strings.TrimPrefix(cfr.IfValue, "regex:"), "error": err}).
|
||||||
"configuration if_value using invalid regexp, cannot do replacement",
|
Warn("configuration if_value using invalid regexp, cannot perform replacement")
|
||||||
zap.String("if_value", strings.TrimPrefix(cfr.IfValue, "regex:")),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,18 +143,18 @@ func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := c.SetP(getKeyValue(value), path)
|
_, err := c.SetP(cfr.getKeyValue(value), path)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks up a configuration value on the Daemon given a dot-notated syntax.
|
// Looks up a configuration value on the Daemon given a dot-notated syntax.
|
||||||
func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplacement) ([]byte, error) {
|
func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplacement) (string, error) {
|
||||||
// If this is not something that we can do a regex lookup on then just continue
|
// If this is not something that we can do a regex lookup on then just continue
|
||||||
// on our merry way. If the value isn't a string, we're not going to be doing anything
|
// on our merry way. If the value isn't a string, we're not going to be doing anything
|
||||||
// with it anyways.
|
// with it anyways.
|
||||||
if cfr.ReplaceWith.Type() != jsonparser.String || !configMatchRegex.Match(cfr.ReplaceWith.Value()) {
|
if cfr.ReplaceWith.Type() != jsonparser.String || !configMatchRegex.Match(cfr.ReplaceWith.Value()) {
|
||||||
return cfr.ReplaceWith.Value(), nil
|
return cfr.ReplaceWith.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a match, lookup the value in the configuration for the Daemon. If no key
|
// If there is a match, lookup the value in the configuration for the Daemon. If no key
|
||||||
@@ -165,11 +165,8 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac
|
|||||||
)
|
)
|
||||||
|
|
||||||
var path []string
|
var path []string
|
||||||
// The camel casing is important here, the configuration for the Daemon does not use
|
|
||||||
// JSON, and as such all of the keys will be generated in CamelCase format, rather than
|
|
||||||
// the expected snake_case from the old Daemon.
|
|
||||||
for _, value := range strings.Split(huntPath, ".") {
|
for _, value := range strings.Split(huntPath, ".") {
|
||||||
path = append(path, strcase.ToCamel(value))
|
path = append(path, strcase.ToSnake(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for the key in the configuration file, and if found return that value to the
|
// Look for the key in the configuration file, and if found return that value to the
|
||||||
@@ -177,21 +174,15 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac
|
|||||||
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return match, errors.WithStack(err)
|
return string(match), errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw(
|
log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist")
|
||||||
"attempted to load a configuration value that does not exist",
|
|
||||||
zap.Strings("path", path),
|
|
||||||
zap.String("filename", f.FileName),
|
|
||||||
)
|
|
||||||
|
|
||||||
// If there is no key, keep the original value intact, that way it is obvious there
|
// If there is no key, keep the original value intact, that way it is obvious there
|
||||||
// is a replace issue at play.
|
// is a replace issue at play.
|
||||||
return match, nil
|
return string(match), nil
|
||||||
} else {
|
} else {
|
||||||
replaced := []byte(configMatchRegex.ReplaceAllString(cfr.ReplaceWith.String(), string(match)))
|
return configMatchRegex.ReplaceAllString(cfr.ReplaceWith.String(), string(match)), nil
|
||||||
|
|
||||||
return replaced, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
112
parser/parser.go
112
parser/parser.go
@@ -3,16 +3,18 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/ghodss/yaml"
|
"github.com/icza/dyno"
|
||||||
"github.com/magiconair/properties"
|
"github.com/magiconair/properties"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +30,10 @@ const (
|
|||||||
|
|
||||||
type ConfigurationParser string
|
type ConfigurationParser string
|
||||||
|
|
||||||
|
func (cp ConfigurationParser) String() string {
|
||||||
|
return string(cp)
|
||||||
|
}
|
||||||
|
|
||||||
// Defines a configuration file for the server startup. These will be looped over
|
// Defines a configuration file for the server startup. These will be looped over
|
||||||
// and modified before the server finishes booting.
|
// and modified before the server finishes booting.
|
||||||
type ConfigurationFile struct {
|
type ConfigurationFile struct {
|
||||||
@@ -40,6 +46,40 @@ type ConfigurationFile struct {
|
|||||||
configuration []byte
|
configuration []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom unmarshaler for configuration files. If there is an error while parsing out the
|
||||||
|
// replacements, don't fail the entire operation, just log a global warning so someone can
|
||||||
|
// find the issue, and return an empty array of replacements.
|
||||||
|
//
|
||||||
|
// I imagine people will notice configuration replacement isn't working correctly and then
|
||||||
|
// the logs should help better expose that issue.
|
||||||
|
func (f *ConfigurationFile) UnmarshalJSON(data []byte) error {
|
||||||
|
var m map[string]*json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(*m["file"], &f.FileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(*m["parser"], &f.Parser); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(*m["replace"], &f.Replace); err != nil {
|
||||||
|
log.WithField("file", f.FileName).WithField("error", err).Warn("failed to unmarshal configuration file replacement")
|
||||||
|
|
||||||
|
f.Replace = []ConfigurationFileReplacement{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex to match paths such as foo[1].bar[2] and convert them into a format that
|
||||||
|
// gabs can work with, such as foo.1.bar.2 in this case. This is applied when creating
|
||||||
|
// the struct for the configuration file replacements.
|
||||||
|
var cfrMatchReplacement = regexp.MustCompile(`\[(\d+)]`)
|
||||||
|
|
||||||
// Defines a single find/replace instance for a given server configuration file.
|
// Defines a single find/replace instance for a given server configuration file.
|
||||||
type ConfigurationFileReplacement struct {
|
type ConfigurationFileReplacement struct {
|
||||||
Match string `json:"match"`
|
Match string `json:"match"`
|
||||||
@@ -52,22 +92,34 @@ type ConfigurationFileReplacement struct {
|
|||||||
func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
||||||
m, err := jsonparser.GetString(data, "match")
|
m, err := jsonparser.GetString(data, "match")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
}
|
}
|
||||||
cfr.Match = m
|
|
||||||
|
// See comment on the replacement regex to understand what exactly this is doing.
|
||||||
|
cfr.Match = cfrMatchReplacement.ReplaceAllString(m, ".$1")
|
||||||
|
|
||||||
iv, err := jsonparser.GetString(data, "if_value")
|
iv, err := jsonparser.GetString(data, "if_value")
|
||||||
// We only check keypath here since match & replace_with should be present on all of
|
// We only check keypath here since match & replace_with should be present on all of
|
||||||
// them, however if_value is optional.
|
// them, however if_value is optional.
|
||||||
if err != nil && err != jsonparser.KeyPathNotFoundError {
|
if err != nil && err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
}
|
}
|
||||||
cfr.IfValue = iv
|
cfr.IfValue = iv
|
||||||
|
|
||||||
rw, dt, _, err := jsonparser.Get(data, "replace_with")
|
rw, dt, _, err := jsonparser.Get(data, "replace_with")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, likely dealing with someone who forgot to upgrade their eggs, so in
|
||||||
|
// that case, fallback to using the old key which was "value".
|
||||||
|
rw, dt, _, err = jsonparser.Get(data, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfr.ReplaceWith = ReplaceValue{
|
cfr.ReplaceWith = ReplaceValue{
|
||||||
value: rw,
|
value: rw,
|
||||||
valueType: dt,
|
valueType: dt,
|
||||||
@@ -79,10 +131,13 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
|||||||
// Parses a given configuration file and updates all of the values within as defined
|
// Parses a given configuration file and updates all of the values within as defined
|
||||||
// in the API response from the Panel.
|
// in the API response from the Panel.
|
||||||
func (f *ConfigurationFile) Parse(path string, internal bool) error {
|
func (f *ConfigurationFile) Parse(path string, internal bool) error {
|
||||||
zap.S().Debugw("parsing configuration file", zap.String("path", path), zap.String("parser", string(f.Parser)))
|
log.WithField("path", path).WithField("parser", f.Parser.String()).Debug("parsing server configuration file")
|
||||||
|
|
||||||
mb, _ := json.Marshal(config.Get())
|
if mb, err := json.Marshal(config.Get()); err != nil {
|
||||||
f.configuration = mb
|
return err
|
||||||
|
} else {
|
||||||
|
f.configuration = mb
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -181,13 +236,13 @@ func (f *ConfigurationFile) parseXmlFile(path string) error {
|
|||||||
|
|
||||||
// Iterate over the elements we found and update their values.
|
// Iterate over the elements we found and update their values.
|
||||||
for _, element := range doc.FindElements(path) {
|
for _, element := range doc.FindElements(path) {
|
||||||
if xmlValueMatchRegex.Match(value) {
|
if xmlValueMatchRegex.MatchString(value) {
|
||||||
k := xmlValueMatchRegex.ReplaceAllString(string(value), "$1")
|
k := xmlValueMatchRegex.ReplaceAllString(value, "$1")
|
||||||
v := xmlValueMatchRegex.ReplaceAllString(string(value), "$2")
|
v := xmlValueMatchRegex.ReplaceAllString(value, "$2")
|
||||||
|
|
||||||
element.CreateAttr(k, v)
|
element.CreateAttr(k, v)
|
||||||
} else {
|
} else {
|
||||||
element.SetText(string(value))
|
element.SetText(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,12 +273,13 @@ func (f *ConfigurationFile) parseXmlFile(path string) error {
|
|||||||
// Parses an ini file.
|
// Parses an ini file.
|
||||||
func (f *ConfigurationFile) parseIniFile(path string) error {
|
func (f *ConfigurationFile) parseIniFile(path string) error {
|
||||||
// Ini package can't handle a non-existent file, so handle that automatically here
|
// Ini package can't handle a non-existent file, so handle that automatically here
|
||||||
// by creating it if not exists.
|
// by creating it if not exists. Then, immediately close the file since we will use
|
||||||
|
// other methods to write the new contents.
|
||||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644)
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
file.Close()
|
||||||
|
|
||||||
cfg, err := ini.Load(path)
|
cfg, err := ini.Load(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -258,24 +314,15 @@ func (f *ConfigurationFile) parseIniFile(path string) error {
|
|||||||
// If the key exists in the file go ahead and set the value, otherwise try to
|
// If the key exists in the file go ahead and set the value, otherwise try to
|
||||||
// create it in the section.
|
// create it in the section.
|
||||||
if s.HasKey(k) {
|
if s.HasKey(k) {
|
||||||
s.Key(k).SetValue(string(value))
|
s.Key(k).SetValue(value)
|
||||||
} else {
|
} else {
|
||||||
if _, err := s.NewKey(k, string(value)); err != nil {
|
if _, err := s.NewKey(k, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate the file before attempting to write the changes.
|
return cfg.SaveTo(path)
|
||||||
if err := os.Truncate(path, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := cfg.WriteTo(file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses a json file updating any matching key/value pairs. If a match is not found, the
|
// Parses a json file updating any matching key/value pairs. If a match is not found, the
|
||||||
@@ -304,10 +351,15 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i := make(map[string]interface{})
|
||||||
|
if err := yaml.Unmarshal(b, &i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Unmarshal the yaml data into a JSON interface such that we can work with
|
// Unmarshal the yaml data into a JSON interface such that we can work with
|
||||||
// any arbitrary data structure. If we don't do this, I can't use gabs which
|
// any arbitrary data structure. If we don't do this, I can't use gabs which
|
||||||
// makes working with unknown JSON signficiantly easier.
|
// makes working with unknown JSON signficiantly easier.
|
||||||
jsonBytes, err := yaml.YAMLToJSON(b)
|
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -320,7 +372,7 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remarshal the JSON into YAML format before saving it back to the disk.
|
// Remarshal the JSON into YAML format before saving it back to the disk.
|
||||||
marshaled, err := yaml.JSONToYAML(data.Bytes())
|
marshaled, err := yaml.Marshal(data.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -392,7 +444,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, err := p.Set(replace.Match, string(data)); err != nil {
|
if _, _, err := p.Set(replace.Match, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ func (cv *ReplaceValue) Value() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cv *ReplaceValue) String() string {
|
func (cv *ReplaceValue) String() string {
|
||||||
return string(cv.value)
|
str, _ := jsonparser.ParseString(cv.value)
|
||||||
|
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *ReplaceValue) Type() jsonparser.ValueType {
|
func (cv *ReplaceValue) Type() jsonparser.ValueType {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -40,6 +40,14 @@ func TrackedServerError(err error, s *server.Server) *RequestError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *RequestError) logger() *log.Entry {
|
||||||
|
if e.server != nil {
|
||||||
|
return e.server.Log().WithField("error_id", e.Uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return log.WithField("error_id", e.Uuid)
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the output message to display to the user in the error.
|
// Sets the output message to display to the user in the error.
|
||||||
func (e *RequestError) SetMessage(msg string) *RequestError {
|
func (e *RequestError) SetMessage(msg string) *RequestError {
|
||||||
e.Message = msg
|
e.Message = msg
|
||||||
@@ -61,19 +69,11 @@ func (e *RequestError) AbortWithStatus(status int, c *gin.Context) {
|
|||||||
|
|
||||||
// Otherwise, log the error to zap, and then report the error back to the user.
|
// Otherwise, log the error to zap, and then report the error back to the user.
|
||||||
if status >= 500 {
|
if status >= 500 {
|
||||||
if e.server != nil {
|
e.logger().WithField("error", e.Err).Error("encountered HTTP/500 error while handling request")
|
||||||
zap.S().Errorw("encountered error while handling HTTP request", zap.String("server", e.server.Uuid), zap.String("error_id", e.Uuid), zap.Error(e.Err))
|
|
||||||
} else {
|
|
||||||
zap.S().Errorw("encountered error while handling HTTP request", zap.String("error_id", e.Uuid), zap.Error(e.Err))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Error(errors.WithStack(e))
|
c.Error(errors.WithStack(e))
|
||||||
} else {
|
} else {
|
||||||
if e.server != nil {
|
e.logger().WithField("error", e.Err).Debug("encountered non-HTTP/500 error while handling request")
|
||||||
zap.S().Debugw("encountered error while handling HTTP request", zap.String("server", e.server.Uuid), zap.String("error_id", e.Uuid), zap.Error(e.Err))
|
|
||||||
} else {
|
|
||||||
zap.S().Debugw("encountered error while handling HTTP request", zap.String("error_id", e.Uuid), zap.Error(e.Err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := "An unexpected error was encountered while processing this request."
|
msg := "An unexpected error was encountered while processing this request."
|
||||||
|
|||||||
@@ -1,13 +1,32 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configures the routing infrastructure for this daemon instance.
|
// Configures the routing infrastructure for this daemon instance.
|
||||||
func Configure() *gin.Engine {
|
func Configure() *gin.Engine {
|
||||||
router := gin.Default()
|
gin.SetMode("release")
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
|
||||||
|
router.Use(gin.Recovery())
|
||||||
router.Use(SetAccessControlHeaders)
|
router.Use(SetAccessControlHeaders)
|
||||||
|
// @todo log this into a different file so you can setup IP blocking for abusive requests and such.
|
||||||
|
// This should still dump requests in debug mode since it does help with understanding the request
|
||||||
|
// lifecycle and quickly seeing what was called leading to the logs. However, it isn't feasible to mix
|
||||||
|
// this output in production and still get meaningful logs from it since they'll likely just be a huge
|
||||||
|
// spamfest.
|
||||||
|
router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"client_ip": params.ClientIP,
|
||||||
|
"status": params.StatusCode,
|
||||||
|
"latency": params.Latency,
|
||||||
|
}).Debugf("%s %s", params.MethodColor()+params.Method+params.ResetColor(), params.Path)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}))
|
||||||
|
|
||||||
router.OPTIONS("/api/system", func(c *gin.Context) {
|
router.OPTIONS("/api/system", func(c *gin.Context) {
|
||||||
c.Status(200)
|
c.Status(200)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -46,7 +46,10 @@ func postServerPower(c *gin.Context) {
|
|||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
var data server.PowerAction
|
var data server.PowerAction
|
||||||
c.BindJSON(&data)
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !data.IsValid() {
|
if !data.IsValid() {
|
||||||
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
||||||
@@ -71,15 +74,12 @@ func postServerPower(c *gin.Context) {
|
|||||||
// Pass the actual heavy processing off to a seperate thread to handle so that
|
// Pass the actual heavy processing off to a seperate thread to handle so that
|
||||||
// we can immediately return a response from the server. Some of these actions
|
// we can immediately return a response from the server. Some of these actions
|
||||||
// can take quite some time, especially stopping or restarting.
|
// can take quite some time, especially stopping or restarting.
|
||||||
go func() {
|
go func(server *server.Server) {
|
||||||
if err := s.HandlePowerAction(data); err != nil {
|
if err := server.HandlePowerAction(data); err != nil {
|
||||||
zap.S().Errorw(
|
server.Log().WithFields(log.Fields{"action": data, "error": err}).
|
||||||
"encountered an error processing a server power action",
|
Error("encountered error processing a server power action in the background")
|
||||||
zap.String("server", s.Uuid),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}()
|
}(s)
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
@@ -98,17 +98,17 @@ func postServerCommands(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var data struct{ Commands []string `json:"commands"` }
|
var data struct {
|
||||||
c.BindJSON(&data)
|
Commands []string `json:"commands"`
|
||||||
|
}
|
||||||
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, command := range data.Commands {
|
for _, command := range data.Commands {
|
||||||
if err := s.Environment.SendCommand(command); err != nil {
|
if err := s.Environment.SendCommand(command); err != nil {
|
||||||
zap.S().Warnw(
|
s.Log().WithFields(log.Fields{"command": command, "error": err}).Warn("failed to send command to server instance")
|
||||||
"failed to send command to server",
|
|
||||||
zap.String("server", s.Uuid),
|
|
||||||
zap.String("command", command),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +136,7 @@ func postServerInstall(c *gin.Context) {
|
|||||||
|
|
||||||
go func(serv *server.Server) {
|
go func(serv *server.Server) {
|
||||||
if err := serv.Install(); err != nil {
|
if err := serv.Install(); err != nil {
|
||||||
zap.S().Errorw(
|
serv.Log().WithField("error", err).Error("failed to execute server installation process")
|
||||||
"failed to execute server installation process",
|
|
||||||
zap.String("server", serv.Uuid),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
@@ -153,11 +149,7 @@ func postServerReinstall(c *gin.Context) {
|
|||||||
|
|
||||||
go func(serv *server.Server) {
|
go func(serv *server.Server) {
|
||||||
if err := serv.Reinstall(); err != nil {
|
if err := serv.Reinstall(); err != nil {
|
||||||
zap.S().Errorw(
|
serv.Log().WithField("error", err).Error("failed to complete server re-install process")
|
||||||
"failed to complete server reinstall process",
|
|
||||||
zap.String("server", serv.Uuid),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
@@ -172,10 +164,15 @@ func deleteServer(c *gin.Context) {
|
|||||||
// to start it while this process is running.
|
// to start it while this process is running.
|
||||||
s.Suspended = true
|
s.Suspended = true
|
||||||
|
|
||||||
|
// If the server is currently installing, abort it.
|
||||||
|
if s.IsInstalling() {
|
||||||
|
s.AbortInstallation()
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the server's archive if it exists. We intentionally don't return
|
// Delete the server's archive if it exists. We intentionally don't return
|
||||||
// here, if the archive fails to delete, the server can still be removed.
|
// here, if the archive fails to delete, the server can still be removed.
|
||||||
if err := s.Archiver.DeleteIfExists(); err != nil {
|
if err := s.Archiver.DeleteIfExists(); err != nil {
|
||||||
zap.S().Warnw("failed to delete server archive during deletion process", zap.String("server", s.Uuid), zap.Error(err))
|
s.Log().WithField("error", err).Warn("failed to delete server archive during deletion process")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsubscribe all of the event listeners.
|
// Unsubscribe all of the event listeners.
|
||||||
@@ -196,7 +193,10 @@ func deleteServer(c *gin.Context) {
|
|||||||
// so we don't want to block the HTTP call while waiting on this.
|
// so we don't want to block the HTTP call while waiting on this.
|
||||||
go func(p string) {
|
go func(p string) {
|
||||||
if err := os.RemoveAll(p); err != nil {
|
if err := os.RemoveAll(p); err != nil {
|
||||||
zap.S().Warnw("failed to remove server files during deletion process", zap.String("path", p), zap.Error(errors.WithStack(err)))
|
log.WithFields(log.Fields{
|
||||||
|
"path": p,
|
||||||
|
"error": errors.WithStack(err),
|
||||||
|
}).Warn("failed to remove server files during deletion process")
|
||||||
}
|
}
|
||||||
}(s.Filesystem.Path())
|
}(s.Filesystem.Path())
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"go.uber.org/zap"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +14,10 @@ func postServerBackup(c *gin.Context) {
|
|||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
data := &backup.Request{}
|
data := &backup.Request{}
|
||||||
c.BindJSON(&data)
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var adapter backup.BackupInterface
|
var adapter backup.BackupInterface
|
||||||
var err error
|
var err error
|
||||||
@@ -37,11 +39,10 @@ func postServerBackup(c *gin.Context) {
|
|||||||
|
|
||||||
go func(b backup.BackupInterface, serv *server.Server) {
|
go func(b backup.BackupInterface, serv *server.Server) {
|
||||||
if err := serv.Backup(b); err != nil {
|
if err := serv.Backup(b); err != nil {
|
||||||
zap.S().Errorw("failed to generate backup for server", zap.Error(err))
|
serv.Log().WithField("error", err).Error("failed to generate backup for server")
|
||||||
}
|
}
|
||||||
}(adapter, s)
|
}(adapter, s)
|
||||||
|
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,24 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the contents of a file on the server.
|
// Returns the contents of a file on the server.
|
||||||
func getServerFileContents(c *gin.Context) {
|
func getServerFileContents(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
cleaned, err := s.Filesystem.SafePath(c.Query("file"))
|
|
||||||
|
p, err := url.QueryUnescape(c.Query("file"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = "/" + strings.TrimLeft(p, "/")
|
||||||
|
|
||||||
|
cleaned, err := s.Filesystem.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
"error": "The file requested could not be found.",
|
"error": "The file requested could not be found.",
|
||||||
@@ -56,7 +66,13 @@ func getServerFileContents(c *gin.Context) {
|
|||||||
func getServerListDirectory(c *gin.Context) {
|
func getServerListDirectory(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
stats, err := s.Filesystem.ListDirectory(c.Query("directory"))
|
d, err := url.QueryUnescape(c.Query("directory"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := s.Filesystem.ListDirectory(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
@@ -69,11 +85,14 @@ func getServerListDirectory(c *gin.Context) {
|
|||||||
func putServerRenameFile(c *gin.Context) {
|
func putServerRenameFile(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
var data struct{
|
var data struct {
|
||||||
RenameFrom string `json:"rename_from"`
|
RenameFrom string `json:"rename_from"`
|
||||||
RenameTo string `json:"rename_to"`
|
RenameTo string `json:"rename_to"`
|
||||||
|
}
|
||||||
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
c.BindJSON(&data)
|
|
||||||
|
|
||||||
if data.RenameFrom == "" || data.RenameTo == "" {
|
if data.RenameFrom == "" || data.RenameTo == "" {
|
||||||
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
||||||
@@ -97,7 +116,10 @@ func postServerCopyFile(c *gin.Context) {
|
|||||||
var data struct {
|
var data struct {
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
}
|
}
|
||||||
c.BindJSON(&data)
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Filesystem.Copy(data.Location); err != nil {
|
if err := s.Filesystem.Copy(data.Location); err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
@@ -114,7 +136,10 @@ func postServerDeleteFile(c *gin.Context) {
|
|||||||
var data struct {
|
var data struct {
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
}
|
}
|
||||||
c.BindJSON(&data)
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Filesystem.Delete(data.Location); err != nil {
|
if err := s.Filesystem.Delete(data.Location); err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
@@ -128,7 +153,14 @@ func postServerDeleteFile(c *gin.Context) {
|
|||||||
func postServerWriteFile(c *gin.Context) {
|
func postServerWriteFile(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
if err := s.Filesystem.Writefile(c.Query("file"), c.Request.Body); err != nil {
|
f, err := url.QueryUnescape(c.Query("file"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f = "/" + strings.TrimLeft(f, "/")
|
||||||
|
|
||||||
|
if err := s.Filesystem.Writefile(f, c.Request.Body); err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -144,7 +176,10 @@ func postServerCreateDirectory(c *gin.Context) {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
c.BindJSON(&data)
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Filesystem.CreateDirectory(data.Name, data.Path); err != nil {
|
if err := s.Filesystem.CreateDirectory(data.Name, data.Path); err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
@@ -152,4 +187,4 @@ func postServerCreateDirectory(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
ws "github.com/gorilla/websocket"
|
ws "github.com/gorilla/websocket"
|
||||||
"github.com/pterodactyl/wings/router/websocket"
|
"github.com/pterodactyl/wings/router/websocket"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Upgrades a connection to a websocket and passes events along between.
|
// Upgrades a connection to a websocket and passes events along between.
|
||||||
@@ -40,7 +39,7 @@ func getServerWebsocket(c *gin.Context) {
|
|||||||
ws.CloseServiceRestart,
|
ws.CloseServiceRestart,
|
||||||
ws.CloseAbnormalClosure,
|
ws.CloseAbnormalClosure,
|
||||||
) {
|
) {
|
||||||
zap.S().Warnw("error handling websocket message", zap.Error(err))
|
s.Log().WithField("error", err).Warn("error handling websocket message for server")
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -53,8 +52,7 @@ func getServerWebsocket(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := handler.HandleInbound(j); err != nil {
|
if err := handler.HandleInbound(j); err != nil {
|
||||||
handler.SendErrorJson(err)
|
handler.SendErrorJson(j, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/installer"
|
"github.com/pterodactyl/wings/installer"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"go.uber.org/zap"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns information about the system that wings is running on.
|
// Returns information about the system that wings is running on.
|
||||||
@@ -59,11 +60,7 @@ func postCreateServer(c *gin.Context) {
|
|||||||
i.Execute()
|
i.Execute()
|
||||||
|
|
||||||
if err := i.Server().Install(); err != nil {
|
if err := i.Server().Install(); err != nil {
|
||||||
zap.S().Errorw(
|
log.WithFields(log.Fields{"server": i.Uuid(), "error": err}).Error("failed to run install process for server")
|
||||||
"failed to run install process for server",
|
|
||||||
zap.String("server", i.Uuid()),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}(install)
|
}(install)
|
||||||
|
|
||||||
@@ -77,7 +74,20 @@ func postUpdateConfiguration(c *gin.Context) {
|
|||||||
// A copy of the configuration we're using to bind the data recevied into.
|
// A copy of the configuration we're using to bind the data recevied into.
|
||||||
cfg := *config.Get()
|
cfg := *config.Get()
|
||||||
|
|
||||||
c.BindJSON(&cfg)
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
||||||
|
if err := c.BindJSON(&cfg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the SSL certificates the same since the Panel will send through Lets Encrypt
|
||||||
|
// default locations. However, if we picked a different location manually we don't
|
||||||
|
// want to override that.
|
||||||
|
//
|
||||||
|
// If you pass through manual locations in the API call this logic will be skipped.
|
||||||
|
if strings.HasPrefix(cfg.Api.Ssl.KeyFile, "/etc/letsencrypt/live/") {
|
||||||
|
cfg.Api.Ssl.KeyFile = ccopy.Api.Ssl.KeyFile
|
||||||
|
cfg.Api.Ssl.CertificateFile = ccopy.Api.Ssl.CertificateFile
|
||||||
|
}
|
||||||
|
|
||||||
config.Set(&cfg)
|
config.Set(&cfg)
|
||||||
if err := config.Get().WriteToDisk(); err != nil {
|
if err := config.Get().WriteToDisk(); err != nil {
|
||||||
@@ -90,4 +100,4 @@ func postUpdateConfiguration(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
@@ -9,7 +11,6 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -84,7 +85,6 @@ func (h *Handler) SendJson(v *Message) error {
|
|||||||
// If we're sending installation output but the user does not have the required
|
// If we're sending installation output but the user does not have the required
|
||||||
// permissions to see the output, don't send it down the line.
|
// permissions to see the output, don't send it down the line.
|
||||||
if v.Event == server.InstallOutputEvent {
|
if v.Event == server.InstallOutputEvent {
|
||||||
zap.S().Debugf("%+v", v.Args)
|
|
||||||
if !j.HasPermission(PermissionReceiveInstall) {
|
if !j.HasPermission(PermissionReceiveInstall) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -137,10 +137,7 @@ func (h *Handler) TokenValid() error {
|
|||||||
// Sends an error back to the connected websocket instance by checking the permissions
|
// Sends an error back to the connected websocket instance by checking the permissions
|
||||||
// of the token. If the user has the "receive-errors" grant we will send back the actual
|
// of the token. If the user has the "receive-errors" grant we will send back the actual
|
||||||
// error message, otherwise we just send back a standard error message.
|
// error message, otherwise we just send back a standard error message.
|
||||||
func (h *Handler) SendErrorJson(err error) error {
|
func (h *Handler) SendErrorJson(msg Message, err error) error {
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
|
|
||||||
j := h.GetJwt()
|
j := h.GetJwt()
|
||||||
|
|
||||||
message := "an unexpected error was encountered while handling this request"
|
message := "an unexpected error was encountered while handling this request"
|
||||||
@@ -154,15 +151,11 @@ func (h *Handler) SendErrorJson(err error) error {
|
|||||||
wsm.Args = []string{m}
|
wsm.Args = []string{m}
|
||||||
|
|
||||||
if !server.IsSuspendedError(err) {
|
if !server.IsSuspendedError(err) {
|
||||||
zap.S().Errorw(
|
h.server.Log().WithFields(log.Fields{"event": msg.Event, "error_identifier": u.String(), "error": err}).
|
||||||
"an error was encountered in the websocket process",
|
Error("failed to handle websocket process; an error was encountered processing an event")
|
||||||
zap.String("server", h.server.Uuid),
|
|
||||||
zap.String("error_identifier", u.String()),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.Connection.WriteJSON(wsm)
|
return h.unsafeSendJson(wsm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts an error message into a more readable representation and returns a UUID
|
// Converts an error message into a more readable representation and returns a UUID
|
||||||
@@ -193,7 +186,7 @@ func (h *Handler) GetJwt() *tokens.WebsocketPayload {
|
|||||||
func (h *Handler) HandleInbound(m Message) error {
|
func (h *Handler) HandleInbound(m Message) error {
|
||||||
if m.Event != AuthenticationEvent {
|
if m.Event != AuthenticationEvent {
|
||||||
if err := h.TokenValid(); err != nil {
|
if err := h.TokenValid(); err != nil {
|
||||||
zap.S().Debugw("jwt token is no longer valid", zap.String("message", err.Error()))
|
log.WithField("message", err.Error()).Debug("jwt for server websocket is no longer valid")
|
||||||
|
|
||||||
h.unsafeSendJson(Message{
|
h.unsafeSendJson(Message{
|
||||||
Event: ErrorEvent,
|
Event: ErrorEvent,
|
||||||
@@ -219,19 +212,57 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.HasPermission(PermissionConnect) {
|
// Check if the user has previously authenticated successfully.
|
||||||
h.setJwt(token)
|
newConnection := h.GetJwt() == nil
|
||||||
}
|
|
||||||
|
|
||||||
// On every authentication event, send the current server status back
|
// Previously there was a HasPermission(PermissionConnect) check around this,
|
||||||
// to the client. :)
|
// however NewTokenPayload will return an error if it doesn't have the connect
|
||||||
h.server.Events().Publish(server.StatusEvent, h.server.GetState())
|
// permission meaning that it was a redundant function call.
|
||||||
|
h.setJwt(token)
|
||||||
|
|
||||||
|
// Tell the client they authenticated successfully.
|
||||||
h.unsafeSendJson(Message{
|
h.unsafeSendJson(Message{
|
||||||
Event: AuthenticationSuccessEvent,
|
Event: AuthenticationSuccessEvent,
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check if the client was refreshing their authentication token
|
||||||
|
// instead of authenticating for the first time.
|
||||||
|
if !newConnection {
|
||||||
|
// This prevents duplicate status messages as outlined in
|
||||||
|
// https://github.com/pterodactyl/panel/issues/2077
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// On every authentication event, send the current server status back
|
||||||
|
// to the client. :)
|
||||||
|
state := h.server.GetState()
|
||||||
|
h.SendJson(&Message{
|
||||||
|
Event: server.StatusEvent,
|
||||||
|
Args: []string{state},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Only send the current disk usage if the server is offline, if docker container is running,
|
||||||
|
// Environment#EnableResourcePolling() will send this data to all clients.
|
||||||
|
if state == server.ProcessOfflineState {
|
||||||
|
_ = h.server.Filesystem.HasSpaceAvailable()
|
||||||
|
|
||||||
|
resources := server.ResourceUsage{
|
||||||
|
Memory: 0,
|
||||||
|
MemoryLimit: 0,
|
||||||
|
CpuAbsolute: 0.0,
|
||||||
|
Disk: h.server.Resources.Disk,
|
||||||
|
}
|
||||||
|
resources.Network.RxBytes = 0
|
||||||
|
resources.Network.TxBytes = 0
|
||||||
|
|
||||||
|
b, _ := json.Marshal(resources)
|
||||||
|
h.SendJson(&Message{
|
||||||
|
Event: server.StatsEvent,
|
||||||
|
Args: []string{string(b)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case SetStateEvent:
|
case SetStateEvent:
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"go.uber.org/zap"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
@@ -17,16 +17,15 @@ func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, suc
|
|||||||
rerr, err := r.SendBackupStatus(uuid, ad.ToRequest(successful))
|
rerr, err := r.SendBackupStatus(uuid, ad.ToRequest(successful))
|
||||||
if rerr != nil || err != nil {
|
if rerr != nil || err != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw(
|
s.Log().WithFields(log.Fields{
|
||||||
"failed to notify panel of backup status due to internal code error",
|
"backup": uuid,
|
||||||
zap.String("backup", s.Uuid),
|
"error": err,
|
||||||
zap.Error(err),
|
}).Error("failed to notify panel of backup status due to internal code error")
|
||||||
)
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Warnw(rerr.String(), zap.String("backup", uuid))
|
s.Log().WithField("backup", uuid).Warn(rerr.String())
|
||||||
|
|
||||||
return errors.New(rerr.String())
|
return errors.New(rerr.String())
|
||||||
}
|
}
|
||||||
@@ -66,7 +65,7 @@ func (s *Server) GetIncludedBackupFiles(ignored []string) (*backup.IncludedFiles
|
|||||||
// of the server files directory, and use that to generate the backup.
|
// of the server files directory, and use that to generate the backup.
|
||||||
if len(ignored) == 0 {
|
if len(ignored) == 0 {
|
||||||
if i, err := s.getServerwideIgnoredFiles(); err != nil {
|
if i, err := s.getServerwideIgnoredFiles(); err != nil {
|
||||||
zap.S().Warnw("failed to retrieve server ignored files", zap.String("server", s.Uuid), zap.Error(err))
|
s.Log().WithField("error", err).Warn("failed to retrieve ignored files listing for server")
|
||||||
} else {
|
} else {
|
||||||
ignored = i
|
ignored = i
|
||||||
}
|
}
|
||||||
@@ -86,9 +85,13 @@ func (s *Server) Backup(b backup.BackupInterface) error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.Generate(inc, s.Filesystem.Path()); err != nil {
|
ad, err := b.Generate(inc, s.Filesystem.Path())
|
||||||
|
if err != nil {
|
||||||
if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil {
|
if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil {
|
||||||
zap.S().Warnw("failed to notify panel of failed backup state", zap.String("backup", b.Identifier()), zap.Error(err))
|
s.Log().WithFields(log.Fields{
|
||||||
|
"backup": b.Identifier(),
|
||||||
|
"error": err,
|
||||||
|
}).Warn("failed to notify panel of failed backup state")
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@@ -96,7 +99,6 @@ func (s *Server) Backup(b backup.BackupInterface) error {
|
|||||||
|
|
||||||
// Try to notify the panel about the status of this backup. If for some reason this request
|
// Try to notify the panel about the status of this backup. If for some reason this request
|
||||||
// fails, delete the archive from the daemon and return that error up the chain to the caller.
|
// fails, delete the archive from the daemon and return that error up the chain to the caller.
|
||||||
ad := b.Details()
|
|
||||||
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
|
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
|
||||||
b.Remove()
|
b.Remove()
|
||||||
|
|
||||||
@@ -112,4 +114,4 @@ func (s *Server) Backup(b backup.BackupInterface) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package backup
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/apex/log"
|
||||||
gzip "github.com/klauspost/pgzip"
|
gzip "github.com/klauspost/pgzip"
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -67,7 +67,7 @@ func (a *Archive) Create(dest string, ctx context.Context) error {
|
|||||||
// Attempt to remove the archive if there is an error, report that error to
|
// Attempt to remove the archive if there is an error, report that error to
|
||||||
// the logger if it fails.
|
// the logger if it fails.
|
||||||
if rerr := os.Remove(dest); rerr != nil && !os.IsNotExist(rerr) {
|
if rerr := os.Remove(dest); rerr != nil && !os.IsNotExist(rerr) {
|
||||||
zap.S().Warnw("failed to delete corrupted backup archive", zap.String("location", dest))
|
log.WithField("location", dest).Warn("failed to delete corrupted backup archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package backup
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -49,7 +49,7 @@ type BackupInterface interface {
|
|||||||
|
|
||||||
// Generates a backup in whatever the configured source for the specific
|
// Generates a backup in whatever the configured source for the specific
|
||||||
// implementation is.
|
// implementation is.
|
||||||
Generate(*IncludedFiles, string) error
|
Generate(*IncludedFiles, string) (*ArchiveDetails, error)
|
||||||
|
|
||||||
// Returns the ignored files for this backup instance.
|
// Returns the ignored files for this backup instance.
|
||||||
Ignored() []string
|
Ignored() []string
|
||||||
@@ -121,7 +121,10 @@ func (b *Backup) Details() *ArchiveDetails {
|
|||||||
|
|
||||||
resp, err := b.Checksum()
|
resp, err := b.Checksum()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw("failed to calculate checksum for backup", zap.String("backup", b.Uuid), zap.Error(err))
|
log.WithFields(log.Fields{
|
||||||
|
"backup": b.Identifier(),
|
||||||
|
"error": err,
|
||||||
|
}).Error("failed to calculate checksum for backup")
|
||||||
}
|
}
|
||||||
|
|
||||||
checksum = hex.EncodeToString(resp)
|
checksum = hex.EncodeToString(resp)
|
||||||
|
|||||||
@@ -41,13 +41,15 @@ func (b *LocalBackup) Remove() error {
|
|||||||
|
|
||||||
// Generates a backup of the selected files and pushes it to the defined location
|
// Generates a backup of the selected files and pushes it to the defined location
|
||||||
// for this instance.
|
// for this instance.
|
||||||
func (b *LocalBackup) Generate(included *IncludedFiles, prefix string) error {
|
func (b *LocalBackup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) {
|
||||||
a := &Archive{
|
a := &Archive{
|
||||||
TrimPrefix: prefix,
|
TrimPrefix: prefix,
|
||||||
Files: included,
|
Files: included,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.Create(b.Path(), context.Background())
|
if err := a.Create(b.Path(), context.Background()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return b.Details(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package backup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -21,7 +22,9 @@ type S3Backup struct {
|
|||||||
|
|
||||||
var _ BackupInterface = (*S3Backup)(nil)
|
var _ BackupInterface = (*S3Backup)(nil)
|
||||||
|
|
||||||
func (s *S3Backup) Generate(included *IncludedFiles, prefix string) error {
|
// Generates a new backup on the disk, moves it into the S3 bucket via the provided
|
||||||
|
// presigned URL, and then deletes the backup from the disk.
|
||||||
|
func (s *S3Backup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) {
|
||||||
defer s.Remove()
|
defer s.Remove()
|
||||||
|
|
||||||
a := &Archive{
|
a := &Archive{
|
||||||
@@ -30,45 +33,26 @@ func (s *S3Backup) Generate(included *IncludedFiles, prefix string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := a.Create(s.Path(), context.Background()); err != nil {
|
if err := a.Create(s.Path(), context.Background()); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(s.PresignedUrl)
|
rc, err := os.Open(s.Path())
|
||||||
|
|
||||||
r, err := http.NewRequest(http.MethodPut, s.PresignedUrl, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
if sz, err := s.Size(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
r.ContentLength = sz
|
|
||||||
r.Header.Add("Content-Length", strconv.Itoa(int(sz)))
|
|
||||||
r.Header.Add("Content-Type", "application/x-gzip")
|
|
||||||
}
|
|
||||||
|
|
||||||
var rc io.ReadCloser
|
|
||||||
if f, err := os.Open(s.Path()); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
rc = f
|
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
r.Body = rc
|
if resp, err := s.generateRemoteRequest(rc); err != nil {
|
||||||
resp, err := http.DefaultClient.Do(r)
|
return nil, err
|
||||||
if err != nil {
|
} else {
|
||||||
return err
|
resp.Body.Close()
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
io.Copy(os.Stdout, resp.Body)
|
return nil, fmt.Errorf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status)
|
||||||
return fmt.Errorf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return s.Details(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes a backup from the system.
|
// Removes a backup from the system.
|
||||||
@@ -76,9 +60,27 @@ func (s *S3Backup) Remove() error {
|
|||||||
return os.Remove(s.Path())
|
return os.Remove(s.Path())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Backup) Details() *ArchiveDetails {
|
// Generates the remote S3 request and begins the upload.
|
||||||
return &ArchiveDetails{
|
func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) (*http.Response, error) {
|
||||||
Checksum: "checksum",
|
r, err := http.NewRequest(http.MethodPut, s.PresignedUrl, nil)
|
||||||
Size: 1024,
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sz, err := s.Size(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
r.ContentLength = sz
|
||||||
|
r.Header.Add("Content-Length", strconv.Itoa(int(sz)))
|
||||||
|
r.Header.Add("Content-Type", "application/x-gzip")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Body = rc
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"endpoint": s.PresignedUrl,
|
||||||
|
"headers": r.Header,
|
||||||
|
}).Debug("uploading backup to remote S3 endpoint")
|
||||||
|
|
||||||
|
return http.DefaultClient.Do(r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pterodactyl/wings/parser"
|
"github.com/pterodactyl/wings/parser"
|
||||||
"go.uber.org/zap"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,15 +16,15 @@ func (s *Server) UpdateConfigurationFiles() {
|
|||||||
go func(f parser.ConfigurationFile, server *Server) {
|
go func(f parser.ConfigurationFile, server *Server) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
p, err := s.Filesystem.SafePath(f.FileName)
|
p, err := server.Filesystem.SafePath(f.FileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw("failed to generate safe path for configuration file", zap.String("server", server.Uuid), zap.Error(err))
|
server.Log().WithField("error", err).Error("failed to generate safe path for configuration file")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.Parse(p, false); err != nil {
|
if err := f.Parse(p, false); err != nil {
|
||||||
zap.S().Errorw("failed to parse and update server configuration file", zap.String("server", server.Uuid), zap.Error(err))
|
server.Log().WithField("error", err).Error("failed to parse and update server configuration file")
|
||||||
}
|
}
|
||||||
}(v, s)
|
}(v, s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,15 +26,13 @@ type CrashDetection struct {
|
|||||||
//
|
//
|
||||||
// If the server is determined to have crashed, the process will be restarted and the
|
// If the server is determined to have crashed, the process will be restarted and the
|
||||||
// counter for the server will be incremented.
|
// counter for the server will be incremented.
|
||||||
//
|
|
||||||
// @todo output event to server console
|
|
||||||
func (s *Server) handleServerCrash() error {
|
func (s *Server) handleServerCrash() error {
|
||||||
// No point in doing anything here if the server isn't currently offline, there
|
// No point in doing anything here if the server isn't currently offline, there
|
||||||
// is no reason to do a crash detection event. If the server crash detection is
|
// is no reason to do a crash detection event. If the server crash detection is
|
||||||
// disabled we want to skip anything after this as well.
|
// disabled we want to skip anything after this as well.
|
||||||
if s.GetState() != ProcessOfflineState || !s.CrashDetection.Enabled {
|
if s.GetState() != ProcessOfflineState || !s.CrashDetection.Enabled {
|
||||||
if !s.CrashDetection.Enabled {
|
if !s.CrashDetection.Enabled {
|
||||||
zap.S().Debugw("server triggered crash detection but handler is disabled for server process", zap.String("server", s.Uuid))
|
s.Log().Debug("server triggered crash detection but handler is disabled for server process")
|
||||||
|
|
||||||
s.PublishConsoleOutputFromDaemon("Server detected as crashed; crash detection is disabled for this instance.")
|
s.PublishConsoleOutputFromDaemon("Server detected as crashed; crash detection is disabled for this instance.")
|
||||||
}
|
}
|
||||||
@@ -51,7 +48,7 @@ func (s *Server) handleServerCrash() error {
|
|||||||
// If the system is not configured to detect a clean exit code as a crash, and the
|
// If the system is not configured to detect a clean exit code as a crash, and the
|
||||||
// crash is not the result of the program running out of memory, do nothing.
|
// crash is not the result of the program running out of memory, do nothing.
|
||||||
if exitCode == 0 && !oomKilled && !config.Get().System.DetectCleanExitAsCrash {
|
if exitCode == 0 && !oomKilled && !config.Get().System.DetectCleanExitAsCrash {
|
||||||
zap.S().Debugw("server exited with successful code; system configured to not detect as crash", zap.String("server", s.Uuid))
|
s.Log().Debug("server exited with successful exit code; system is configured to not detect this as a crash")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
@@ -15,7 +16,6 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -122,11 +122,13 @@ func (d *DockerEnvironment) InSituUpdate() error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), time.Second * 10)
|
||||||
u := container.UpdateConfig{
|
u := container.UpdateConfig{
|
||||||
Resources: d.getResourcesForServer(),
|
Resources: d.getResourcesForServer(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := d.Client.ContainerUpdate(context.Background(), d.Server.Uuid, u); err != nil {
|
d.Server.Log().WithField("limits", fmt.Sprintf("%+v", u.Resources)).Debug("updating server container on-the-fly with passed limits")
|
||||||
|
if _, err := d.Client.ContainerUpdate(ctx, d.Server.Uuid, u); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +143,7 @@ func (d *DockerEnvironment) InSituUpdate() error {
|
|||||||
// state. This ensures that unexpected container deletion while Wings is running does
|
// state. This ensures that unexpected container deletion while Wings is running does
|
||||||
// not result in the server becoming unbootable.
|
// not result in the server becoming unbootable.
|
||||||
func (d *DockerEnvironment) OnBeforeStart() error {
|
func (d *DockerEnvironment) OnBeforeStart() error {
|
||||||
zap.S().Infow("syncing server configuration with Panel", zap.String("server", d.Server.Uuid))
|
d.Server.Log().Info("syncing server configuration with panel")
|
||||||
if err := d.Server.Sync(); err != nil {
|
if err := d.Server.Sync(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -182,6 +184,10 @@ func (d *DockerEnvironment) Start() error {
|
|||||||
// that point.
|
// that point.
|
||||||
defer func() {
|
defer func() {
|
||||||
if sawError {
|
if sawError {
|
||||||
|
// If we don't set it to stopping first, you'll trigger crash detection which
|
||||||
|
// we don't want to do at this point since it'll just immediately try to do the
|
||||||
|
// exact same action that lead to it crashing in the first place...
|
||||||
|
d.Server.SetState(ProcessStoppingState)
|
||||||
d.Server.SetState(ProcessOfflineState)
|
d.Server.SetState(ProcessOfflineState)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -248,8 +254,8 @@ func (d *DockerEnvironment) Start() error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := types.ContainerStartOptions{}
|
ctx, _ := context.WithTimeout(context.Background(), time.Second * 10)
|
||||||
if err := d.Client.ContainerStart(context.Background(), d.Server.Uuid, opts); err != nil {
|
if err := d.Client.ContainerStart(ctx, d.Server.Uuid, types.ContainerStartOptions{}); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,11 +349,21 @@ func (d *DockerEnvironment) Destroy() error {
|
|||||||
// Avoid crash detection firing off.
|
// Avoid crash detection firing off.
|
||||||
d.Server.SetState(ProcessStoppingState)
|
d.Server.SetState(ProcessStoppingState)
|
||||||
|
|
||||||
return d.Client.ContainerRemove(ctx, d.Server.Uuid, types.ContainerRemoveOptions{
|
err := d.Client.ContainerRemove(ctx, d.Server.Uuid, types.ContainerRemoveOptions{
|
||||||
RemoveVolumes: true,
|
RemoveVolumes: true,
|
||||||
RemoveLinks: false,
|
RemoveLinks: false,
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Don't trigger a destroy failure if we try to delete a container that does not
|
||||||
|
// exist on the system. We're just a step ahead of ourselves in that case.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/2001
|
||||||
|
if err != nil && client.IsErrNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the container exit state and return the exit code and wether or not
|
// Determine the container exit state and return the exit code and wether or not
|
||||||
@@ -408,7 +424,7 @@ func (d *DockerEnvironment) Attach() error {
|
|||||||
d.attached = true
|
d.attached = true
|
||||||
go func() {
|
go func() {
|
||||||
if err := d.EnableResourcePolling(); err != nil {
|
if err := d.EnableResourcePolling(); err != nil {
|
||||||
zap.S().Warnw("failed to enabled resource polling on server", zap.String("server", d.Server.Uuid), zap.Error(errors.WithStack(err)))
|
d.Server.Log().WithField("error", errors.WithStack(err)).Warn("failed to enable resource polling on server")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -456,7 +472,7 @@ func (d *DockerEnvironment) FollowConsoleOutput() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Err(); err != nil {
|
if err := s.Err(); err != nil {
|
||||||
zap.S().Warnw("error processing scanner line in console output", zap.String("server", d.Server.Uuid), zap.Error(err))
|
d.Server.Log().WithField("error", err).Warn("error processing scanner line in console output")
|
||||||
}
|
}
|
||||||
}(reader)
|
}(reader)
|
||||||
|
|
||||||
@@ -486,7 +502,7 @@ func (d *DockerEnvironment) EnableResourcePolling() error {
|
|||||||
|
|
||||||
if err := dec.Decode(&v); err != nil {
|
if err := dec.Decode(&v); err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
zap.S().Warnw("encountered error processing server stats; stopping collection", zap.Error(err))
|
d.Server.Log().WithField("error", err).Warn("encountered error processing server stats, stopping collection")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.DisableResourcePolling()
|
d.DisableResourcePolling()
|
||||||
@@ -537,17 +553,51 @@ func (d *DockerEnvironment) DisableResourcePolling() error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pulls the image from Docker.
|
// Pulls the image from Docker. If there is an error while pulling the image from the source
|
||||||
|
// but the image already exists locally, we will report that error to the logger but continue
|
||||||
|
// with the process.
|
||||||
|
//
|
||||||
|
// The reasoning behind this is that Quay has had some serious outages as of late, and we don't
|
||||||
|
// need to block all of the servers from booting just because of that. I'd imagine in a lot of
|
||||||
|
// cases an outage shouldn't affect users too badly. It'll at least keep existing servers working
|
||||||
|
// correctly if anything.
|
||||||
//
|
//
|
||||||
// @todo handle authorization & local images
|
// @todo handle authorization & local images
|
||||||
func (d *DockerEnvironment) ensureImageExists(c *client.Client) error {
|
func (d *DockerEnvironment) ensureImageExists(c *client.Client) error {
|
||||||
out, err := c.ImagePull(context.Background(), d.Server.Container.Image, types.ImagePullOptions{All: false})
|
// Give it up to 15 minutes to pull the image. I think this should cover 99.8% of cases where an
|
||||||
|
// image pull might fail. I can't imagine it will ever take more than 15 minutes to fully pull
|
||||||
|
// an image. Let me know when I am inevitably wrong here...
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), time.Minute*15)
|
||||||
|
|
||||||
|
out, err := c.ImagePull(ctx, d.Server.Container.Image, types.ImagePullOptions{All: false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
images, ierr := c.ImageList(ctx, types.ImageListOptions{})
|
||||||
|
if ierr != nil {
|
||||||
|
// Well damn, something has gone really wrong here, just go ahead and abort there
|
||||||
|
// isn't much anything we can do to try and self-recover from this.
|
||||||
|
return ierr
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, img := range images {
|
||||||
|
for _, t := range img.RepoTags {
|
||||||
|
if t == d.Server.Container.Image {
|
||||||
|
d.Server.Log().WithFields(log.Fields{
|
||||||
|
"image": d.Server.Container.Image,
|
||||||
|
"error": errors.New(err.Error()),
|
||||||
|
}).Warn("unable to pull requested image from remote source, however the image exists locally")
|
||||||
|
|
||||||
|
// Okay, we found a matching container image, in that case just go ahead and return
|
||||||
|
// from this function, since there is nothing else we need to do here.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
|
||||||
zap.S().Debugw("pulling docker image... this could take a bit of time", zap.String("image", d.Server.Container.Image))
|
log.WithField("image", d.Server.Container.Image).Debug("pulling docker image... this could take a bit of time")
|
||||||
|
|
||||||
// I'm not sure what the best approach here is, but this will block execution until the image
|
// I'm not sure what the best approach here is, but this will block execution until the image
|
||||||
// is done being pulled, which is what we need.
|
// is done being pulled, which is what we need.
|
||||||
@@ -655,7 +705,7 @@ func (d *DockerEnvironment) Create() error {
|
|||||||
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
|
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
|
||||||
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap",
|
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap",
|
||||||
},
|
},
|
||||||
NetworkMode: "pterodactyl_nw",
|
NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := cli.ContainerCreate(ctx, conf, hostConf, nil, d.Server.Uuid); err != nil {
|
if _, err := cli.ContainerCreate(ctx, conf, hostConf, nil, d.Server.Uuid); err != nil {
|
||||||
@@ -774,7 +824,7 @@ eloop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out = append(out, fmt.Sprintf("%s=\"%s\"", strings.ToUpper(k), v))
|
out = append(out, fmt.Sprintf("%s=%s", strings.ToUpper(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
ignore "github.com/sabhiram/go-gitignore"
|
ignore "github.com/sabhiram/go-gitignore"
|
||||||
"go.uber.org/zap"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -134,7 +133,7 @@ func (fs *Filesystem) HasSpaceAvailable() bool {
|
|||||||
// the cache once we've gotten it.
|
// the cache once we've gotten it.
|
||||||
size, err := fs.DirectorySize("/")
|
size, err := fs.DirectorySize("/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Warnw("failed to determine directory size", zap.String("server", fs.Server.Uuid), zap.Error(err))
|
fs.Server.Log().WithField("error", err).Warn("failed to determine root server directory size")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always cache the size, even if there is an error. We want to always return that value
|
// Always cache the size, even if there is an error. We want to always return that value
|
||||||
@@ -401,13 +400,12 @@ func (fs *Filesystem) Copy(p string) error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, err := os.Stat(cleaned); (err != nil && os.IsNotExist(err)) || s.IsDir() || !s.Mode().IsRegular() {
|
if s, err := os.Stat(cleaned); err != nil {
|
||||||
// For now I think I am okay just returning a nil response if the thing
|
return err
|
||||||
// we're trying to copy doesn't exist. Probably will want to come back and
|
} else if s.IsDir() || !s.Mode().IsRegular() {
|
||||||
// re-evaluate if this is a smart decision (I'm guessing not).
|
// If this is a directory or not a regular file, just throw a not-exist error
|
||||||
return nil
|
// since anything calling this function should understand what that means.
|
||||||
} else if err != nil {
|
return os.ErrNotExist
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base := filepath.Base(cleaned)
|
base := filepath.Base(cleaned)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
@@ -11,13 +12,14 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
"golang.org/x/sync/semaphore"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Executes the installation stack for a server process. Bubbles any errors up to the calling
|
// Executes the installation stack for a server process. Bubbles any errors up to the calling
|
||||||
@@ -25,14 +27,18 @@ import (
|
|||||||
func (s *Server) Install() error {
|
func (s *Server) Install() error {
|
||||||
err := s.internalInstall()
|
err := s.internalInstall()
|
||||||
|
|
||||||
zap.S().Debugw("notifying panel of server install state", zap.String("server", s.Uuid))
|
s.Log().Debug("notifying panel of server install state")
|
||||||
if serr := s.SyncInstallState(err == nil); serr != nil {
|
if serr := s.SyncInstallState(err == nil); serr != nil {
|
||||||
zap.S().Warnw(
|
l := s.Log().WithField("was_successful", err == nil)
|
||||||
"failed to notify panel of server install state",
|
|
||||||
zap.String("server", s.Uuid),
|
// If the request was successful but there was an error with this request, attach the
|
||||||
zap.Bool("was_successful", err == nil),
|
// error to this log entry. Otherwise ignore it in this log since whatever is calling
|
||||||
zap.Error(serr),
|
// this function should handle the error and will end up logging the same one.
|
||||||
)
|
if err == nil {
|
||||||
|
l.WithField("error", serr)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Warn("failed to notify panel of server install state")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -42,7 +48,7 @@ func (s *Server) Install() error {
|
|||||||
// does not touch any existing files for the server, other than what the script modifies.
|
// does not touch any existing files for the server, other than what the script modifies.
|
||||||
func (s *Server) Reinstall() error {
|
func (s *Server) Reinstall() error {
|
||||||
if s.GetState() != ProcessOfflineState {
|
if s.GetState() != ProcessOfflineState {
|
||||||
zap.S().Debugw("waiting for server instance to enter a stopped state", zap.String("server", s.Uuid))
|
s.Log().Debug("waiting for server instance to enter a stopped state")
|
||||||
if err := s.Environment.WaitForStop(10, true); err != nil {
|
if err := s.Environment.WaitForStop(10, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -67,14 +73,12 @@ func (s *Server) internalInstall() error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infow("beginning installation process for server", zap.String("server", s.Uuid))
|
s.Log().Info("beginning installation process for server")
|
||||||
|
|
||||||
if err := p.Run(); err != nil {
|
if err := p.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infow("completed installation process for server", zap.String("server", s.Uuid))
|
s.Log().Info("completed installation process for server")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +86,8 @@ type InstallationProcess struct {
|
|||||||
Server *Server
|
Server *Server
|
||||||
Script *api.InstallationScript
|
Script *api.InstallationScript
|
||||||
|
|
||||||
client *client.Client
|
client *client.Client
|
||||||
mutex *sync.Mutex
|
context context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a new installation process struct that will be used to create containers,
|
// Generates a new installation process struct that will be used to create containers,
|
||||||
@@ -92,24 +96,99 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
|
|||||||
proc := &InstallationProcess{
|
proc := &InstallationProcess{
|
||||||
Script: script,
|
Script: script,
|
||||||
Server: s,
|
Server: s,
|
||||||
mutex: &sync.Mutex{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
s.installer.cancel = &cancel
|
||||||
|
|
||||||
if c, err := client.NewClientWithOpts(client.FromEnv); err != nil {
|
if c, err := client.NewClientWithOpts(client.FromEnv); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
} else {
|
} else {
|
||||||
proc.client = c
|
proc.client = c
|
||||||
|
proc.context = ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
return proc, nil
|
return proc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to obtain an exclusive lock on the installation process for the server. Waits up to 10
|
||||||
|
// seconds before aborting with a context timeout.
|
||||||
|
func (s *Server) acquireInstallationLock() error {
|
||||||
|
if s.installer.sem == nil {
|
||||||
|
s.installer.sem = semaphore.NewWeighted(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
|
||||||
|
return s.installer.sem.Acquire(ctx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if the server is actively running the installation process by checking the status
|
||||||
|
// of the semaphore lock.
|
||||||
|
func (s *Server) IsInstalling() bool {
|
||||||
|
if s.installer.sem == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.installer.sem.TryAcquire(1) {
|
||||||
|
// If we made it into this block it means we were able to obtain an exclusive lock
|
||||||
|
// on the semaphore. In that case, go ahead and release that lock immediately, and
|
||||||
|
// return false.
|
||||||
|
s.installer.sem.Release(1)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aborts the server installation process by calling the cancel function on the installer
|
||||||
|
// context.
|
||||||
|
func (s *Server) AbortInstallation() {
|
||||||
|
if !s.IsInstalling() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.installer.cancel != nil {
|
||||||
|
cancel := *s.installer.cancel
|
||||||
|
|
||||||
|
s.Log().Warn("aborting running installation process")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes the installer container for the server.
|
||||||
|
func (ip *InstallationProcess) RemoveContainer() {
|
||||||
|
err := ip.client.ContainerRemove(ip.context, ip.Server.Uuid+"_installer", types.ContainerRemoveOptions{
|
||||||
|
RemoveVolumes: true,
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil && !client.IsErrNotFound(err) {
|
||||||
|
ip.Server.Log().WithField("error", errors.WithStack(err)).Warn("failed to delete server install container")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Runs the installation process, this is done as a backgrounded thread. This will configure
|
// Runs the installation process, this is done as a backgrounded thread. This will configure
|
||||||
// the required environment, and then spin up the installation container.
|
// the required environment, and then spin up the installation container.
|
||||||
//
|
//
|
||||||
// Once the container finishes installing the results will be stored in an installation
|
// Once the container finishes installing the results will be stored in an installation
|
||||||
// log in the server's configuration directory.
|
// log in the server's configuration directory.
|
||||||
func (ip *InstallationProcess) Run() error {
|
func (ip *InstallationProcess) Run() error {
|
||||||
|
ip.Server.Log().Debug("acquiring installation process lock")
|
||||||
|
if err := ip.Server.acquireInstallationLock(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now have an exclusive lock on this installation process. Ensure that whenever this
|
||||||
|
// process is finished that the semaphore is released so that other processes and be executed
|
||||||
|
// without encounting a wait timeout.
|
||||||
|
defer func() {
|
||||||
|
ip.Server.Log().Debug("releasing installation process lock")
|
||||||
|
ip.Server.installer.sem.Release(1)
|
||||||
|
ip.Server.installer.cancel = nil
|
||||||
|
}()
|
||||||
|
|
||||||
installPath, err := ip.BeforeExecute()
|
installPath, err := ip.BeforeExecute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -117,13 +196,15 @@ func (ip *InstallationProcess) Run() error {
|
|||||||
|
|
||||||
cid, err := ip.Execute(installPath)
|
cid, err := ip.Execute(installPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ip.RemoveContainer()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this step fails, log a warning but don't exit out of the process. This is completely
|
// If this step fails, log a warning but don't exit out of the process. This is completely
|
||||||
// internal to the daemon's functionality, and does not affect the status of the server itself.
|
// internal to the daemon's functionality, and does not affect the status of the server itself.
|
||||||
if err := ip.AfterExecute(cid); err != nil {
|
if err := ip.AfterExecute(cid); err != nil {
|
||||||
zap.S().Warnw("failed to complete after-execute step of installation process", zap.String("server", ip.Server.Uuid), zap.Error(err))
|
ip.Server.Log().WithField("error", err).Warn("failed to complete after-execute step of installation process")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -167,7 +248,7 @@ func (ip *InstallationProcess) writeScriptToDisk() (string, error) {
|
|||||||
|
|
||||||
// Pulls the docker image to be used for the installation container.
|
// Pulls the docker image to be used for the installation container.
|
||||||
func (ip *InstallationProcess) pullInstallationImage() error {
|
func (ip *InstallationProcess) pullInstallationImage() error {
|
||||||
r, err := ip.client.ImagePull(context.Background(), ip.Script.ContainerImage, types.ImagePullOptions{})
|
r, err := ip.client.ImagePull(ip.context, ip.Script.ContainerImage, types.ImagePullOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -175,7 +256,7 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
|||||||
// Block continuation until the image has been pulled successfully.
|
// Block continuation until the image has been pulled successfully.
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
zap.S().Debugw(scanner.Text())
|
log.Debug(scanner.Text())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
@@ -221,7 +302,7 @@ func (ip *InstallationProcess) BeforeExecute() (string, error) {
|
|||||||
Force: true,
|
Force: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ip.client.ContainerRemove(context.Background(), ip.Server.Uuid+"_installer", opts); err != nil {
|
if err := ip.client.ContainerRemove(ip.context, ip.Server.Uuid+"_installer", opts); err != nil {
|
||||||
if !client.IsErrNotFound(err) {
|
if !client.IsErrNotFound(err) {
|
||||||
e = append(e, err)
|
e = append(e, err)
|
||||||
}
|
}
|
||||||
@@ -248,10 +329,10 @@ func (ip *InstallationProcess) GetLogPath() string {
|
|||||||
// process to store in the server configuration directory, and then destroys the associated
|
// process to store in the server configuration directory, and then destroys the associated
|
||||||
// installation container.
|
// installation container.
|
||||||
func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
||||||
ctx := context.Background()
|
defer ip.RemoveContainer()
|
||||||
|
|
||||||
zap.S().Debugw("pulling installation logs for server", zap.String("server", ip.Server.Uuid), zap.String("container_id", containerId))
|
ip.Server.Log().WithField("container_id", containerId).Debug("pulling installation logs for server")
|
||||||
reader, err := ip.client.ContainerLogs(ctx, containerId, types.ContainerLogsOptions{
|
reader, err := ip.client.ContainerLogs(ip.context, containerId, types.ContainerLogsOptions{
|
||||||
ShowStdout: true,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
Follow: false,
|
Follow: false,
|
||||||
@@ -273,30 +354,11 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugw("removing server installation container", zap.String("server", ip.Server.Uuid), zap.String("container_id", containerId))
|
|
||||||
rErr := ip.client.ContainerRemove(ctx, containerId, types.ContainerRemoveOptions{
|
|
||||||
RemoveVolumes: true,
|
|
||||||
RemoveLinks: false,
|
|
||||||
Force: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if rErr != nil && !client.IsErrNotFound(rErr) {
|
|
||||||
return errors.WithStack(rErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executes the installation process inside a specially created docker container.
|
// Executes the installation process inside a specially created docker container.
|
||||||
func (ip *InstallationProcess) Execute(installPath string) (string, error) {
|
func (ip *InstallationProcess) Execute(installPath string) (string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
zap.S().Debugw(
|
|
||||||
"creating server installer container",
|
|
||||||
zap.String("server", ip.Server.Uuid),
|
|
||||||
zap.String("script_path", installPath+"/install.sh"),
|
|
||||||
)
|
|
||||||
|
|
||||||
conf := &container.Config{
|
conf := &container.Config{
|
||||||
Hostname: "installer",
|
Hostname: "installer",
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
@@ -331,7 +393,7 @@ func (ip *InstallationProcess) Execute(installPath string) (string, error) {
|
|||||||
Tmpfs: map[string]string{
|
Tmpfs: map[string]string{
|
||||||
"/tmp": "rw,exec,nosuid,size=50M",
|
"/tmp": "rw,exec,nosuid,size=50M",
|
||||||
},
|
},
|
||||||
DNS: []string{"1.1.1.1", "8.8.8.8"},
|
DNS: config.Get().Docker.Network.Dns,
|
||||||
LogConfig: container.LogConfig{
|
LogConfig: container.LogConfig{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Config: map[string]string{
|
Config: map[string]string{
|
||||||
@@ -341,37 +403,29 @@ func (ip *InstallationProcess) Execute(installPath string) (string, error) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Privileged: true,
|
Privileged: true,
|
||||||
NetworkMode: "pterodactyl_nw",
|
NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infow("creating installer container for server process", zap.String("server", ip.Server.Uuid))
|
ip.Server.Log().WithField("install_script", installPath+"/install.sh").Info("creating install container for server process")
|
||||||
r, err := ip.client.ContainerCreate(ctx, conf, hostConf, nil, ip.Server.Uuid+"_installer")
|
r, err := ip.client.ContainerCreate(ip.context, conf, hostConf, nil, ip.Server.Uuid+"_installer")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infow(
|
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
|
||||||
"running installation script for server in container",
|
if err := ip.client.ContainerStart(ip.context, r.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
zap.String("server", ip.Server.Uuid),
|
|
||||||
zap.String("container_id", r.ID),
|
|
||||||
)
|
|
||||||
if err := ip.client.ContainerStart(ctx, r.ID, types.ContainerStartOptions{}); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(id string) {
|
go func(id string) {
|
||||||
ip.Server.Events().Publish(DaemonMessageEvent, "Starting installation process, this could take a few minutes...")
|
ip.Server.Events().Publish(DaemonMessageEvent, "Starting installation process, this could take a few minutes...")
|
||||||
if err := ip.StreamOutput(id); err != nil {
|
if err := ip.StreamOutput(id); err != nil {
|
||||||
zap.S().Errorw(
|
ip.Server.Log().WithField("error", err).Error("error while handling output stream for server install process")
|
||||||
"error handling streaming output for server install process",
|
|
||||||
zap.String("container_id", id),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
|
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
|
||||||
}(r.ID)
|
}(r.ID)
|
||||||
|
|
||||||
sChann, eChann := ip.client.ContainerWait(ctx, r.ID, container.WaitConditionNotRunning)
|
sChann, eChann := ip.client.ContainerWait(ip.context, r.ID, container.WaitConditionNotRunning)
|
||||||
select {
|
select {
|
||||||
case err := <-eChann:
|
case err := <-eChann:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -387,7 +441,7 @@ func (ip *InstallationProcess) Execute(installPath string) (string, error) {
|
|||||||
// directory, as well as to a websocket listener so that the process can be viewed in
|
// directory, as well as to a websocket listener so that the process can be viewed in
|
||||||
// the panel by administrators.
|
// the panel by administrators.
|
||||||
func (ip *InstallationProcess) StreamOutput(id string) error {
|
func (ip *InstallationProcess) StreamOutput(id string) error {
|
||||||
reader, err := ip.client.ContainerLogs(context.Background(), id, types.ContainerLogsOptions{
|
reader, err := ip.client.ContainerLogs(ip.context, id, types.ContainerLogsOptions{
|
||||||
ShowStdout: true,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
Follow: true,
|
Follow: true,
|
||||||
@@ -405,12 +459,10 @@ func (ip *InstallationProcess) StreamOutput(id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Err(); err != nil {
|
if err := s.Err(); err != nil {
|
||||||
zap.S().Warnw(
|
ip.Server.Log().WithFields(log.Fields{
|
||||||
"error processing scanner line in installation output for server",
|
"container_id": id,
|
||||||
zap.String("server", ip.Server.Uuid),
|
"error": errors.WithStack(err),
|
||||||
zap.String("container_id", id),
|
}).Warn("error processing scanner line in installation output for server")
|
||||||
zap.Error(errors.WithStack(err)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"go.uber.org/zap"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,9 +28,10 @@ func (s *Server) onConsoleOutput(data string) {
|
|||||||
// set the server to that state. Only do this if the server is not currently stopped
|
// set the server to that state. Only do this if the server is not currently stopped
|
||||||
// or stopping.
|
// or stopping.
|
||||||
if s.GetState() == ProcessStartingState && strings.Contains(data, s.processConfiguration.Startup.Done) {
|
if s.GetState() == ProcessStartingState && strings.Contains(data, s.processConfiguration.Startup.Done) {
|
||||||
zap.S().Debugw(
|
s.Log().WithFields(log.Fields{
|
||||||
"detected server in running state based on line output", zap.String("match", s.processConfiguration.Startup.Done), zap.String("against", data),
|
"match": s.processConfiguration.Startup.Done,
|
||||||
)
|
"against": data,
|
||||||
|
}).Debug("detected server in running state based on console line output")
|
||||||
|
|
||||||
s.SetState(ProcessRunningState)
|
s.SetState(ProcessRunningState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"go.uber.org/zap"
|
"golang.org/x/sync/semaphore"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -71,11 +73,27 @@ type Server struct {
|
|||||||
// started, and then cached here.
|
// started, and then cached here.
|
||||||
processConfiguration *api.ProcessConfiguration
|
processConfiguration *api.ProcessConfiguration
|
||||||
|
|
||||||
|
// Tracks the installation process for this server and prevents a server from running
|
||||||
|
// two installer processes at the same time. This also allows us to cancel a running
|
||||||
|
// installation process, for example when a server is deleted from the panel while the
|
||||||
|
// installer process is still running.
|
||||||
|
installer InstallerDetails
|
||||||
|
|
||||||
// Internal mutex used to block actions that need to occur sequentially, such as
|
// Internal mutex used to block actions that need to occur sequentially, such as
|
||||||
// writing the configuration to the disk.
|
// writing the configuration to the disk.
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InstallerDetails struct {
|
||||||
|
// The cancel function for the installer. This will be a non-nil value while there
|
||||||
|
// is an installer running for the server.
|
||||||
|
cancel *context.CancelFunc
|
||||||
|
|
||||||
|
// Installer lock. You should obtain an exclusive lock on this context while running
|
||||||
|
// the installation process and release it when finished.
|
||||||
|
sem *semaphore.Weighted
|
||||||
|
}
|
||||||
|
|
||||||
// The build settings for a given server that impact docker container creation and
|
// The build settings for a given server that impact docker container creation and
|
||||||
// resource limits for a server instance.
|
// resource limits for a server instance.
|
||||||
type BuildSettings struct {
|
type BuildSettings struct {
|
||||||
@@ -194,13 +212,13 @@ func LoadDirectory() error {
|
|||||||
|
|
||||||
s, err := FromConfiguration(data)
|
s, err := FromConfiguration(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw("failed to load server, skipping...", zap.String("server", uuid), zap.Error(err))
|
log.WithField("server", uuid).WithField("error", err).Error("failed to load server, skipping...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if state, exists := states[s.Uuid]; exists {
|
if state, exists := states[s.Uuid]; exists {
|
||||||
s.SetState(state)
|
s.SetState(state)
|
||||||
zap.S().Debugw("loaded server state from cache", zap.String("server", s.Uuid), zap.String("state", s.GetState()))
|
s.Log().WithField("state", s.GetState()).Debug("loaded server state from cache file")
|
||||||
}
|
}
|
||||||
|
|
||||||
servers.Add(s)
|
servers.Add(s)
|
||||||
@@ -282,6 +300,10 @@ eloop:
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) Log() *log.Entry {
|
||||||
|
return log.WithField("server", s.Uuid)
|
||||||
|
}
|
||||||
|
|
||||||
// Syncs the state of the server on the Panel with Wings. This ensures that we're always
|
// Syncs the state of the server on the Panel with Wings. This ensures that we're always
|
||||||
// using the state of the server from the Panel and allows us to not require successful
|
// using the state of the server from the Panel and allows us to not require successful
|
||||||
// API calls to Wings to do things.
|
// API calls to Wings to do things.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -82,7 +81,7 @@ func (s *Server) SetState(state string) error {
|
|||||||
s.State = state
|
s.State = state
|
||||||
|
|
||||||
// Emit the event to any listeners that are currently registered.
|
// Emit the event to any listeners that are currently registered.
|
||||||
zap.S().Debugw("saw server status change event", zap.String("server", s.Uuid), zap.String("status", s.State))
|
s.Log().WithField("status", s.State).Debug("saw server status change event")
|
||||||
s.Events().Publish(StatusEvent, s.State)
|
s.Events().Publish(StatusEvent, s.State)
|
||||||
|
|
||||||
// Release the lock as it is no longer needed for the following actions.
|
// Release the lock as it is no longer needed for the following actions.
|
||||||
@@ -98,7 +97,7 @@ func (s *Server) SetState(state string) error {
|
|||||||
// to the disk should we forget to do it elsewhere.
|
// to the disk should we forget to do it elsewhere.
|
||||||
go func() {
|
go func() {
|
||||||
if err := saveServerStates(); err != nil {
|
if err := saveServerStates(); err != nil {
|
||||||
zap.S().Warnw("failed to write server states to disk", zap.Error(err))
|
s.Log().WithField("error", err).Warn("failed to write server states to disk")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -111,14 +110,14 @@ func (s *Server) SetState(state string) error {
|
|||||||
// separate thread as to not block any actions currently taking place in the flow
|
// separate thread as to not block any actions currently taking place in the flow
|
||||||
// that called this function.
|
// that called this function.
|
||||||
if (prevState == ProcessStartingState || prevState == ProcessRunningState) && s.GetState() == ProcessOfflineState {
|
if (prevState == ProcessStartingState || prevState == ProcessRunningState) && s.GetState() == ProcessOfflineState {
|
||||||
zap.S().Infow("detected server as entering a potentially crashed state; running handler", zap.String("server", s.Uuid))
|
s.Log().Info("detected server as entering a crashed state; running crash handler")
|
||||||
|
|
||||||
go func(server *Server) {
|
go func(server *Server) {
|
||||||
if err := server.handleServerCrash(); err != nil {
|
if err := server.handleServerCrash(); err != nil {
|
||||||
if IsTooFrequentCrashError(err) {
|
if IsTooFrequentCrashError(err) {
|
||||||
zap.S().Infow("did not restart server after crash; occurred too soon after last", zap.String("server", server.Uuid))
|
server.Log().Info("did not restart server after crash; occurred too soon after the last")
|
||||||
} else {
|
} else {
|
||||||
zap.S().Errorw("failed to handle server crash state", zap.String("server", server.Uuid), zap.Error(err))
|
server.Log().WithField("error", err).Error("failed to handle server crash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Merges data passed through in JSON form into the existing server object.
|
// Merges data passed through in JSON form into the existing server object.
|
||||||
@@ -34,6 +33,16 @@ func (s *Server) UpdateDataStructure(data []byte, background bool) error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't explode if we're setting CPU limits to 0. Mergo sees that as an empty value
|
||||||
|
// so it won't override the value we've passed through in the API call. However, we can
|
||||||
|
// safely assume that we're passing through valid data structures here. I foresee this
|
||||||
|
// backfiring at some point, but until then...
|
||||||
|
//
|
||||||
|
// We'll go ahead and do this with swap as well.
|
||||||
|
s.Build.CpuLimit = src.Build.CpuLimit
|
||||||
|
s.Build.Swap = src.Build.Swap
|
||||||
|
s.Build.DiskSpace = src.Build.DiskSpace
|
||||||
|
|
||||||
// Mergo can't quite handle this boolean value correctly, so for now we'll just
|
// Mergo can't quite handle this boolean value correctly, so for now we'll just
|
||||||
// handle this edge case manually since none of the other data passed through in this
|
// handle this edge case manually since none of the other data passed through in this
|
||||||
// request is going to be boolean. Allegedly.
|
// request is going to be boolean. Allegedly.
|
||||||
@@ -81,12 +90,9 @@ func (s *Server) runBackgroundActions() {
|
|||||||
// Update the environment in place, allowing memory and CPU usage to be adjusted
|
// Update the environment in place, allowing memory and CPU usage to be adjusted
|
||||||
// on the fly without the user needing to reboot (theoretically).
|
// on the fly without the user needing to reboot (theoretically).
|
||||||
go func(server *Server) {
|
go func(server *Server) {
|
||||||
|
server.Log().Info("performing server limit modification on-the-fly")
|
||||||
if err := server.Environment.InSituUpdate(); err != nil {
|
if err := server.Environment.InSituUpdate(); err != nil {
|
||||||
zap.S().Warnw(
|
server.Log().WithField("error", err).Warn("failed to perform on-the-fly update of the server environment")
|
||||||
"failed to perform in-situ update of server environment",
|
|
||||||
zap.String("server", server.Uuid),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
@@ -94,14 +100,10 @@ func (s *Server) runBackgroundActions() {
|
|||||||
// yet, do it immediately.
|
// yet, do it immediately.
|
||||||
go func(server *Server) {
|
go func(server *Server) {
|
||||||
if server.Suspended && server.GetState() != ProcessOfflineState {
|
if server.Suspended && server.GetState() != ProcessOfflineState {
|
||||||
zap.S().Infow("server suspended with running process state, terminating now", zap.String("server", server.Uuid))
|
server.Log().Info("server suspended with running process state, terminating now")
|
||||||
|
|
||||||
if err := server.Environment.WaitForStop(10, true); err != nil {
|
if err := server.Environment.WaitForStop(10, true); err != nil {
|
||||||
zap.S().Warnw(
|
server.Log().WithField("error", err).Warn("failed to terminate server environment after suspension")
|
||||||
"failed to stop server environment after seeing suspension",
|
|
||||||
zap.String("server", server.Uuid),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package sftp
|
package sftp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/sftp-server"
|
"github.com/pterodactyl/sftp-server"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Initialize(config *config.Configuration) error {
|
func Initialize(config *config.Configuration) error {
|
||||||
@@ -21,8 +21,6 @@ func Initialize(config *config.Configuration) error {
|
|||||||
ReadOnly: config.System.Sftp.ReadOnly,
|
ReadOnly: config.System.Sftp.ReadOnly,
|
||||||
BindAddress: config.System.Sftp.Address,
|
BindAddress: config.System.Sftp.Address,
|
||||||
BindPort: config.System.Sftp.Port,
|
BindPort: config.System.Sftp.Port,
|
||||||
ServerDataFolder: path.Join(config.System.Data, "/servers"),
|
|
||||||
DisableDiskCheck: config.System.Sftp.DisableDiskChecking,
|
|
||||||
},
|
},
|
||||||
CredentialValidator: validateCredentials,
|
CredentialValidator: validateCredentials,
|
||||||
PathValidator: validatePath,
|
PathValidator: validatePath,
|
||||||
@@ -41,7 +39,7 @@ func Initialize(config *config.Configuration) error {
|
|||||||
// a long running operation.
|
// a long running operation.
|
||||||
go func(instance *sftp_server.Server) {
|
go func(instance *sftp_server.Server) {
|
||||||
if err := c.Initalize(); err != nil {
|
if err := c.Initalize(); err != nil {
|
||||||
zap.S().Named("sftp").Errorw("failed to initialize SFTP subsystem", zap.Error(errors.WithStack(err)))
|
log.WithField("subsystem", "sftp").WithField("error", errors.WithStack(err)).Error("failed to initialize SFTP subsystem")
|
||||||
}
|
}
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
@@ -76,6 +74,8 @@ func validateDiskSpace(fs sftp_server.FileSystem) bool {
|
|||||||
// the server's UUID if the credentials were valid.
|
// the server's UUID if the credentials were valid.
|
||||||
func validateCredentials(c sftp_server.AuthenticationRequest) (*sftp_server.AuthenticationResponse, error) {
|
func validateCredentials(c sftp_server.AuthenticationRequest) (*sftp_server.AuthenticationResponse, error) {
|
||||||
resp, err := api.NewRequester().ValidateSftpCredentials(c)
|
resp, err := api.NewRequester().ValidateSftpCredentials(c)
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"subsystem": "sftp", "username": c.User}).Debug("validating credentials for SFTP connection")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@@ -85,8 +85,10 @@ func validateCredentials(c sftp_server.AuthenticationRequest) (*sftp_server.Auth
|
|||||||
})
|
})
|
||||||
|
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return resp, errors.New("no server found with that UUID")
|
return resp, errors.New("no matching server with UUID found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Log().WithFields(log.Fields{"subsystem": "sftp", "username": c.User}).Debug("matched user to server instance, credentials successfully validated")
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package system
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// The current version of this software.
|
// The current version of this software.
|
||||||
Version = "0.0.1"
|
Version = "1.0.0-beta.7"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user