Remove all of the remaining API logic and port it all to the remote.Client type

This commit is contained in:
Dane Everitt 2021-02-01 21:28:46 -08:00
parent 62cbe5e135
commit 98c68142cd
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
26 changed files with 290 additions and 649 deletions

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -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()
}

View File

@ -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,
} }

View File

@ -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()

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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()
}

View File

@ -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)

View File

@ -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) {

View File

@ -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
View 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"`
}

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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")
} }
}() }()

View File

@ -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
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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)
} }
} }

View File

@ -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()