Compare commits

...

24 Commits

Author SHA1 Message Date
Dane Everitt
96256ac63e [security] fix vulnerability when handling remote file redirects
Also adds the ability for an admin to just completely disable this service if it is not needed on the node.
2021-01-09 17:52:27 -08:00
Dane Everitt
6701aa6dc1 Merge branch 'dane/self-upgrade' into develop 2021-01-09 17:38:18 -08:00
Dane Everitt
ff8926bba8 bye bye command 2021-01-09 17:37:58 -08:00
Dane Everitt
217ca72eb3 Merge pull request #85 from pterodactyl/schrej/formatting
organize imports with gopls format
2021-01-09 17:31:46 -08:00
Jakob Schrettenbrunner
648072436f organize imports with gopls format 2021-01-10 01:22:39 +00:00
Dane Everitt
6fe2468a5a foundation for self-upgrade logic 2021-01-08 22:49:19 -08:00
Dane Everitt
948d927eb9 Cleanup command running a bit 2021-01-08 22:19:23 -08:00
Dane Everitt
b2eaa3f7f8 Update CHANGELOG.md 2021-01-08 21:31:06 -08:00
Dane Everitt
93417dddb1 Update CHANGELOG.md 2021-01-08 21:23:25 -08:00
Dane Everitt
044c46fc9a Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2021-01-08 21:21:37 -08:00
Dane Everitt
c9d972d544 Revert usage of ContainerWait, return to io.Copy blocking
Until https://github.com/moby/moby/issues/41827 is resolved this code causes chaos to unfold on machines and causes servers to be non-terminatable.

