Compare commits

...

43 Commits

Author SHA1 Message Date
Dane Everitt
4c3d4f112b Bump for release 2020-06-29 20:57:01 -07:00
Dane Everitt
d6a3d9adb1 Don't obliterate custom SSL locations if defined
closes pterodactyl/panel#2121
2020-06-29 20:56:13 -07:00
Dane Everitt
d284c4aec9 Fix lock obtainment to avoid freeze 2020-06-29 20:42:26 -07:00
Dane Everitt
05a4730489 Fix configuration file saving to disk using the config command
closes pterodactyl/panel#2135
2020-06-29 20:33:54 -07:00
Dane Everitt
2dad3102e0 Fix saving of ini configuration files to the disk 2020-06-29 20:21:41 -07:00
Dane Everitt
b33f14ddd9 Correctly handle replacements with escaped values; closes #2041 2020-06-29 20:08:36 -07:00
Dane Everitt
1f6789cba3 Acquire exclusive lock when installing a server
Also allows aborting a server install mid-process when the server is deleted before the process finishes.
2020-06-22 21:38:16 -07:00
Dane Everitt
073247e4e1 Use 15 minute context timeout for pulling, not 10 seconds... closes #2130 2020-06-22 20:56:55 -07:00
Dane Everitt
a3d83d23bd Don't try to send space available when loading from a configuration
Server is not always installed when this function is called, this will cause errors in those cases.
2020-06-22 20:52:23 -07:00
Dane Everitt
f318962371 Ensure that more error stacks get recorded 2020-06-22 20:51:52 -07:00
Dane Everitt
db31722cfc Don't cause a double stacktrace on certain errors 2020-06-22 20:51:41 -07:00
Dane Everitt
d91de3d912 Fix makefile 2020-06-18 21:05:00 -07:00
Dane Everitt
495ad4defd Cleaner gin logging in debug 2020-06-13 10:54:38 -07:00
Dane Everitt
b03aa20c8d Always use release mode 2020-06-13 10:45:52 -07:00
Dane Everitt
7d4a8d7f7e Update everything expect transfers & sftp to not use zap 2020-06-13 10:40:26 -07:00
Dane Everitt
65b1b96b06 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-06-13 10:26:37 -07:00
Dane Everitt
198a22f446 Replace even more zap occurences 2020-06-13 10:26:35 -07:00
Dane Everitt
e1531802cf Merge pull request #35 from pterodactyl/issue/2077
Fix multiple server status messages
2020-06-11 20:52:15 -07:00
Matthew Penner
5c2686fc6d Fix multiple server status messages 2020-06-10 15:00:59 -06:00
Dane Everitt
0ae286d617 Correctly handle empty values from the API requests; mergo by default thinks these "empty" values should be skipped 2020-05-31 12:42:10 -07:00
Dane Everitt
62e5547c6d Merge pull request #32 from pterodactyl/issue/1796
Send disk usage when server is offline
2020-05-31 10:54:21 -07:00
Dane Everitt
00a026c2a5 Merge pull request #34 from rahonavis/2078-bindjson
#2078 - fix BindJSON calls
2020-05-30 10:21:11 -07:00
Carlo Field
359564bd91 #2078 - fix BindJSON calls 2020-05-29 17:44:49 +02:00
Dane Everitt
f8bffd8391 Start the process of migrating from zap to a cleaner CLI output for logs 2020-05-28 22:07:53 -07:00
Dane Everitt
4b366ae19e Don't cause a crash loop when part of the pre-server-boot process fails 2020-05-28 19:53:12 -07:00
Dane Everitt
82ffb9804d Don't lock up websocket when sending error json; ref pterodactyl/panel#2076 2020-05-28 19:52:47 -07:00
Dane Everitt
54510057bb Don't block server boot if there is an error pulling an image
Obviously this requires the image to exist locally, but should avoid widespread issues when Quay inevitably goes down again.

closes pterodactyl/panel#2076
2020-05-28 19:26:41 -07:00
Dane Everitt
6d7ab865d7 Fix SFTP default configuration; closes pterodactyl/panel#2045 2020-05-25 15:51:36 -07:00
Matthew Penner
74097cc4ad Fix sending status event to all subscribers, send disk usage when server is offline 2020-05-22 11:01:27 -06:00
Dane Everitt
bd063682dc Better match handling I think? 2020-05-17 18:22:06 -07:00
Dane Everitt
c802a3397e Fix warnings about configuration values; should always use the snake case since we're using a marshaled value 2020-05-17 17:25:53 -07:00
Dane Everitt
276bd2be33 Don't quote environment variables; it is not needed in docker for this; closes pterodactyl/panel#2030 2020-05-17 17:06:01 -07:00
Dane Everitt
e83495a09e Update makefile to for easier builds 2020-05-17 16:12:42 -07:00
Dane Everitt
64cad5c35d Don't blow up when working with bad egg configurations; avoids boot crash; closes pterodactyl/panel#2035 2020-05-17 15:57:59 -07:00
Dane Everitt
911b809a4e Clean debugging output to not include the full authorization key in debug output 2020-05-17 15:28:04 -07:00
Dane Everitt
3fe884670d Fix handling of files with special characters and spaces
closes pterodactyl/panel#2040
closes pterodactyl/panel#2038
2020-05-17 15:07:11 -07:00
Dane Everitt
804f3d5ca9 Always clean up the installer containers, even if there is an error during the process; closes pterodactyl/panel#2015 2020-05-09 19:57:29 -07:00
Dane Everitt
0bd28a4480 Cleanup S3 support; send actual backup details in response 2020-05-09 19:24:30 -07:00
Dane Everitt
326b5b6554 Merge pull request #31 from kawaiinekololis/network_option
Added network option to docker configuration
2020-05-09 18:53:59 -07:00
Kawaii Neko Lolis
cfca0d7f07 Added network option to docker configuration 2020-05-10 03:29:56 +02:00
Dane Everitt
5e60cb2eb0 Update to latest version of sftp-server; closes pterodactyl/panel#2014 2020-05-09 17:47:12 -07:00
Dane Everitt
d178a0d96b Don't fail deletion if container doesn't exist; closes pterodactyl/panel#2001 2020-05-09 17:16:41 -07:00
Dane Everitt
fd83424ee2 Change default config location (again); support auto-locating and moving old configs 2020-05-09 15:37:49 -07:00
40 changed files with 1018 additions and 445 deletions

View File

@@ -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

View File

@@ -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
View 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)
}

View File

@@ -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)
} }

View File

@@ -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)
}

View File

@@ -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
} }

View File

@@ -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.

View File

@@ -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
} }

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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
View 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
}

View File

@@ -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
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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 {

View File

@@ -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."

View File

@@ -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)

View File

@@ -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())

View File

@@ -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)
} }

View File

@@ -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)
} }

View File

@@ -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)
} }
} }
} }

View File

@@ -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)
} }

View File

@@ -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:

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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)
} }

View File

@@ -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)
} }

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
} }

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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"
) )