package remote import ( "context" "encoding/json" "fmt" "strconv" "sync" "github.com/pterodactyl/wings/api" "golang.org/x/sync/errgroup" ) const ( ProcessStopCommand = "command" ProcessStopSignal = "signal" ProcessStopNativeStop = "stop" ) // ServerConfigurationResponse holds the server configuration data returned from // the Panel. When a server process is started, Wings communicates with the // Panel to fetch the latest build information as well as get all of the details // needed to parse the given Egg. // // This means we do not need to hit Wings each time part of the server is // updated, and the Panel serves as the source of truth at all times. This also // means if a configuration is accidentally wiped on Wings we can self-recover // without too much hassle, so long as Wings is aware of what servers should // exist on it. type ServerConfigurationResponse struct { Settings json.RawMessage `json:"settings"` ProcessConfiguration *api.ProcessConfiguration `json:"process_configuration"` } // InstallationScript defines installation script information for a server // process. This is used when a server is installed for the first time, and when // a server is marked for re-installation. type InstallationScript struct { ContainerImage string `json:"container_image"` Entrypoint string `json:"entrypoint"` Script string `json:"script"` } // RawServerData is a raw response from the API for a server. type RawServerData struct { Uuid string `json:"uuid"` Settings json.RawMessage `json:"settings"` ProcessConfiguration json.RawMessage `json:"process_configuration"` } // GetServers returns all of the servers that are present on the Panel making // parallel API calls to the endpoint if more than one page of servers is // returned. func (c *client) GetServers(ctx context.Context, limit int) ([]RawServerData, error) { servers, meta, err := c.getServersPaged(ctx, 0, limit) if err != nil { return nil, err } var mu sync.Mutex if meta.LastPage > 1 { g, ctx := errgroup.WithContext(ctx) for page := meta.CurrentPage + 1; page <= meta.LastPage; page++ { page := page g.Go(func() error { ps, _, err := c.getServersPaged(ctx, int(page), limit) if err != nil { return err } mu.Lock() servers = append(servers, ps...) mu.Unlock() return nil }) } if err := g.Wait(); err != nil { return nil, err } } return servers, nil } func (c *client) GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error) { var config ServerConfigurationResponse res, err := c.get(ctx, fmt.Sprintf("/servers/%s", uuid), nil) if err != nil { return config, err } defer res.Body.Close() if res.HasError() { return config, err } err = res.BindJSON(&config) return config, err } func (c *client) GetInstallationScript(ctx context.Context, uuid string) (InstallationScript, error) { res, err := c.get(ctx, fmt.Sprintf("/servers/%s/install", uuid), nil) if err != nil { return InstallationScript{}, err } defer res.Body.Close() if res.HasError() { return InstallationScript{}, err } var config InstallationScript err = res.BindJSON(&config) return config, err } func (c *client) SetInstallationStatus(ctx context.Context, uuid string, successful bool) error { resp, err := c.post(ctx, fmt.Sprintf("/servers/%s/install", uuid), d{"successful": successful}) if err != nil { return err } defer resp.Body.Close() return resp.Error() } func (c *client) SetArchiveStatus(ctx context.Context, uuid string, successful bool) error { resp, err := c.post(ctx, fmt.Sprintf("/servers/%s/archive", uuid), d{"successful": successful}) if err != nil { return err } defer resp.Body.Close() return resp.Error() } func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful bool) error { state := "failure" if successful { state = "success" } resp, err := c.get(ctx, fmt.Sprintf("/servers/%s/transfer/%s", uuid, state), nil) if err != nil { return err } defer resp.Body.Close() return resp.Error() } // getServersPaged returns a subset of servers from the Panel API using the // pagination query parameters. func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, api.Pagination, error) { res, err := c.get(ctx, "/servers", q{ "page": strconv.Itoa(page), "per_page": strconv.Itoa(limit), }) if err != nil { return nil, api.Pagination{}, err } defer res.Body.Close() if res.HasError() { return nil, api.Pagination{}, res.Error() } var r struct { Data []RawServerData `json:"data"` Meta api.Pagination `json:"meta"` } if err := res.BindJSON(&r); err != nil { return nil, api.Pagination{}, err } return r.Data, r.Meta, nil }