This logic was intially changed to logical purposes, but this io.Copy logic works perfectly fine (even if not immediately intuitive).
2021-01-08 21:21:09 -08:00
Matthew Penner
0aab4b1ac2 environment(docker): re-attach to container logs after EOF 2021-01-08 08:19:33 -07:00
Matthew Penner
4f4b4fd2e6 environment(docker): cleanup code 2021-01-08 08:15:40 -07:00
Matthew Penner
66c9be357c Potential fix for servers being marked as stopping after being marked as offline 2021-01-07 19:32:15 -07:00
Matthew Penner
1d36811dfe Fix v being shown twice on wings boot 2021-01-07 16:44:09 -07:00
Dane Everitt
6e74123c65 Update CHANGELOG.md 2021-01-06 21:42:09 -08:00
Dane Everitt
b82f5f9a32 [security] deny downloading files from internal locations 2021-01-06 21:34:18 -08:00
Dane Everitt
1937d0366d cleanup; fix environment stats not reporting network TX correctly 2021-01-06 20:47:44 -08:00
Dane Everitt
963a906c30 Less obtuse logic for polling resource usage when attaching a container 2021-01-06 20:36:29 -08:00
Jakob
3f6eb7e41a no need for additional decode (#81)
file paths used to be url-encoded twice, which is no longer the case.
2021-01-03 17:20:16 -08:00
Omar Kamel
a822c7c340 typo in docker-compose file (#82)
minor typo i noticed while messing around
2021-01-03 16:24:28 -08:00
Matthew Penner
b8fb86f5a4 Update Dockerfile to use busybox 1.33.0 2021-01-03 12:46:06 -07:00
Matthew Penner
ee0c7f09b3 Fix user problems when running inside of Docker 2021-01-02 12:58:58 -07:00
Matthew Penner
d3ddf8cf39 Mark server as not transferring after archive failure 2021-01-02 10:11:25 -07:00
63 changed files with 576 additions and 338 deletions

View File

@@ -48,3 +48,9 @@ debug
.DS_Store .DS_Store
*.pprof *.pprof
*.pdf *.pdf
Dockerfile
CHANGELOG.md
Makefile
README.md
wings-api.paw

View File

@@ -1,5 +1,30 @@
# Changelog # Changelog
## v1.2.3
### Fixed
* **[Security]** Fixes a remaining security vulnerability in the code handling remote file downloads for servers relating to redirect validation.
### Added
* Adds a configuration key at `api.disable_remote_download` that can be set to `true` to completely download the remote download system.
## v1.2.2
### Fixed
* Reverts changes to logic handling blocking until a server process is done running when polling stats. This change exposed a bug in the underlying Docker system causing servers to enter a state in which Wings was unable to terminate the process and Docker commands would hang if executed against the container.
### Changed
* Adds logic to handle a console stream unexpectedly returning an EOF when reading console logs. New code should automatically re-attach the stream avoiding issues where the console would stop live updating for servers.
## v1.2.1
### Fixed
* Fixes servers not be properly marked as no longer transfering if an error occurs during the archive process.
* Fixes problems with user detection when running Wings inside a Docker container.
* Fixes filename decoding issues with multiple endpoints related to the file manager (namely move/copy/delete).
* **[Security]** Fixes vulnerability allowing a malicious user to abuse the remote file download utilitity to scan or access resources on the local network.
* Fixes network `tx` stats not correctly being reported (was previously reporting `rx` for both `rx` and `tx`).
### Changed
* Cleans up the logic related to polling resources for the server to make a little more sense and not do pointless `io.Copy()` operations.
## v1.2.0 ## v1.2.0
### Fixed ### Fixed
* Fixes log compression being set on the Docker containers being created to avoid errors on some versions of Docker. * Fixes log compression being set on the Docker containers being created to avoid errors on some versions of Docker.

View File

@@ -24,16 +24,9 @@ RUN upx wings
# --------------------------------------- # # --------------------------------------- #
# Stage 2 (Final) # Stage 2 (Final)
FROM busybox:1.32.0 FROM busybox:1.33.0
LABEL org.opencontainers.image.title="Wings" RUN echo "ID=\"busybox\"" > /etc/os-release
LABEL org.opencontainers.image.version="$VERSION"
LABEL org.opencontainers.image.description="The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind."
LABEL org.opencontainers.image.url="https://pterodactyl.io"
LABEL org.opencontainers.image.documentation="https://pterodactyl.io/project/introduction.html"
LABEL org.opencontainers.image.vendor="Pterodactyl Software"
LABEL org.opencontainers.image.source="https://github.com/pterodactyl/wings"
LABEL org.opencontainers.image.licenses="MIT"
COPY --from=builder /app/wings /usr/bin/ COPY --from=builder /app/wings /usr/bin/

View File

@@ -2,17 +2,18 @@ package api
import ( import (
"bytes" "bytes"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
) )
// Initializes the requester instance. // Initializes the requester instance.

View File

@@ -2,10 +2,11 @@ package api
import ( import (
"encoding/json" "encoding/json"
"github.com/apex/log"
"github.com/pterodactyl/wings/parser"
"regexp" "regexp"
"strings" "strings"
"github.com/apex/log"
"github.com/pterodactyl/wings/parser"
) )
type OutputLineMatcher struct { type OutputLineMatcher struct {

View File

@@ -4,11 +4,12 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"sync"
"github.com/apex/log" "github.com/apex/log"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"strconv"
"sync"
) )
const ( const (

View File

@@ -1,9 +1,10 @@
package api package api
import ( import (
"regexp"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"regexp"
) )
type SftpAuthRequest struct { type SftpAuthRequest struct {

View File

@@ -1,9 +1,10 @@
package cmd package cmd
import ( import (
"github.com/pterodactyl/wings/config"
"os" "os"
"path/filepath" "path/filepath"
"github.com/pterodactyl/wings/config"
) )
// We've gone through a couple of iterations of where the configuration is stored. This // We've gone through a couple of iterations of where the configuration is stored. This

View File

@@ -4,10 +4,6 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/pterodactyl/wings/config"
"github.com/spf13/cobra"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
@@ -15,6 +11,11 @@ import (
"path" "path"
"regexp" "regexp"
"time" "time"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/pterodactyl/wings/config"
"github.com/spf13/cobra"
) )
var ( var (

View File

@@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/pterodactyl/wings/environment"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -16,6 +15,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/pterodactyl/wings/environment"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal" "github.com/AlecAivazis/survey/v2/terminal"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"

View File

@@ -2,8 +2,15 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"emperror.dev/errors"
"fmt" "fmt"
log2 "log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"emperror.dev/errors"
"github.com/NYTimes/logrotate" "github.com/NYTimes/logrotate"
"github.com/apex/log" "github.com/apex/log"
"github.com/apex/log/handlers/multi" "github.com/apex/log/handlers/multi"
@@ -21,47 +28,54 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/acme" "golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"net/http"
"os"
"path"
"path/filepath"
"strings"
) )
var ( var (
profiler = "" configPath = config.DefaultLocation
configPath = config.DefaultLocation debug = false
debug = false
useAutomaticTls = false
tlsHostname = ""
showVersion = false
ignoreCertificateErrors = false
) )
var root = &cobra.Command{ var rootCommand = &cobra.Command{
Use: "wings", Use: "wings",
Short: "The wings of the pterodactyl game management panel", Short: "Runs the API server allowing programatic control of game servers for Pterodactyl Panel.",
Long: ``,
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
if useAutomaticTls && len(tlsHostname) == 0 { if tls, _ := cmd.Flags().GetBool("auto-tls"); tls {
fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com") if host, _ := cmd.Flags().GetString("tls-hostname"); host == "" {
os.Exit(1) fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com")
os.Exit(1)
}
} }
}, },
Run: rootCmdRun, Run: rootCmdRun,
} }
func init() { var versionCommand = &cobra.Command{
root.PersistentFlags().BoolVar(&showVersion, "version", false, "show the version and exit") Use: "version",
root.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file") Short: "Prints the current executable version and exits.",
root.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode") Run: func(cmd *cobra.Command, _ []string) {
root.PersistentFlags().StringVar(&profiler, "profiler", "", "the profiler to run for this instance") fmt.Printf("wings v%s\nCopyright © 2018 - 2021 Dane Everitt & Contributors\n", system.Version)
root.PersistentFlags().BoolVar(&useAutomaticTls, "auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt") },
root.PersistentFlags().StringVar(&tlsHostname, "tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate") }
root.PersistentFlags().BoolVar(&ignoreCertificateErrors, "ignore-certificate-errors", false, "if passed any SSL certificate errors will be ignored by wings")
root.AddCommand(configureCmd) func Execute() {
root.AddCommand(diagnosticsCmd) if err := rootCommand.Execute(); err != nil {
log2.Fatalf("failed to execute command: %s", err)
}
}
func init() {
rootCommand.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
rootCommand.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
// Flags specifically used when running the API.
rootCommand.Flags().String("profiler", "", "the profiler to run for this instance")
rootCommand.Flags().Bool("auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt")
rootCommand.Flags().String("tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate")
rootCommand.Flags().Bool("ignore-certificate-errors", false, "ignore certificate verification errors when executing API calls")
rootCommand.AddCommand(versionCommand)
rootCommand.AddCommand(configureCmd)
rootCommand.AddCommand(diagnosticsCmd)
} }
// Get the configuration path based on the arguments provided. // Get the configuration path based on the arguments provided.
@@ -85,13 +99,8 @@ func readConfiguration() (*config.Configuration, error) {
return config.ReadConfiguration(p) return config.ReadConfiguration(p)
} }
func rootCmdRun(*cobra.Command, []string) { func rootCmdRun(cmd *cobra.Command, _ []string) {
if showVersion { switch cmd.Flag("profiler").Value.String() {
fmt.Println(system.Version)
os.Exit(0)
}
switch profiler {
case "cpu": case "cpu":
defer profile.Start(profile.CPUProfile).Stop() defer profile.Start(profile.CPUProfile).Stop()
case "mem": case "mem":
@@ -117,7 +126,6 @@ func rootCmdRun(*cobra.Command, []string) {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
exitWithConfigurationNotice() exitWithConfigurationNotice()
} }
panic(err) panic(err)
} }
} }
@@ -141,7 +149,7 @@ func rootCmdRun(*cobra.Command, []string) {
log.Debug("running in debug mode") log.Debug("running in debug mode")
} }
if ignoreCertificateErrors { if ok, _ := cmd.Flags().GetBool("ignore-certificate-errors"); ok {
log.Warn("running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified") log.Warn("running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified")
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
@@ -280,9 +288,15 @@ func rootCmdRun(*cobra.Command, []string) {
log.WithField("error", err).Error("failed to create backup directory") log.WithField("error", err).Error("failed to create backup directory")
} }
autotls, _ := cmd.Flags().GetBool("auto-tls")
tlshostname, _ := cmd.Flags().GetString("tls-hostname")
if autotls && tlshostname == "" {
autotls = false
}
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"use_ssl": c.Api.Ssl.Enabled, "use_ssl": c.Api.Ssl.Enabled,
"use_auto_tls": useAutomaticTls && len(tlsHostname) > 0, "use_auto_tls": autotls,
"host_address": c.Api.Host, "host_address": c.Api.Host,
"host_port": c.Api.Port, "host_port": c.Api.Port,
}).Info("configuring internal webserver") }).Info("configuring internal webserver")
@@ -293,7 +307,6 @@ func rootCmdRun(*cobra.Command, []string) {
s := &http.Server{ s := &http.Server{
Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port), Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port),
Handler: r, Handler: r,
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
NextProtos: []string{"h2", "http/1.1"}, NextProtos: []string{"h2", "http/1.1"},
// @see https://blog.cloudflare.com/exposing-go-on-the-internet // @see https://blog.cloudflare.com/exposing-go-on-the-internet
@@ -313,14 +326,14 @@ func rootCmdRun(*cobra.Command, []string) {
} }
// Check if the server should run with TLS but using autocert. // Check if the server should run with TLS but using autocert.
if useAutomaticTls && len(tlsHostname) > 0 { if autotls {
m := autocert.Manager{ m := autocert.Manager{
Prompt: autocert.AcceptTOS, Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache(path.Join(c.System.RootDirectory, "/.tls-cache")), Cache: autocert.DirCache(path.Join(c.System.RootDirectory, "/.tls-cache")),
HostPolicy: autocert.HostWhitelist(tlsHostname), HostPolicy: autocert.HostWhitelist(tlshostname),
} }
log.WithField("hostname", tlsHostname). log.WithField("hostname", tlshostname).
Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt") Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
// Hook autocert into the main http server. // Hook autocert into the main http server.
@@ -336,7 +349,7 @@ func rootCmdRun(*cobra.Command, []string) {
// Start the main http server with TLS using autocert. // Start the main http server with TLS using autocert.
if err := s.ListenAndServeTLS("", ""); err != nil { if err := s.ListenAndServeTLS("", ""); err != nil {
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlsHostname, "error": err}). log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlshostname, "error": err}).
Fatal("failed to configure HTTP server using auto-tls") Fatal("failed to configure HTTP server using auto-tls")
} }
@@ -364,11 +377,6 @@ func rootCmdRun(*cobra.Command, []string) {
} }
} }
// Execute calls cobra to handle cli commands
func Execute() error {
return root.Execute()
}
// Configures the global logger for Zap so that we can call it from any location // Configures the global logger for Zap so that we can call it from any location
// in the code without having to pass around a logger instance. // in the code without having to pass around a logger instance.
func configureLogging(logDir string, debug bool) error { func configureLogging(logDir string, debug bool) error {
@@ -379,20 +387,15 @@ func configureLogging(logDir string, debug bool) error {
p := filepath.Join(logDir, "/wings.log") p := filepath.Join(logDir, "/wings.log")
w, err := logrotate.NewFile(p) w, err := logrotate.NewFile(p)
if err != nil { if err != nil {
panic(errors.WithMessage(err, "failed to open process log file")) return err
} }
log.SetLevel(log.InfoLevel)
if debug { if debug {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.InfoLevel)
} }
log.SetHandler(multi.New( log.SetHandler(multi.New(cli.Default, cli.New(w.File, false)))
cli.Default,
cli.New(w.File, false),
))
log.WithField("path", p).Info("writing log files to disk") log.WithField("path", p).Info("writing log files to disk")
return nil return nil
@@ -406,7 +409,7 @@ __ [blue][bold]Pterodactyl[reset] _____/___/_______ _______ ______
\_____\ \/\/ / / / __ / ___/ \_____\ \/\/ / / / __ / ___/
\___\ / / / / /_/ /___ / \___\ / / / / /_/ /___ /
\___/\___/___/___/___/___ /______/ \___/\___/___/___/___/___ /______/
/_______/ [bold]v%s[reset] /_______/ [bold]%s[reset]
Copyright © 2018 - 2021 Dane Everitt & Contributors Copyright © 2018 - 2021 Dane Everitt & Contributors

View File

@@ -1,12 +1,7 @@
package config package config
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@@ -14,6 +9,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"emperror.dev/errors"
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"gopkg.in/yaml.v2"
) )
const DefaultLocation = "/etc/pterodactyl/config.yml" const DefaultLocation = "/etc/pterodactyl/config.yml"
@@ -87,11 +88,16 @@ type ApiConfiguration struct {
// SSL configuration for the daemon. // SSL configuration for the daemon.
Ssl struct { Ssl struct {
Enabled bool `default:"false"` Enabled bool `json:"enabled" yaml:"enabled"`
CertificateFile string `json:"cert" yaml:"cert"` CertificateFile string `json:"cert" yaml:"cert"`
KeyFile string `json:"key" yaml:"key"` KeyFile string `json:"key" yaml:"key"`
} }
// Determines if functionality for allowing remote download of files into server directories
// is enabled on this instance. If set to "true" remote downloads will not be possible for
// servers.
DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"`
// The maximum size for files uploaded through the Panel in bytes. // The maximum size for files uploaded through the Panel in bytes.
UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"` UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
} }
@@ -223,6 +229,36 @@ func (c *Configuration) GetPath() string {
// If files are not owned by this user there will be issues with permissions on Docker // If files are not owned by this user there will be issues with permissions on Docker
// mount points. // mount points.
func (c *Configuration) EnsurePterodactylUser() (*user.User, error) { func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
sysName, err := getSystemName()
if err != nil {
return nil, err
}
// Our way of detecting if wings is running inside of Docker.
if sysName == "busybox" {
uid := os.Getenv("WINGS_UID")
if uid == "" {
uid = "988"
}
gid := os.Getenv("WINGS_GID")
if gid == "" {
gid = "988"
}
username := os.Getenv("WINGS_USERNAME")
if username == "" {
username = "pterodactyl"
}
u := &user.User{
Uid: uid,
Gid: gid,
Username: username,
}
return u, c.setSystemUser(u)
}
u, err := user.Lookup(c.System.Username) u, err := user.Lookup(c.System.Username)
// If an error is returned but it isn't the unknown user error just abort // If an error is returned but it isn't the unknown user error just abort
@@ -233,17 +269,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
return nil, err return nil, err
} }
sysName, err := getSystemName() command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", c.System.Username)
if err != nil {
return nil, err
}
command := fmt.Sprintf("useradd --system --no-create-home --shell /bin/false %s", c.System.Username)
// 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 /sbin/nologin %[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
// to the user creation process. // to the user creation process.
@@ -267,8 +298,15 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
// Set the system user into the configuration and then write it to the disk so that // Set the system user into the configuration and then write it to the disk so that
// it is persisted on boot. // it is persisted on boot.
func (c *Configuration) setSystemUser(u *user.User) error { func (c *Configuration) setSystemUser(u *user.User) error {
uid, _ := strconv.Atoi(u.Uid) uid, err := strconv.Atoi(u.Uid)
gid, _ := strconv.Atoi(u.Gid) if err != nil {
return err
}
gid, err := strconv.Atoi(u.Gid)
if err != nil {
return err
}
c.Lock() c.Lock()
c.System.Username = u.Username c.System.Username = u.Username

View File

@@ -3,6 +3,7 @@ package config
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
) )

