Compare commits
43 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
893cf9f7e2 | ||
|
|
bd063682dc | ||
|
|
c802a3397e | ||
|
|
276bd2be33 | ||
|
|
e83495a09e | ||
|
|
64cad5c35d | ||
|
|
911b809a4e | ||
|
|
3fe884670d | ||
|
|
804f3d5ca9 | ||
|
|
0bd28a4480 | ||
|
|
326b5b6554 | ||
|
|
cfca0d7f07 | ||
|
|
5e60cb2eb0 | ||
|
|
d178a0d96b | ||
|
|
fd83424ee2 | ||
|
|
483b652087 | ||
|
|
a6645aa741 | ||
|
|
ffd7357a1c | ||
|
|
b36f0de337 | ||
|
|
b2cf222a3a | ||
|
|
ced8a5bcbd | ||
|
|
7bba1d4fd6 | ||
|
|
1b2eb50a32 | ||
|
|
fab5d36917 | ||
|
|
ee184768b8 | ||
|
|
2e055cf630 | ||
|
|
fab489d264 | ||
|
|
7f93e5f9d5 | ||
|
|
ac011214f7 | ||
|
|
58262aa252 | ||
|
|
eba5aa8cbe | ||
|
|
b2797ed292 | ||
|
|
507d0100cf | ||
|
|
91d12ab9a7 | ||
|
|
1e2da95d26 | ||
|
|
2828eaed32 | ||
|
|
12d43a9f49 | ||
|
|
00ed6f3985 | ||
|
|
377cae4d48 | ||
|
|
9c5855663c | ||
|
|
da093e7cf7 | ||
|
|
df9c4835c4 | ||
|
|
65102966a1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,3 +46,4 @@ test_*/
|
|||||||
!.gitkeep
|
!.gitkeep
|
||||||
debug
|
debug
|
||||||
data/.states.json
|
data/.states.json
|
||||||
|
.DS_Store
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0-beta.3
|
||||||
|
### Fixed
|
||||||
|
* Daemon will no longer crash if someone requests a websocket for a deleted server.
|
||||||
|
* Temporary directories are now created properly if missing during the server installation process.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Added support for using Amazon S3 as a backup location for archives.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Memory overhead for containers is now 5/10/15% higher than the passed limit to account for JVM heap and prevent crashing.
|
||||||
|
|
||||||
## v1.0.0-alpha.2
|
## v1.0.0-alpha.2
|
||||||
### Added
|
### Added
|
||||||
* Ability to run an installation process for a server and notify the panel when completed.
|
* Ability to run an installation process for a server and notify the panel when completed.
|
||||||
|
|||||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# ----------------------------------
|
||||||
|
# Pterodactyl Panel Dockerfile
|
||||||
|
# ----------------------------------
|
||||||
|
|
||||||
|
FROM golang:1.14-alpine
|
||||||
|
COPY . /go/wings/
|
||||||
|
WORKDIR /go/wings/
|
||||||
|
RUN apk add --no-cache upx \
|
||||||
|
&& go build -ldflags="-s -w" \
|
||||||
|
&& upx --brute wings
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
COPY --from=0 /go/wings/wings /usr/bin/
|
||||||
|
CMD ["wings","--config", "/var/lib/pterodactyl/config.yml"]
|
||||||
32
Makefile
32
Makefile
@@ -1,28 +1,10 @@
|
|||||||
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
|
|
||||||
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
|
|
||||||
26
api/api.go
26
api/api.go
@@ -45,6 +45,28 @@ 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)"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
zap.S().Debugw(
|
||||||
|
"making request to external HTTP endpoint",
|
||||||
|
zap.String("method", req.Method),
|
||||||
|
zap.String("endpoint", req.URL.String()),
|
||||||
|
zap.Any("headers", headers),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
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 +77,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 +92,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)
|
||||||
}
|
}
|
||||||
|
|||||||
60
cmd/config_finder.go
Normal file
60
cmd/config_finder.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We've gone through a couple of iterations of where the configuration is stored. This
|
||||||
|
// helpful little function will look through the three areas it might have ended up, and
|
||||||
|
// return it.
|
||||||
|
//
|
||||||
|
// We only run this if the configuration flag for the instance is not actually passed in
|
||||||
|
// via the command line. Once found, the configuration is moved into the expected default
|
||||||
|
// location. Only errors are returned from this function, you can safely assume that after
|
||||||
|
// running this the configuration can be found in the correct default location.
|
||||||
|
func RelocateConfiguration() error {
|
||||||
|
var match string
|
||||||
|
check := []string{
|
||||||
|
config.DefaultLocation,
|
||||||
|
"/var/lib/pterodactyl/config.yml",
|
||||||
|
"/etc/wings/config.yml",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all of the configuration paths, and return which one we found, if
|
||||||
|
// any.
|
||||||
|
for _, p := range check {
|
||||||
|
if s, err := os.Stat(p); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !s.IsDir() {
|
||||||
|
match = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just return a generic not exist error at this point if we didn't have a match, this
|
||||||
|
// will allow the caller to handle displaying a more friendly error to the user. If we
|
||||||
|
// did match in the default location, go ahead and return successfully.
|
||||||
|
if match == "" {
|
||||||
|
return os.ErrNotExist
|
||||||
|
} else if match == config.DefaultLocation {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of this function simply creates the new default location and moves the
|
||||||
|
// old configuration file over to the new location, then sets the permissions on the
|
||||||
|
// file correctly so that only the user running this process can read it.
|
||||||
|
p, _ := filepath.Split(config.DefaultLocation)
|
||||||
|
if err := os.MkdirAll(p, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(match, config.DefaultLocation); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Chmod(config.DefaultLocation, 0600)
|
||||||
|
}
|
||||||
45
cmd/root.go
45
cmd/root.go
@@ -3,6 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -62,11 +63,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)
|
||||||
@@ -213,17 +225,6 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
zap.S().Fatalw("failed to configure HTTP server", zap.Error(err))
|
zap.S().Fatalw("failed to configure HTTP server", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// r := &Router{
|
|
||||||
// token: c.AuthenticationToken,
|
|
||||||
// upgrader: websocket.Upgrader{
|
|
||||||
// // Ensure that the websocket request is originating from the Panel itself,
|
|
||||||
// // and not some other location.
|
|
||||||
// CheckOrigin: func(r *http.Request) bool {
|
|
||||||
// return r.Header.Get("Origin") == c.PanelLocation
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute calls cobra to handle cli commands
|
// Execute calls cobra to handle cli commands
|
||||||
@@ -270,3 +271,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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
26
docker-compose.example.yml
Normal file
26
docker-compose.example.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
daemon:
|
||||||
|
build: .
|
||||||
|
restart: always
|
||||||
|
hostname: daemon
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "2022:2022"
|
||||||
|
tty: true
|
||||||
|
environment:
|
||||||
|
- "DEBUG=false"
|
||||||
|
volumes:
|
||||||
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
|
- "/var/lib/docker/containers/:/var/lib/docker/containers/"
|
||||||
|
- "/var/lib/pterodactyl/:/var/lib/pterodactyl/"
|
||||||
|
- "/srv/daemon-data/:/srv/daemon-data/"
|
||||||
|
- "/tmp/pterodactyl/:/tmp/pterodactyl/"
|
||||||
|
- "/etc/timezone:/etc/timezone:ro"
|
||||||
|
## Required for ssl if you user let's encrypt. uncomment to use.
|
||||||
|
## - "/etc/letsencrypt/:/etc/letsencrypt/"
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.21.0.0/16
|
||||||
26
go.mod
26
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/pterodactyl/wings
|
module github.com/pterodactyl/wings
|
||||||
|
|
||||||
go 1.12
|
go 1.13
|
||||||
|
|
||||||
// Uncomment this in development environments to make changes to the core SFTP
|
// Uncomment this in development environments to make changes to the core SFTP
|
||||||
// server software. This assumes you're using the official Pterodactyl Environment
|
// server software. This assumes you're using the official Pterodactyl Environment
|
||||||
@@ -17,6 +17,7 @@ require (
|
|||||||
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/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/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929
|
github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929
|
||||||
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
|
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
|
||||||
@@ -35,6 +36,7 @@ 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
|
||||||
@@ -49,24 +51,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/testify v1.5.1 // indirect
|
github.com/stretchr/objx v0.2.0 // indirect
|
||||||
go.uber.org/atomic v1.5.1 // indirect
|
github.com/yuin/goldmark v1.1.30 // indirect
|
||||||
go.uber.org/multierr v1.4.0 // indirect
|
go.uber.org/zap v1.15.0
|
||||||
go.uber.org/zap v1.13.0
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 // indirect
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // 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-20200324143707-d3edc9973b7e // 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-20200413165638-669c56c373c4 // indirect
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
||||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 // 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
|
||||||
)
|
)
|
||||||
|
|||||||
38
go.sum
38
go.sum
@@ -21,6 +21,8 @@ github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/
|
|||||||
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.30.14 h1:vZfX2b/fknc9wKcytbLWykM7in5k6dbQ8iHTJDUP1Ng=
|
||||||
|
github.com/aws/aws-sdk-go v1.30.14/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
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=
|
||||||
@@ -80,6 +82,7 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
|
|||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||||
@@ -120,10 +123,14 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDG
|
|||||||
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/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.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||||
|
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/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=
|
||||||
@@ -211,6 +218,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=
|
||||||
@@ -224,6 +233,8 @@ 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=
|
||||||
@@ -250,6 +261,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@@ -271,6 +283,7 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
|
|||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
@@ -278,11 +291,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=
|
||||||
@@ -290,6 +307,8 @@ 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=
|
||||||
@@ -300,6 +319,10 @@ golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||||
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/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=
|
||||||
@@ -307,7 +330,9 @@ 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=
|
||||||
@@ -317,9 +342,12 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
|
||||||
|
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -342,6 +370,10 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
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/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=
|
||||||
@@ -360,8 +392,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/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=
|
||||||
@@ -393,3 +430,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=
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"go.uber.org/zap"
|
"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 {
|
||||||
@@ -143,7 +145,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -165,11 +167,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
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"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"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,6 +42,44 @@ 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 {
|
||||||
|
zap.S().Warnw(
|
||||||
|
"failed to unmarshal configuration file replacement",
|
||||||
|
zap.String("file", f.FileName),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -81,8 +133,11 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
|||||||
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)))
|
zap.S().Debugw("parsing configuration file", zap.String("path", path), zap.String("parser", string(f.Parser)))
|
||||||
|
|
||||||
mb, _ := json.Marshal(config.Get())
|
if mb, err := json.Marshal(config.Get()); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
f.configuration = mb
|
f.configuration = mb
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -304,10 +359,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 +380,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func ServerExists(c *gin.Context) {
|
|||||||
u, err := uuid.Parse(c.Param("server"))
|
u, err := uuid.Parse(c.Param("server"))
|
||||||
if err != nil || GetServer(u.String()) == nil {
|
if err != nil || GetServer(u.String()) == nil {
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
"error": "The requested server does not exist.",
|
"error": "The resource you requested does not exist.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ func Configure() *gin.Engine {
|
|||||||
// This route is special it sits above all of the other requests because we are
|
// This route is special it sits above all of the other requests because we are
|
||||||
// using a JWT to authorize access to it, therefore it needs to be publicly
|
// using a JWT to authorize access to it, therefore it needs to be publicly
|
||||||
// accessible.
|
// accessible.
|
||||||
router.GET("/api/servers/:server/ws", getServerWebsocket)
|
router.GET("/api/servers/:server/ws", ServerExists, getServerWebsocket)
|
||||||
|
|
||||||
// This request is called by another daemon when a server is going to be transferred out.
|
// This request is called by another daemon when a server is going to be transferred out.
|
||||||
// This request does not need the AuthorizationMiddleware as the panel should never call it
|
// This request does not need the AuthorizationMiddleware as the panel should never call it
|
||||||
// and requests are authenticated through a JWT the panel issues to the other daemon.
|
// and requests are authenticated through a JWT the panel issues to the other daemon.
|
||||||
router.GET("/api/servers/:server/archive", getServerArchive)
|
router.GET("/api/servers/:server/archive", ServerExists, getServerArchive)
|
||||||
|
|
||||||
// All of the routes beyond this mount will use an authorization middleware
|
// All of the routes beyond this mount will use an authorization middleware
|
||||||
// and will not be accessible without the correct Authorization header provided.
|
// and will not be accessible without the correct Authorization header provided.
|
||||||
|
|||||||
@@ -178,6 +178,9 @@ func deleteServer(c *gin.Context) {
|
|||||||
zap.S().Warnw("failed to delete server archive during deletion process", zap.String("server", s.Uuid), zap.Error(err))
|
zap.S().Warnw("failed to delete server archive during deletion process", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unsubscribe all of the event listeners.
|
||||||
|
s.Events().UnsubscribeAll()
|
||||||
|
|
||||||
// Destroy the environment; in Docker this will handle a running container and
|
// Destroy the environment; in Docker this will handle a running container and
|
||||||
// forcibly terminate it before removing the container, so we do not need to handle
|
// forcibly terminate it before removing the container, so we do not need to handle
|
||||||
// that here.
|
// that here.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"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"
|
||||||
@@ -12,14 +14,33 @@ import (
|
|||||||
func postServerBackup(c *gin.Context) {
|
func postServerBackup(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
data := &backup.LocalBackup{}
|
data := &backup.Request{}
|
||||||
c.BindJSON(&data)
|
c.BindJSON(&data)
|
||||||
|
|
||||||
go func(b *backup.LocalBackup, serv *server.Server) {
|
var adapter backup.BackupInterface
|
||||||
if err := serv.BackupLocal(b); err != nil {
|
var err error
|
||||||
|
|
||||||
|
switch data.Adapter {
|
||||||
|
case backup.LocalBackupAdapter:
|
||||||
|
adapter, err = data.NewLocalBackup()
|
||||||
|
case backup.S3BackupAdapter:
|
||||||
|
adapter, err = data.NewS3Backup()
|
||||||
|
default:
|
||||||
|
err = errors.New(fmt.Sprintf("unknown backup adapter [%s] provided", data.Adapter))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(b backup.BackupInterface, serv *server.Server) {
|
||||||
|
if err := serv.Backup(b); err != nil {
|
||||||
zap.S().Errorw("failed to generate backup for server", zap.Error(err))
|
zap.S().Errorw("failed to generate backup for server", zap.Error(err))
|
||||||
}
|
}
|
||||||
}(data, s)
|
}(adapter, s)
|
||||||
|
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,24 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the contents of a file on the server.
|
// Returns the contents of a file on the server.
|
||||||
func getServerFileContents(c *gin.Context) {
|
func getServerFileContents(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
cleaned, err := s.Filesystem.SafePath(c.Query("file"))
|
|
||||||
|
p, err := url.QueryUnescape(c.Query("file"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = "/" + strings.TrimLeft(p, "/")
|
||||||
|
|
||||||
|
cleaned, err := s.Filesystem.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
"error": "The file requested could not be found.",
|
"error": "The file requested could not be found.",
|
||||||
@@ -56,7 +66,13 @@ func getServerFileContents(c *gin.Context) {
|
|||||||
func getServerListDirectory(c *gin.Context) {
|
func getServerListDirectory(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
stats, err := s.Filesystem.ListDirectory(c.Query("directory"))
|
d, err := url.QueryUnescape(c.Query("directory"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := s.Filesystem.ListDirectory(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
@@ -128,7 +144,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,42 +34,62 @@ func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, suc
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a server backup and then emits the event over the server websocket. We
|
// Get all of the ignored files for a server based on its .pteroignore file in the root.
|
||||||
// let the actual backup system handle notifying the panel of the status, but that
|
func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
|
||||||
// won't emit a websocket event.
|
var ignored []string
|
||||||
func (s *Server) BackupLocal(b *backup.LocalBackup) error {
|
|
||||||
// If no ignored files are present in the request, check for a .pteroignore file in the root
|
|
||||||
// of the server files directory, and use that to generate the backup.
|
|
||||||
if len(b.IgnoredFiles) == 0 {
|
|
||||||
f, err := os.Open(path.Join(s.Filesystem.Path(), ".pteroignore"))
|
f, err := os.Open(path.Join(s.Filesystem.Path(), ".pteroignore"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
zap.S().Warnw("failed to open .pteroignore file in server directory", zap.String("server", s.Uuid), zap.Error(errors.WithStack(err)))
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// Only include non-empty lines, for the sake of clarity...
|
// Only include non-empty lines, for the sake of clarity...
|
||||||
if t := scanner.Text(); t != "" {
|
if t := scanner.Text(); t != "" {
|
||||||
b.IgnoredFiles = append(b.IgnoredFiles, t)
|
ignored = append(ignored, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
zap.S().Warnw("failed to scan .pteroignore file for lines", zap.String("server", s.Uuid), zap.Error(errors.WithStack(err)))
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ignored, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the backup files to include when generating it.
|
||||||
|
func (s *Server) GetIncludedBackupFiles(ignored []string) (*backup.IncludedFiles, error) {
|
||||||
|
// If no ignored files are present in the request, check for a .pteroignore file in the root
|
||||||
|
// of the server files directory, and use that to generate the backup.
|
||||||
|
if len(ignored) == 0 {
|
||||||
|
if i, err := s.getServerwideIgnoredFiles(); err != nil {
|
||||||
|
zap.S().Warnw("failed to retrieve server ignored files", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
|
} else {
|
||||||
|
ignored = i
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the included files based on the root path and the ignored files provided.
|
// Get the included files based on the root path and the ignored files provided.
|
||||||
inc, err := s.Filesystem.GetIncludedFiles(s.Filesystem.Path(), b.IgnoredFiles)
|
return s.Filesystem.GetIncludedFiles(s.Filesystem.Path(), ignored)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs a server backup and then emits the event over the server websocket. We
|
||||||
|
// let the actual backup system handle notifying the panel of the status, but that
|
||||||
|
// won't emit a websocket event.
|
||||||
|
func (s *Server) Backup(b backup.BackupInterface) error {
|
||||||
|
// Get the included files based on the root path and the ignored files provided.
|
||||||
|
inc, err := s.GetIncludedBackupFiles(b.Ignored())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.Backup(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.Uuid), zap.Error(err))
|
zap.S().Warnw("failed to notify panel of failed backup state", zap.String("backup", b.Identifier()), zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@@ -77,7 +97,6 @@ func (s *Server) BackupLocal(b *backup.LocalBackup) 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()
|
||||||
|
|
||||||
@@ -86,8 +105,8 @@ func (s *Server) BackupLocal(b *backup.LocalBackup) error {
|
|||||||
|
|
||||||
// Emit an event over the socket so we can update the backup in realtime on
|
// Emit an event over the socket so we can update the backup in realtime on
|
||||||
// the frontend for the server.
|
// the frontend for the server.
|
||||||
s.Events().PublishJson(BackupCompletedEvent+":"+b.Uuid, map[string]interface{}{
|
s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
|
||||||
"uuid": b.Uuid,
|
"uuid": b.Identifier(),
|
||||||
"sha256_hash": ad.Checksum,
|
"sha256_hash": ad.Checksum,
|
||||||
"file_size": ad.Size,
|
"file_size": ad.Size,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,31 +1,22 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backup interface {
|
const (
|
||||||
// Returns the UUID of this backup as tracked by the panel instance.
|
LocalBackupAdapter = "wings"
|
||||||
Identifier() string
|
S3BackupAdapter = "s3"
|
||||||
|
)
|
||||||
// Generates a backup in whatever the configured source for the specific
|
|
||||||
// implementation is.
|
|
||||||
Backup(*IncludedFiles, string) error
|
|
||||||
|
|
||||||
// Returns a SHA256 checksum for the generated backup.
|
|
||||||
Checksum() ([]byte, error)
|
|
||||||
|
|
||||||
// Returns the size of the generated backup.
|
|
||||||
Size() (int64, error)
|
|
||||||
|
|
||||||
// Returns the path to the backup on the machine. This is not always the final
|
|
||||||
// storage location of the backup, simply the location we're using to store
|
|
||||||
// it until it is moved to the final spot.
|
|
||||||
Path() string
|
|
||||||
|
|
||||||
// Returns details about the archive.
|
|
||||||
Details() *ArchiveDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
type ArchiveDetails struct {
|
type ArchiveDetails struct {
|
||||||
Checksum string `json:"checksum"`
|
Checksum string `json:"checksum"`
|
||||||
@@ -40,3 +31,121 @@ func (ad *ArchiveDetails) ToRequest(successful bool) api.BackupRequest {
|
|||||||
Successful: successful,
|
Successful: successful,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Backup struct {
|
||||||
|
// The UUID of this backup object. This must line up with a backup from
|
||||||
|
// the panel instance.
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
|
||||||
|
// An array of files to ignore when generating this backup. This should be
|
||||||
|
// compatible with a standard .gitignore structure.
|
||||||
|
IgnoredFiles []string `json:"ignored_files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection GoNameStartsWithPackageName
|
||||||
|
type BackupInterface interface {
|
||||||
|
// Returns the UUID of this backup as tracked by the panel instance.
|
||||||
|
Identifier() string
|
||||||
|
|
||||||
|
// Generates a backup in whatever the configured source for the specific
|
||||||
|
// implementation is.
|
||||||
|
Generate(*IncludedFiles, string) (*ArchiveDetails, error)
|
||||||
|
|
||||||
|
// Returns the ignored files for this backup instance.
|
||||||
|
Ignored() []string
|
||||||
|
|
||||||
|
// Returns a SHA256 checksum for the generated backup.
|
||||||
|
Checksum() ([]byte, error)
|
||||||
|
|
||||||
|
// Returns the size of the generated backup.
|
||||||
|
Size() (int64, error)
|
||||||
|
|
||||||
|
// Returns the path to the backup on the machine. This is not always the final
|
||||||
|
// storage location of the backup, simply the location we're using to store
|
||||||
|
// it until it is moved to the final spot.
|
||||||
|
Path() string
|
||||||
|
|
||||||
|
// Returns details about the archive.
|
||||||
|
Details() *ArchiveDetails
|
||||||
|
|
||||||
|
// Removes a backup file.
|
||||||
|
Remove() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backup) Identifier() string {
|
||||||
|
return b.Uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the path for this specific backup.
|
||||||
|
func (b *Backup) Path() string {
|
||||||
|
return path.Join(config.Get().System.BackupDirectory, b.Identifier()+".tar.gz")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the size of the generated backup.
|
||||||
|
func (b *Backup) Size() (int64, error) {
|
||||||
|
st, err := os.Stat(b.Path())
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return st.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the SHA256 checksum of a backup.
|
||||||
|
func (b *Backup) Checksum() ([]byte, error) {
|
||||||
|
h := sha256.New()
|
||||||
|
|
||||||
|
f, err := os.Open(b.Path())
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
|
return []byte{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns details of the archive by utilizing two go-routines to get the checksum and
|
||||||
|
// the size of the archive.
|
||||||
|
func (b *Backup) Details() *ArchiveDetails {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
var checksum string
|
||||||
|
// Calculate the checksum for the file.
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
resp, err := b.Checksum()
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorw("failed to calculate checksum for backup", zap.String("backup", b.Uuid), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum = hex.EncodeToString(resp)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var sz int64
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if s, err := b.Size(); err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
sz = s
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return &ArchiveDetails{
|
||||||
|
Checksum: checksum,
|
||||||
|
Size: sz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backup) Ignored() []string {
|
||||||
|
return b.IgnoredFiles
|
||||||
|
}
|
||||||
@@ -2,35 +2,24 @@ package backup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocalBackup struct {
|
type LocalBackup struct {
|
||||||
// The UUID of this backup object. This must line up with a backup from
|
Backup
|
||||||
// the panel instance.
|
|
||||||
Uuid string `json:"uuid"`
|
|
||||||
|
|
||||||
// An array of files to ignore when generating this backup. This should be
|
|
||||||
// compatible with a standard .gitignore structure.
|
|
||||||
IgnoredFiles []string `json:"ignored_files"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Backup = (*LocalBackup)(nil)
|
var _ BackupInterface = (*LocalBackup)(nil)
|
||||||
|
|
||||||
// Locates the backup for a server and returns the local path. This will obviously only
|
// Locates the backup for a server and returns the local path. This will obviously only
|
||||||
// work if the backup was created as a local backup.
|
// work if the backup was created as a local backup.
|
||||||
func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
|
func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
|
||||||
b := &LocalBackup{
|
b := &LocalBackup{
|
||||||
|
Backup{
|
||||||
Uuid: uuid,
|
Uuid: uuid,
|
||||||
IgnoredFiles: nil,
|
IgnoredFiles: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := os.Stat(b.Path())
|
st, err := os.Stat(b.Path())
|
||||||
@@ -45,32 +34,6 @@ func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
|
|||||||
return b, st, nil
|
return b, st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackup) Identifier() string {
|
|
||||||
return b.Uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the path for this specific backup.
|
|
||||||
func (b *LocalBackup) Path() string {
|
|
||||||
return path.Join(config.Get().System.BackupDirectory, b.Uuid+".tar.gz")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the SHA256 checksum of a backup.
|
|
||||||
func (b *LocalBackup) Checksum() ([]byte, error) {
|
|
||||||
h := sha256.New()
|
|
||||||
|
|
||||||
f, err := os.Open(b.Path())
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
|
||||||
return []byte{}, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.Sum(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes a backup from the system.
|
// Removes a backup from the system.
|
||||||
func (b *LocalBackup) Remove() error {
|
func (b *LocalBackup) Remove() error {
|
||||||
return os.Remove(b.Path())
|
return os.Remove(b.Path())
|
||||||
@@ -78,77 +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) Backup(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 the size of the generated backup.
|
return b.Details(), nil
|
||||||
func (b *LocalBackup) Size() (int64, error) {
|
|
||||||
st, err := os.Stat(b.Path())
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return st.Size(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns details of the archive by utilizing two go-routines to get the checksum and
|
|
||||||
// the size of the archive.
|
|
||||||
func (b *LocalBackup) Details() *ArchiveDetails {
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(2)
|
|
||||||
|
|
||||||
var checksum string
|
|
||||||
// Calculate the checksum for the file.
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
resp, err := b.Checksum()
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorw("failed to calculate checksum for backup", zap.String("backup", b.Uuid), zap.Error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
checksum = hex.EncodeToString(resp)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var sz int64
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
st, err := os.Stat(b.Path())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sz = st.Size()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return &ArchiveDetails{
|
|
||||||
Checksum: checksum,
|
|
||||||
Size: sz,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensures that the local backup destination for files exists.
|
|
||||||
func (b *LocalBackup) ensureLocalBackupLocation() error {
|
|
||||||
d := config.Get().System.BackupDirectory
|
|
||||||
|
|
||||||
if _, err := os.Stat(d); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.MkdirAll(d, 0700)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
46
server/backup/backup_request.go
Normal file
46
server/backup/backup_request.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Adapter string `json:"adapter"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
IgnoredFiles []string `json:"ignored_files"`
|
||||||
|
PresignedUrl string `json:"presigned_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a new local backup struct.
|
||||||
|
func (r *Request) NewLocalBackup() (*LocalBackup, error) {
|
||||||
|
if r.Adapter != LocalBackupAdapter {
|
||||||
|
return nil, errors.New(fmt.Sprintf("cannot create local backup using [%s] adapter", r.Adapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LocalBackup{
|
||||||
|
Backup{
|
||||||
|
Uuid: r.Uuid,
|
||||||
|
IgnoredFiles: r.IgnoredFiles,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a new S3 backup struct.
|
||||||
|
func (r *Request) NewS3Backup() (*S3Backup, error) {
|
||||||
|
if r.Adapter != S3BackupAdapter {
|
||||||
|
return nil, errors.New(fmt.Sprintf("cannot create s3 backup using [%s] adapter", r.Adapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.PresignedUrl) == 0 {
|
||||||
|
return nil, errors.New("a valid presigned S3 upload URL must be provided to use the [s3] adapter")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &S3Backup{
|
||||||
|
Backup: Backup{
|
||||||
|
Uuid: r.Uuid,
|
||||||
|
IgnoredFiles: r.IgnoredFiles,
|
||||||
|
},
|
||||||
|
PresignedUrl: r.PresignedUrl,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -1,37 +1,83 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
type S3Backup struct {
|
type S3Backup struct {
|
||||||
// The UUID of this backup object. This must line up with a backup from
|
Backup
|
||||||
// the panel instance.
|
|
||||||
Uuid string
|
|
||||||
|
|
||||||
// An array of files to ignore when generating this backup. This should be
|
// The pre-signed upload endpoint for the generated backup. This must be
|
||||||
// compatible with a standard .gitignore structure.
|
// provided otherwise this request will fail. This allows us to keep all
|
||||||
IgnoredFiles []string
|
// of the keys off the daemon instances and the panel can handle generating
|
||||||
|
// the credentials for us.
|
||||||
|
PresignedUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Backup = (*S3Backup)(nil)
|
var _ BackupInterface = (*S3Backup)(nil)
|
||||||
|
|
||||||
func (s *S3Backup) Identifier() string {
|
// Generates a new backup on the disk, moves it into the S3 bucket via the provided
|
||||||
return s.Uuid
|
// presigned URL, and then deletes the backup from the disk.
|
||||||
|
func (s *S3Backup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) {
|
||||||
|
defer s.Remove()
|
||||||
|
|
||||||
|
a := &Archive{
|
||||||
|
TrimPrefix: prefix,
|
||||||
|
Files: included,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Backup) Backup(included *IncludedFiles, prefix string) error {
|
if err := a.Create(s.Path(), context.Background()); err != nil {
|
||||||
panic("implement me")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Backup) Checksum() ([]byte, error) {
|
rc, err := os.Open(s.Path())
|
||||||
return []byte(""), nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
if resp, err := s.generateRemoteRequest(rc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Backup) Size() (int64, error) {
|
return s.Details(), err
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Backup) Path() string {
|
// Removes a backup from the system.
|
||||||
return ""
|
func (s *S3Backup) Remove() error {
|
||||||
|
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) {
|
||||||
|
r, err := http.NewRequest(http.MethodPut, s.PresignedUrl, nil)
|
||||||
|
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
|
||||||
|
|
||||||
|
zap.S().Debugw("uploading backup to remote S3 endpoint", zap.String("endpoint", s.PresignedUrl), zap.Any("headers", r.Header))
|
||||||
|
|
||||||
|
return http.DefaultClient.Do(r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -197,18 +196,33 @@ func (d *DockerEnvironment) Start() error {
|
|||||||
return &suspendedError{}
|
return &suspendedError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
|
if c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid); err != nil {
|
||||||
if err != nil && !client.IsErrNotFound(err) {
|
// Do nothing if the container is not found, we just don't want to continue
|
||||||
|
// to the next block of code here. This check was inlined here to guard againt
|
||||||
|
// a nil-pointer when checking c.State below.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/2000
|
||||||
|
if !client.IsErrNotFound(err) {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// No reason to try starting a container that is already running.
|
// If the server is running update our internal state and continue on with the attach.
|
||||||
if c.State.Running {
|
if c.State.Running {
|
||||||
d.Server.SetState(ProcessRunningState)
|
d.Server.SetState(ProcessRunningState)
|
||||||
|
|
||||||
return d.Attach()
|
return d.Attach()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate the log file so we don't end up outputting a bunch of useless log information
|
||||||
|
// to the websocket and whatnot. Check first that the path and file exist before trying
|
||||||
|
// to truncate them.
|
||||||
|
if _, err := os.Stat(c.LogPath); err == nil {
|
||||||
|
if err := os.Truncate(c.LogPath, 0); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.Server.SetState(ProcessStartingState)
|
d.Server.SetState(ProcessStartingState)
|
||||||
// Set this to true for now, we will set it to false once we reach the
|
// Set this to true for now, we will set it to false once we reach the
|
||||||
// end of this chain.
|
// end of this chain.
|
||||||
@@ -221,15 +235,6 @@ func (d *DockerEnvironment) Start() error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate the log file so we don't end up outputting a bunch of useless log information
|
|
||||||
// to the websocket and whatnot. Check first that the path and file exist before trying
|
|
||||||
// to truncate them.
|
|
||||||
if _, err := os.Stat(c.LogPath); err == nil {
|
|
||||||
if err := os.Truncate(c.LogPath, 0); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the configuration files defined for the server before beginning the boot process.
|
// Update the configuration files defined for the server before beginning the boot process.
|
||||||
// This process executes a bunch of parallel updates, so we just block until that process
|
// This process executes a bunch of parallel updates, so we just block until that process
|
||||||
// is completed. Any errors as a result of this will just be bubbled out in the logger,
|
// is completed. Any errors as a result of this will just be bubbled out in the logger,
|
||||||
@@ -338,11 +343,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
|
||||||
@@ -350,6 +365,19 @@ func (d *DockerEnvironment) Destroy() error {
|
|||||||
func (d *DockerEnvironment) ExitState() (uint32, bool, error) {
|
func (d *DockerEnvironment) ExitState() (uint32, bool, error) {
|
||||||
c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
|
c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// I'm not entirely sure how this can happen to be honest. I tried deleting a
|
||||||
|
// container _while_ a server was running and wings gracefully saw the crash and
|
||||||
|
// created a new container for it.
|
||||||
|
//
|
||||||
|
// However, someone reported an error in Discord about this scenario happening,
|
||||||
|
// so I guess this should prevent it? They didn't tell me how they caused it though
|
||||||
|
// so thats a mystery that will have to go unsolved.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/2003
|
||||||
|
if client.IsErrNotFound(err) {
|
||||||
|
return 1, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
return 0, false, errors.WithStack(err)
|
return 0, false, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +511,7 @@ func (d *DockerEnvironment) EnableResourcePolling() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.Resources.CpuAbsolute = s.Resources.CalculateAbsoluteCpu(&v.PreCPUStats, &v.CPUStats)
|
s.Resources.CpuAbsolute = s.Resources.CalculateAbsoluteCpu(&v.PreCPUStats, &v.CPUStats)
|
||||||
s.Resources.Memory = v.MemoryStats.Usage
|
s.Resources.Memory = s.Resources.CalculateDockerMemory(v.MemoryStats)
|
||||||
s.Resources.MemoryLimit = v.MemoryStats.Limit
|
s.Resources.MemoryLimit = v.MemoryStats.Limit
|
||||||
|
|
||||||
// Why you ask? This already has the logic for caching disk space in use and then
|
// Why you ask? This already has the logic for caching disk space in use and then
|
||||||
@@ -547,8 +575,6 @@ func (d *DockerEnvironment) ensureImageExists(c *client.Client) error {
|
|||||||
|
|
||||||
// Creates a new container for the server using all of the data that is currently
|
// Creates a new container for the server using all of the data that is currently
|
||||||
// available for it. If the container already exists it will be returned.
|
// available for it. If the container already exists it will be returned.
|
||||||
//
|
|
||||||
// @todo pull the image being requested if it doesn't exist currently.
|
|
||||||
func (d *DockerEnvironment) Create() error {
|
func (d *DockerEnvironment) Create() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
@@ -639,20 +665,9 @@ 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),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretty sure TZ=X in the environment variables negates the need for this
|
|
||||||
// to happen. Leaving it until I can confirm that works for everything.
|
|
||||||
//
|
|
||||||
// if err := mountTimezoneData(hostConf); err != nil {
|
|
||||||
// if os.IsNotExist(err) {
|
|
||||||
// zap.S().Warnw("the timezone data path configured does not exist on the system", zap.Error(errors.WithStack(err)))
|
|
||||||
// } else {
|
|
||||||
// zap.S().Warnw("failed to mount timezone data into container", zap.Error(errors.WithStack(err)))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
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 {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -769,7 +784,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
|
||||||
@@ -824,23 +839,21 @@ func (d *DockerEnvironment) exposedPorts() nat.PortSet {
|
|||||||
|
|
||||||
// Formats the resources available to a server instance in such as way that Docker will
|
// Formats the resources available to a server instance in such as way that Docker will
|
||||||
// generate a matching environment in the container.
|
// generate a matching environment in the container.
|
||||||
|
//
|
||||||
|
// This will set the actual memory limit on the container using the multiplier which is the
|
||||||
|
// hard limit for the container (after which will result in a crash). We then set the
|
||||||
|
// reservation to be the expected memory limit based on simply multiplication.
|
||||||
|
//
|
||||||
|
// The swap value is either -1 to disable it, or set to the value of the hard memory limit
|
||||||
|
// plus the additional swap assigned to the server since Docker expects this value to be
|
||||||
|
// the same or higher than the memory limit.
|
||||||
func (d *DockerEnvironment) getResourcesForServer() container.Resources {
|
func (d *DockerEnvironment) getResourcesForServer() container.Resources {
|
||||||
overhead := 1.05
|
|
||||||
// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
|
|
||||||
// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
|
|
||||||
// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
|
|
||||||
if d.Server.Build.MemoryLimit <= 2048 {
|
|
||||||
overhead = 1.15
|
|
||||||
} else if d.Server.Build.MemoryLimit <= 4096 {
|
|
||||||
overhead = 1.10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return container.Resources{
|
return container.Resources{
|
||||||
Memory: int64(math.Round(float64(d.Server.Build.MemoryLimit) * 1000000.0 * overhead)),
|
Memory: d.Server.Build.BoundedMemoryLimit(),
|
||||||
MemoryReservation: d.Server.Build.MemoryLimit * 1000000,
|
MemoryReservation: d.Server.Build.MemoryLimit * 1_000_000,
|
||||||
MemorySwap: d.Server.Build.ConvertedSwap(),
|
MemorySwap: d.Server.Build.ConvertedSwap(),
|
||||||
CPUQuota: d.Server.Build.ConvertedCpuLimit(),
|
CPUQuota: d.Server.Build.ConvertedCpuLimit(),
|
||||||
CPUPeriod: 100000,
|
CPUPeriod: 100_000,
|
||||||
CPUShares: 1024,
|
CPUShares: 1024,
|
||||||
BlkioWeight: d.Server.Build.IoWeight,
|
BlkioWeight: d.Server.Build.IoWeight,
|
||||||
OomKillDisable: &d.Server.Container.OomDisabled,
|
OomKillDisable: &d.Server.Container.OomDisabled,
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ type Event struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EventBus struct {
|
type EventBus struct {
|
||||||
|
sync.RWMutex
|
||||||
|
|
||||||
subscribers map[string][]chan Event
|
subscribers map[string][]chan Event
|
||||||
mu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the server's emitter instance.
|
// Returns the server's emitter instance.
|
||||||
@@ -40,8 +41,8 @@ func (s *Server) Events() *EventBus {
|
|||||||
|
|
||||||
// Publish data to a given topic.
|
// Publish data to a given topic.
|
||||||
func (e *EventBus) Publish(topic string, data string) {
|
func (e *EventBus) Publish(topic string, data string) {
|
||||||
e.mu.Lock()
|
e.RLock()
|
||||||
defer e.mu.Unlock()
|
defer e.RUnlock()
|
||||||
|
|
||||||
t := topic
|
t := topic
|
||||||
// Some of our topics for the socket support passing a more specific namespace,
|
// Some of our topics for the socket support passing a more specific namespace,
|
||||||
@@ -79,8 +80,8 @@ func (e *EventBus) PublishJson(topic string, data interface{}) error {
|
|||||||
|
|
||||||
// Subscribe to an emitter topic using a channel.
|
// Subscribe to an emitter topic using a channel.
|
||||||
func (e *EventBus) Subscribe(topic string, ch chan Event) {
|
func (e *EventBus) Subscribe(topic string, ch chan Event) {
|
||||||
e.mu.Lock()
|
e.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.Unlock()
|
||||||
|
|
||||||
if p, ok := e.subscribers[topic]; ok {
|
if p, ok := e.subscribers[topic]; ok {
|
||||||
e.subscribers[topic] = append(p, ch)
|
e.subscribers[topic] = append(p, ch)
|
||||||
@@ -91,8 +92,8 @@ func (e *EventBus) Subscribe(topic string, ch chan Event) {
|
|||||||
|
|
||||||
// Unsubscribe a channel from a topic.
|
// Unsubscribe a channel from a topic.
|
||||||
func (e *EventBus) Unsubscribe(topic string, ch chan Event) {
|
func (e *EventBus) Unsubscribe(topic string, ch chan Event) {
|
||||||
e.mu.Lock()
|
e.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.Unlock()
|
||||||
|
|
||||||
if _, ok := e.subscribers[topic]; ok {
|
if _, ok := e.subscribers[topic]; ok {
|
||||||
for i := range e.subscribers[topic] {
|
for i := range e.subscribers[topic] {
|
||||||
@@ -102,3 +103,18 @@ func (e *EventBus) Unsubscribe(topic string, ch chan Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes all of the event listeners for the server. This is used when a server
|
||||||
|
// is being deleted to avoid a bunch of de-reference errors cropping up. Obviously
|
||||||
|
// should also check elsewhere and handle a server reference going nil, but this
|
||||||
|
// won't hurt.
|
||||||
|
func (e *EventBus) UnsubscribeAll() {
|
||||||
|
e.Lock()
|
||||||
|
defer e.Unlock()
|
||||||
|
|
||||||
|
// Loop over all of the subscribers and just remove all of the events
|
||||||
|
// for them.
|
||||||
|
for t := range e.subscribers {
|
||||||
|
e.subscribers[t] = make([]chan Event, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -401,13 +401,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)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -103,6 +104,18 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
|
|||||||
return proc, nil
|
return proc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes the installer container for the server.
|
||||||
|
func (ip *InstallationProcess) RemoveContainer() {
|
||||||
|
err := ip.client.ContainerRemove(context.Background(), ip.Server.Uuid + "_installer", types.ContainerRemoveOptions{
|
||||||
|
RemoveVolumes: true,
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil && !client.IsErrNotFound(err) {
|
||||||
|
zap.S().Warnw("failed to delete server installer container", zap.String("server", ip.Server.Uuid), zap.Error(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
@@ -116,6 +129,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +146,12 @@ func (ip *InstallationProcess) Run() error {
|
|||||||
// Writes the installation script to a temporary file on the host machine so that it
|
// Writes the installation script to a temporary file on the host machine so that it
|
||||||
// can be properly mounted into the installation container and then executed.
|
// can be properly mounted into the installation container and then executed.
|
||||||
func (ip *InstallationProcess) writeScriptToDisk() (string, error) {
|
func (ip *InstallationProcess) writeScriptToDisk() (string, error) {
|
||||||
|
// Make sure the temp directory root exists before trying to make a directory within it. The
|
||||||
|
// ioutil.TempDir call expects this base to exist, it won't create it for you.
|
||||||
|
if err := os.MkdirAll(path.Join(os.TempDir(), "pterodactyl/"), 0700); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
d, err := ioutil.TempDir("", "pterodactyl/")
|
d, err := ioutil.TempDir("", "pterodactyl/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", errors.WithStack(err)
|
||||||
@@ -242,6 +263,7 @@ func (ip *InstallationProcess) GetLogPath() string {
|
|||||||
// installation container.
|
// installation container.
|
||||||
func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
||||||
ctx := context.Background()
|
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))
|
zap.S().Debugw("pulling installation logs for server", zap.String("server", ip.Server.Uuid), zap.String("container_id", containerId))
|
||||||
reader, err := ip.client.ContainerLogs(ctx, containerId, types.ContainerLogsOptions{
|
reader, err := ip.client.ContainerLogs(ctx, containerId, types.ContainerLogsOptions{
|
||||||
@@ -266,17 +288,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +335,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{
|
||||||
@@ -334,7 +345,7 @@ 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))
|
zap.S().Infow("creating installer container for server process", zap.String("server", ip.Server.Uuid))
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned
|
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned
|
||||||
// since that is not dependent on the server being running to collect that data.
|
// since that is not dependent on the server being running to collect that data.
|
||||||
type ResourceUsage struct {
|
type ResourceUsage struct {
|
||||||
// The total amount of memory, in bytes, that this server instance is consuming.
|
// The total amount of memory, in bytes, that this server instance is consuming. This is
|
||||||
|
// calculated slightly differently than just using the raw Memory field that the stats
|
||||||
|
// return from the container, so please check the code setting this value for how that
|
||||||
|
// is calculated.
|
||||||
Memory uint64 `json:"memory_bytes"`
|
Memory uint64 `json:"memory_bytes"`
|
||||||
// The total amount of memory this container or resource can use. Inside Docker this is
|
// The total amount of memory this container or resource can use. Inside Docker this is
|
||||||
// going to be higher than you'd expect because we're automatically allocating overhead
|
// going to be higher than you'd expect because we're automatically allocating overhead
|
||||||
@@ -28,6 +31,27 @@ type ResourceUsage struct {
|
|||||||
} `json:"network"`
|
} `json:"network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The "docker stats" CLI call does not return the same value as the types.MemoryStats.Usage
|
||||||
|
// value which can be rather confusing to people trying to compare panel usage to
|
||||||
|
// their stats output.
|
||||||
|
//
|
||||||
|
// This math is straight up lifted from their CLI repository in order to show the same
|
||||||
|
// values to avoid people bothering me about it. It should also reflect a slightly more
|
||||||
|
// correct memory value anyways.
|
||||||
|
//
|
||||||
|
// @see https://github.com/docker/cli/blob/96e1d1d6/cli/command/container/stats_helpers.go#L227-L249
|
||||||
|
func (ru *ResourceUsage) CalculateDockerMemory(stats types.MemoryStats) uint64 {
|
||||||
|
if v, ok := stats.Stats["total_inactive_file"]; ok && v < stats.Usage {
|
||||||
|
return stats.Usage - v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := stats.Stats["inactive_file"]; v < stats.Usage {
|
||||||
|
return stats.Usage - v
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.Usage
|
||||||
|
}
|
||||||
|
|
||||||
// Calculates the absolute CPU usage used by the server process on the system, not constrained
|
// Calculates the absolute CPU usage used by the server process on the system, not constrained
|
||||||
// by the defined CPU limits on the container.
|
// by the defined CPU limits on the container.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -112,6 +113,23 @@ func (b *BuildSettings) ConvertedCpuLimit() int64 {
|
|||||||
return b.CpuLimit * 1000
|
return b.CpuLimit * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
|
||||||
|
// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
|
||||||
|
// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
|
||||||
|
func (b *BuildSettings) MemoryOverheadMultiplier() float64 {
|
||||||
|
if b.MemoryLimit <= 2048 {
|
||||||
|
return 1.15
|
||||||
|
} else if b.MemoryLimit <= 4096 {
|
||||||
|
return 1.10
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.05
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuildSettings) BoundedMemoryLimit() int64 {
|
||||||
|
return int64(math.Round(float64(b.MemoryLimit) * b.MemoryOverheadMultiplier() * 1_000_000))
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the amount of swap available as a total in bytes. This is returned as the amount
|
// Returns the amount of swap available as a total in bytes. This is returned as the amount
|
||||||
// of memory available to the server initially, PLUS the amount of additional swap to include
|
// of memory available to the server initially, PLUS the amount of additional swap to include
|
||||||
// which is the format used by Docker.
|
// which is the format used by Docker.
|
||||||
@@ -120,7 +138,7 @@ func (b *BuildSettings) ConvertedSwap() int64 {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
return (b.Swap * 1000000) + (b.MemoryLimit * 1000000)
|
return (b.Swap * 1_000_000) + b.BoundedMemoryLimit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines the allocations available for a given server. When using the Docker environment
|
// Defines the allocations available for a given server. When using the Docker environment
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"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 +20,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,
|
||||||
@@ -76,6 +73,7 @@ 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)
|
||||||
|
zap.S().Named("sftp").Debugw("validating credentials for SFTP connection", zap.String("username", c.User))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@@ -85,8 +83,9 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zap.S().Named("sftp").Debugw("matched user to server instance, credentials successfully validated", zap.String("username", c.User), zap.String("server", s.Uuid))
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package system
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// The current version of this software.
|
// The current version of this software.
|
||||||
Version = "1.0.0-beta.2"
|
Version = "1.0.0-beta.5"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user