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