Remove all of the remaining API logic and port it all to the remote.Client type
This commit is contained in:
		
							parent
							
								
									62cbe5e135
								
							
						
					
					
						commit
						98c68142cd
					
				
							
								
								
									
										197
									
								
								api/api.go
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								api/api.go
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								api/error.go
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								api/error.go
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								remote/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								remote/types.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user