View File

@@ -2,9 +2,7 @@ package config
import ( import (
"context" "context"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/apex/log"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -13,6 +11,9 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"time" "time"
"emperror.dev/errors"
"github.com/apex/log"
) )
// Defines basic system configuration settings. // Defines basic system configuration settings.

View File

@@ -1,4 +1,5 @@
version: '3.8' version: '3.8'
services: services:
wings: wings:
image: ghcr.io/pterodactyl/wings:latest image: ghcr.io/pterodactyl/wings:latest
@@ -11,7 +12,9 @@ services:
tty: true tty: true
environment: environment:
TZ: "UTC" TZ: "UTC"
DEBUG: "false" WINGS_UID: 988
WINGS_GID: 988
WINGS_USERNAME: pterodactyl
volumes: volumes:
- "/var/run/docker.sock:/var/run/docker.sock" - "/var/run/docker.sock:/var/run/docker.sock"
- "/var/lib/docker/containers/:/var/lib/docker/containers/" - "/var/lib/docker/containers/:/var/lib/docker/containers/"
@@ -21,8 +24,9 @@ services:
- "/tmp/pterodactyl/:/tmp/pterodactyl/" - "/tmp/pterodactyl/:/tmp/pterodactyl/"
# you may need /srv/daemon-data if you are upgrading from an old daemon # you may need /srv/daemon-data if you are upgrading from an old daemon
#- "/srv/daemon-data/:/srv/daemon-data/" #- "/srv/daemon-data/:/srv/daemon-data/"
# Required for ssl if you user let's encrypt. uncomment to use. # Required for ssl if you use let's encrypt. uncomment to use.
#- "/etc/letsencrypt/:/etc/letsencrypt/" #- "/etc/letsencrypt/:/etc/letsencrypt/"
networks: networks:
wings0: wings0:
name: wings0 name: wings0
@@ -31,4 +35,4 @@ networks:
config: config:
- subnet: "172.21.0.0/16" - subnet: "172.21.0.0/16"
driver_opts: driver_opts:
com.docker.network.bridge.name: wings0 com.docker.network.bridge.name: wings0

View File

@@ -2,9 +2,10 @@ package environment
import ( import (
"fmt" "fmt"
"strconv"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"strconv"
) )
// 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

View File

