From 98c68142cd1e4fa631dce79b0d697f072bddbed3 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 1 Feb 2021 21:28:46 -0800 Subject: [PATCH] Remove all of the remaining API logic and port it all to the remote.Client type --- api/api.go | 197 ------------------------------ api/backup_endpoints.go | 60 --------- api/error.go | 33 ----- api/process_configuration.go | 69 ----------- api/server_endpoints.go | 118 ------------------ cmd/root.go | 2 +- environment/docker/environment.go | 6 +- environment/docker/power.go | 6 +- installer/installer.go | 22 ++-- remote/backup.go | 29 +++-- remote/client.go | 7 +- remote/servers.go | 65 +++++----- remote/sftp.go | 53 -------- remote/types.go | 133 ++++++++++++++++++++ router/middleware/middleware.go | 18 +++ router/router.go | 5 +- router/router_server_backup.go | 16 +-- router/router_system.go | 4 +- router/router_transfer.go | 28 ++--- server/backup.go | 8 +- server/backup/backup.go | 13 +- server/backup/backup_local.go | 14 +-- server/backup/backup_s3.go | 8 +- server/install.go | 13 +- server/listeners.go | 3 +- server/server.go | 9 +- 26 files changed, 290 insertions(+), 649 deletions(-) delete mode 100644 api/api.go delete mode 100644 api/backup_endpoints.go delete mode 100644 api/error.go delete mode 100644 api/process_configuration.go delete mode 100644 api/server_endpoints.go delete mode 100644 remote/sftp.go create mode 100644 remote/types.go diff --git a/api/api.go b/api/api.go deleted file mode 100644 index cdb9954..0000000 --- a/api/api.go +++ /dev/null @@ -1,197 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "strings" - "time" - - "emperror.dev/errors" - "github.com/apex/log" - "github.com/pterodactyl/wings/config" - "github.com/pterodactyl/wings/system" -) - -// Initializes the requester instance. -func New() *Request { - return &Request{} -} - -// A generic type allowing for easy binding use when making requests to API endpoints -// that only expect a singular argument or something that would not benefit from being -// a typed struct. -// -// Inspired by gin.H, same concept. -type D map[string]interface{} - -// Same concept as D, but a map of strings, used for querying GET requests. -type Q map[string]string - -// A custom API requester struct for Wings. -type Request struct{} - -// A custom response type that allows for commonly used error handling and response -// parsing from the Panel API. This just embeds the normal HTTP response from Go and -// we attach a few helper functions to it. -type Response struct { - *http.Response -} - -// A pagination struct matching the expected pagination response from the Panel API. -type Pagination struct { - CurrentPage uint `json:"current_page"` - From uint `json:"from"` - LastPage uint `json:"last_page"` - PerPage uint `json:"per_page"` - To uint `json:"to"` - Total uint `json:"total"` -} - -// Builds the base request instance that can be used with the HTTP client. -func (r *Request) Client() *http.Client { - return &http.Client{Timeout: time.Second * time.Duration(config.Get().RemoteQuery.Timeout)} -} - -// Returns the given endpoint formatted as a URL to the Panel API. -func (r *Request) Endpoint(endpoint string) string { - return fmt.Sprintf( - "%s/api/remote/%s", - strings.TrimSuffix(config.Get().PanelLocation, "/"), - strings.TrimPrefix(strings.TrimPrefix(endpoint, "/"), "api/remote/"), - ) -} - -// Makes a HTTP request to the given endpoint, attaching the necessary request headers from -// Wings to ensure that the request is properly handled by the Panel. -func (r *Request) Make(method, url string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) { - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - - req.Header.Set("User-Agent", fmt.Sprintf("Pterodactyl Wings/v%s (id:%s)", system.Version, config.Get().AuthenticationTokenId)) - req.Header.Set("Accept", "application/vnd.pterodactyl.v1+json") - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s.%s", config.Get().AuthenticationTokenId, config.Get().AuthenticationToken)) - - // Make any options calls that will allow us to make modifications to the request - // before it is sent off. - for _, cb := range opts { - cb(req) - } - - r.debug(req) - - res, err := r.Client().Do(req) - - return &Response{Response: res}, err -} - -// 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 *Request) debug(req *http.Request) { - headers := make(map[string][]string) - for k, v := range req.Header { - if k != "Authorization" || len(v) == 0 { - headers[k] = v - continue - } - - headers[k] = []string{v[0][0:15] + "(redacted)"} - } - - log.WithFields(log.Fields{ - "method": req.Method, - "endpoint": req.URL.String(), - "headers": headers, - }).Debug("making request to external HTTP endpoint") -} - -// Makes a GET request to the given Panel API endpoint. If any data is passed as the -// second argument it will be passed through on the request as URL parameters. -func (r *Request) Get(url string, data Q) (*Response, error) { - return r.Make(http.MethodGet, r.Endpoint(url), nil, func(r *http.Request) { - q := r.URL.Query() - for k, v := range data { - q.Set(k, v) - } - - r.URL.RawQuery = q.Encode() - }) -} - -// Makes a POST request to the given Panel API endpoint. -func (r *Request) Post(url string, data interface{}) (*Response, error) { - b, err := json.Marshal(data) - if err != nil { - return nil, err - } - - return r.Make(http.MethodPost, r.Endpoint(url), bytes.NewBuffer(b)) -} - -// Determines if the API call encountered an error. If no request has been made -// the response will be false. This function will evaluate to true if the response -// code is anything 300 or higher. -func (r *Response) HasError() bool { - if r.Response == nil { - return false - } - - return r.StatusCode >= 300 || r.StatusCode < 200 -} - -// Reads the body from the response and returns it, then replaces it on the response -// so that it can be read again later. This does not close the response body, so any -// functions calling this should be sure to manually defer a Body.Close() call. -func (r *Response) Read() ([]byte, error) { - var b []byte - if r.Response == nil { - return nil, errors.New("no response exists on interface") - } - - if r.Response.Body != nil { - b, _ = ioutil.ReadAll(r.Response.Body) - } - - r.Response.Body = ioutil.NopCloser(bytes.NewBuffer(b)) - - return b, nil -} - -// Binds a given interface with the data returned in the response. This is a shortcut -// for calling Read and then manually calling json.Unmarshal on the raw bytes. -func (r *Response) Bind(v interface{}) error { - b, err := r.Read() - if err != nil { - return err - } - - return json.Unmarshal(b, &v) -} - -// Returns the error message from the API call as a string. The error message will be formatted -// similar to the below example: -// -// HttpNotFoundException: The requested resource does not exist. (HTTP/404) -func (r *Response) Error() error { - if !r.HasError() { - return nil - } - - var bag RequestErrorBag - _ = r.Bind(&bag) - - e := &RequestError{} - if len(bag.Errors) > 0 { - e = &bag.Errors[0] - } - - e.response = r.Response - - return e -} diff --git a/api/backup_endpoints.go b/api/backup_endpoints.go deleted file mode 100644 index 6f8a4c1..0000000 --- a/api/backup_endpoints.go +++ /dev/null @@ -1,60 +0,0 @@ -package api - -import ( - "fmt" - "strconv" -) - -type BackupRemoteUploadResponse struct { - Parts []string `json:"parts"` - PartSize int64 `json:"part_size"` -} - -func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupRemoteUploadResponse, error) { - resp, err := r.Get(fmt.Sprintf("/backups/%s", backup), Q{"size": strconv.FormatInt(size, 10)}) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.HasError() { - return nil, resp.Error() - } - - var res BackupRemoteUploadResponse - if err := resp.Bind(&res); err != nil { - return nil, err - } - - return &res, nil -} - -type BackupRequest struct { - Checksum string `json:"checksum"` - ChecksumType string `json:"checksum_type"` - Size int64 `json:"size"` - Successful bool `json:"successful"` -} - -// SendBackupStatus notifies the panel that a specific backup has been completed -// and is now available for a user to view and download. -func (r *Request) SendBackupStatus(backup string, data BackupRequest) error { - resp, err := r.Post(fmt.Sprintf("/backups/%s", backup), data) - if err != nil { - return err - } - defer resp.Body.Close() - return resp.Error() -} - -// SendRestorationStatus triggers a request to the Panel to notify it that a -// restoration has been completed and the server should be marked as being -// activated again. -func (r *Request) SendRestorationStatus(backup string, successful bool) error { - resp, err := r.Post(fmt.Sprintf("/backups/%s/restore", backup), D{"successful": successful}) - if err != nil { - return err - } - defer resp.Body.Close() - return resp.Error() -} \ No newline at end of file diff --git a/api/error.go b/api/error.go deleted file mode 100644 index 5290488..0000000 --- a/api/error.go +++ /dev/null @@ -1,33 +0,0 @@ -package api - -import ( - "fmt" - "net/http" -) - -type RequestErrorBag struct { - Errors []RequestError `json:"errors"` -} - -type RequestError struct { - response *http.Response - Code string `json:"code"` - Status string `json:"status"` - Detail string `json:"detail"` -} - -func IsRequestError(err error) bool { - _, ok := err.(*RequestError) - - return ok -} - -// Returns the error response in a string form that can be more easily consumed. -func (re *RequestError) Error() string { - c := 0 - if re.response != nil { - c = re.response.StatusCode - } - - return fmt.Sprintf("Error response from Panel: %s: %s (HTTP/%d)", re.Code, re.Detail, c) -} diff --git a/api/process_configuration.go b/api/process_configuration.go deleted file mode 100644 index b8849a7..0000000 --- a/api/process_configuration.go +++ /dev/null @@ -1,69 +0,0 @@ -package api - -import ( - "encoding/json" - "regexp" - "strings" - - "github.com/apex/log" - "github.com/pterodactyl/wings/parser" -) - -type OutputLineMatcher struct { - // The raw string to match against. This may or may not be prefixed with - // regex: which indicates we want to match against the regex expression. - raw string - reg *regexp.Regexp -} - -// Determine if a given string "s" matches the given line. -func (olm *OutputLineMatcher) Matches(s string) bool { - if olm.reg == nil { - return strings.Contains(s, olm.raw) - } - - return olm.reg.MatchString(s) -} - -// Return the matcher's raw comparison string. -func (olm *OutputLineMatcher) String() string { - return olm.raw -} - -// Unmarshal the startup lines into individual structs for easier matching abilities. -func (olm *OutputLineMatcher) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &olm.raw); err != nil { - return err - } - - if strings.HasPrefix(olm.raw, "regex:") && len(olm.raw) > 6 { - r, err := regexp.Compile(strings.TrimPrefix(olm.raw, "regex:")) - if err != nil { - log.WithField("error", err).WithField("raw", olm.raw).Warn("failed to compile output line marked as being regex") - } - - olm.reg = r - } - - return nil -} - -type ProcessStopConfiguration struct { - Type string `json:"type"` - Value string `json:"value"` -} - -// Defines the process configuration for a given server instance. This sets what the -// daemon is looking for to mark a server as done starting, what to do when stopping, -// and what changes to make to the configuration file for a server. -type ProcessConfiguration struct { - Startup struct { - Done []*OutputLineMatcher `json:"done"` - UserInteraction []string `json:"user_interaction"` - StripAnsi bool `json:"strip_ansi"` - } `json:"startup"` - - Stop ProcessStopConfiguration `json:"stop"` - - ConfigurationFiles []parser.ConfigurationFile `json:"configs"` -} diff --git a/api/server_endpoints.go b/api/server_endpoints.go deleted file mode 100644 index 386732d..0000000 --- a/api/server_endpoints.go +++ /dev/null @@ -1,118 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" -) - -const ( - ProcessStopCommand = "command" - ProcessStopSignal = "signal" - ProcessStopNativeStop = "stop" -) - -// Holds the server configuration data returned from the Panel. When a server process -// is started, Wings communicates with the Panel to fetch the latest build information -// as well as get all of the details needed to parse the given Egg. -// -// This means we do not need to hit Wings each time part of the server is updated, and -// the Panel serves as the source of truth at all times. This also means if a configuration -// is accidentally wiped on Wings we can self-recover without too much hassle, so long -// as Wings is aware of what servers should exist on it. -type ServerConfigurationResponse struct { - Settings json.RawMessage `json:"settings"` - ProcessConfiguration *ProcessConfiguration `json:"process_configuration"` -} - -// Defines installation script information for a server process. This is used when -// a server is installed for the first time, and when a server is marked for re-installation. -type InstallationScript struct { - ContainerImage string `json:"container_image"` - Entrypoint string `json:"entrypoint"` - Script string `json:"script"` -} - -type RawServerData struct { - Uuid string `json:"uuid"` - Settings json.RawMessage `json:"settings"` - ProcessConfiguration json.RawMessage `json:"process_configuration"` -} - -// Fetches the server configuration and returns the struct for it. -func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationResponse, error) { - var cfg ServerConfigurationResponse - - resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid), nil) - if err != nil { - return cfg, err - } - defer resp.Body.Close() - - if resp.HasError() { - return cfg, resp.Error() - } - - if err := resp.Bind(&cfg); err != nil { - return cfg, err - } - - return cfg, nil -} - -// Fetches installation information for the server process. -func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error) { - var is InstallationScript - resp, err := r.Get(fmt.Sprintf("/servers/%s/install", uuid), nil) - if err != nil { - return is, err - } - defer resp.Body.Close() - - if resp.HasError() { - return is, resp.Error() - } - - if err := resp.Bind(&is); err != nil { - return is, err - } - - return is, nil -} - -// Marks a server as being installed successfully or unsuccessfully on the panel. -func (r *Request) SendInstallationStatus(uuid string, successful bool) error { - resp, err := r.Post(fmt.Sprintf("/servers/%s/install", uuid), D{"successful": successful}) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.HasError() { - return resp.Error() - } - - return nil -} - -func (r *Request) SendArchiveStatus(uuid string, successful bool) error { - resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), D{"successful": successful}) - if err != nil { - return err - } - defer resp.Body.Close() - - return resp.Error() -} - -func (r *Request) SendTransferStatus(uuid string, successful bool) error { - state := "failure" - if successful { - state = "success" - } - resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/%s", uuid, state), nil) - if err != nil { - return err - } - defer resp.Body.Close() - return resp.Error() -} diff --git a/cmd/root.go b/cmd/root.go index a0558d0..c0d47b1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -293,7 +293,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) { // and external clients. s := &http.Server{ Addr: api.Host + ":" + strconv.Itoa(api.Port), - Handler: router.Configure(manager), + Handler: router.Configure(manager, pclient), TLSConfig: config.DefaultTLSConfig, } diff --git a/environment/docker/environment.go b/environment/docker/environment.go index c8b6448..99282b0 100644 --- a/environment/docker/environment.go +++ b/environment/docker/environment.go @@ -10,15 +10,15 @@ import ( "github.com/apex/log" "github.com/docker/docker/api/types" "github.com/docker/docker/client" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/events" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/system" ) type Metadata struct { Image string - Stop api.ProcessStopConfiguration + Stop remote.ProcessStopConfiguration } // Ensure that the Docker environment is always implementing all of the methods @@ -177,7 +177,7 @@ func (e *Environment) Config() *environment.Configuration { } // Sets the stop configuration for the environment. -func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) { +func (e *Environment) SetStopConfiguration(c remote.ProcessStopConfiguration) { e.mu.Lock() defer e.mu.Unlock() diff --git a/environment/docker/power.go b/environment/docker/power.go index 20f4803..9cc8b9e 100644 --- a/environment/docker/power.go +++ b/environment/docker/power.go @@ -9,6 +9,8 @@ import ( "github.com/docker/docker/client" "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/environment" + "github.com/pterodactyl/wings/remote" + "os" "strings" "syscall" @@ -133,7 +135,7 @@ func (e *Environment) Stop() error { // A native "stop" as the Type field value will just skip over all of this // logic and end up only executing the container stop command (which may or // may not work as expected). - if s.Type == "" || s.Type == api.ProcessStopSignal { + if s.Type == "" || s.Type == remote.ProcessStopSignal { if s.Type == "" { log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure") } @@ -160,7 +162,7 @@ func (e *Environment) Stop() error { // Only attempt to send the stop command to the instance if we are actually attached to // the instance. If we are not for some reason, just send the container stop event. - if e.IsAttached() && s.Type == api.ProcessStopCommand { + if e.IsAttached() && s.Type == remote.ProcessStopCommand { return e.SendCommand(s.Value) } diff --git a/installer/installer.go b/installer/installer.go index 2065fd0..495f27c 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -1,13 +1,14 @@ package installer import ( + "context" "encoding/json" "emperror.dev/errors" "github.com/asaskevich/govalidator" "github.com/buger/jsonparser" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/environment" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/server" ) @@ -15,10 +16,10 @@ type Installer struct { server *server.Server } -// Validates the received data to ensure that all of the required fields +// New validates the received data to ensure that all of the required fields // have been passed along in the request. This should be manually run before // calling Execute(). -func New(data []byte) (*Installer, error) { +func New(ctx context.Context, manager *server.Manager, data []byte) (*Installer, error) { if !govalidator.IsUUIDv4(getString(data, "uuid")) { return nil, NewValidationError("uuid provided was not in a valid format") } @@ -64,30 +65,27 @@ func New(data []byte) (*Installer, error) { cfg.Container.Image = getString(data, "container", "image") - c, err := api.New().GetServerConfiguration(cfg.Uuid) + c, err := manager.Client().GetServerConfiguration(ctx, cfg.Uuid) if err != nil { - if !api.IsRequestError(err) { + if !remote.IsRequestError(err) { return nil, err } - return nil, errors.New(err.Error()) } // Create a new server instance using the configuration we wrote to the disk // so that everything gets instantiated correctly on the struct. - s, err := server.FromConfiguration(c) + s, err := manager.InitServer(c) - return &Installer{ - server: s, - }, err + return &Installer{server: s}, err } -// Returns the UUID associated with this installer instance. +// Uuid returns the UUID associated with this installer instance. func (i *Installer) Uuid() string { return i.server.Id() } -// Return the server instance. +// Server returns the server instance. func (i *Installer) Server() *server.Server { return i.server } diff --git a/remote/backup.go b/remote/backup.go index 52a8abf..bfb36ba 100644 --- a/remote/backup.go +++ b/remote/backup.go @@ -4,27 +4,25 @@ import ( "context" "fmt" "strconv" - - "github.com/pterodactyl/wings/api" ) -func (c *client) GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (api.BackupRemoteUploadResponse, error) { +func (c *client) GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, error) { + var data BackupRemoteUploadResponse res, err := c.get(ctx, fmt.Sprintf("/backups/%s", backup), q{"size": strconv.FormatInt(size, 10)}) if err != nil { - return api.BackupRemoteUploadResponse{}, err + return data, err } defer res.Body.Close() if res.HasError() { - return api.BackupRemoteUploadResponse{}, res.Error() + return data, res.Error() } - r := api.BackupRemoteUploadResponse{} - err = res.BindJSON(&r) - return r, err + err = res.BindJSON(&data) + return data, err } -func (c *client) SetBackupStatus(ctx context.Context, backup string, data api.BackupRequest) error { +func (c *client) SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error { resp, err := c.post(ctx, fmt.Sprintf("/backups/%s", backup), data) if err != nil { return err @@ -32,3 +30,16 @@ func (c *client) SetBackupStatus(ctx context.Context, backup string, data api.Ba defer resp.Body.Close() return resp.Error() } + + +// SendRestorationStatus triggers a request to the Panel to notify it that a +// restoration has been completed and the server should be marked as being +// activated again. +func (c *client) SendRestorationStatus(ctx context.Context, backup string, successful bool) error { + resp, err := c.post(ctx, fmt.Sprintf("/backups/%s/restore", backup), d{"successful": successful}) + if err != nil { + return err + } + defer resp.Body.Close() + return resp.Error() +} \ No newline at end of file diff --git a/remote/client.go b/remote/client.go index 1e0a0d0..7d15c4e 100644 --- a/remote/client.go +++ b/remote/client.go @@ -5,17 +5,16 @@ import ( "net/http" "strings" "time" - - "github.com/pterodactyl/wings/api" ) type Client interface { - GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (api.BackupRemoteUploadResponse, error) + GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, error) GetInstallationScript(ctx context.Context, uuid string) (InstallationScript, error) GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error) GetServers(context context.Context, perPage int) ([]RawServerData, error) SetArchiveStatus(ctx context.Context, uuid string, successful bool) error - SetBackupStatus(ctx context.Context, backup string, data api.BackupRequest) error + SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error + SendRestorationStatus(ctx context.Context, backup string, successful bool) error SetInstallationStatus(ctx context.Context, uuid string, successful bool) error SetTransferStatus(ctx context.Context, uuid string, successful bool) error ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) diff --git a/remote/servers.go b/remote/servers.go index e579236..e490936 100644 --- a/remote/servers.go +++ b/remote/servers.go @@ -2,11 +2,12 @@ package remote import ( "context" - "encoding/json" "fmt" "strconv" "sync" + "emperror.dev/errors" + "github.com/apex/log" "github.com/pterodactyl/wings/api" "golang.org/x/sync/errgroup" ) @@ -17,37 +18,6 @@ const ( ProcessStopNativeStop = "stop" ) -// ServerConfigurationResponse holds the server configuration data returned from -// the Panel. When a server process is started, Wings communicates with the -// Panel to fetch the latest build information as well as get all of the details -// needed to parse the given Egg. -// -// This means we do not need to hit Wings each time part of the server is -// updated, and the Panel serves as the source of truth at all times. This also -// means if a configuration is accidentally wiped on Wings we can self-recover -// without too much hassle, so long as Wings is aware of what servers should -// exist on it. -type ServerConfigurationResponse struct { - Settings json.RawMessage `json:"settings"` - ProcessConfiguration *api.ProcessConfiguration `json:"process_configuration"` -} - -// InstallationScript defines installation script information for a server -// process. This is used when a server is installed for the first time, and when -// a server is marked for re-installation. -type InstallationScript struct { - ContainerImage string `json:"container_image"` - Entrypoint string `json:"entrypoint"` - Script string `json:"script"` -} - -// RawServerData is a raw response from the API for a server. -type RawServerData struct { - Uuid string `json:"uuid"` - Settings json.RawMessage `json:"settings"` - ProcessConfiguration json.RawMessage `json:"process_configuration"` -} - // GetServers returns all of the servers that are present on the Panel making // parallel API calls to the endpoint if more than one page of servers is // returned. @@ -144,6 +114,37 @@ func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful return resp.Error() } +// ValidateSftpCredentials makes a request to determine if the username and +// password combination provided is associated with a valid server on the instance +// using the Panel's authentication control mechanisms. This will get itself +// throttled if too many requests are made, allowing us to completely offload +// all of the authorization security logic to the Panel. +func (c *client) ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) { + var auth SftpAuthResponse + res, err := c.post(ctx, "/sftp/auth", request) + if err != nil { + return auth, err + } + + e := res.Error() + if e != nil { + if res.StatusCode >= 400 && res.StatusCode < 500 { + log.WithFields(log.Fields{ + "subsystem": "sftp", + "username": request.User, + "ip": request.IP, + }).Warn(e.Error()) + + return auth, &SftpInvalidCredentialsError{} + } + + return auth, errors.New(e.Error()) + } + + err = res.BindJSON(&auth) + return auth, err +} + // getServersPaged returns a subset of servers from the Panel API using the // pagination query parameters. func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, api.Pagination, error) { diff --git a/remote/sftp.go b/remote/sftp.go deleted file mode 100644 index 0284d67..0000000 --- a/remote/sftp.go +++ /dev/null @@ -1,53 +0,0 @@ -package remote - -import ( - "context" - "errors" - - "github.com/apex/log" -) - -type SftpAuthRequest struct { - User string `json:"username"` - Pass string `json:"password"` - IP string `json:"ip"` - SessionID []byte `json:"session_id"` - ClientVersion []byte `json:"client_version"` -} - -type SftpAuthResponse struct { - Server string `json:"server"` - Token string `json:"token"` - Permissions []string `json:"permissions"` -} - -// ValidateSftpCredentials makes a request to determine if the username and -// password combination provided is associated with a valid server on the instance -// using the Panel's authentication control mechanisms. This will get itself -// throttled if too many requests are made, allowing us to completely offload -// all of the authorization security logic to the Panel. -func (c *client) ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) { - var auth SftpAuthResponse - res, err := c.post(ctx, "/sftp/auth", request) - if err != nil { - return auth, err - } - - e := res.Error() - if e != nil { - if res.StatusCode >= 400 && res.StatusCode < 500 { - log.WithFields(log.Fields{ - "subsystem": "sftp", - "username": request.User, - "ip": request.IP, - }).Warn(e.Error()) - - return auth, &SftpInvalidCredentialsError{} - } - - return auth, errors.New(e.Error()) - } - - err = res.BindJSON(&auth) - return auth, err -} diff --git a/remote/types.go b/remote/types.go new file mode 100644 index 0000000..6649ae0 --- /dev/null +++ b/remote/types.go @@ -0,0 +1,133 @@ +package remote + +import ( + "encoding/json" + "regexp" + "strings" + + "github.com/apex/log" + "github.com/pterodactyl/wings/parser" +) + +// ServerConfigurationResponse holds the server configuration data returned from +// the Panel. When a server process is started, Wings communicates with the +// Panel to fetch the latest build information as well as get all of the details +// needed to parse the given Egg. +// +// This means we do not need to hit Wings each time part of the server is +// updated, and the Panel serves as the source of truth at all times. This also +// means if a configuration is accidentally wiped on Wings we can self-recover +// without too much hassle, so long as Wings is aware of what servers should +// exist on it. +type ServerConfigurationResponse struct { + Settings json.RawMessage `json:"settings"` + ProcessConfiguration *ProcessConfiguration `json:"process_configuration"` +} + +// InstallationScript defines installation script information for a server +// process. This is used when a server is installed for the first time, and when +// a server is marked for re-installation. +type InstallationScript struct { + ContainerImage string `json:"container_image"` + Entrypoint string `json:"entrypoint"` + Script string `json:"script"` +} + +// RawServerData is a raw response from the API for a server. +type RawServerData struct { + Uuid string `json:"uuid"` + Settings json.RawMessage `json:"settings"` + ProcessConfiguration json.RawMessage `json:"process_configuration"` +} + +// SftpAuthRequest defines the request details that are passed along to the Panel +// when determining if the credentials provided to Wings are valid. +type SftpAuthRequest struct { + User string `json:"username"` + Pass string `json:"password"` + IP string `json:"ip"` + SessionID []byte `json:"session_id"` + ClientVersion []byte `json:"client_version"` +} + +// SftpAuthResponse is returned by the Panel when a pair of SFTP credentials +// is successfully validated. This will include the specific server that was +// matched as well as the permissions that are assigned to the authenticated +// user for the SFTP subsystem. +type SftpAuthResponse struct { + Server string `json:"server"` + Token string `json:"token"` + Permissions []string `json:"permissions"` +} + +type OutputLineMatcher struct { + // The raw string to match against. This may or may not be prefixed with + // regex: which indicates we want to match against the regex expression. + raw string + reg *regexp.Regexp +} + +// Matches determines if a given string "s" matches the given line. +func (olm *OutputLineMatcher) Matches(s string) bool { + if olm.reg == nil { + return strings.Contains(s, olm.raw) + } + + return olm.reg.MatchString(s) +} + +// String returns the matcher's raw comparison string. +func (olm *OutputLineMatcher) String() string { + return olm.raw +} + +// UnmarshalJSON unmarshals the startup lines into individual structs for easier +// matching abilities. +func (olm *OutputLineMatcher) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &olm.raw); err != nil { + return err + } + + if strings.HasPrefix(olm.raw, "regex:") && len(olm.raw) > 6 { + r, err := regexp.Compile(strings.TrimPrefix(olm.raw, "regex:")) + if err != nil { + log.WithField("error", err).WithField("raw", olm.raw).Warn("failed to compile output line marked as being regex") + } + + olm.reg = r + } + + return nil +} + +// ProcessStopConfiguration defines what is used when stopping an instance. +type ProcessStopConfiguration struct { + Type string `json:"type"` + Value string `json:"value"` +} + +// ProcessConfiguration defines the process configuration for a given server +// instance. This sets what Wings is looking for to mark a server as done starting +// what to do when stopping, and what changes to make to the configuration file +// for a server. +type ProcessConfiguration struct { + Startup struct { + Done []*OutputLineMatcher `json:"done"` + UserInteraction []string `json:"user_interaction"` + StripAnsi bool `json:"strip_ansi"` + } `json:"startup"` + Stop ProcessStopConfiguration `json:"stop"` + ConfigurationFiles []parser.ConfigurationFile `json:"configs"` +} + +type BackupRemoteUploadResponse struct { + Parts []string `json:"parts"` + PartSize int64 `json:"part_size"` +} + +type BackupRequest struct { + Checksum string `json:"checksum"` + ChecksumType string `json:"checksum_type"` + Size int64 `json:"size"` + Successful bool `json:"successful"` +} diff --git a/router/middleware/middleware.go b/router/middleware/middleware.go index 745f28e..36a83c1 100644 --- a/router/middleware/middleware.go +++ b/router/middleware/middleware.go @@ -13,6 +13,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server/filesystem" ) @@ -168,6 +169,15 @@ func AttachServerManager(m *server.Manager) gin.HandlerFunc { } } +// AttachApiClient attaches the application API client which allows routes to +// access server resources from the Panel easily. +func AttachApiClient(client remote.Client) gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("api_client", client) + c.Next() + } +} + // CaptureAndAbort aborts the request and attaches the provided error to the gin // context so it can be reported properly. If the error is missing a stacktrace // at the time it is called the stack will be attached. @@ -327,6 +337,14 @@ func ExtractServer(c *gin.Context) *server.Server { return v.(*server.Server) } +// ExtractApiClient returns the API client defined for the routes. +func ExtractApiClient(c *gin.Context) remote.Client { + if v, ok := c.Get("api_client"); ok { + return v.(remote.Client) + } + panic("middleware/middlware: cannot extract api clinet: not present in context") +} + // ExtractManager returns the server manager instance set on the request context. func ExtractManager(c *gin.Context) *server.Manager { if v, ok := c.Get("manager"); ok { diff --git a/router/router.go b/router/router.go index 526858e..ca6a42c 100644 --- a/router/router.go +++ b/router/router.go @@ -3,18 +3,19 @@ package router import ( "github.com/apex/log" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/router/middleware" "github.com/pterodactyl/wings/server" ) // Configure configures the routing infrastructure for this daemon instance. -func Configure(m *server.Manager) *gin.Engine { +func Configure(m *server.Manager, client remote.Client) *gin.Engine { gin.SetMode("release") router := gin.New() router.Use(gin.Recovery()) router.Use(middleware.AttachRequestID(), middleware.CaptureErrors(), middleware.SetAccessControlHeaders()) - router.Use(middleware.AttachServerManager(m)) + router.Use(middleware.AttachServerManager(m), middleware.AttachApiClient(client)) // @todo log this into a different file so you can setup IP blocking for abusive requests and such. // This should still dump requests in debug mode since it does help with understanding the request // lifecycle and quickly seeing what was called leading to the logs. However, it isn't feasible to mix diff --git a/router/router_server_backup.go b/router/router_server_backup.go index 22c6b5f..b3e4ad8 100644 --- a/router/router_server_backup.go +++ b/router/router_server_backup.go @@ -17,6 +17,7 @@ import ( // provided backup adapter. func postServerBackup(c *gin.Context) { s := middleware.ExtractServer(c) + client := middleware.ExtractApiClient(c) logger := middleware.ExtractLogger(c) var data struct { Adapter backup.AdapterType `json:"adapter"` @@ -30,9 +31,9 @@ func postServerBackup(c *gin.Context) { var adapter backup.BackupInterface switch data.Adapter { case backup.LocalBackupAdapter: - adapter = backup.NewLocal(data.Uuid, data.Ignore) + adapter = backup.NewLocal(client, data.Uuid, data.Ignore) case backup.S3BackupAdapter: - adapter = backup.NewS3(data.Uuid, data.Ignore) + adapter = backup.NewS3(client, data.Uuid, data.Ignore) default: middleware.CaptureAndAbort(c, errors.New("router/backups: provided adapter is not valid: "+string(data.Adapter))) return @@ -65,6 +66,7 @@ func postServerBackup(c *gin.Context) { // TODO: stop the server if it is running; internally mark it as suspended func postServerRestoreBackup(c *gin.Context) { s := middleware.ExtractServer(c) + client := middleware.ExtractApiClient(c) logger := middleware.ExtractLogger(c) var data struct { @@ -94,7 +96,7 @@ func postServerRestoreBackup(c *gin.Context) { // Now that we've cleaned up the data directory if necessary, grab the backup file // and attempt to restore it into the server directory. if data.Adapter == backup.LocalBackupAdapter { - b, _, err := backup.LocateLocal(c.Param("backup")) + b, _, err := backup.LocateLocal(client, c.Param("backup")) if err != nil { middleware.CaptureAndAbort(c, err) return @@ -114,7 +116,7 @@ func postServerRestoreBackup(c *gin.Context) { // Since this is not a local backup we need to stream the archive and then // parse over the contents as we go in order to restore it to the server. - client := http.Client{} + httpClient := http.Client{} logger.Info("downloading backup from remote location...") // TODO: this will hang if there is an issue. We can't use c.Request.Context() (or really any) // since it will be canceled when the request is closed which happens quickly since we push @@ -127,7 +129,7 @@ func postServerRestoreBackup(c *gin.Context) { middleware.CaptureAndAbort(c, err) return } - res, err := client.Do(req) + res, err := httpClient.Do(req) if err != nil { middleware.CaptureAndAbort(c, err) return @@ -143,7 +145,7 @@ func postServerRestoreBackup(c *gin.Context) { go func(s *server.Server, uuid string, logger *log.Entry) { logger.Info("starting restoration process for server backup using S3 driver") - if err := s.RestoreBackup(backup.NewS3(uuid, ""), res.Body); err != nil { + if err := s.RestoreBackup(backup.NewS3(client, uuid, ""), res.Body); err != nil { logger.WithField("error", errors.WithStack(err)).Error("failed to restore remote S3 backup to server") } s.Events().Publish(server.DaemonMessageEvent, "Completed server restoration from S3 backup.") @@ -159,7 +161,7 @@ func postServerRestoreBackup(c *gin.Context) { // endpoint can make its own decisions as to how it wants to handle that // response. func deleteServerBackup(c *gin.Context) { - b, _, err := backup.LocateLocal(c.Param("backup")) + b, _, err := backup.LocateLocal(middleware.ExtractApiClient(c), c.Param("backup")) if err != nil { // Just return from the function at this point if the backup was not located. if errors.Is(err, os.ErrNotExist) { diff --git a/router/router_system.go b/router/router_system.go index 166e8b6..b9b0460 100644 --- a/router/router_system.go +++ b/router/router_system.go @@ -34,10 +34,11 @@ func getAllServers(c *gin.Context) { // Creates a new server on the wings daemon and begins the installation process // for it. func postCreateServer(c *gin.Context) { + manager := middleware.ExtractManager(c) buf := bytes.Buffer{} buf.ReadFrom(c.Request.Body) - install, err := installer.New(buf.Bytes()) + install, err := installer.New(c.Request.Context(), manager, buf.Bytes()) if err != nil { if installer.IsValidationError(err) { c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{ @@ -52,7 +53,6 @@ func postCreateServer(c *gin.Context) { // Plop that server instance onto the request so that it can be referenced in // requests from here-on out. - manager := middleware.ExtractManager(c) manager.Add(install.Server()) // Begin the installation process in the background to not block the request diff --git a/router/router_transfer.go b/router/router_transfer.go index 1a4d987..ec92dd5 100644 --- a/router/router_transfer.go +++ b/router/router_transfer.go @@ -2,6 +2,7 @@ package router import ( "bufio" + "context" "crypto/sha256" "encoding/hex" "encoding/json" @@ -22,9 +23,9 @@ import ( "github.com/juju/ratelimit" "github.com/mholt/archiver/v3" "github.com/mitchellh/colorstring" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/installer" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/router/middleware" "github.com/pterodactyl/wings/router/tokens" "github.com/pterodactyl/wings/server" @@ -109,10 +110,10 @@ func getServerArchive(c *gin.Context) { } func postServerArchive(c *gin.Context) { - s := ExtractServer(c) + s := middleware.ExtractServer(c) + manager := middleware.ExtractManager(c) go func(s *server.Server) { - r := api.New() l := log.WithField("server", s.Id()) // This function automatically adds the Source Node prefix and Timestamp to the log @@ -133,12 +134,11 @@ func postServerArchive(c *gin.Context) { // Mark the server as not being transferred so it can actually be used. s.SetTransferring(false) - s.Events().Publish(server.TransferStatusEvent, "failure") sendTransferLog("Attempting to notify panel of archive failure..") - if err := r.SendArchiveStatus(s.Id(), false); err != nil { - if !api.IsRequestError(err) { + if err := manager.Client().SetArchiveStatus(s.Context(), s.Id(), false); err != nil { + if !remote.IsRequestError(err) { sendTransferLog("Failed to notify panel of archive failure: " + err.Error()) l.WithField("error", err).Error("failed to notify panel of failed archive status") return @@ -174,8 +174,8 @@ func postServerArchive(c *gin.Context) { sendTransferLog("Successfully created archive, attempting to notify panel..") l.Info("successfully created server transfer archive, notifying panel..") - if err := r.SendArchiveStatus(s.Id(), true); err != nil { - if !api.IsRequestError(err) { + if err := manager.Client().SetArchiveStatus(s.Context(), s.Id(), true); err != nil { + if !remote.IsRequestError(err) { sendTransferLog("Failed to notify panel of archive success: " + err.Error()) l.WithField("error", err).Error("failed to notify panel of successful archive status") return @@ -275,10 +275,10 @@ func (str serverTransferRequest) verifyChecksum(matches string) (bool, string, e } // Sends a notification to the Panel letting it know what the status of this transfer is. -func (str serverTransferRequest) sendTransferStatus(successful bool) error { +func (str serverTransferRequest) sendTransferStatus(client remote.Client, successful bool) error { lg := str.log().WithField("transfer_successful", successful) lg.Info("notifying Panel of server transfer state") - if err := api.New().SendTransferStatus(str.ServerID, successful); err != nil { + if err := client.SetTransferStatus(context.Background(), str.ServerID, successful); err != nil { lg.WithField("error", err).Error("error notifying panel of transfer state") return err } @@ -294,6 +294,7 @@ func postTransfer(c *gin.Context) { return } + manager := middleware.ExtractManager(c) u, err := uuid.Parse(data.ServerID) if err != nil { WithError(c, err) @@ -310,9 +311,9 @@ func postTransfer(c *gin.Context) { // Create a new server installer. This will only configure the environment and not // run the installer scripts. - i, err := installer.New(data.Server) + i, err := installer.New(context.Background(), manager, data.Server) if err != nil { - _ = data.sendTransferStatus(false) + _ = data.sendTransferStatus(manager.Client(), false) data.log().WithField("error", err).Error("failed to validate received server data") return } @@ -324,7 +325,6 @@ func postTransfer(c *gin.Context) { i.Server().Events().Publish(server.TransferLogsEvent, output) } - manager := middleware.ExtractManager(c) // Mark the server as transferring to prevent problems later on during the process and // then push the server into the global server collection for this instance. i.Server().SetTransferring(true) @@ -332,7 +332,7 @@ func postTransfer(c *gin.Context) { defer func(s *server.Server) { // In the event that this transfer call fails, remove the server from the global // server tracking so that we don't have a dangling instance. - if err := data.sendTransferStatus(!hasError); hasError || err != nil { + if err := data.sendTransferStatus(manager.Client(), !hasError); hasError || err != nil { sendTransferLog("Server transfer failed, check Wings logs for additional information.") s.Events().Publish(server.TransferStatusEvent, "failure") manager.Remove(func(match *server.Server) bool { diff --git a/server/backup.go b/server/backup.go index f247c4b..7d65882 100644 --- a/server/backup.go +++ b/server/backup.go @@ -8,16 +8,16 @@ import ( "emperror.dev/errors" "github.com/apex/log" "github.com/docker/docker/client" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/environment" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/server/backup" ) // Notifies the panel of a backup's state and returns an error if one is encountered // while performing this action. func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error { - if err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful)); err != nil { - if !api.IsRequestError(err) { + if err := s.client.SetBackupStatus(s.Context(), uuid, ad.ToRequest(successful)); err != nil { + if !remote.IsRequestError(err) { s.Log().WithFields(log.Fields{ "backup": uuid, "error": err, @@ -131,7 +131,7 @@ func (s *Server) RestoreBackup(b backup.BackupInterface, reader io.ReadCloser) ( // Send an API call to the Panel as soon as this function is done running so that // the Panel is informed of the restoration status of this backup. defer func() { - if rerr := api.New().SendRestorationStatus(b.Identifier(), err == nil); rerr != nil { + if rerr := s.client.SendRestorationStatus(s.Context(), b.Identifier(), err == nil); rerr != nil { s.Log().WithField("error", rerr).WithField("backup", b.Identifier()).Error("failed to notify Panel of backup restoration status") } }() diff --git a/server/backup/backup.go b/server/backup/backup.go index 184cf96..5e32826 100644 --- a/server/backup/backup.go +++ b/server/backup/backup.go @@ -9,8 +9,8 @@ import ( "sync" "github.com/apex/log" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/remote" ) type AdapterType string @@ -31,8 +31,8 @@ type ArchiveDetails struct { } // ToRequest returns a request object. -func (ad *ArchiveDetails) ToRequest(successful bool) api.BackupRequest { - return api.BackupRequest{ +func (ad *ArchiveDetails) ToRequest(successful bool) remote.BackupRequest { + return remote.BackupRequest{ Checksum: ad.Checksum, ChecksumType: ad.ChecksumType, Size: ad.Size, @@ -49,12 +49,15 @@ type Backup struct { // compatible with a standard .gitignore structure. Ignore string `json:"ignore"` + client remote.Client adapter AdapterType logContext map[string]interface{} } // noinspection GoNameStartsWithPackageName type BackupInterface interface { + // SetClient sets the API request client on the backup interface. + SetClient(c remote.Client) // Identifier returns the UUID of this backup as tracked by the panel // instance. Identifier() string @@ -84,6 +87,10 @@ type BackupInterface interface { Restore(reader io.Reader, callback RestoreCallback) error } +func (b *Backup) SetClient(c remote.Client) { + b.client = c +} + func (b *Backup) Identifier() string { return b.Uuid } diff --git a/server/backup/backup_local.go b/server/backup/backup_local.go index b7f132c..55c514f 100644 --- a/server/backup/backup_local.go +++ b/server/backup/backup_local.go @@ -6,6 +6,7 @@ import ( "os" "github.com/mholt/archiver/v3" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/system" ) @@ -15,9 +16,10 @@ type LocalBackup struct { var _ BackupInterface = (*LocalBackup)(nil) -func NewLocal(uuid string, ignore string) *LocalBackup { +func NewLocal(client remote.Client, uuid string, ignore string) *LocalBackup { return &LocalBackup{ Backup{ + client: client, Uuid: uuid, Ignore: ignore, adapter: LocalBackupAdapter, @@ -27,14 +29,8 @@ func NewLocal(uuid string, ignore string) *LocalBackup { // LocateLocal finds 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, - Ignore: "", - }, - } - +func LocateLocal(client remote.Client, uuid string) (*LocalBackup, os.FileInfo, error) { + b := NewLocal(client, uuid, "") st, err := os.Stat(b.Path()) if err != nil { return nil, nil, err diff --git a/server/backup/backup_s3.go b/server/backup/backup_s3.go index 65184c9..da7dc38 100644 --- a/server/backup/backup_s3.go +++ b/server/backup/backup_s3.go @@ -3,6 +3,7 @@ package backup import ( "archive/tar" "compress/gzip" + "context" "fmt" "io" "net/http" @@ -10,8 +11,8 @@ import ( "strconv" "github.com/juju/ratelimit" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/remote" ) type S3Backup struct { @@ -20,9 +21,10 @@ type S3Backup struct { var _ BackupInterface = (*S3Backup)(nil) -func NewS3(uuid string, ignore string) *S3Backup { +func NewS3(client remote.Client, uuid string, ignore string) *S3Backup { return &S3Backup{ Backup{ + client: client, Uuid: uuid, Ignore: ignore, adapter: S3BackupAdapter, @@ -91,7 +93,7 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error { s.log().WithField("size", size).Debug("got size of backup") s.log().Debug("attempting to get S3 upload urls from Panel...") - urls, err := api.New().GetBackupRemoteUploadURLs(s.Backup.Uuid, size) + urls, err := s.client.GetBackupRemoteUploadURLs(context.Background(), s.Backup.Uuid, size) if err != nil { return err } diff --git a/server/install.go b/server/install.go index fe0fcea..dd00d3c 100644 --- a/server/install.go +++ b/server/install.go @@ -20,6 +20,7 @@ import ( "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/environment" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/system" ) @@ -88,9 +89,9 @@ func (s *Server) Reinstall() error { // Internal installation function used to simplify reporting back to the Panel. func (s *Server) internalInstall() error { - script, err := api.New().GetInstallationScript(s.Id()) + script, err := s.client.GetInstallationScript(s.Context(), s.Id()) if err != nil { - if !api.IsRequestError(err) { + if !remote.IsRequestError(err) { return err } @@ -113,7 +114,7 @@ func (s *Server) internalInstall() error { type InstallationProcess struct { Server *Server - Script *api.InstallationScript + Script *remote.InstallationScript client *client.Client context context.Context @@ -121,7 +122,7 @@ type InstallationProcess struct { // Generates a new installation process struct that will be used to create containers, // and otherwise perform installation commands for a server. -func NewInstallationProcess(s *Server, script *api.InstallationScript) (*InstallationProcess, error) { +func NewInstallationProcess(s *Server, script *remote.InstallationScript) (*InstallationProcess, error) { proc := &InstallationProcess{ Script: script, Server: s, @@ -532,9 +533,9 @@ func (ip *InstallationProcess) StreamOutput(ctx context.Context, id string) erro // value of "true" means everything was successful, "false" means something went // wrong and the server must be deleted and re-created. func (s *Server) SyncInstallState(successful bool) error { - err := api.New().SendInstallationStatus(s.Id(), successful) + err := s.client.SetInstallationStatus(s.Context(), s.Id(), successful) if err != nil { - if !api.IsRequestError(err) { + if !remote.IsRequestError(err) { return err } diff --git a/server/listeners.go b/server/listeners.go index 77f6cf6..abeb2c1 100644 --- a/server/listeners.go +++ b/server/listeners.go @@ -11,6 +11,7 @@ import ( "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/events" + "github.com/pterodactyl/wings/remote" ) var dockerEvents = []string{ @@ -186,7 +187,7 @@ func (s *Server) onConsoleOutput(data string) { if s.IsRunning() { stop := processConfiguration.Stop - if stop.Type == api.ProcessStopCommand && data == stop.Value { + if stop.Type == remote.ProcessStopCommand && data == stop.Value { s.Environment.SetState(environment.ProcessOfflineState) } } diff --git a/server/server.go b/server/server.go index 020eb5e..9efe1e5 100644 --- a/server/server.go +++ b/server/server.go @@ -10,7 +10,6 @@ import ( "emperror.dev/errors" "github.com/apex/log" "github.com/creasty/defaults" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment/docker" @@ -54,7 +53,7 @@ type Server struct { // Defines the process configuration for the server instance. This is dynamically // fetched from the Pterodactyl Server instance each time the server process is // started, and then cached here. - procConfig *api.ProcessConfiguration + procConfig *remote.ProcessConfiguration // Tracks the installation process for this server and prevents a server from running // two installer processes at the same time. This also allows us to cancel a running @@ -152,11 +151,11 @@ func (s *Server) Log() *log.Entry { func (s *Server) Sync() error { cfg, err := s.client.GetServerConfiguration(s.Context(), s.Id()) if err != nil { - if !api.IsRequestError(err) { + if !remote.IsRequestError(err) { return err } - if err.(*api.RequestError).Status == "404" { + if err.(*remote.RequestError).Status == "404" { return &serverDoesNotExist{} } @@ -220,7 +219,7 @@ func (s *Server) IsSuspended() bool { return s.Config().Suspended } -func (s *Server) ProcessConfiguration() *api.ProcessConfiguration { +func (s *Server) ProcessConfiguration() *remote.ProcessConfiguration { s.RLock() defer s.RUnlock()