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.
 | 
						// and external clients.
 | 
				
			||||||
	s := &http.Server{
 | 
						s := &http.Server{
 | 
				
			||||||
		Addr:      api.Host + ":" + strconv.Itoa(api.Port),
 | 
							Addr:      api.Host + ":" + strconv.Itoa(api.Port),
 | 
				
			||||||
		Handler:   router.Configure(manager),
 | 
							Handler:   router.Configure(manager, pclient),
 | 
				
			||||||
		TLSConfig: config.DefaultTLSConfig,
 | 
							TLSConfig: config.DefaultTLSConfig,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,15 +10,15 @@ import (
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types"
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
	"github.com/pterodactyl/wings/events"
 | 
						"github.com/pterodactyl/wings/events"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/system"
 | 
						"github.com/pterodactyl/wings/system"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Metadata struct {
 | 
					type Metadata struct {
 | 
				
			||||||
	Image string
 | 
						Image string
 | 
				
			||||||
	Stop  api.ProcessStopConfiguration
 | 
						Stop  remote.ProcessStopConfiguration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Ensure that the Docker environment is always implementing all of the methods
 | 
					// 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.
 | 
					// Sets the stop configuration for the environment.
 | 
				
			||||||
func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
 | 
					func (e *Environment) SetStopConfiguration(c remote.ProcessStopConfiguration) {
 | 
				
			||||||
	e.mu.Lock()
 | 
						e.mu.Lock()
 | 
				
			||||||
	defer e.mu.Unlock()
 | 
						defer e.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,8 @@ import (
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
						"github.com/pterodactyl/wings/api"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"syscall"
 | 
						"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
 | 
						// 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
 | 
						// logic and end up only executing the container stop command (which may or
 | 
				
			||||||
	// may not work as expected).
 | 
						// may not work as expected).
 | 
				
			||||||
	if s.Type == "" || s.Type == api.ProcessStopSignal {
 | 
						if s.Type == "" || s.Type == remote.ProcessStopSignal {
 | 
				
			||||||
		if s.Type == "" {
 | 
							if s.Type == "" {
 | 
				
			||||||
			log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure")
 | 
								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
 | 
						// 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.
 | 
						// 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)
 | 
							return e.SendCommand(s.Value)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,14 @@
 | 
				
			||||||
package installer
 | 
					package installer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/asaskevich/govalidator"
 | 
						"github.com/asaskevich/govalidator"
 | 
				
			||||||
	"github.com/buger/jsonparser"
 | 
						"github.com/buger/jsonparser"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server"
 | 
						"github.com/pterodactyl/wings/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,10 +16,10 @@ type Installer struct {
 | 
				
			||||||
	server *server.Server
 | 
						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
 | 
					// have been passed along in the request. This should be manually run before
 | 
				
			||||||
// calling Execute().
 | 
					// 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")) {
 | 
						if !govalidator.IsUUIDv4(getString(data, "uuid")) {
 | 
				
			||||||
		return nil, NewValidationError("uuid provided was not in a valid format")
 | 
							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")
 | 
						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 err != nil {
 | 
				
			||||||
		if !api.IsRequestError(err) {
 | 
							if !remote.IsRequestError(err) {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil, errors.New(err.Error())
 | 
							return nil, errors.New(err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create a new server instance using the configuration we wrote to the disk
 | 
						// Create a new server instance using the configuration we wrote to the disk
 | 
				
			||||||
	// so that everything gets instantiated correctly on the struct.
 | 
						// so that everything gets instantiated correctly on the struct.
 | 
				
			||||||
	s, err := server.FromConfiguration(c)
 | 
						s, err := manager.InitServer(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Installer{
 | 
						return &Installer{server: s}, err
 | 
				
			||||||
		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 {
 | 
					func (i *Installer) Uuid() string {
 | 
				
			||||||
	return i.server.Id()
 | 
						return i.server.Id()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Return the server instance.
 | 
					// Server returns the server instance.
 | 
				
			||||||
func (i *Installer) Server() *server.Server {
 | 
					func (i *Installer) Server() *server.Server {
 | 
				
			||||||
	return i.server
 | 
						return i.server
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,27 +4,25 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strconv"
 | 
						"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)})
 | 
						res, err := c.get(ctx, fmt.Sprintf("/backups/%s", backup), q{"size": strconv.FormatInt(size, 10)})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return api.BackupRemoteUploadResponse{}, err
 | 
							return data, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer res.Body.Close()
 | 
						defer res.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if res.HasError() {
 | 
						if res.HasError() {
 | 
				
			||||||
		return api.BackupRemoteUploadResponse{}, res.Error()
 | 
							return data, res.Error()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r := api.BackupRemoteUploadResponse{}
 | 
						err = res.BindJSON(&data)
 | 
				
			||||||
	err = res.BindJSON(&r)
 | 
						return data, err
 | 
				
			||||||
	return r, 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)
 | 
						resp, err := c.post(ctx, fmt.Sprintf("/backups/%s", backup), data)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
| 
						 | 
					@ -32,3 +30,16 @@ func (c *client) SetBackupStatus(ctx context.Context, backup string, data api.Ba
 | 
				
			||||||
	defer resp.Body.Close()
 | 
						defer resp.Body.Close()
 | 
				
			||||||
	return resp.Error()
 | 
						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"
 | 
						"net/http"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Client interface {
 | 
					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)
 | 
						GetInstallationScript(ctx context.Context, uuid string) (InstallationScript, error)
 | 
				
			||||||
	GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error)
 | 
						GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error)
 | 
				
			||||||
	GetServers(context context.Context, perPage int) ([]RawServerData, error)
 | 
						GetServers(context context.Context, perPage int) ([]RawServerData, error)
 | 
				
			||||||
	SetArchiveStatus(ctx context.Context, uuid string, successful bool) 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
 | 
						SetInstallationStatus(ctx context.Context, uuid string, successful bool) error
 | 
				
			||||||
	SetTransferStatus(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)
 | 
						ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,12 @@ package remote
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"emperror.dev/errors"
 | 
				
			||||||
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
						"github.com/pterodactyl/wings/api"
 | 
				
			||||||
	"golang.org/x/sync/errgroup"
 | 
						"golang.org/x/sync/errgroup"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -17,37 +18,6 @@ const (
 | 
				
			||||||
	ProcessStopNativeStop = "stop"
 | 
						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
 | 
					// 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
 | 
					// parallel API calls to the endpoint if more than one page of servers is
 | 
				
			||||||
// returned.
 | 
					// returned.
 | 
				
			||||||
| 
						 | 
					@ -144,6 +114,37 @@ func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful
 | 
				
			||||||
	return resp.Error()
 | 
						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
 | 
					// getServersPaged returns a subset of servers from the Panel API using the
 | 
				
			||||||
// pagination query parameters.
 | 
					// pagination query parameters.
 | 
				
			||||||
func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, api.Pagination, error) {
 | 
					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/gin-gonic/gin"
 | 
				
			||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server"
 | 
						"github.com/pterodactyl/wings/server"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server/filesystem"
 | 
						"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
 | 
					// 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
 | 
					// 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.
 | 
					// 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)
 | 
						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.
 | 
					// ExtractManager returns the server manager instance set on the request context.
 | 
				
			||||||
func ExtractManager(c *gin.Context) *server.Manager {
 | 
					func ExtractManager(c *gin.Context) *server.Manager {
 | 
				
			||||||
	if v, ok := c.Get("manager"); ok {
 | 
						if v, ok := c.Get("manager"); ok {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,18 +3,19 @@ package router
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/router/middleware"
 | 
						"github.com/pterodactyl/wings/router/middleware"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server"
 | 
						"github.com/pterodactyl/wings/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure configures the routing infrastructure for this daemon instance.
 | 
					// 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")
 | 
						gin.SetMode("release")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	router := gin.New()
 | 
						router := gin.New()
 | 
				
			||||||
	router.Use(gin.Recovery())
 | 
						router.Use(gin.Recovery())
 | 
				
			||||||
	router.Use(middleware.AttachRequestID(), middleware.CaptureErrors(), middleware.SetAccessControlHeaders())
 | 
						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.
 | 
						// @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
 | 
						// 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
 | 
						// 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.
 | 
					// provided backup adapter.
 | 
				
			||||||
func postServerBackup(c *gin.Context) {
 | 
					func postServerBackup(c *gin.Context) {
 | 
				
			||||||
	s := middleware.ExtractServer(c)
 | 
						s := middleware.ExtractServer(c)
 | 
				
			||||||
 | 
						client := middleware.ExtractApiClient(c)
 | 
				
			||||||
	logger := middleware.ExtractLogger(c)
 | 
						logger := middleware.ExtractLogger(c)
 | 
				
			||||||
	var data struct {
 | 
						var data struct {
 | 
				
			||||||
		Adapter backup.AdapterType `json:"adapter"`
 | 
							Adapter backup.AdapterType `json:"adapter"`
 | 
				
			||||||
| 
						 | 
					@ -30,9 +31,9 @@ func postServerBackup(c *gin.Context) {
 | 
				
			||||||
	var adapter backup.BackupInterface
 | 
						var adapter backup.BackupInterface
 | 
				
			||||||
	switch data.Adapter {
 | 
						switch data.Adapter {
 | 
				
			||||||
	case backup.LocalBackupAdapter:
 | 
						case backup.LocalBackupAdapter:
 | 
				
			||||||
		adapter = backup.NewLocal(data.Uuid, data.Ignore)
 | 
							adapter = backup.NewLocal(client, data.Uuid, data.Ignore)
 | 
				
			||||||
	case backup.S3BackupAdapter:
 | 
						case backup.S3BackupAdapter:
 | 
				
			||||||
		adapter = backup.NewS3(data.Uuid, data.Ignore)
 | 
							adapter = backup.NewS3(client, data.Uuid, data.Ignore)
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		middleware.CaptureAndAbort(c, errors.New("router/backups: provided adapter is not valid: "+string(data.Adapter)))
 | 
							middleware.CaptureAndAbort(c, errors.New("router/backups: provided adapter is not valid: "+string(data.Adapter)))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					@ -65,6 +66,7 @@ func postServerBackup(c *gin.Context) {
 | 
				
			||||||
// TODO: stop the server if it is running; internally mark it as suspended
 | 
					// TODO: stop the server if it is running; internally mark it as suspended
 | 
				
			||||||
func postServerRestoreBackup(c *gin.Context) {
 | 
					func postServerRestoreBackup(c *gin.Context) {
 | 
				
			||||||
	s := middleware.ExtractServer(c)
 | 
						s := middleware.ExtractServer(c)
 | 
				
			||||||
 | 
						client := middleware.ExtractApiClient(c)
 | 
				
			||||||
	logger := middleware.ExtractLogger(c)
 | 
						logger := middleware.ExtractLogger(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var data struct {
 | 
						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
 | 
						// Now that we've cleaned up the data directory if necessary, grab the backup file
 | 
				
			||||||
	// and attempt to restore it into the server directory.
 | 
						// and attempt to restore it into the server directory.
 | 
				
			||||||
	if data.Adapter == backup.LocalBackupAdapter {
 | 
						if data.Adapter == backup.LocalBackupAdapter {
 | 
				
			||||||
		b, _, err := backup.LocateLocal(c.Param("backup"))
 | 
							b, _, err := backup.LocateLocal(client, c.Param("backup"))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			middleware.CaptureAndAbort(c, err)
 | 
								middleware.CaptureAndAbort(c, err)
 | 
				
			||||||
			return
 | 
								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
 | 
						// 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.
 | 
						// 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...")
 | 
						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)
 | 
						// 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
 | 
						//  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)
 | 
							middleware.CaptureAndAbort(c, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	res, err := client.Do(req)
 | 
						res, err := httpClient.Do(req)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		middleware.CaptureAndAbort(c, err)
 | 
							middleware.CaptureAndAbort(c, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					@ -143,7 +145,7 @@ func postServerRestoreBackup(c *gin.Context) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go func(s *server.Server, uuid string, logger *log.Entry) {
 | 
						go func(s *server.Server, uuid string, logger *log.Entry) {
 | 
				
			||||||
		logger.Info("starting restoration process for server backup using S3 driver")
 | 
							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")
 | 
								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.")
 | 
							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
 | 
					// endpoint can make its own decisions as to how it wants to handle that
 | 
				
			||||||
// response.
 | 
					// response.
 | 
				
			||||||
func deleteServerBackup(c *gin.Context) {
 | 
					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 {
 | 
						if err != nil {
 | 
				
			||||||
		// Just return from the function at this point if the backup was not located.
 | 
							// Just return from the function at this point if the backup was not located.
 | 
				
			||||||
		if errors.Is(err, os.ErrNotExist) {
 | 
							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
 | 
					// Creates a new server on the wings daemon and begins the installation process
 | 
				
			||||||
// for it.
 | 
					// for it.
 | 
				
			||||||
func postCreateServer(c *gin.Context) {
 | 
					func postCreateServer(c *gin.Context) {
 | 
				
			||||||
 | 
						manager := middleware.ExtractManager(c)
 | 
				
			||||||
	buf := bytes.Buffer{}
 | 
						buf := bytes.Buffer{}
 | 
				
			||||||
	buf.ReadFrom(c.Request.Body)
 | 
						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 err != nil {
 | 
				
			||||||
		if installer.IsValidationError(err) {
 | 
							if installer.IsValidationError(err) {
 | 
				
			||||||
			c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
 | 
								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
 | 
						// Plop that server instance onto the request so that it can be referenced in
 | 
				
			||||||
	// requests from here-on out.
 | 
						// requests from here-on out.
 | 
				
			||||||
	manager := middleware.ExtractManager(c)
 | 
					 | 
				
			||||||
	manager.Add(install.Server())
 | 
						manager.Add(install.Server())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Begin the installation process in the background to not block the request
 | 
						// Begin the installation process in the background to not block the request
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"crypto/sha256"
 | 
						"crypto/sha256"
 | 
				
			||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
| 
						 | 
					@ -22,9 +23,9 @@ import (
 | 
				
			||||||
	"github.com/juju/ratelimit"
 | 
						"github.com/juju/ratelimit"
 | 
				
			||||||
	"github.com/mholt/archiver/v3"
 | 
						"github.com/mholt/archiver/v3"
 | 
				
			||||||
	"github.com/mitchellh/colorstring"
 | 
						"github.com/mitchellh/colorstring"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
	"github.com/pterodactyl/wings/installer"
 | 
						"github.com/pterodactyl/wings/installer"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/router/middleware"
 | 
						"github.com/pterodactyl/wings/router/middleware"
 | 
				
			||||||
	"github.com/pterodactyl/wings/router/tokens"
 | 
						"github.com/pterodactyl/wings/router/tokens"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server"
 | 
						"github.com/pterodactyl/wings/server"
 | 
				
			||||||
| 
						 | 
					@ -109,10 +110,10 @@ func getServerArchive(c *gin.Context) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func postServerArchive(c *gin.Context) {
 | 
					func postServerArchive(c *gin.Context) {
 | 
				
			||||||
	s := ExtractServer(c)
 | 
						s := middleware.ExtractServer(c)
 | 
				
			||||||
 | 
						manager := middleware.ExtractManager(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go func(s *server.Server) {
 | 
						go func(s *server.Server) {
 | 
				
			||||||
		r := api.New()
 | 
					 | 
				
			||||||
		l := log.WithField("server", s.Id())
 | 
							l := log.WithField("server", s.Id())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// This function automatically adds the Source Node prefix and Timestamp to the log
 | 
							// 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.
 | 
								// Mark the server as not being transferred so it can actually be used.
 | 
				
			||||||
			s.SetTransferring(false)
 | 
								s.SetTransferring(false)
 | 
				
			||||||
 | 
					 | 
				
			||||||
			s.Events().Publish(server.TransferStatusEvent, "failure")
 | 
								s.Events().Publish(server.TransferStatusEvent, "failure")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			sendTransferLog("Attempting to notify panel of archive failure..")
 | 
								sendTransferLog("Attempting to notify panel of archive failure..")
 | 
				
			||||||
			if err := r.SendArchiveStatus(s.Id(), false); err != nil {
 | 
								if err := manager.Client().SetArchiveStatus(s.Context(), s.Id(), false); err != nil {
 | 
				
			||||||
				if !api.IsRequestError(err) {
 | 
									if !remote.IsRequestError(err) {
 | 
				
			||||||
					sendTransferLog("Failed to notify panel of archive failure: " + err.Error())
 | 
										sendTransferLog("Failed to notify panel of archive failure: " + err.Error())
 | 
				
			||||||
					l.WithField("error", err).Error("failed to notify panel of failed archive status")
 | 
										l.WithField("error", err).Error("failed to notify panel of failed archive status")
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
| 
						 | 
					@ -174,8 +174,8 @@ func postServerArchive(c *gin.Context) {
 | 
				
			||||||
		sendTransferLog("Successfully created archive, attempting to notify panel..")
 | 
							sendTransferLog("Successfully created archive, attempting to notify panel..")
 | 
				
			||||||
		l.Info("successfully created server transfer archive, notifying panel..")
 | 
							l.Info("successfully created server transfer archive, notifying panel..")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := r.SendArchiveStatus(s.Id(), true); err != nil {
 | 
							if err := manager.Client().SetArchiveStatus(s.Context(), s.Id(), true); err != nil {
 | 
				
			||||||
			if !api.IsRequestError(err) {
 | 
								if !remote.IsRequestError(err) {
 | 
				
			||||||
				sendTransferLog("Failed to notify panel of archive success: " + err.Error())
 | 
									sendTransferLog("Failed to notify panel of archive success: " + err.Error())
 | 
				
			||||||
				l.WithField("error", err).Error("failed to notify panel of successful archive status")
 | 
									l.WithField("error", err).Error("failed to notify panel of successful archive status")
 | 
				
			||||||
				return
 | 
									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.
 | 
					// 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 := str.log().WithField("transfer_successful", successful)
 | 
				
			||||||
	lg.Info("notifying Panel of server transfer state")
 | 
						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")
 | 
							lg.WithField("error", err).Error("error notifying panel of transfer state")
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -294,6 +294,7 @@ func postTransfer(c *gin.Context) {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						manager := middleware.ExtractManager(c)
 | 
				
			||||||
	u, err := uuid.Parse(data.ServerID)
 | 
						u, err := uuid.Parse(data.ServerID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		WithError(c, err)
 | 
							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
 | 
							// Create a new server installer. This will only configure the environment and not
 | 
				
			||||||
		// run the installer scripts.
 | 
							// run the installer scripts.
 | 
				
			||||||
		i, err := installer.New(data.Server)
 | 
							i, err := installer.New(context.Background(), manager, data.Server)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			_ = data.sendTransferStatus(false)
 | 
								_ = data.sendTransferStatus(manager.Client(), false)
 | 
				
			||||||
			data.log().WithField("error", err).Error("failed to validate received server data")
 | 
								data.log().WithField("error", err).Error("failed to validate received server data")
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -324,7 +325,6 @@ func postTransfer(c *gin.Context) {
 | 
				
			||||||
			i.Server().Events().Publish(server.TransferLogsEvent, output)
 | 
								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
 | 
							// 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.
 | 
							// then push the server into the global server collection for this instance.
 | 
				
			||||||
		i.Server().SetTransferring(true)
 | 
							i.Server().SetTransferring(true)
 | 
				
			||||||
| 
						 | 
					@ -332,7 +332,7 @@ func postTransfer(c *gin.Context) {
 | 
				
			||||||
		defer func(s *server.Server) {
 | 
							defer func(s *server.Server) {
 | 
				
			||||||
			// In the event that this transfer call fails, remove the server from the global
 | 
								// 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.
 | 
								// 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.")
 | 
									sendTransferLog("Server transfer failed, check Wings logs for additional information.")
 | 
				
			||||||
				s.Events().Publish(server.TransferStatusEvent, "failure")
 | 
									s.Events().Publish(server.TransferStatusEvent, "failure")
 | 
				
			||||||
				manager.Remove(func(match *server.Server) bool {
 | 
									manager.Remove(func(match *server.Server) bool {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,16 +8,16 @@ import (
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server/backup"
 | 
						"github.com/pterodactyl/wings/server/backup"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Notifies the panel of a backup's state and returns an error if one is encountered
 | 
					// Notifies the panel of a backup's state and returns an error if one is encountered
 | 
				
			||||||
// while performing this action.
 | 
					// while performing this action.
 | 
				
			||||||
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
 | 
					func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
 | 
				
			||||||
	if err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful)); err != nil {
 | 
						if err := s.client.SetBackupStatus(s.Context(), uuid, ad.ToRequest(successful)); err != nil {
 | 
				
			||||||
		if !api.IsRequestError(err) {
 | 
							if !remote.IsRequestError(err) {
 | 
				
			||||||
			s.Log().WithFields(log.Fields{
 | 
								s.Log().WithFields(log.Fields{
 | 
				
			||||||
				"backup": uuid,
 | 
									"backup": uuid,
 | 
				
			||||||
				"error":  err,
 | 
									"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
 | 
						// 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.
 | 
						// the Panel is informed of the restoration status of this backup.
 | 
				
			||||||
	defer func() {
 | 
						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")
 | 
								s.Log().WithField("error", rerr).WithField("backup", b.Identifier()).Error("failed to notify Panel of backup restoration status")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,8 @@ import (
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AdapterType string
 | 
					type AdapterType string
 | 
				
			||||||
| 
						 | 
					@ -31,8 +31,8 @@ type ArchiveDetails struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ToRequest returns a request object.
 | 
					// ToRequest returns a request object.
 | 
				
			||||||
func (ad *ArchiveDetails) ToRequest(successful bool) api.BackupRequest {
 | 
					func (ad *ArchiveDetails) ToRequest(successful bool) remote.BackupRequest {
 | 
				
			||||||
	return api.BackupRequest{
 | 
						return remote.BackupRequest{
 | 
				
			||||||
		Checksum:     ad.Checksum,
 | 
							Checksum:     ad.Checksum,
 | 
				
			||||||
		ChecksumType: ad.ChecksumType,
 | 
							ChecksumType: ad.ChecksumType,
 | 
				
			||||||
		Size:         ad.Size,
 | 
							Size:         ad.Size,
 | 
				
			||||||
| 
						 | 
					@ -49,12 +49,15 @@ type Backup struct {
 | 
				
			||||||
	// compatible with a standard .gitignore structure.
 | 
						// compatible with a standard .gitignore structure.
 | 
				
			||||||
	Ignore string `json:"ignore"`
 | 
						Ignore string `json:"ignore"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client     remote.Client
 | 
				
			||||||
	adapter    AdapterType
 | 
						adapter    AdapterType
 | 
				
			||||||
	logContext map[string]interface{}
 | 
						logContext map[string]interface{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// noinspection GoNameStartsWithPackageName
 | 
					// noinspection GoNameStartsWithPackageName
 | 
				
			||||||
type BackupInterface interface {
 | 
					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
 | 
						// Identifier returns the UUID of this backup as tracked by the panel
 | 
				
			||||||
	// instance.
 | 
						// instance.
 | 
				
			||||||
	Identifier() string
 | 
						Identifier() string
 | 
				
			||||||
| 
						 | 
					@ -84,6 +87,10 @@ type BackupInterface interface {
 | 
				
			||||||
	Restore(reader io.Reader, callback RestoreCallback) error
 | 
						Restore(reader io.Reader, callback RestoreCallback) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *Backup) SetClient(c remote.Client) {
 | 
				
			||||||
 | 
						b.client = c
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (b *Backup) Identifier() string {
 | 
					func (b *Backup) Identifier() string {
 | 
				
			||||||
	return b.Uuid
 | 
						return b.Uuid
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import (
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mholt/archiver/v3"
 | 
						"github.com/mholt/archiver/v3"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/system"
 | 
						"github.com/pterodactyl/wings/system"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,9 +16,10 @@ type LocalBackup struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ BackupInterface = (*LocalBackup)(nil)
 | 
					var _ BackupInterface = (*LocalBackup)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewLocal(uuid string, ignore string) *LocalBackup {
 | 
					func NewLocal(client remote.Client, uuid string, ignore string) *LocalBackup {
 | 
				
			||||||
	return &LocalBackup{
 | 
						return &LocalBackup{
 | 
				
			||||||
		Backup{
 | 
							Backup{
 | 
				
			||||||
 | 
								client: client,
 | 
				
			||||||
			Uuid:    uuid,
 | 
								Uuid:    uuid,
 | 
				
			||||||
			Ignore:  ignore,
 | 
								Ignore:  ignore,
 | 
				
			||||||
			adapter: LocalBackupAdapter,
 | 
								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
 | 
					// 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.
 | 
					// will obviously only work if the backup was created as a local backup.
 | 
				
			||||||
func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
 | 
					func LocateLocal(client remote.Client, uuid string) (*LocalBackup, os.FileInfo, error) {
 | 
				
			||||||
	b := &LocalBackup{
 | 
						b := NewLocal(client, uuid, "")
 | 
				
			||||||
		Backup{
 | 
					 | 
				
			||||||
			Uuid:   uuid,
 | 
					 | 
				
			||||||
			Ignore: "",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	st, err := os.Stat(b.Path())
 | 
						st, err := os.Stat(b.Path())
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package backup
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"archive/tar"
 | 
						"archive/tar"
 | 
				
			||||||
	"compress/gzip"
 | 
						"compress/gzip"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
| 
						 | 
					@ -10,8 +11,8 @@ import (
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/juju/ratelimit"
 | 
						"github.com/juju/ratelimit"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type S3Backup struct {
 | 
					type S3Backup struct {
 | 
				
			||||||
| 
						 | 
					@ -20,9 +21,10 @@ type S3Backup struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ BackupInterface = (*S3Backup)(nil)
 | 
					var _ BackupInterface = (*S3Backup)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewS3(uuid string, ignore string) *S3Backup {
 | 
					func NewS3(client remote.Client, uuid string, ignore string) *S3Backup {
 | 
				
			||||||
	return &S3Backup{
 | 
						return &S3Backup{
 | 
				
			||||||
		Backup{
 | 
							Backup{
 | 
				
			||||||
 | 
								client:  client,
 | 
				
			||||||
			Uuid:    uuid,
 | 
								Uuid:    uuid,
 | 
				
			||||||
			Ignore:  ignore,
 | 
								Ignore:  ignore,
 | 
				
			||||||
			adapter: S3BackupAdapter,
 | 
								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().WithField("size", size).Debug("got size of backup")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s.log().Debug("attempting to get S3 upload urls from Panel...")
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ import (
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
						"github.com/pterodactyl/wings/api"
 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/system"
 | 
						"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.
 | 
					// Internal installation function used to simplify reporting back to the Panel.
 | 
				
			||||||
func (s *Server) internalInstall() error {
 | 
					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 err != nil {
 | 
				
			||||||
		if !api.IsRequestError(err) {
 | 
							if !remote.IsRequestError(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,7 +114,7 @@ func (s *Server) internalInstall() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type InstallationProcess struct {
 | 
					type InstallationProcess struct {
 | 
				
			||||||
	Server *Server
 | 
						Server *Server
 | 
				
			||||||
	Script *api.InstallationScript
 | 
						Script *remote.InstallationScript
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	client  *client.Client
 | 
						client  *client.Client
 | 
				
			||||||
	context context.Context
 | 
						context context.Context
 | 
				
			||||||
| 
						 | 
					@ -121,7 +122,7 @@ type InstallationProcess struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Generates a new installation process struct that will be used to create containers,
 | 
					// Generates a new installation process struct that will be used to create containers,
 | 
				
			||||||
// and otherwise perform installation commands for a server.
 | 
					// 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{
 | 
						proc := &InstallationProcess{
 | 
				
			||||||
		Script: script,
 | 
							Script: script,
 | 
				
			||||||
		Server: s,
 | 
							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
 | 
					// value of "true" means everything was successful, "false" means something went
 | 
				
			||||||
// wrong and the server must be deleted and re-created.
 | 
					// wrong and the server must be deleted and re-created.
 | 
				
			||||||
func (s *Server) SyncInstallState(successful bool) error {
 | 
					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 err != nil {
 | 
				
			||||||
		if !api.IsRequestError(err) {
 | 
							if !remote.IsRequestError(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import (
 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
	"github.com/pterodactyl/wings/events"
 | 
						"github.com/pterodactyl/wings/events"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var dockerEvents = []string{
 | 
					var dockerEvents = []string{
 | 
				
			||||||
| 
						 | 
					@ -186,7 +187,7 @@ func (s *Server) onConsoleOutput(data string) {
 | 
				
			||||||
	if s.IsRunning() {
 | 
						if s.IsRunning() {
 | 
				
			||||||
		stop := processConfiguration.Stop
 | 
							stop := processConfiguration.Stop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if stop.Type == api.ProcessStopCommand && data == stop.Value {
 | 
							if stop.Type == remote.ProcessStopCommand && data == stop.Value {
 | 
				
			||||||
			s.Environment.SetState(environment.ProcessOfflineState)
 | 
								s.Environment.SetState(environment.ProcessOfflineState)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ import (
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/creasty/defaults"
 | 
						"github.com/creasty/defaults"
 | 
				
			||||||
	"github.com/pterodactyl/wings/api"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment/docker"
 | 
						"github.com/pterodactyl/wings/environment/docker"
 | 
				
			||||||
| 
						 | 
					@ -54,7 +53,7 @@ type Server struct {
 | 
				
			||||||
	// Defines the process configuration for the server instance. This is dynamically
 | 
						// Defines the process configuration for the server instance. This is dynamically
 | 
				
			||||||
	// fetched from the Pterodactyl Server instance each time the server process is
 | 
						// fetched from the Pterodactyl Server instance each time the server process is
 | 
				
			||||||
	// started, and then cached here.
 | 
						// started, and then cached here.
 | 
				
			||||||
	procConfig *api.ProcessConfiguration
 | 
						procConfig *remote.ProcessConfiguration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Tracks the installation process for this server and prevents a server from running
 | 
						// 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
 | 
						// 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 {
 | 
					func (s *Server) Sync() error {
 | 
				
			||||||
	cfg, err := s.client.GetServerConfiguration(s.Context(), s.Id())
 | 
						cfg, err := s.client.GetServerConfiguration(s.Context(), s.Id())
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if !api.IsRequestError(err) {
 | 
							if !remote.IsRequestError(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err.(*api.RequestError).Status == "404" {
 | 
							if err.(*remote.RequestError).Status == "404" {
 | 
				
			||||||
			return &serverDoesNotExist{}
 | 
								return &serverDoesNotExist{}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -220,7 +219,7 @@ func (s *Server) IsSuspended() bool {
 | 
				
			||||||
	return s.Config().Suspended
 | 
						return s.Config().Suspended
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Server) ProcessConfiguration() *api.ProcessConfiguration {
 | 
					func (s *Server) ProcessConfiguration() *remote.ProcessConfiguration {
 | 
				
			||||||
	s.RLock()
 | 
						s.RLock()
 | 
				
			||||||
	defer s.RUnlock()
 | 
						defer s.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user