@@ -2,10 +2,11 @@ package environment
import ( import (
"context" "context"
"github.com/apex/log"
"strconv" "strconv"
"sync" "sync"
"github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"

View File

@@ -1,20 +0,0 @@
package docker
import "io"
type Console struct {
HandlerFunc *func(string)
}
var _ io.Writer = Console{}
func (c Console) Write(b []byte) (int, error) {
if c.HandlerFunc != nil {
l := make([]byte, len(b))
copy(l, b)
(*c.HandlerFunc)(string(l))
}
return len(b), nil
}

View File

@@ -26,10 +26,26 @@ type imagePullStatus struct {
Progress string `json:"progress"` Progress string `json:"progress"`
} }
// A custom console writer that allows us to keep a function blocked until the
// given stream is properly closed. This does nothing special, only exists to
// make a noop io.Writer.
type noopWriter struct{}
var _ io.Writer = noopWriter{}
// Implement the required Write function to satisfy the io.Writer interface.
func (nw noopWriter) Write(b []byte) (int, error) {
return len(b), nil
}
// Attaches to the docker container itself and ensures that we can pipe data in and out // Attaches to the docker container itself and ensures that we can pipe data in and out
// of the process stream. This should not be used for reading console data as you *will* // of the process stream. This should not be used for reading console data as you *will*
// miss important output at the beginning because of the time delay with attaching to the // miss important output at the beginning because of the time delay with attaching to the
// output. // output.
//
// Calling this function will poll resources for the container in the background until the
// provided context is canceled by the caller. Failure to cancel said context will cause
// background memory leaks as the goroutine will not exit.
func (e *Environment) Attach() error { func (e *Environment) Attach() error {
if e.IsAttached() { if e.IsAttached() {
return nil return nil
@@ -53,10 +69,8 @@ func (e *Environment) Attach() error {
e.SetStream(&st) e.SetStream(&st)
} }
c := new(Console) go func() {
go func(console *Console) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
defer e.stream.Close() defer e.stream.Close()
defer func() { defer func() {
@@ -64,27 +78,32 @@ func (e *Environment) Attach() error {
e.SetStream(nil) e.SetStream(nil)
}() }()
// Poll resources in a separate thread since this will block the copy call below go func() {
// from being reached until it is completed if not run in a separate process. However,
// we still want it to be stopped when the copy operation below is finished running which
// indicates that the container is no longer running.
go func(ctx context.Context) {
if err := e.pollResources(ctx); err != nil { if err := e.pollResources(ctx); err != nil {
l := log.WithField("environment_id", e.Id)
if !errors.Is(err, context.Canceled) { if !errors.Is(err, context.Canceled) {
l.WithField("error", err).Error("error during environment resource polling") e.log().WithField("error", err).Error("error during environment resource polling")
} else { } else {
l.Warn("stopping server resource polling: context canceled") e.log().Warn("stopping server resource polling: context canceled")
} }
} }
}(ctx) }()
// Stream the reader output to the console which will then fire off events and handle console // Block the completion of this routine until the container is no longer running. This allows
// throttling and sending the output to the user. // the pollResources function to run until it needs to be stopped. Because the container
if _, err := io.Copy(console, e.stream.Reader); err != nil { // can be polled for resource usage, even when stopped, we need to have this logic present
log.WithField("environment_id", e.Id).WithField("error", err).Error("error while copying environment output to console") // in order to cancel the context and therefore stop the routine that is spawned.
//
// For now, DO NOT use client#ContainerWait from the Docker package. There is a nasty
// bug causing containers to hang on deletion and cause servers to lock up on the system.
//
// This weird code isn't intuitive, but it keeps the function from ending until the container
// is stopped and therefore the stream reader ends up closed.
// @see https://github.com/moby/moby/issues/41827
c := new(noopWriter)
if _, err := io.Copy(c, e.stream.Reader); err != nil {
e.log().WithField("error", err).Error("could not copy from environment stream to noop writer")
} }
}(c) }()
return nil return nil
} }
@@ -259,6 +278,8 @@ func (e *Environment) Destroy() error {
Force: true, Force: true,
}) })
e.SetState(environment.ProcessOfflineState)
// Don't trigger a destroy failure if we try to delete a container that does not // 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. // exist on the system. We're just a step ahead of ourselves in that case.
// //
@@ -267,8 +288,6 @@ func (e *Environment) Destroy() error {
return nil return nil
} }
e.SetState(environment.ProcessOfflineState)
return err return err
} }
@@ -280,7 +299,6 @@ func (e *Environment) followOutput() error {
if err != nil { if err != nil {
return err return err
} }
return errors.New(fmt.Sprintf("no such container: %s", e.Id)) return errors.New(fmt.Sprintf("no such container: %s", e.Id))
} }
@@ -295,19 +313,39 @@ func (e *Environment) followOutput() error {
if err != nil { if err != nil {
return err return err
} }
go func(reader io.ReadCloser) {
defer reader.Close() go e.scanOutput(reader)
evts := e.Events()
err := system.ScanReader(reader, func(line string) {
evts.Publish(environment.ConsoleOutputEvent, line)
})
if err != nil && err != io.EOF {
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
}
}(reader)
return nil return nil
} }
func (e *Environment) scanOutput(reader io.ReadCloser) {
defer reader.Close()
events := e.Events()
err := system.ScanReader(reader, func(line string) {
events.Publish(environment.ConsoleOutputEvent, line)
})
if err != nil && err != io.EOF {
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
return
}
// Return here if the server is offline or currently stopping.
if e.State() == environment.ProcessStoppingState || e.State() == environment.ProcessOfflineState {
return
}
// Close the current reader before starting a new one, the defer will still run
// but it will do nothing if we already closed the stream.
_ = reader.Close()
// Start following the output of the server again.
go e.followOutput()
}
// Pulls the image from Docker. If there is an error while pulling the image from the source // Pulls the image from Docker. If there is an error while pulling the image from the source
// but the image already exists locally, we will report that error to the logger but continue // but the image already exists locally, we will report that error to the logger but continue
// with the process. // with the process.
@@ -391,9 +429,11 @@ func (e *Environment) ensureImageExists(image string) error {
// I'm not sure what the best approach here is, but this will block execution until the image // I'm not sure what the best approach here is, but this will block execution until the image
// is done being pulled, which is what we need. // is done being pulled, which is what we need.
scanner := bufio.NewScanner(out) scanner := bufio.NewScanner(out)
for scanner.Scan() { for scanner.Scan() {
s := imagePullStatus{} s := imagePullStatus{}
fmt.Println(scanner.Text()) fmt.Println(scanner.Text())
if err := json.Unmarshal(scanner.Bytes(), &s); err == nil { if err := json.Unmarshal(scanner.Bytes(), &s); err == nil {
e.Events().Publish(environment.DockerImagePullStatus, s.Status+" "+s.Progress) e.Events().Publish(environment.DockerImagePullStatus, s.Status+" "+s.Progress)
} }

View File

@@ -2,6 +2,7 @@ package docker
import ( import (
"context" "context"
"github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
@@ -70,6 +71,10 @@ func New(id string, m *Metadata, c *environment.Configuration) (*Environment, er
return e, nil return e, nil
} }
func (e *Environment) log() *log.Entry {
return log.WithField("environment", e.Type()).WithField("container_id", e.Id)
}
func (e *Environment) Type() string { func (e *Environment) Type() string {
return "docker" return "docker"
} }
@@ -77,8 +82,9 @@ func (e *Environment) Type() string {
// Set if this process is currently attached to the process. // Set if this process is currently attached to the process.
func (e *Environment) SetStream(s *types.HijackedResponse) { func (e *Environment) SetStream(s *types.HijackedResponse) {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock()
e.stream = s e.stream = s
e.mu.Unlock()
} }
// Determine if the this process is currently attached to the container. // Determine if the this process is currently attached to the container.
@@ -93,6 +99,7 @@ func (e *Environment) Events() *events.EventBus {
e.eventMu.Do(func() { e.eventMu.Do(func() {
e.emitter = events.New() e.emitter = events.New()
}) })
return e.emitter return e.emitter
} }
@@ -169,12 +176,14 @@ func (e *Environment) Config() *environment.Configuration {
// Sets the stop configuration for the environment. // Sets the stop configuration for the environment.
func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) { func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock()
e.meta.Stop = c e.meta.Stop = c
e.mu.Unlock()
} }
func (e *Environment) SetImage(i string) { func (e *Environment) SetImage(i string) {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock()
e.meta.Image = i e.meta.Image = i
e.mu.Unlock()
} }

View File

@@ -20,10 +20,9 @@ import (
// //
// This process will also confirm that the server environment exists and is in a bootable // This process will also confirm that the server environment exists and is in a bootable
// state. This ensures that unexpected container deletion while Wings is running does // state. This ensures that unexpected container deletion while Wings is running does
// not result in the server becoming unbootable. // not result in the server becoming un-bootable.
func (e *Environment) OnBeforeStart() error { func (e *Environment) OnBeforeStart() error {
// 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 usee.
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil { if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
if !client.IsErrNotFound(err) { if !client.IsErrNotFound(err) {
return errors.WithMessage(err, "failed to remove server docker container during pre-boot") return errors.WithMessage(err, "failed to remove server docker container during pre-boot")
@@ -49,6 +48,7 @@ func (e *Environment) OnBeforeStart() error {
// call to OnBeforeStart(). // call to OnBeforeStart().
func (e *Environment) Start() error { func (e *Environment) Start() error {
sawError := false sawError := false
// If sawError is set to true there was an error somewhere in the pipeline that // If sawError is set to true there was an error somewhere in the pipeline that
// got passed up, but we also want to ensure we set the server to be offline at // got passed up, but we also want to ensure we set the server to be offline at
// that point. // that point.
@@ -235,7 +235,7 @@ func (e *Environment) Terminate(signal os.Signal) error {
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed") sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil { if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil && !client.IsErrNotFound(err) {
return err return err
} }

View File

@@ -4,12 +4,10 @@ import (
"context" "context"
"emperror.dev/errors" "emperror.dev/errors"
"encoding/json" "encoding/json"
"github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"io" "io"
"math" "math"
"sync/atomic"
) )
// Attach to the instance and then automatically emit an event whenever the resource usage for the // Attach to the instance and then automatically emit an event whenever the resource usage for the
@@ -19,63 +17,51 @@ func (e *Environment) pollResources(ctx context.Context) error {
return errors.New("cannot enable resource polling on a stopped server") return errors.New("cannot enable resource polling on a stopped server")
} }
l := log.WithField("container_id", e.Id) e.log().Info("starting resource polling for container")
l.Debug("starting resource polling for container") defer e.log().Debug("stopped resource polling for container")
defer l.Debug("stopped resource polling for container")
stats, err := e.client.ContainerStats(context.Background(), e.Id, true) stats, err := e.client.ContainerStats(ctx, e.Id, true)
if err != nil { if err != nil {
return err return err
} }
defer stats.Body.Close() defer stats.Body.Close()
dec := json.NewDecoder(stats.Body) dec := json.NewDecoder(stats.Body)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
var v *types.StatsJSON var v types.StatsJSON
if err := dec.Decode(&v); err != nil { if err := dec.Decode(&v); err != nil {
if err != io.EOF { if err != io.EOF && !errors.Is(err, context.Canceled) {
l.WithField("error", err).Warn("error while processing Docker stats output for container") e.log().WithField("error", err).Warn("error while processing Docker stats output for container")
} else { } else {
l.Debug("io.EOF encountered during stats decode, stopping polling...") e.log().Debug("io.EOF encountered during stats decode, stopping polling...")
} }
return nil return nil
} }
// Disable collection if the server is in an offline state and this process is still running. // Disable collection if the server is in an offline state and this process is still running.
if e.st.Load() == environment.ProcessOfflineState { if e.st.Load() == environment.ProcessOfflineState {
l.Debug("process in offline state while resource polling is still active; stopping poll") e.log().Debug("process in offline state while resource polling is still active; stopping poll")
return nil return nil
} }
var rx uint64
var tx uint64
for _, nw := range v.Networks {
atomic.AddUint64(&rx, nw.RxBytes)
atomic.AddUint64(&tx, nw.RxBytes)
}
st := environment.Stats{ st := environment.Stats{
Memory: calculateDockerMemory(v.MemoryStats), Memory: calculateDockerMemory(v.MemoryStats),
MemoryLimit: v.MemoryStats.Limit, MemoryLimit: v.MemoryStats.Limit,
CpuAbsolute: calculateDockerAbsoluteCpu(&v.PreCPUStats, &v.CPUStats), CpuAbsolute: calculateDockerAbsoluteCpu(v.PreCPUStats, v.CPUStats),
Network: struct { Network: environment.NetworkStats{},
RxBytes uint64 `json:"rx_bytes"` }
TxBytes uint64 `json:"tx_bytes"`
}{ for _, nw := range v.Networks {
RxBytes: rx, st.Network.RxBytes += nw.RxBytes
TxBytes: tx, st.Network.TxBytes += nw.TxBytes
},
} }
if b, err := json.Marshal(st); err != nil { if b, err := json.Marshal(st); err != nil {
l.WithField("error", err).Warn("error while marshaling stats object for environment") e.log().WithField("error", err).Warn("error while marshaling stats object for environment")
} else { } else {
e.Events().Publish(environment.ResourceEvent, string(b)) e.Events().Publish(environment.ResourceEvent, string(b))
} }
@@ -108,7 +94,7 @@ func calculateDockerMemory(stats types.MemoryStats) uint64 {
// by the defined CPU limits on the container. // by the defined CPU limits on the container.
// //
// @see https://github.com/docker/cli/blob/aa097cf1aa19099da70930460250797c8920b709/cli/command/container/stats_helpers.go#L166 // @see https://github.com/docker/cli/blob/aa097cf1aa19099da70930460250797c8920b709/cli/command/container/stats_helpers.go#L166
func calculateDockerAbsoluteCpu(pStats *types.CPUStats, stats *types.CPUStats) float64 { func calculateDockerAbsoluteCpu(pStats types.CPUStats, stats types.CPUStats) float64 {
// Calculate the change in CPU usage between the current and previous reading. // Calculate the change in CPU usage between the current and previous reading.
cpuDelta := float64(stats.CPUUsage.TotalUsage) - float64(pStats.CPUUsage.TotalUsage) cpuDelta := float64(stats.CPUUsage.TotalUsage) - float64(pStats.CPUUsage.TotalUsage)

View File

@@ -19,8 +19,9 @@ var ErrNotAttached = errors.New("not attached to instance")
func (e *Environment) setStream(s *types.HijackedResponse) { func (e *Environment) setStream(s *types.HijackedResponse) {
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock()
e.stream = s e.stream = s
e.mu.Unlock()
} }
// Sends the specified command to the stdin of the running container instance. There is no // Sends the specified command to the stdin of the running container instance. There is no
@@ -71,7 +72,7 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
// Docker stores the logs for server output in a JSON format. This function will iterate over the JSON // Docker stores the logs for server output in a JSON format. This function will iterate over the JSON
// that was read from the log file and parse it into a more human readable format. // that was read from the log file and parse it into a more human readable format.
func (e *Environment) parseLogToStrings(b []byte) ([]string, error) { func (e *Environment) parseLogToStrings(b []byte) ([]string, error) {
var hasError = false hasError := false
var out []string var out []string
scanner := bufio.NewScanner(bytes.NewReader(b)) scanner := bufio.NewScanner(bytes.NewReader(b))

View File

@@ -1,8 +1,9 @@
package environment package environment
import ( import (
"github.com/pterodactyl/wings/events"
"os" "os"
"github.com/pterodactyl/wings/events"
) )
const ( const (

View File

@@ -2,9 +2,10 @@ package environment
import ( import (
"fmt" "fmt"
"github.com/apex/log"
"math" "math"
"strconv" "strconv"
"github.com/apex/log"
) )
type Mount struct { type Mount struct {

View File

@@ -24,8 +24,10 @@ type Stats struct {
// Disk int64 `json:"disk_bytes"` // Disk int64 `json:"disk_bytes"`
// Current network transmit in & out for a container. // Current network transmit in & out for a container.
Network struct { Network NetworkStats `json:"network"`
RxBytes uint64 `json:"rx_bytes"` }
TxBytes uint64 `json:"tx_bytes"`
} `json:"network"` type NetworkStats struct {
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
} }

View File

@@ -2,9 +2,10 @@ package events
import ( import (
"encoding/json" "encoding/json"
"github.com/gammazero/workerpool"
"strings" "strings"
"sync" "sync"
"github.com/gammazero/workerpool"
) )
type Event struct { type Event struct {

View File

@@ -1,8 +1,9 @@
package events package events
import ( import (
"github.com/gammazero/workerpool"
"reflect" "reflect"
"github.com/gammazero/workerpool"
) )
type CallbackPool struct { type CallbackPool struct {

1
go.mod
View File

@@ -63,6 +63,7 @@ require (
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
github.com/sirupsen/logrus v1.7.0 // indirect github.com/sirupsen/logrus v1.7.0 // indirect
github.com/spf13/cobra v1.1.1 github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/ugorji/go v1.2.2 // indirect github.com/ugorji/go v1.2.2 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect github.com/ulikunitz/xz v0.5.9 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad

View File

@@ -1,8 +1,9 @@
package installer package installer
import ( import (
"emperror.dev/errors"
"encoding/json" "encoding/json"
"emperror.dev/errors"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"

View File

@@ -2,16 +2,17 @@ package parser
import ( import (
"bytes" "bytes"
"emperror.dev/errors"
"github.com/Jeffail/gabs/v2"
"github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/iancoleman/strcase"
"io/ioutil" "io/ioutil"
"os" "os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"emperror.dev/errors"
"github.com/Jeffail/gabs/v2"
"github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/iancoleman/strcase"
) )
// Regex to match anything that has a value matching the format of {{ config.$1 }} which // Regex to match anything that has a value matching the format of {{ config.$1 }} which

View File

@@ -2,8 +2,14 @@ package parser
import ( import (
"bufio" "bufio"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
@@ -12,11 +18,6 @@ import (
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
) )
// The file parsing options that are available for a server configuration file. // The file parsing options that are available for a server configuration file.

View File

@@ -4,17 +4,65 @@ import (
"context" "context"
"emperror.dev/errors" "emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"io" "io"
"net"
"net/http" "net/http"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
) )
var client = &http.Client{
Timeout: time.Hour * 12,
// Disallow any redirect on a HTTP call. This is a security requirement: do not modify
// this logic without first ensuring that the new target location IS NOT within the current
// instance's local network.
//
// This specific error response just causes the client to not follow the redirect and
// returns the actual redirect response to the caller. Not perfect, but simple and most
// people won't be using URLs that redirect anyways hopefully?
//
// We'll re-evaluate this down the road if needed.
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
var instance = &Downloader{
// Tracks all of the active downloads.
downloadCache: make(map[string]*Download),
// Tracks all of the downloads active for a given server instance. This is
// primarily used to make things quicker and keep the code a little more
// legible throughout here.
serverCache: make(map[string][]string),
}
// Regex to match the end of an IPv4/IPv6 address. This allows the port to be removed
// so that we are just working with the raw IP address in question.
var ipMatchRegex = regexp.MustCompile(`(:\d+)$`)
// Internal IP ranges that should be blocked if the resource requested resolves within.
var internalRanges = []*net.IPNet{
mustParseCIDR("127.0.0.1/8"),
mustParseCIDR("10.0.0.0/8"),
mustParseCIDR("172.16.0.0/12"),
mustParseCIDR("192.168.0.0/16"),
mustParseCIDR("169.254.0.0/16"),
mustParseCIDR("::1/128"),
mustParseCIDR("fe80::/10"),
mustParseCIDR("fc00::/7"),
}
const ErrInternalResolution = errors.Sentinel("downloader: destination resolves to internal network location")
const ErrInvalidIPAddress = errors.Sentinel("downloader: invalid IP address")
const ErrDownloadFailed = errors.Sentinel("downloader: download request failed")
type Counter struct { type Counter struct {
total int total int
onWrite func(total int) onWrite func(total int)
@@ -27,12 +75,6 @@ func (c *Counter) Write(p []byte) (int, error) {
return n, nil return n, nil
} }
type Downloader struct {
mu sync.RWMutex
downloadCache map[string]*Download
serverCache map[string][]string
}
type DownloadRequest struct { type DownloadRequest struct {
URL *url.URL URL *url.URL
Directory string Directory string
@@ -47,16 +89,6 @@ type Download struct {
cancelFunc *context.CancelFunc cancelFunc *context.CancelFunc
} }
var client = &http.Client{Timeout: time.Hour * 12}
var instance = &Downloader{
// Tracks all of the active downloads.
downloadCache: make(map[string]*Download),
// Tracks all of the downloads active for a given server instance. This is
// primarily used to make things quicker and keep the code a little more
// legible throughout here.
serverCache: make(map[string][]string),
}
// Starts a new tracked download which allows for cancelation later on by calling // Starts a new tracked download which allows for cancelation later on by calling
// the Downloader.Cancel function. // the Downloader.Cancel function.
func New(s *server.Server, r DownloadRequest) *Download { func New(s *server.Server, r DownloadRequest) *Download {
@@ -108,15 +140,24 @@ func (dl *Download) Execute() error {
dl.cancelFunc = &cancel dl.cancelFunc = &cancel
defer dl.Cancel() defer dl.Cancel()
// Always ensure that we're checking the destination for the download to avoid a malicious
// user from accessing internal network resources.
if err := dl.isExternalNetwork(ctx); err != nil {
return err
}
// At this point we have verified the destination is not within the local network, so we can
// now make a request to that URL and pull down the file, saving it to the server's data
// directory.
req, err := http.NewRequestWithContext(ctx, http.MethodGet, dl.req.URL.String(), nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, dl.req.URL.String(), nil)
if err != nil { if err != nil {
return errors.WrapIf(err, "downloader: failed to create request") return errors.WrapIf(err, "downloader: failed to create request")
} }
req.Header.Set("User-Agent", "Pterodactyl Panel (https://pterodactyl.io)") req.Header.Set("User-Agent", "Pterodactyl Panel (https://pterodactyl.io)")
res, err := client.Do(req) // lgtm [go/request-forgery] res, err := client.Do(req)
if err != nil { if err != nil {
return errors.New("downloader: failed opening request to download file") return ErrDownloadFailed
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
@@ -178,6 +219,52 @@ func (dl *Download) counter(contentLength int64) *Counter {
} }
} }
// Verifies that a given download resolves to a location not within the current local
// network for the machine. If the final destination of a resource is within the local
// network an ErrInternalResolution error is returned.
func (dl *Download) isExternalNetwork(ctx context.Context) error {
dialer := &net.Dialer{
LocalAddr: nil,
}
host := dl.req.URL.Host
if !ipMatchRegex.MatchString(host) {
if dl.req.URL.Scheme == "https" {
host = host + ":443"
} else {
host = host + ":80"
}
}
c, err := dialer.DialContext(ctx, "tcp", host)
if err != nil {
return errors.WithStack(err)
}
c.Close()
ip := net.ParseIP(ipMatchRegex.ReplaceAllString(c.RemoteAddr().String(), ""))
if ip == nil {
return errors.WithStack(ErrInvalidIPAddress)
}
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast() {
return errors.WithStack(ErrInternalResolution)
}
for _, block := range internalRanges {
if block.Contains(ip) {
return errors.WithStack(ErrInternalResolution)
}
}
return nil
}
// Defines a global downloader struct that keeps track of all currently processing downloads
// for the machine.
type Downloader struct {
mu sync.RWMutex
downloadCache map[string]*Download
serverCache map[string][]string
}
// Tracks a download in the internal cache for this instance. // Tracks a download in the internal cache for this instance.
func (d *Downloader) track(dl *Download) { func (d *Downloader) track(dl *Download) {
d.mu.Lock() d.mu.Lock()
@@ -222,3 +309,11 @@ func (d *Downloader) remove(dlid string) {
d.serverCache[sid] = out d.serverCache[sid] = out
} }
} }
func mustParseCIDR(ip string) *net.IPNet {
_, block, err := net.ParseCIDR(ip)
if err != nil {
panic(fmt.Errorf("downloader: failed to parse CIDR: %s", err))
}
return block
}

View File

@@ -1,16 +1,17 @@
package router package router
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"net/http"
"os"
"strings"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
"net/http"
"os"
"strings"
) )
type RequestError struct { type RequestError struct {

View File

@@ -1,14 +1,15 @@
package router package router
import ( import (
"io"
"net/http"
"strings"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"io"
"net/http"
"strings"
) )
type Middleware struct{} type Middleware struct{}
@@ -119,6 +120,21 @@ func (m *Middleware) ServerExists() gin.HandlerFunc {
} }
} }
// Checks if remote file downloading is enabled on this instance before allowing access
// to the given endpoint.
func (m *Middleware) CheckRemoteDownloadEnabled() gin.HandlerFunc {
disabled := config.Get().Api.DisableRemoteDownload
return func(c *gin.Context) {
if disabled {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "This functionality is not currently enabled on this instance.",
})
return
}
c.Next()
}
}
// Returns the server instance from the gin context. If there is no server set in the // Returns the server instance from the gin context. If there is no server set in the
// context (e.g. calling from a controller not protected by ServerExists) this function // context (e.g. calling from a controller not protected by ServerExists) this function
// will panic. // will panic.

View File

@@ -88,9 +88,9 @@ func Configure() *gin.Engine {
files.POST("/decompress", postServerDecompressFiles) files.POST("/decompress", postServerDecompressFiles)
files.POST("/chmod", postServerChmodFile) files.POST("/chmod", postServerChmodFile)
files.GET("/pull", getServerPullingFiles) files.GET("/pull", m.CheckRemoteDownloadEnabled(), getServerPullingFiles)
files.POST("/pull", postServerPullRemoteFile) files.POST("/pull", m.CheckRemoteDownloadEnabled(), postServerPullRemoteFile)
files.DELETE("/pull/:download", deleteServerPullRemoteFile) files.DELETE("/pull/:download", m.CheckRemoteDownloadEnabled(), deleteServerPullRemoteFile)
} }
backup := server.Group("/backup") backup := server.Group("/backup")

View File

@@ -3,12 +3,13 @@ package router
import ( import (
"bufio" "bufio"
"errors" "errors"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server/backup"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server/backup"
) )
// Handle a download request for a server backup. // Handle a download request for a server backup.

View File

@@ -3,15 +3,16 @@ package router
import ( import (
"bytes" "bytes"
"context" "context"
"net/http"
"os"
"strconv"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader" "github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens" "github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"net/http"
"os"
"strconv"
) )
type serverProcData struct { type serverProcData struct {

View File

@@ -1,13 +1,14 @@
package router package router
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"net/http"
"os"
"emperror.dev/errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/backup" "github.com/pterodactyl/wings/server/backup"
"net/http"
"os"
) )
// Backs up a server. // Backs up a server.

View File

@@ -2,14 +2,6 @@ package router
import ( import (
"context" "context"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
@@ -18,16 +10,21 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
) )
// 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 := ExtractServer(c) s := ExtractServer(c)
f, err := url.QueryUnescape(c.Query("file")) f := c.Query("file")
if err != nil {
WithError(c, err)
return
}
p := "/" + strings.TrimLeft(f, "/") p := "/" + strings.TrimLeft(f, "/")
st, err := s.Filesystem().Stat(p) st, err := s.Filesystem().Stat(p)
if err != nil { if err != nil {
@@ -64,11 +61,7 @@ func getServerFileContents(c *gin.Context) {
// Returns the contents of a directory for a server. // Returns the contents of a directory for a server.
func getServerListDirectory(c *gin.Context) { func getServerListDirectory(c *gin.Context) {
s := ExtractServer(c) s := ExtractServer(c)
dir, err := url.QueryUnescape(c.Query("directory")) dir := c.Query("directory")
if err != nil {
WithError(c, err)
return
}
if stats, err := s.Filesystem().ListDirectory(dir); err != nil { if stats, err := s.Filesystem().ListDirectory(dir); err != nil {
WithError(c, err) WithError(c, err)
} else { } else {
@@ -212,11 +205,7 @@ func postServerDeleteFiles(c *gin.Context) {
func postServerWriteFile(c *gin.Context) { func postServerWriteFile(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
f, err := url.QueryUnescape(c.Query("file")) f := c.Query("file")
if err != nil {
NewServerError(err, s).Abort(c)
return
}
f = "/" + strings.TrimLeft(f, "/") f = "/" + strings.TrimLeft(f, "/")
if err := s.Filesystem().Writefile(f, c.Request.Body); err != nil { if err := s.Filesystem().Writefile(f, c.Request.Body); err != nil {

View File

@@ -3,10 +3,11 @@ package router
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
ws "github.com/gorilla/websocket" ws "github.com/gorilla/websocket"
"github.com/pterodactyl/wings/router/websocket" "github.com/pterodactyl/wings/router/websocket"
"time"
) )
// Upgrades a connection to a websocket and passes events along between. // Upgrades a connection to a websocket and passes events along between.

View File

@@ -2,14 +2,15 @@ package router
import ( import (
"bytes" "bytes"
"net/http"
"strings"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer" "github.com/pterodactyl/wings/installer"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"net/http"
"strings"
) )
// Returns information about the system that wings is running on. // Returns information about the system that wings is running on.

View File

@@ -3,10 +3,19 @@ package router
import ( import (
"bufio" "bufio"
"crypto/sha256" "crypto/sha256"
"emperror.dev/errors"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
@@ -19,14 +28,6 @@ import (
"github.com/pterodactyl/wings/router/tokens" "github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
) )
// Number of ticks in the progress bar // Number of ticks in the progress bar
@@ -129,6 +130,9 @@ func postServerArchive(c *gin.Context) {
return return
} }
// Mark the server as not being transferred so it can actually be used.
s.SetTransferring(false)
s.Events().Publish(server.TransferStatusEvent, "failure") s.Events().Publish(server.TransferStatusEvent, "failure")
sendTransferLog("Attempting to notify panel of archive failure..") sendTransferLog("Attempting to notify panel of archive failure..")

View File

@@ -2,15 +2,16 @@ package server
import ( import (
"crypto/sha256" "crypto/sha256"
"emperror.dev/errors"
"encoding/hex" "encoding/hex"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"emperror.dev/errors"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem"
) )
// Archiver represents a Server Archiver. // Archiver represents a Server Archiver.

View File

@@ -1,12 +1,13 @@
package server package server
import ( import (
"io/ioutil"
"os"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/server/backup" "github.com/pterodactyl/wings/server/backup"
"io/ioutil"
"os"
) )
// Notifies the panel of a backup's state and returns an error if one is encountered // Notifies the panel of a backup's state and returns an error if one is encountered

View File

@@ -1,8 +1,9 @@
package server package server
import ( import (
"github.com/gammazero/workerpool"
"runtime" "runtime"
"github.com/gammazero/workerpool"
) )
// Parent function that will update all of the defined configuration files for a server // Parent function that will update all of the defined configuration files for a server

View File

@@ -1,8 +1,9 @@
package server package server
import ( import (
"github.com/pterodactyl/wings/environment"
"sync" "sync"
"github.com/pterodactyl/wings/environment"
) )
type Configuration struct { type Configuration struct {

View File

@@ -2,14 +2,15 @@ package server
import ( import (
"context" "context"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"emperror.dev/errors"
"github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
) )
var ErrTooMuchConsoleData = errors.New("console is outputting too much data") var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
@@ -114,7 +115,7 @@ func (ct *ConsoleThrottler) Increment(onTrigger func()) error {
func (s *Server) Throttler() *ConsoleThrottler { func (s *Server) Throttler() *ConsoleThrottler {
s.throttleOnce.Do(func() { s.throttleOnce.Do(func() {
s.throttler = &ConsoleThrottler{ s.throttler = &ConsoleThrottler{
isThrottled: system.NewAtomicBool(false), isThrottled: system.NewAtomicBool(false),
ConsoleThrottles: config.Get().Throttles, ConsoleThrottles: config.Get().Throttles,
} }
}) })

View File

@@ -2,11 +2,12 @@ package server
import ( import (
"fmt" "fmt"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"strconv" "strconv"
"sync" "sync"
"time" "time"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
) )
type CrashHandler struct { type CrashHandler struct {

View File

@@ -1,8 +1,9 @@
package server package server
import ( import (
"github.com/pterodactyl/wings/server/filesystem"
"os" "os"
"github.com/pterodactyl/wings/server/filesystem"
) )
func (s *Server) Filesystem() *filesystem.Filesystem { func (s *Server) Filesystem() *filesystem.Filesystem {

View File

@@ -4,6 +4,13 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"html/template"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@@ -14,12 +21,6 @@ import (
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"html/template"
"io"
"os"
"path/filepath"
"strconv"
"strings"
) )
// Executes the installation stack for a server process. Bubbles any errors up to the calling // Executes the installation stack for a server process. Bubbles any errors up to the calling

View File

@@ -2,14 +2,15 @@ package server
import ( import (
"encoding/json" "encoding/json"
"regexp"
"strconv"
"sync"
"github.com/apex/log" "github.com/apex/log"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/events" "github.com/pterodactyl/wings/events"
"regexp"
"strconv"
"sync"
) )
var dockerEvents = []string{ var dockerEvents = []string{
@@ -64,9 +65,11 @@ func (s *Server) StartEventListeners() {
// to terminate again. // to terminate again.
if s.Environment.State() != environment.ProcessStoppingState { if s.Environment.State() != environment.ProcessStoppingState {
s.Environment.SetState(environment.ProcessStoppingState) s.Environment.SetState(environment.ProcessStoppingState)
go func() { go func() {
s.Log().Warn("stopping server instance, violating throttle limits") s.Log().Warn("stopping server instance, violating throttle limits")
s.PublishConsoleOutputFromDaemon("Your server is being stopped for outputting too much data in a short period of time.") s.PublishConsoleOutputFromDaemon("Your server is being stopped for outputting too much data in a short period of time.")
// Completely skip over server power actions and terminate the running instance. This gives the // Completely skip over server power actions and terminate the running instance. This gives the
// server 15 seconds to finish stopping gracefully before it is forcefully terminated. // server 15 seconds to finish stopping gracefully before it is forcefully terminated.
if err := s.Environment.WaitForStop(config.Get().Throttles.StopGracePeriod, true); err != nil { if err := s.Environment.WaitForStop(config.Get().Throttles.StopGracePeriod, true); err != nil {

View File

@@ -1,9 +1,14 @@
package server package server
import ( import (
"emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"path/filepath"
"runtime"
"time"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/gammazero/workerpool" "github.com/gammazero/workerpool"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
@@ -11,10 +16,6 @@ import (
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/environment/docker"
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
"os"
"path/filepath"
"runtime"
"time"
) )
var servers = NewCollection(nil) var servers = NewCollection(nil)

View File

@@ -1,11 +1,12 @@
package server package server
import ( import (
"path/filepath"
"strings"
"github.com/apex/log" "github.com/apex/log"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"path/filepath"
"strings"
) )
// To avoid confusion when working with mounts, assume that a server.Mount has not been properly // To avoid confusion when working with mounts, assume that a server.Mount has not been properly

View File

@@ -2,12 +2,13 @@ package server
import ( import (
"context" "context"
"os"
"time"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"os"
"time"
) )
type PowerAction string type PowerAction string

View File

@@ -1,10 +1,11 @@
package server package server
import ( import (
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
) )
// Defines the current resource usage for a given server instance. If a server is offline you // Defines the current resource usage for a given server instance. If a server is offline you

View File

@@ -2,8 +2,11 @@ package server
import ( import (
"context" "context"
"emperror.dev/errors"
"fmt" "fmt"
"strings"
"sync"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/creasty/defaults" "github.com/creasty/defaults"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
@@ -14,8 +17,6 @@ import (
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"strings"
"sync"
) )
// High level definition for a server instance being controlled by Wings. // High level definition for a server instance being controlled by Wings.

View File

@@ -2,12 +2,13 @@ package server
import ( import (
"encoding/json" "encoding/json"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"sync" "sync"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
) )
var stateMutex sync.Mutex var stateMutex sync.Mutex

View File

@@ -1,8 +1,9 @@
package server package server
import ( import (
"emperror.dev/errors"
"encoding/json" "encoding/json"
"emperror.dev/errors"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"

View File

@@ -2,8 +2,9 @@ package server
import ( import (
"context" "context"
"github.com/google/uuid"
"sync" "sync"
"github.com/google/uuid"
) )
type WebsocketBag struct { type WebsocketBag struct {

View File

@@ -1,14 +1,15 @@
package sftp package sftp
import ( import (
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
) )
type FileSystem struct { type FileSystem struct {

View File

@@ -6,11 +6,6 @@ import (
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
"github.com/pterodactyl/wings/api"
"golang.org/x/crypto/ssh"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@@ -18,6 +13,12 @@ import (
"path" "path"
"strings" "strings"
"time" "time"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
"github.com/pterodactyl/wings/api"
"golang.org/x/crypto/ssh"
) )
type Settings struct { type Settings struct {

View File

@@ -1,8 +1,9 @@
package system package system
import ( import (
"github.com/docker/docker/pkg/parsers/kernel"
"runtime" "runtime"
"github.com/docker/docker/pkg/parsers/kernel"
) )
type Information struct { type Information struct {