Compare commits
64 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 | ||
|
|
151b00de23 | ||
|
|
b2f6863399 | ||
|
|
46056dbce9 | ||
|
|
7321fe1421 | ||
|
|
b0fa5abe31 | ||
|
|
6395b8b56c | ||
|
|
df6d98bbda | ||
|
|
8eaf590f78 | ||
|
|
3bca54655b | ||
|
|
9dcf06d106 | ||
|
|
71d38ff62e | ||
|
|
0a815d72a5 | ||
|
|
a60e261a49 | ||
|
|
9e0cacc076 | ||
|
|
4279fa510e | ||
|
|
4ff7bd2777 | ||
|
|
dbe403ef6e | ||
|
|
9c5855663c | ||
|
|
da093e7cf7 | ||
|
|
11035b561a | ||
|
|
df9c4835c4 | ||
|
|
65102966a1 | ||
|
|
fd9487ea4d | ||
|
|
7d1ca0a695 | ||
|
|
12c97c41b0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,3 +46,4 @@ test_*/
|
|||||||
!.gitkeep
|
!.gitkeep
|
||||||
debug
|
debug
|
||||||
data/.states.json
|
data/.states.json
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ dist: xenial
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.11.x
|
|
||||||
- 1.13.x
|
- 1.13.x
|
||||||
|
|
||||||
go_import_path: "github.com/pterodactyl/wings"
|
go_import_path: "github.com/pterodactyl/wings"
|
||||||
@@ -19,6 +18,8 @@ install:
|
|||||||
- go get github.com/haya14busa/goverage
|
- go get github.com/haya14busa/goverage
|
||||||
- go get github.com/schrej/godacov
|
- go get github.com/schrej/godacov
|
||||||
|
|
||||||
|
- go mod download
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make cross-build
|
- make cross-build
|
||||||
- goverage -v -coverprofile=coverage.out ./...
|
- goverage -v -coverprofile=coverage.out ./...
|
||||||
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
35
api/backup_endpoints.go
Normal file
35
api/backup_endpoints.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackupRequest struct {
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Successful bool `json:"successful"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifies the panel that a specific backup has been completed and is now
|
||||||
|
// available for a user to view and download.
|
||||||
|
func (r *PanelRequest) SendBackupStatus(backup string, data BackupRequest) (*RequestError, error) {
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.Post(fmt.Sprintf("/backups/%s", backup), b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
r.Response = resp
|
||||||
|
if r.HasError() {
|
||||||
|
return r.Error(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
@@ -202,30 +202,4 @@ func (r *PanelRequest) SendTransferSuccess(uuid string) (*RequestError, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type BackupRequest struct {
|
|
||||||
Successful bool `json:"successful"`
|
|
||||||
Sha256Hash string `json:"sha256_hash"`
|
|
||||||
FileSize int64 `json:"file_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PanelRequest) SendBackupStatus(uuid string, backup string, data BackupRequest) (*RequestError, error) {
|
|
||||||
b, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := r.Post(fmt.Sprintf("/servers/%s/backup/%s", uuid, backup), b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
r.Response = resp
|
|
||||||
if r.HasError() {
|
|
||||||
return r.Error(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/sftp-server"
|
"github.com/pterodactyl/sftp-server"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *PanelRequest) ValidateSftpCredentials(request sftp_server.AuthenticationRequest) (*sftp_server.AuthenticationResponse, error) {
|
func (r *PanelRequest) ValidateSftpCredentials(request sftp_server.AuthenticationRequest) (*sftp_server.AuthenticationResponse, error) {
|
||||||
@@ -21,11 +22,16 @@ func (r *PanelRequest) ValidateSftpCredentials(request sftp_server.Authenticatio
|
|||||||
r.Response = resp
|
r.Response = resp
|
||||||
|
|
||||||
if r.HasError() {
|
if r.HasError() {
|
||||||
if r.HttpResponseCode() == 403 {
|
if r.HttpResponseCode() >= 400 && r.HttpResponseCode() < 500 {
|
||||||
return nil, sftp_server.InvalidCredentialsError{}
|
zap.S().Debugw("failed to validate server credentials for SFTP", zap.String("error", r.Error().String()))
|
||||||
|
|
||||||
|
return nil, new(sftp_server.InvalidCredentialsError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.WithStack(errors.New(r.Error().String()))
|
rerr := errors.New(r.Error().String())
|
||||||
|
zap.S().Warnw("error validating SFTP credentials", zap.Error(rerr))
|
||||||
|
|
||||||
|
return nil, rerr
|
||||||
}
|
}
|
||||||
|
|
||||||
response := new(sftp_server.AuthenticationResponse)
|
response := new(sftp_server.AuthenticationResponse)
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ var (
|
|||||||
configureArgs struct {
|
configureArgs struct {
|
||||||
PanelURL string
|
PanelURL string
|
||||||
Token string
|
Token string
|
||||||
|
ConfigPath string
|
||||||
Node string
|
Node string
|
||||||
Override bool
|
Override bool
|
||||||
AllowInsecure bool
|
AllowInsecure bool
|
||||||
@@ -40,6 +41,7 @@ func init() {
|
|||||||
configureCmd.PersistentFlags().StringVarP(&configureArgs.PanelURL, "panel-url", "p", "", "The base URL for this daemon's panel")
|
configureCmd.PersistentFlags().StringVarP(&configureArgs.PanelURL, "panel-url", "p", "", "The base URL for this daemon's panel")
|
||||||
configureCmd.PersistentFlags().StringVarP(&configureArgs.Token, "token", "t", "", "The API key to use for fetching node information")
|
configureCmd.PersistentFlags().StringVarP(&configureArgs.Token, "token", "t", "", "The API key to use for fetching node information")
|
||||||
configureCmd.PersistentFlags().StringVarP(&configureArgs.Node, "node", "n", "", "The ID of the node which will be connected to this daemon")
|
configureCmd.PersistentFlags().StringVarP(&configureArgs.Node, "node", "n", "", "The ID of the node which will be connected to this daemon")
|
||||||
|
configureCmd.PersistentFlags().StringVarP(&configureArgs.ConfigPath, "config-path", "c", config.DefaultLocation, "The path where the configuration file should be made")
|
||||||
configureCmd.PersistentFlags().BoolVar(&configureArgs.Override, "override", false, "Set to true to override an existing configuration for this node")
|
configureCmd.PersistentFlags().BoolVar(&configureArgs.Override, "override", false, "Set to true to override an existing configuration for this node")
|
||||||
configureCmd.PersistentFlags().BoolVar(&configureArgs.AllowInsecure, "allow-insecure", false, "Set to true to disable certificate checking")
|
configureCmd.PersistentFlags().BoolVar(&configureArgs.AllowInsecure, "allow-insecure", false, "Set to true to disable certificate checking")
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ func configureCmdRun(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat("config.yml"); err == nil && !configureArgs.Override {
|
if _, err := os.Stat(configureArgs.ConfigPath); err == nil && !configureArgs.Override {
|
||||||
survey.AskOne(&survey.Confirm{Message: "Override existing configuration file"}, &configureArgs.Override)
|
survey.AskOne(&survey.Confirm{Message: "Override existing configuration file"}, &configureArgs.Override)
|
||||||
if !configureArgs.Override {
|
if !configureArgs.Override {
|
||||||
fmt.Println("Aborting process; a configuration file already exists for this node.")
|
fmt.Println("Aborting process; a configuration file already exists for this node.")
|
||||||
|
|||||||
86
cmd/root.go
86
cmd/root.go
@@ -3,8 +3,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
@@ -19,7 +22,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configPath = "config.yml"
|
var configPath = config.DefaultLocation
|
||||||
var debug = false
|
var debug = false
|
||||||
var shouldRunProfiler = false
|
var shouldRunProfiler = false
|
||||||
|
|
||||||
@@ -31,20 +34,52 @@ var root = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
root.PersistentFlags().StringVar(&configPath, "config", "config.yml", "set the location for the configuration file")
|
root.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
|
||||||
root.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
|
root.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
|
||||||
root.PersistentFlags().BoolVar(&shouldRunProfiler, "profile", false, "pass in order to profile wings")
|
root.PersistentFlags().BoolVar(&shouldRunProfiler, "profile", false, "pass in order to profile wings")
|
||||||
|
|
||||||
root.AddCommand(configureCmd)
|
root.AddCommand(configureCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the configuration path based on the arguments provided.
|
||||||
|
func readConfiguration() (*config.Configuration, error) {
|
||||||
|
var p = configPath
|
||||||
|
if !strings.HasPrefix(p, "/") {
|
||||||
|
d, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p = path.Clean(path.Join(d, configPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, err := os.Stat(p); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
} else if s.IsDir() {
|
||||||
|
return nil, errors.New("cannot use directory as configuration file path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.ReadConfiguration(p)
|
||||||
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := config.ReadConfiguration(configPath)
|
// 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()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -58,7 +93,7 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Infof("using configuration from path: %s", configPath)
|
zap.S().Infof("using configuration from path: %s", c.GetPath())
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
zap.S().Debugw("running in debug mode")
|
zap.S().Debugw("running in debug mode")
|
||||||
zap.S().Infow("certificate checking is disabled")
|
zap.S().Infow("certificate checking is disabled")
|
||||||
@@ -71,6 +106,11 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
config.Set(c)
|
config.Set(c)
|
||||||
config.SetDebugViaFlag(debug)
|
config.SetDebugViaFlag(debug)
|
||||||
|
|
||||||
|
if err := c.System.ConfigureDirectories(); err != nil {
|
||||||
|
zap.S().Panicw("failed to configure system directories for pterodactyl", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.Username)
|
zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.Username)
|
||||||
if su, err := c.EnsurePterodactylUser(); err != nil {
|
if su, err := c.EnsurePterodactylUser(); err != nil {
|
||||||
zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err))
|
zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err))
|
||||||
@@ -185,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
|
||||||
@@ -236,4 +265,29 @@ func printLogo() {
|
|||||||
fmt.Println(` \___/\___/___/___/___/___ /______/`)
|
fmt.Println(` \___/\___/___/___/___/___ /______/`)
|
||||||
fmt.Println(` /_______/ v` + system.Version)
|
fmt.Println(` /_______/ v` + system.Version)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
fmt.Println(`Website: https://pterodactyl.io`)
|
||||||
|
fmt.Println(`Source: https://github.com/pterodactyl/wings`)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(`Copyright © 2018 - 2020 Dane Everitt & Contributors`)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
137
config/config.go
137
config/config.go
@@ -1,7 +1,9 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/cobaugh/osrelease"
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -17,9 +19,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DefaultLocation = "/etc/pterodactyl/config.yml"
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
sync.RWMutex `json:"-" yaml:"-"`
|
sync.RWMutex `json:"-" yaml:"-"`
|
||||||
|
|
||||||
|
// The location from which this configuration instance was instantiated.
|
||||||
|
path string
|
||||||
|
|
||||||
// Locker specific to writing the configuration to the disk, this happens
|
// Locker specific to writing the configuration to the disk, this happens
|
||||||
// in areas that might already be locked so we don't want to crash the process.
|
// in areas that might already be locked so we don't want to crash the process.
|
||||||
writeLock sync.Mutex
|
writeLock sync.Mutex
|
||||||
@@ -39,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
|
||||||
@@ -75,48 +82,6 @@ type Configuration struct {
|
|||||||
PanelLocation string `json:"remote" yaml:"remote"`
|
PanelLocation string `json:"remote" yaml:"remote"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines basic system configuration settings.
|
|
||||||
type SystemConfiguration struct {
|
|
||||||
// Directory where the server data is stored at.
|
|
||||||
Data string `default:"/srv/daemon-data" yaml:"data"`
|
|
||||||
|
|
||||||
// Directory where server archives for transferring will be stored.
|
|
||||||
ArchiveDirectory string `default:"/srv/daemon-data/.archives" yaml:"archive_directory"`
|
|
||||||
|
|
||||||
// Directory where local backups will be stored on the machine.
|
|
||||||
BackupDirectory string `default:"/srv/daemon-data/.backups" yaml:"backup_directory"`
|
|
||||||
|
|
||||||
// The user that should own all of the server files, and be used for containers.
|
|
||||||
Username string `default:"pterodactyl" yaml:"username"`
|
|
||||||
|
|
||||||
// Definitions for the user that gets created to ensure that we can quickly access
|
|
||||||
// this information without constantly having to do a system lookup.
|
|
||||||
User struct {
|
|
||||||
Uid int
|
|
||||||
Gid int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determines if permissions for a server should be set automatically on
|
|
||||||
// daemon boot. This can take a long time on systems with many servers, or on
|
|
||||||
// systems with servers containing thousands of files.
|
|
||||||
//
|
|
||||||
// Setting this to true by default helps us avoid a lot of support requests
|
|
||||||
// from people that keep trying to move files around as a root user leading
|
|
||||||
// to server permission issues.
|
|
||||||
//
|
|
||||||
// In production and heavy use environments where boot speed is essential,
|
|
||||||
// this should be set to false as servers will self-correct permissions on
|
|
||||||
// boot anyways.
|
|
||||||
SetPermissionsOnBoot bool `default:"true" yaml:"set_permissions_on_boot"`
|
|
||||||
|
|
||||||
// Determines if Wings should detect a server that stops with a normal exit code of
|
|
||||||
// "0" as being crashed if the process stopped without any Wings interaction. E.g.
|
|
||||||
// the user did not press the stop button, but the process stopped cleanly.
|
|
||||||
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
|
|
||||||
|
|
||||||
Sftp *SftpConfiguration `yaml:"sftp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the configuration of the internal SFTP server.
|
// Defines the configuration of the internal SFTP server.
|
||||||
type SftpConfiguration struct {
|
type SftpConfiguration struct {
|
||||||
// If set to false, the internal SFTP server will not be booted and you will need
|
// If set to false, the internal SFTP server will not be booted and you will need
|
||||||
@@ -133,54 +98,6 @@ type SftpConfiguration struct {
|
|||||||
ReadOnly bool `default:"false" yaml:"read_only"`
|
ReadOnly bool `default:"false" yaml:"read_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type dockerNetworkInterfaces struct {
|
|
||||||
V4 struct {
|
|
||||||
Subnet string `default:"172.18.0.0/16"`
|
|
||||||
Gateway string `default:"172.18.0.1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
V6 struct {
|
|
||||||
Subnet string `default:"fdba:17c8:6c94::/64"`
|
|
||||||
Gateway string `default:"fdba:17c8:6c94::1011"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerNetworkConfiguration struct {
|
|
||||||
// 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.
|
|
||||||
Interface string `default:"172.18.0.1"`
|
|
||||||
|
|
||||||
// The name of the network to use. If this network already exists it will not
|
|
||||||
// be created. If it is not found, a new network will be created using the interface
|
|
||||||
// defined.
|
|
||||||
Name string `default:"pterodactyl_nw"`
|
|
||||||
ISPN bool `default:"false" yaml:"ispn"`
|
|
||||||
Driver string `default:"bridge"`
|
|
||||||
IsInternal bool `default:"false" yaml:"is_internal"`
|
|
||||||
EnableICC bool `default:"true" yaml:"enable_icc"`
|
|
||||||
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the docker configuration used by the daemon when interacting with
|
|
||||||
// containers and networks on the system.
|
|
||||||
type DockerConfiguration struct {
|
|
||||||
// Network configuration that should be used when creating a new network
|
|
||||||
// for containers run through the daemon.
|
|
||||||
Network DockerNetworkConfiguration `json:"network" yaml:"network"`
|
|
||||||
|
|
||||||
// If true, container images will be updated when a server starts if there
|
|
||||||
// is an update available. If false the daemon will not attempt updates and will
|
|
||||||
// defer to the host system to manage image updates.
|
|
||||||
UpdateImages bool `default:"true" json:"update_images" yaml:"update_images"`
|
|
||||||
|
|
||||||
// The location of the Docker socket.
|
|
||||||
Socket string `default:"/var/run/docker.sock"`
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
TimezonePath string `default:"/etc/timezone" json:"timezone_path" yaml:"timezone_path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the configuration for the internal API that is exposed by the
|
// Defines the configuration for the internal API that is exposed by the
|
||||||
// daemon webserver.
|
// daemon webserver.
|
||||||
type ApiConfiguration struct {
|
type ApiConfiguration struct {
|
||||||
@@ -217,6 +134,9 @@ func ReadConfiguration(path string) (*Configuration, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track the location where we created this configuration.
|
||||||
|
c.path = path
|
||||||
|
|
||||||
// Replace environment variables within the configuration file with their
|
// Replace environment variables within the configuration file with their
|
||||||
// values from the host system.
|
// values from the host system.
|
||||||
b = []byte(os.ExpandEnv(string(b)))
|
b = []byte(os.ExpandEnv(string(b)))
|
||||||
@@ -269,6 +189,11 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
|
|||||||
return _jwtAlgo
|
return _jwtAlgo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the path for this configuration file.
|
||||||
|
func (c *Configuration) GetPath() string {
|
||||||
|
return c.path
|
||||||
|
}
|
||||||
|
|
||||||
// Ensures that the Pterodactyl core user exists on the system. This user will be the
|
// Ensures that the Pterodactyl core user exists on the system. This user will be the
|
||||||
// owner of all data in the root data directory and is used as the user within containers.
|
// owner of all data in the root data directory and is used as the user within containers.
|
||||||
//
|
//
|
||||||
@@ -294,7 +219,7 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
|
|||||||
|
|
||||||
// Alpine Linux is the only OS we currently support that doesn't work with the useradd command, so
|
// Alpine Linux is the only OS we currently support that doesn't work with the useradd command, so
|
||||||
// in those cases we just modify the command a bit to work as expected.
|
// in those cases we just modify the command a bit to work as expected.
|
||||||
if strings.HasPrefix(sysName, "Alpine") {
|
if strings.HasPrefix(sysName, "alpine") {
|
||||||
command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /bin/false %[1]s", c.System.Username)
|
command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /bin/false %[1]s", c.System.Username)
|
||||||
|
|
||||||
// We have to create the group first on Alpine, so do that here before continuing on
|
// We have to create the group first on Alpine, so do that here before continuing on
|
||||||
@@ -395,6 +320,10 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
ccopy.Debug = false
|
ccopy.Debug = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.path == "" {
|
||||||
|
return errors.New("cannot write configuration, no path defined in struct")
|
||||||
|
}
|
||||||
|
|
||||||
b, err := yaml.Marshal(&ccopy)
|
b, err := yaml.Marshal(&ccopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -404,7 +333,7 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
c.writeLock.Lock()
|
c.writeLock.Lock()
|
||||||
defer c.writeLock.Unlock()
|
defer c.writeLock.Unlock()
|
||||||
|
|
||||||
if err := ioutil.WriteFile("config.yml", b, 0644); err != nil {
|
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,20 +342,10 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
|
|
||||||
// Gets the system release name.
|
// Gets the system release name.
|
||||||
func getSystemName() (string, error) {
|
func getSystemName() (string, error) {
|
||||||
// alpine doesn't have lsb_release
|
// use osrelease to get release version and ID
|
||||||
_, err := os.Stat("/etc/alpine-release")
|
if release, err := osrelease.Read(); err != nil {
|
||||||
if os.IsNotExist(err) {
|
return "", err
|
||||||
// if the alpine release file doesn't exist. run lsb_release
|
|
||||||
cmd := exec.Command("lsb_release", "-is")
|
|
||||||
|
|
||||||
b, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(b), nil
|
|
||||||
} else {
|
} else {
|
||||||
// if the alpine release file does exist return string
|
return release["ID"], nil
|
||||||
return "Alpine", err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
config/config_docker.go
Normal file
53
config/config_docker.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type dockerNetworkInterfaces struct {
|
||||||
|
V4 struct {
|
||||||
|
Subnet string `default:"172.18.0.0/16"`
|
||||||
|
Gateway string `default:"172.18.0.1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
V6 struct {
|
||||||
|
Subnet string `default:"fdba:17c8:6c94::/64"`
|
||||||
|
Gateway string `default:"fdba:17c8:6c94::1011"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DockerNetworkConfiguration struct {
|
||||||
|
// 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.
|
||||||
|
Interface string `default:"172.18.0.1" json:"interface" yaml:"interface"`
|
||||||
|
|
||||||
|
// The DNS settings for containers.
|
||||||
|
Dns []string `default:"[\"1.1.1.1\", \"1.0.0.1\"]"`
|
||||||
|
|
||||||
|
// The name of the network to use. If this network already exists it will not
|
||||||
|
// be created. If it is not found, a new network will be created using the interface
|
||||||
|
// defined.
|
||||||
|
Name string `default:"pterodactyl_nw"`
|
||||||
|
ISPN bool `default:"false" yaml:"ispn"`
|
||||||
|
Driver string `default:"bridge"`
|
||||||
|
Mode string `default:"pterodactyl_nw" yaml:"network_mode"`
|
||||||
|
IsInternal bool `default:"false" yaml:"is_internal"`
|
||||||
|
EnableICC bool `default:"true" yaml:"enable_icc"`
|
||||||
|
Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the docker configuration used by the daemon when interacting with
|
||||||
|
// containers and networks on the system.
|
||||||
|
type DockerConfiguration struct {
|
||||||
|
// Network configuration that should be used when creating a new network
|
||||||
|
// for containers run through the daemon.
|
||||||
|
Network DockerNetworkConfiguration `json:"network" yaml:"network"`
|
||||||
|
|
||||||
|
// If true, container images will be updated when a server starts if there
|
||||||
|
// is an update available. If false the daemon will not attempt updates and will
|
||||||
|
// defer to the host system to manage image updates.
|
||||||
|
UpdateImages bool `default:"true" json:"update_images" yaml:"update_images"`
|
||||||
|
|
||||||
|
// The location of the Docker socket.
|
||||||
|
Socket string `default:"/var/run/docker.sock" json:"socket" yaml:"socket"`
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
TimezonePath string `default:"/etc/timezone" json:"timezone_path" yaml:"timezone_path"`
|
||||||
|
}
|
||||||
96
config/config_system.go
Normal file
96
config/config_system.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines basic system configuration settings.
|
||||||
|
type SystemConfiguration struct {
|
||||||
|
// The root directory where all of the pterodactyl data is stored at.
|
||||||
|
RootDirectory string `default:"/var/lib/pterodactyl" yaml:"root_directory"`
|
||||||
|
|
||||||
|
// Directory where logs for server installations and other wings events are logged.
|
||||||
|
LogDirectory string `default:"/var/log/pterodactyl" yaml:"log_directory"`
|
||||||
|
|
||||||
|
// Directory where the server data is stored at.
|
||||||
|
Data string `default:"/var/lib/pterodactyl/volumes" yaml:"data"`
|
||||||
|
|
||||||
|
// Directory where server archives for transferring will be stored.
|
||||||
|
ArchiveDirectory string `default:"/var/lib/pterodactyl/archives" yaml:"archive_directory"`
|
||||||
|
|
||||||
|
// Directory where local backups will be stored on the machine.
|
||||||
|
BackupDirectory string `default:"/var/lib/pterodactyl/backups" yaml:"backup_directory"`
|
||||||
|
|
||||||
|
// The user that should own all of the server files, and be used for containers.
|
||||||
|
Username string `default:"pterodactyl" yaml:"username"`
|
||||||
|
|
||||||
|
// Definitions for the user that gets created to ensure that we can quickly access
|
||||||
|
// this information without constantly having to do a system lookup.
|
||||||
|
User struct {
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if permissions for a server should be set automatically on
|
||||||
|
// daemon boot. This can take a long time on systems with many servers, or on
|
||||||
|
// systems with servers containing thousands of files.
|
||||||
|
//
|
||||||
|
// Setting this to true by default helps us avoid a lot of support requests
|
||||||
|
// from people that keep trying to move files around as a root user leading
|
||||||
|
// to server permission issues.
|
||||||
|
//
|
||||||
|
// In production and heavy use environments where boot speed is essential,
|
||||||
|
// this should be set to false as servers will self-correct permissions on
|
||||||
|
// boot anyways.
|
||||||
|
SetPermissionsOnBoot bool `default:"true" yaml:"set_permissions_on_boot"`
|
||||||
|
|
||||||
|
// Determines if Wings should detect a server that stops with a normal exit code of
|
||||||
|
// "0" as being crashed if the process stopped without any Wings interaction. E.g.
|
||||||
|
// the user did not press the stop button, but the process stopped cleanly.
|
||||||
|
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
|
||||||
|
|
||||||
|
Sftp *SftpConfiguration `yaml:"sftp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that all of the system directories exist on the system. These directories are
|
||||||
|
// created so that only the owner can read the data, and no other users.
|
||||||
|
func (sc *SystemConfiguration) ConfigureDirectories() error {
|
||||||
|
zap.S().Debugw("ensuring root data directory exists", zap.String("path", sc.RootDirectory))
|
||||||
|
if err := os.MkdirAll(sc.RootDirectory, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw("ensuring log directory exists", zap.String("path", sc.LogDirectory))
|
||||||
|
if err := os.MkdirAll(path.Join(sc.LogDirectory, "/install"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw("ensuring server data directory exists", zap.String("path", sc.Data))
|
||||||
|
if err := os.MkdirAll(sc.Data, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw("ensuring archive data directory exists", zap.String("path", sc.ArchiveDirectory))
|
||||||
|
if err := os.MkdirAll(sc.ArchiveDirectory, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Debugw("ensuring backup data directory exists", zap.String("path", sc.BackupDirectory))
|
||||||
|
if err := os.MkdirAll(sc.BackupDirectory, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the location of the JSON file that tracks server states.
|
||||||
|
func (sc *SystemConfiguration) GetStatesPath() string {
|
||||||
|
return path.Join(sc.RootDirectory, "states.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the location of the JSON file that tracks server states.
|
||||||
|
func (sc *SystemConfiguration) GetInstallLogPath() string {
|
||||||
|
return path.Join(sc.LogDirectory, "install/")
|
||||||
|
}
|
||||||
4
data/.gitignore
vendored
4
data/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
servers/*.yml
|
|
||||||
!install_logs/.gitkeep
|
|
||||||
install_logs/*
|
|
||||||
states.json
|
|
||||||
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
|
||||||
32
go.mod
32
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,8 +17,10 @@ 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/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 // indirect
|
||||||
github.com/creasty/defaults v1.3.0
|
github.com/creasty/defaults v1.3.0
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
@@ -34,7 +36,9 @@ 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/magiconair/properties v1.8.1
|
github.com/magiconair/properties v1.8.1
|
||||||
github.com/mattn/go-shellwords v1.0.10 // indirect
|
github.com/mattn/go-shellwords v1.0.10 // indirect
|
||||||
github.com/mholt/archiver/v3 v3.3.0
|
github.com/mholt/archiver/v3 v3.3.0
|
||||||
@@ -45,24 +49,26 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.8.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/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-20200403201458-baeed622b8d8 // 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/sys v0.0.0-20200331124033-c3d80250170d // indirect
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
||||||
golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b // 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
|
||||||
)
|
)
|
||||||
|
|||||||
60
go.sum
60
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=
|
||||||
@@ -29,6 +31,8 @@ github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929 h1:MW/JDk68Rny52y
|
|||||||
github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
|
github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:R0IDH8daQ3lODvu8YtxnIqqth5qMGCJyADoUQvmLx4o=
|
||||||
|
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249/go.mod h1:EHKW9yNEYSBpTKzuu7Y9oOrft/UlzH57rMIB03oev6M=
|
||||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4=
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4=
|
||||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
@@ -78,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=
|
||||||
@@ -118,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=
|
||||||
@@ -139,6 +148,8 @@ github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
|||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
|
github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
|
||||||
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/klauspost/pgzip v1.2.3 h1:Ce2to9wvs/cuJ2b86/CKQoTYr9VHfpanYosZ0UBJqdw=
|
||||||
|
github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
@@ -199,12 +210,16 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
|||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/profile v1.4.0 h1:uCmaf4vVbWAOZz36k1hrQD7ijGRzLwaME8Am/7a4jZI=
|
github.com/pkg/profile v1.4.0 h1:uCmaf4vVbWAOZz36k1hrQD7ijGRzLwaME8Am/7a4jZI=
|
||||||
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
||||||
github.com/pkg/sftp v1.8.3 h1:9jSe2SxTM8/3bXZjtqnkgTBW+lA8db0knZJyns7gpBA=
|
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=
|
||||||
@@ -218,11 +233,15 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
|||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/pterodactyl/sftp-server v1.1.1 h1:IjuOy21BNZxfejKnXG1RgLxXAYylDqBVpbKZ6+fG5FQ=
|
github.com/pterodactyl/sftp-server v1.1.1 h1:IjuOy21BNZxfejKnXG1RgLxXAYylDqBVpbKZ6+fG5FQ=
|
||||||
github.com/pterodactyl/sftp-server v1.1.1/go.mod h1:b1VVWYv0RF9rxSZQqaD/rYXriiRMNPsbV//CKMXR4ag=
|
github.com/pterodactyl/sftp-server v1.1.1/go.mod h1:b1VVWYv0RF9rxSZQqaD/rYXriiRMNPsbV//CKMXR4ag=
|
||||||
|
github.com/pterodactyl/sftp-server v1.1.2 h1:5bI9upe0kBRn9ALDabn9S2GVU5gkYvSErYgs32dAKjk=
|
||||||
|
github.com/pterodactyl/sftp-server v1.1.2/go.mod h1:KjSONrenRr1oCh94QIVAU6yEzMe+Hd7r/JHrh5/oQHs=
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY=
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY=
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
||||||
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
@@ -242,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=
|
||||||
@@ -263,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=
|
||||||
@@ -270,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=
|
||||||
@@ -282,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=
|
||||||
@@ -290,8 +317,12 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
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-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk=
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||||
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/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=
|
||||||
@@ -299,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=
|
||||||
@@ -309,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=
|
||||||
@@ -320,6 +356,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -330,8 +368,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/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=
|
||||||
@@ -350,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-20200403190813-44a64ad78b9b h1:AFZdJUT7jJYXQEC29hYH/WZkoV7+KhwxQGmdZ19yYoY=
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
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/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=
|
||||||
@@ -383,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 {
|
||||||
f.configuration = mb
|
return err
|
||||||
|
} else {
|
||||||
|
f.configuration = mb
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -204,6 +259,11 @@ func (f *ConfigurationFile) parseXmlFile(path string) error {
|
|||||||
// Ensure the XML is indented properly.
|
// Ensure the XML is indented properly.
|
||||||
doc.Indent(2)
|
doc.Indent(2)
|
||||||
|
|
||||||
|
// Truncate the file before attempting to write the changes.
|
||||||
|
if err := os.Truncate(path, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Write the XML to the file.
|
// Write the XML to the file.
|
||||||
_, err = doc.WriteTo(file)
|
_, err = doc.WriteTo(file)
|
||||||
|
|
||||||
@@ -261,6 +321,11 @@ func (f *ConfigurationFile) parseIniFile(path string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate the file before attempting to write the changes.
|
||||||
|
if err := os.Truncate(path, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := cfg.WriteTo(file); err != nil {
|
if _, err := cfg.WriteTo(file); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -294,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
|
||||||
}
|
}
|
||||||
@@ -310,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
|
||||||
}
|
}
|
||||||
@@ -387,7 +457,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
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.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -25,13 +26,20 @@ func getDownloadBackup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p, st, err := s.LocateBackup(token.BackupUuid)
|
b, st, err := backup.LocateLocal(token.BackupUuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "The requested backup was not found on this server.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(p)
|
f, err := os.Open(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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,28 +1,46 @@
|
|||||||
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"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backs up a server.
|
// Backs up a server.
|
||||||
func postServerBackup(c *gin.Context) {
|
func postServerBackup(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
var data struct{
|
data := &backup.Request{}
|
||||||
Uuid string `json:"uuid"`
|
|
||||||
IgnoredFiles []string `json:"ignored_files"`
|
|
||||||
}
|
|
||||||
c.BindJSON(&data)
|
c.BindJSON(&data)
|
||||||
|
|
||||||
go func(backup *server.Backup) {
|
var adapter backup.BackupInterface
|
||||||
if err := backup.BackupAndNotify(); 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))
|
||||||
}
|
}
|
||||||
}(s.NewBackup(data.Uuid, data.IgnoredFiles))
|
}(adapter, s)
|
||||||
|
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
@@ -31,16 +49,16 @@ func postServerBackup(c *gin.Context) {
|
|||||||
func deleteServerBackup(c *gin.Context) {
|
func deleteServerBackup(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
p, _, err := s.LocateBackup(c.Param("backup"))
|
b, _, err := backup.LocateLocal(c.Param("backup"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Remove(p); err != nil {
|
if err := b.Remove(); err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,24 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the contents of a file on the server.
|
// Returns the contents of a file on the server.
|
||||||
func getServerFileContents(c *gin.Context) {
|
func getServerFileContents(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
cleaned, err := s.Filesystem.SafePath(c.Query("file"))
|
|
||||||
|
p, err := url.QueryUnescape(c.Query("file"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = "/" + strings.TrimLeft(p, "/")
|
||||||
|
|
||||||
|
cleaned, err := s.Filesystem.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
"error": "The file requested could not be found.",
|
"error": "The file requested could not be found.",
|
||||||
@@ -56,7 +66,13 @@ func getServerFileContents(c *gin.Context) {
|
|||||||
func getServerListDirectory(c *gin.Context) {
|
func getServerListDirectory(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
stats, err := s.Filesystem.ListDirectory(c.Query("directory"))
|
d, err := url.QueryUnescape(c.Query("directory"))
|
||||||
|
if err != nil {
|
||||||
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := s.Filesystem.ListDirectory(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
TrackedServerError(err, s).AbortWithServerError(c)
|
||||||
return
|
return
|
||||||
@@ -69,9 +85,9 @@ func getServerListDirectory(c *gin.Context) {
|
|||||||
func putServerRenameFile(c *gin.Context) {
|
func putServerRenameFile(c *gin.Context) {
|
||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
var data struct{
|
var data struct {
|
||||||
RenameFrom string `json:"rename_from"`
|
RenameFrom string `json:"rename_from"`
|
||||||
RenameTo string `json:"rename_to"`
|
RenameTo string `json:"rename_to"`
|
||||||
}
|
}
|
||||||
c.BindJSON(&data)
|
c.BindJSON(&data)
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -152,4 +175,4 @@ func postServerCreateDirectory(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var alg *jwt.HMACSHA
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PermissionConnect = "websocket.*"
|
PermissionConnect = "websocket.connect"
|
||||||
PermissionSendCommand = "control.console"
|
PermissionSendCommand = "control.console"
|
||||||
PermissionSendPowerStart = "control.start"
|
PermissionSendPowerStart = "control.start"
|
||||||
PermissionSendPowerStop = "control.stop"
|
PermissionSendPowerStop = "control.stop"
|
||||||
PermissionSendPowerRestart = "control.restart"
|
PermissionSendPowerRestart = "control.restart"
|
||||||
PermissionReceiveErrors = "admin.errors"
|
PermissionReceiveErrors = "admin.websocket.errors"
|
||||||
PermissionReceiveInstall = "admin.install"
|
PermissionReceiveInstall = "admin.websocket.install"
|
||||||
PermissionReceiveBackups = "backup.read"
|
PermissionReceiveBackups = "backup.read"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
271
server/backup.go
271
server/backup.go
@@ -1,210 +1,115 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"bufio"
|
||||||
"encoding/hex"
|
|
||||||
"github.com/mholt/archiver/v3"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backup struct {
|
// Notifies the panel of a backup's state and returns an error if one is encountered
|
||||||
Uuid string `json:"uuid"`
|
// while performing this action.
|
||||||
IgnoredFiles []string `json:"ignored_files"`
|
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
|
||||||
server *Server
|
|
||||||
localDirectory string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new Backup struct from data passed through in a request.
|
|
||||||
func (s *Server) NewBackup(uuid string, ignore []string) *Backup {
|
|
||||||
return &Backup{
|
|
||||||
Uuid: uuid,
|
|
||||||
IgnoredFiles: ignore,
|
|
||||||
server: s,
|
|
||||||
localDirectory: path.Join(config.Get().System.BackupDirectory, s.Uuid),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (s *Server) LocateBackup(uuid string) (string, os.FileInfo, error) {
|
|
||||||
p := path.Join(config.Get().System.BackupDirectory, s.Uuid, uuid+".tar.gz")
|
|
||||||
|
|
||||||
st, err := os.Stat(p)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if st.IsDir() {
|
|
||||||
return "", nil, errors.New("invalid archive found; is directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, st, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensures that the local backup destination for files exists.
|
|
||||||
func (b *Backup) ensureLocalBackupLocation() error {
|
|
||||||
if _, err := os.Stat(b.localDirectory); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.MkdirAll(b.localDirectory, 0700)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the path for this specific backup.
|
|
||||||
func (b *Backup) GetPath() string {
|
|
||||||
return path.Join(b.localDirectory, b.Uuid+".tar.gz")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Backup) GetChecksum() ([]byte, error) {
|
|
||||||
h := sha256.New()
|
|
||||||
|
|
||||||
f, err := os.Open(b.GetPath())
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a backup of the selected files and pushes it to the defined location
|
|
||||||
// for this instance.
|
|
||||||
func (b *Backup) Backup() (*api.BackupRequest, error) {
|
|
||||||
rootPath := b.server.Filesystem.Path()
|
|
||||||
|
|
||||||
if err := b.ensureLocalBackupLocation(); err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
zap.S().Debugw("starting archive of server files for backup", zap.String("server", b.server.Uuid), zap.String("backup", b.Uuid))
|
|
||||||
if err := archiver.Archive([]string{rootPath}, b.GetPath()); err != nil {
|
|
||||||
if strings.HasPrefix(err.Error(), "file already exists") {
|
|
||||||
zap.S().Debugw("backup already exists on system, removing and re-attempting", zap.String("backup", b.Uuid))
|
|
||||||
|
|
||||||
if rerr := os.Remove(b.GetPath()); rerr != nil {
|
|
||||||
return nil, errors.WithStack(rerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-attempt this backup.
|
|
||||||
return b.Backup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there was some error with the archive, just go ahead and ensure the backup
|
|
||||||
// is completely destroyed at this point. Ignore any errors from this function.
|
|
||||||
os.Remove(b.GetPath())
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(2)
|
|
||||||
|
|
||||||
var checksum string
|
|
||||||
// Calculate the checksum for the file.
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
resp, err := b.GetChecksum()
|
|
||||||
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 s int64
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
st, err := os.Stat(b.GetPath())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s = st.Size()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return &api.BackupRequest{
|
|
||||||
Successful: true,
|
|
||||||
Sha256Hash: checksum,
|
|
||||||
FileSize: s,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performs a server backup and then notifies the Panel of the completed status
|
|
||||||
// so that the backup shows up for the user correctly.
|
|
||||||
func (b *Backup) BackupAndNotify() error {
|
|
||||||
resp, err := b.Backup()
|
|
||||||
if err != nil {
|
|
||||||
b.notifyPanel(resp)
|
|
||||||
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.notifyPanel(resp); err != nil {
|
|
||||||
// These errors indicate that the Panel will not know about the status of this
|
|
||||||
// backup, so let's just go ahead and delete it, and let the Panel handle the
|
|
||||||
// cleanup process for the backups.
|
|
||||||
//
|
|
||||||
// @todo perhaps in the future we can sync the backups from the servers on boot?
|
|
||||||
os.Remove(b.GetPath())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit an event over the socket so we can update the backup in realtime on
|
|
||||||
// the frontend for the server.
|
|
||||||
b.server.Events().PublishJson(BackupCompletedEvent+":"+b.Uuid, map[string]interface{}{
|
|
||||||
"uuid": b.Uuid,
|
|
||||||
"sha256_hash": resp.Sha256Hash,
|
|
||||||
"file_size": resp.FileSize,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Backup) notifyPanel(request *api.BackupRequest) error {
|
|
||||||
r := api.NewRequester()
|
r := api.NewRequester()
|
||||||
|
rerr, err := r.SendBackupStatus(uuid, ad.ToRequest(successful))
|
||||||
rerr, err := r.SendBackupStatus(b.server.Uuid, b.Uuid, *request)
|
|
||||||
if rerr != nil || err != nil {
|
if rerr != nil || err != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorw(
|
zap.S().Errorw(
|
||||||
"failed to notify panel of backup status due to internal code error",
|
"failed to notify panel of backup status due to internal code error",
|
||||||
zap.String("server", b.server.Uuid),
|
zap.String("backup", s.Uuid),
|
||||||
zap.String("backup", b.Uuid),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Warnw(
|
zap.S().Warnw(rerr.String(), zap.String("backup", uuid))
|
||||||
rerr.String(),
|
|
||||||
zap.String("server", b.server.Uuid),
|
|
||||||
zap.String("backup", b.Uuid),
|
|
||||||
)
|
|
||||||
|
|
||||||
return errors.New(rerr.String())
|
return errors.New(rerr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all of the ignored files for a server based on its .pteroignore file in the root.
|
||||||
|
func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
|
||||||
|
var ignored []string
|
||||||
|
|
||||||
|
f, err := os.Open(path.Join(s.Filesystem.Path(), ".pteroignore"))
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
// Only include non-empty lines, for the sake of clarity...
|
||||||
|
if t := scanner.Text(); t != "" {
|
||||||
|
ignored = append(ignored, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
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.
|
||||||
|
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 {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ad, err := b.Generate(inc, s.Filesystem.Path())
|
||||||
|
if err != nil {
|
||||||
|
if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil {
|
||||||
|
zap.S().Warnw("failed to notify panel of failed backup state", zap.String("backup", b.Identifier()), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
|
||||||
|
b.Remove()
|
||||||
|
|
||||||
|
return notifyError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit an event over the socket so we can update the backup in realtime on
|
||||||
|
// the frontend for the server.
|
||||||
|
s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
|
||||||
|
"uuid": b.Identifier(),
|
||||||
|
"sha256_hash": ad.Checksum,
|
||||||
|
"file_size": ad.Size,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
111
server/backup/archiver.go
Normal file
111
server/backup/archiver.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"context"
|
||||||
|
gzip "github.com/klauspost/pgzip"
|
||||||
|
"github.com/remeh/sizedwaitgroup"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Archive struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
TrimPrefix string
|
||||||
|
Files *IncludedFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates an archive at dest with all of the files definied in the included files struct.
|
||||||
|
func (a *Archive) Create(dest string, ctx context.Context) error {
|
||||||
|
f, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
gzw := gzip.NewWriter(f)
|
||||||
|
defer gzw.Close()
|
||||||
|
|
||||||
|
tw := tar.NewWriter(gzw)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
wg := sizedwaitgroup.New(10)
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
// Iterate over all of the files to be included and put them into the archive. This is
|
||||||
|
// done as a concurrent goroutine to speed things along. If an error is encountered at
|
||||||
|
// any step, the entire process is aborted.
|
||||||
|
for p, s := range a.Files.All() {
|
||||||
|
if (*s).IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pa := p
|
||||||
|
st := s
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
wg.Add()
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
return a.addToArchive(pa, st, tw)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until the entire routine is completed.
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
// Attempt to remove the archive if there is an error, report that error to
|
||||||
|
// the logger if it fails.
|
||||||
|
if rerr := os.Remove(dest); rerr != nil && !os.IsNotExist(rerr) {
|
||||||
|
zap.S().Warnw("failed to delete corrupted backup archive", zap.String("location", dest))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a single file to the existing tar archive writer.
|
||||||
|
func (a *Archive) addToArchive(p string, s *os.FileInfo, w *tar.Writer) error {
|
||||||
|
f, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
st := *s
|
||||||
|
header := &tar.Header{
|
||||||
|
// Trim the long server path from the name of the file so that the resulting
|
||||||
|
// archive is exactly how the user would see it in the panel file manager.
|
||||||
|
Name: strings.TrimPrefix(p, a.TrimPrefix),
|
||||||
|
Size: st.Size(),
|
||||||
|
Mode: int64(st.Mode()),
|
||||||
|
ModTime: st.ModTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// These actions must occur sequentially, even if this function is called multiple
|
||||||
|
// in parallel. You'll get some nasty panic's otherwise.
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
|
|
||||||
|
if err = w.WriteHeader(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(w, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
151
server/backup/backup.go
Normal file
151
server/backup/backup.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/pterodactyl/wings/api"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocalBackupAdapter = "wings"
|
||||||
|
S3BackupAdapter = "s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArchiveDetails struct {
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a request object.
|
||||||
|
func (ad *ArchiveDetails) ToRequest(successful bool) api.BackupRequest {
|
||||||
|
return api.BackupRequest{
|
||||||
|
Checksum: ad.Checksum,
|
||||||
|
Size: ad.Size,
|
||||||
|
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
|
||||||
|
}
|
||||||
55
server/backup/backup_local.go
Normal file
55
server/backup/backup_local.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocalBackup struct {
|
||||||
|
Backup
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ BackupInterface = (*LocalBackup)(nil)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
|
||||||
|
b := &LocalBackup{
|
||||||
|
Backup{
|
||||||
|
Uuid: uuid,
|
||||||
|
IgnoredFiles: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err := os.Stat(b.Path())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if st.IsDir() {
|
||||||
|
return nil, nil, errors.New("invalid archive found; is directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a backup from the system.
|
||||||
|
func (b *LocalBackup) Remove() error {
|
||||||
|
return os.Remove(b.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a backup of the selected files and pushes it to the defined location
|
||||||
|
// for this instance.
|
||||||
|
func (b *LocalBackup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) {
|
||||||
|
a := &Archive{
|
||||||
|
TrimPrefix: prefix,
|
||||||
|
Files: included,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Create(b.Path(), context.Background()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Details(), 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
|
||||||
|
}
|
||||||
83
server/backup/backup_s3.go
Normal file
83
server/backup/backup_s3.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type S3Backup struct {
|
||||||
|
Backup
|
||||||
|
|
||||||
|
// The pre-signed upload endpoint for the generated backup. This must be
|
||||||
|
// provided otherwise this request will fail. This allows us to keep all
|
||||||
|
// of the keys off the daemon instances and the panel can handle generating
|
||||||
|
// the credentials for us.
|
||||||
|
PresignedUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ BackupInterface = (*S3Backup)(nil)
|
||||||
|
|
||||||
|
// Generates a new backup on the disk, moves it into the S3 bucket via the provided
|
||||||
|
// presigned URL, and then deletes the backup from the disk.
|
||||||
|
func (s *S3Backup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) {
|
||||||
|
defer s.Remove()
|
||||||
|
|
||||||
|
a := &Archive{
|
||||||
|
TrimPrefix: prefix,
|
||||||
|
Files: included,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Create(s.Path(), context.Background()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := os.Open(s.Path())
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Details(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a backup from the system.
|
||||||
|
func (s *S3Backup) Remove() error {
|
||||||
|
return os.Remove(s.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates the remote S3 request and begins the upload.
|
||||||
|
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)
|
||||||
|
}
|
||||||
31
server/backup/included.go
Normal file
31
server/backup/included.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package backup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IncludedFiles struct {
|
||||||
|
sync.RWMutex
|
||||||
|
files map[string]*os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pushes an additional file or folder onto the struct.
|
||||||
|
func (i *IncludedFiles) Push(info *os.FileInfo, p string) {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
if i.files == nil {
|
||||||
|
i.files = make(map[string]*os.FileInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.files[p] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns all of the files that were marked as being included.
|
||||||
|
func (i *IncludedFiles) All() map[string]*os.FileInfo {
|
||||||
|
i.RLock()
|
||||||
|
defer i.RUnlock()
|
||||||
|
|
||||||
|
return i.files
|
||||||
|
}
|
||||||
@@ -146,6 +146,10 @@ func (d *DockerEnvironment) OnBeforeStart() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !d.Server.Filesystem.HasSpaceAvailable() {
|
||||||
|
return errors.New("cannot start server, not enough disk space available")
|
||||||
|
}
|
||||||
|
|
||||||
// Always destroy and re-create the server container to ensure that synced data from
|
// Always destroy and re-create the server container to ensure that synced data from
|
||||||
// the Panel is used.
|
// the Panel is used.
|
||||||
if err := d.Client.ContainerRemove(context.Background(), d.Server.Uuid, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
if err := d.Client.ContainerRemove(context.Background(), d.Server.Uuid, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
||||||
@@ -192,16 +196,31 @@ 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
|
||||||
return errors.WithStack(err)
|
// 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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the server is running update our internal state and continue on with the attach.
|
||||||
|
if c.State.Running {
|
||||||
|
d.Server.SetState(ProcessRunningState)
|
||||||
|
|
||||||
// No reason to try starting a container that is already running.
|
return d.Attach()
|
||||||
if c.State.Running {
|
}
|
||||||
d.Server.SetState(ProcessRunningState)
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -216,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,
|
||||||
@@ -333,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
|
||||||
@@ -345,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,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
|
||||||
@@ -542,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)
|
||||||
@@ -614,8 +645,7 @@ func (d *DockerEnvironment) Create() error {
|
|||||||
// from the Panel.
|
// from the Panel.
|
||||||
Resources: d.getResourcesForServer(),
|
Resources: d.getResourcesForServer(),
|
||||||
|
|
||||||
// @todo make this configurable again
|
DNS: config.Get().Docker.Network.Dns,
|
||||||
DNS: []string{"1.1.1.1", "8.8.8.8"},
|
|
||||||
|
|
||||||
// Configure logging for the container to make it easier on the Daemon to grab
|
// Configure logging for the container to make it easier on the Daemon to grab
|
||||||
// the server output. Ensure that we don't use too much space on the host machine
|
// the server output. Ensure that we don't use too much space on the host machine
|
||||||
@@ -635,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)
|
||||||
}
|
}
|
||||||
@@ -765,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
|
||||||
@@ -820,14 +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 {
|
||||||
return container.Resources{
|
return container.Resources{
|
||||||
// @todo memory limit should be slightly higher than the reservation
|
Memory: d.Server.Build.BoundedMemoryLimit(),
|
||||||
Memory: d.Server.Build.MemoryLimit * 1000000,
|
MemoryReservation: d.Server.Build.MemoryLimit * 1_000_000,
|
||||||
MemoryReservation: d.Server.Build.MemoryLimit * 1000000,
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package server
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
|
ignore "github.com/sabhiram/go-gitignore"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -67,7 +70,7 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
|
|||||||
// Range over all of the path parts and form directory pathings from the end
|
// Range over all of the path parts and form directory pathings from the end
|
||||||
// moving up until we have a valid resolution or we run out of paths to try.
|
// moving up until we have a valid resolution or we run out of paths to try.
|
||||||
for k := range parts {
|
for k := range parts {
|
||||||
try = strings.Join(parts[:(len(parts) - k)], "/")
|
try = strings.Join(parts[:(len(parts)-k)], "/")
|
||||||
|
|
||||||
if !strings.HasPrefix(try, fs.Path()) {
|
if !strings.HasPrefix(try, fs.Path()) {
|
||||||
break
|
break
|
||||||
@@ -118,23 +121,27 @@ func (fs *Filesystem) HasSpaceAvailable() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var size int64
|
// If we have a match in the cache, use that value in the return. No need to perform an expensive
|
||||||
|
// disk operation, even if this is an empty value.
|
||||||
if x, exists := fs.Server.Cache.Get("disk_used"); exists {
|
if x, exists := fs.Server.Cache.Get("disk_used"); exists {
|
||||||
size = x.(int64)
|
fs.Server.Resources.Disk = x.(int64)
|
||||||
|
return (x.(int64) / 1000.0 / 1000.0) <= space
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no size its either because there is no data (in which case running this function
|
// If there is no size its either because there is no data (in which case running this function
|
||||||
// will have effectively no impact), or there is nothing in the cache, in which case we need to
|
// will have effectively no impact), or there is nothing in the cache, in which case we need to
|
||||||
// grab the size of their data directory. This is a taxing operation, so we want to store it in
|
// grab the size of their data directory. This is a taxing operation, so we want to store it in
|
||||||
// the cache once we've gotten it.
|
// the cache once we've gotten it.
|
||||||
if size == 0 {
|
size, err := fs.DirectorySize("/")
|
||||||
if size, err := fs.DirectorySize("/"); err != nil {
|
if err != nil {
|
||||||
zap.S().Warnw("failed to determine directory size", zap.String("server", fs.Server.Uuid), zap.Error(err))
|
zap.S().Warnw("failed to determine directory size", zap.String("server", fs.Server.Uuid), zap.Error(err))
|
||||||
} else {
|
|
||||||
fs.Server.Cache.Set("disk_used", size, time.Second * 60)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always cache the size, even if there is an error. We want to always return that value
|
||||||
|
// so that we don't cause an endless loop of determining the disk size if there is a temporary
|
||||||
|
// error encountered.
|
||||||
|
fs.Server.Cache.Set("disk_used", size, time.Second*60)
|
||||||
|
|
||||||
// Determine if their folder size, in bytes, is smaller than the amount of space they've
|
// Determine if their folder size, in bytes, is smaller than the amount of space they've
|
||||||
// been allocated.
|
// been allocated.
|
||||||
fs.Server.Resources.Disk = size
|
fs.Server.Resources.Disk = size
|
||||||
@@ -146,42 +153,20 @@ func (fs *Filesystem) HasSpaceAvailable() bool {
|
|||||||
// through all of the folders. Returns the size in bytes. This can be a fairly taxing operation
|
// through all of the folders. Returns the size in bytes. This can be a fairly taxing operation
|
||||||
// on locations with tons of files, so it is recommended that you cache the output.
|
// on locations with tons of files, so it is recommended that you cache the output.
|
||||||
func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
|
func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
|
||||||
|
w := fs.NewWalker()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
var size int64
|
var size int64
|
||||||
var wg sync.WaitGroup
|
err := w.Walk(dir, ctx, func(f os.FileInfo, _ string) bool {
|
||||||
|
// Only increment the size when we're dealing with a file specifically, otherwise
|
||||||
cleaned, err := fs.SafePath(dir)
|
// just continue digging deeper until there are no more directories to iterate over.
|
||||||
if err != nil {
|
if !f.IsDir() {
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(cleaned)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all of the files and directories. If it is a file, immediately add its size
|
|
||||||
// to the total size being returned. If we're dealing with a directory, call this function
|
|
||||||
// on a seperate thread until we have gotten the size of everything nested within the given
|
|
||||||
// directory.
|
|
||||||
for _, f := range files {
|
|
||||||
if f.IsDir() {
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func(p string) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
s, _ := fs.DirectorySize(p)
|
|
||||||
|
|
||||||
atomic.AddInt64(&size, s)
|
|
||||||
}(filepath.Join(cleaned, f.Name()))
|
|
||||||
} else {
|
|
||||||
atomic.AddInt64(&size, f.Size())
|
atomic.AddInt64(&size, f.Size())
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
wg.Wait()
|
return size, err
|
||||||
|
|
||||||
return size, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads a file on the system and returns it as a byte representation in a file
|
// Reads a file on the system and returns it as a byte representation in a file
|
||||||
@@ -416,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)
|
||||||
@@ -578,3 +562,43 @@ func (fs *Filesystem) EnsureDataDirectory() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given a directory, iterate through all of the files and folders within it and determine
|
||||||
|
// if they should be included in the output based on an array of ignored matches. This uses
|
||||||
|
// standard .gitignore formatting to make that determination.
|
||||||
|
//
|
||||||
|
// If no ignored files are passed through you'll get the entire directory listing.
|
||||||
|
func (fs *Filesystem) GetIncludedFiles(dir string, ignored []string) (*backup.IncludedFiles, error) {
|
||||||
|
cleaned, err := fs.SafePath(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := fs.NewWalker()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
i, err := ignore.CompileIgnoreLines(ignored...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk through all of the files and directories on a server. This callback only returns
|
||||||
|
// files found, and will keep walking deeper and deeper into directories.
|
||||||
|
inc := new(backup.IncludedFiles)
|
||||||
|
if err := w.Walk(cleaned, ctx, func(f os.FileInfo, p string) bool {
|
||||||
|
// Avoid unnecessary parsing if there are no ignored files, nothing will match anyways
|
||||||
|
// so no reason to call the function.
|
||||||
|
if len(ignored) == 0 || !i.MatchesPath(strings.TrimPrefix(p, fs.Path() + "/")) {
|
||||||
|
inc.Push(&f, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't just abort if the path is technically ignored. It is possible there is a nested
|
||||||
|
// file or folder that should not be excluded, so in this case we need to just keep going
|
||||||
|
// until we get to a final state.
|
||||||
|
return true
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return inc, nil
|
||||||
|
}
|
||||||
|
|||||||
70
server/filesystem_walker.go
Normal file
70
server/filesystem_walker.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileWalker struct {
|
||||||
|
*Filesystem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new walker instance.
|
||||||
|
func (fs *Filesystem) NewWalker() *FileWalker {
|
||||||
|
return &FileWalker{fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all of the files and directories within a given directory. When a file is
|
||||||
|
// found the callback will be called with the file information. If a directory is encountered
|
||||||
|
// it will be recursively passed back through to this function.
|
||||||
|
func (fw *FileWalker) Walk(dir string, ctx context.Context, callback func (os.FileInfo, string) bool) error {
|
||||||
|
cleaned, err := fw.SafePath(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all of the files from this directory.
|
||||||
|
files, err := ioutil.ReadDir(cleaned)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an error group that we can use to run processes in parallel while retaining
|
||||||
|
// the ability to cancel the entire process immediately should any of it fail.
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
if f.IsDir() {
|
||||||
|
fi := f
|
||||||
|
p := filepath.Join(cleaned, f.Name())
|
||||||
|
// Recursively call this function to continue digging through the directory tree within
|
||||||
|
// a seperate goroutine. If the context is canceled abort this process.
|
||||||
|
g.Go(func() error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
// If the callback returns true, go ahead and keep walking deeper. This allows
|
||||||
|
// us to programatically continue deeper into directories, or stop digging
|
||||||
|
// if that pathway knows it needs nothing else.
|
||||||
|
if callback(fi, p) {
|
||||||
|
return fw.Walk(p, ctx, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// If this isn't a directory, go ahead and pass the file information into the
|
||||||
|
// callback. We don't care about the response since we won't be stepping into
|
||||||
|
// anything from here.
|
||||||
|
callback(f, filepath.Join(cleaned, f.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until all of the routines finish and have returned a value.
|
||||||
|
return g.Wait()
|
||||||
|
}
|
||||||
@@ -10,10 +10,12 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -102,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.
|
||||||
//
|
//
|
||||||
@@ -115,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +146,13 @@ 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) {
|
||||||
d, err := ioutil.TempDir("", "pterodactyl")
|
// 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/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -231,11 +253,17 @@ func (ip *InstallationProcess) BeforeExecute() (string, error) {
|
|||||||
return fileName, nil
|
return fileName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the log path for the installation process.
|
||||||
|
func (ip *InstallationProcess) GetLogPath() string {
|
||||||
|
return filepath.Join(config.Get().System.GetInstallLogPath(), ip.Server.Uuid+".log")
|
||||||
|
}
|
||||||
|
|
||||||
// Cleans up after the execution of the installation process. This grabs the logs from the
|
// Cleans up after the execution of the installation process. This grabs the logs from the
|
||||||
// process to store in the server configuration directory, and then destroys the associated
|
// process to store in the server configuration directory, and then destroys the associated
|
||||||
// installation container.
|
// installation container.
|
||||||
func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
||||||
ctx := context.Background()
|
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{
|
||||||
@@ -248,7 +276,7 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(filepath.Join("data/install_logs/", ip.Server.Uuid+".log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
f, err := os.OpenFile(ip.GetLogPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -260,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,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{
|
||||||
@@ -328,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.
|
||||||
//
|
//
|
||||||
@@ -51,4 +75,4 @@ func (ru *ResourceUsage) CalculateAbsoluteCpu(pStats *types.CPUStats, stats *typ
|
|||||||
}
|
}
|
||||||
|
|
||||||
return math.Round(percent*1000) / 1000
|
return math.Round(percent*1000) / 1000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -230,7 +248,6 @@ func FromConfiguration(data *api.ServerConfigurationResponse) (*Server, error) {
|
|||||||
s.Resources = ResourceUsage{}
|
s.Resources = ResourceUsage{}
|
||||||
|
|
||||||
// Forces the configuration to be synced with the panel.
|
// Forces the configuration to be synced with the panel.
|
||||||
zap.S().Debugw("syncing config with panel", zap.String("server", s.Uuid))
|
|
||||||
if err := s.SyncWithConfiguration(data); err != nil {
|
if err := s.SyncWithConfiguration(data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -11,8 +12,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
const stateFileLocation = "data/.states.json"
|
|
||||||
|
|
||||||
var stateMutex sync.Mutex
|
var stateMutex sync.Mutex
|
||||||
|
|
||||||
// Returns the state of the servers.
|
// Returns the state of the servers.
|
||||||
@@ -22,7 +21,7 @@ func getServerStates() (map[string]string, error) {
|
|||||||
defer stateMutex.Unlock()
|
defer stateMutex.Unlock()
|
||||||
|
|
||||||
// Open the states file.
|
// Open the states file.
|
||||||
f, err := os.OpenFile(stateFileLocation, os.O_RDONLY|os.O_CREATE, 0644)
|
f, err := os.OpenFile(config.Get().System.GetStatesPath(), os.O_RDONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -55,7 +54,7 @@ func saveServerStates() error {
|
|||||||
defer stateMutex.Unlock()
|
defer stateMutex.Unlock()
|
||||||
|
|
||||||
// Write the data to the file
|
// Write the data to the file
|
||||||
if err := ioutil.WriteFile(stateFileLocation, data, 0644); err != nil {
|
if err := ioutil.WriteFile(config.Get().System.GetStatesPath(), data, 0644); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,4 +140,4 @@ func (s *Server) GetState() string {
|
|||||||
// not the response from Docker.
|
// not the response from Docker.
|
||||||
func (s *Server) IsRunning() bool {
|
func (s *Server) IsRunning() bool {
|
||||||
return s.GetState() == ProcessRunningState || s.GetState() == ProcessStartingState
|
return s.GetState() == ProcessRunningState || s.GetState() == ProcessStartingState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,6 @@ func (s *Server) UpdateDataStructure(data []byte, background bool) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.Suspended = v
|
s.Suspended = v
|
||||||
if s.Suspended {
|
|
||||||
zap.S().Debugw("server has been suspended", zap.String("server", s.Uuid))
|
|
||||||
} else {
|
|
||||||
zap.S().Debugw("server has been unsuspended", zap.String("server", s.Uuid))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Environment and Mappings should be treated as a full update at all times, never a
|
// Environment and Mappings should be treated as a full update at all times, never a
|
||||||
|
|||||||
@@ -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 = "0.0.1"
|
Version = "1.0.0-beta.5"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user