package api import ( "context" "encoding/json" "fmt" "github.com/apex/log" "github.com/pkg/errors" "github.com/pterodactyl/wings/config" "golang.org/x/sync/errgroup" "sync" ) 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 allServerResponse struct { Data []RawServerData `json:"data"` Meta Pagination `json:"meta"` } type RawServerData struct { Uuid string `json:"uuid"` Settings json.RawMessage `json:"settings"` ProcessConfiguration json.RawMessage `json:"process_configuration"` } // Fetches all of the server configurations from the Panel API. This will initially load the // first 50 servers, and then check the pagination response to determine if more pages should // be loaded. If so, those requests are spun-up in additional routines and the final resulting // slice of all servers will be returned. func (r *Request) GetServers() ([]RawServerData, error) { resp, err := r.Get("/servers", D{"per_page": config.Get().RemoteQuery.BootServersPerPage}) if err != nil { return nil, errors.WithStack(err) } defer resp.Body.Close() if resp.HasError() { return nil, resp.Error() } var res allServerResponse if err := resp.Bind(&res); err != nil { return nil, errors.WithStack(err) } var mu sync.Mutex ret := res.Data // Check for pagination, and if it exists we'll need to then make a request to the API // for each page that would exist and get all of the resulting servers. if res.Meta.LastPage > 1 { pp := res.Meta.PerPage log.WithField("per_page", pp). WithField("total_pages", res.Meta.LastPage). Debug("detected multiple pages of server configurations, fetching remaining...") g, ctx := errgroup.WithContext(context.Background()) for i := res.Meta.CurrentPage + 1; i <= res.Meta.LastPage; i++ { page := i g.Go(func() error { select { case <-ctx.Done(): return ctx.Err() default: { resp, err := r.Get("/servers", D{"page": page, "per_page": pp}) if err != nil { return err } defer resp.Body.Close() if resp.Error() != nil { return resp.Error() } var servers allServerResponse if err := resp.Bind(&servers); err != nil { return err } mu.Lock() defer mu.Unlock() ret = append(ret, servers.Data...) return nil } } }) } if err := g.Wait(); err != nil { return nil, errors.WithStack(err) } } return ret, nil } // 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, errors.WithStack(err) } defer resp.Body.Close() if resp.HasError() { return cfg, resp.Error() } if err := resp.Bind(&cfg); err != nil { return cfg, errors.WithStack(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, errors.WithStack(err) } defer resp.Body.Close() if resp.HasError() { return is, resp.Error() } if err := resp.Bind(&is); err != nil { return is, errors.WithStack(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 errors.WithStack(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 errors.WithStack(err) } defer resp.Body.Close() return resp.Error() } func (r *Request) SendTransferFailure(uuid string) error { resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/failure", uuid), nil) if err != nil { return errors.WithStack(err) } defer resp.Body.Close() return resp.Error() } func (r *Request) SendTransferSuccess(uuid string) error { resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/success", uuid), nil) if err != nil { return errors.WithStack(err) } defer resp.Body.Close() return resp.Error() }