replace servers.servers singleton with server.Manager

This commit is contained in:
Jakob Schrettenbrunner 2021-01-08 23:14:56 +00:00
parent 94f4207d60
commit 8192244fec
16 changed files with 162 additions and 108 deletions

View File

@ -2,6 +2,7 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
log2 "log" log2 "log"
"net/http" "net/http"
@ -9,8 +10,8 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"emperror.dev/errors"
"github.com/NYTimes/logrotate" "github.com/NYTimes/logrotate"
"github.com/apex/log" "github.com/apex/log"
"github.com/apex/log/handlers/multi" "github.com/apex/log/handlers/multi"
@ -21,6 +22,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/loggers/cli" "github.com/pterodactyl/wings/loggers/cli"
"github.com/pterodactyl/wings/panelapi"
"github.com/pterodactyl/wings/router" "github.com/pterodactyl/wings/router"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/sftp" "github.com/pterodactyl/wings/sftp"
@ -188,7 +190,17 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
}).Info("configured system user successfully") }).Info("configured system user successfully")
} }
if err := server.LoadDirectory(); err != nil { panelClient := panelapi.CreateClient(
config.Get().PanelLocation,
config.Get().AuthenticationTokenId,
config.Get().AuthenticationToken,
panelapi.WithTimeout(time.Second*time.Duration(config.Get().RemoteQuery.Timeout)),
)
_ = panelClient
serverManager := server.NewManager(panelClient)
if err := serverManager.Initialize(int(c.RemoteQuery.BootServersPerPage)); err != nil {
log.WithField("error", err).Fatal("failed to load server configurations") log.WithField("error", err).Fatal("failed to load server configurations")
return return
} }
@ -203,7 +215,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
} }
// Just for some nice log output. // Just for some nice log output.
for _, s := range server.GetServers().All() { for _, s := range serverManager.GetAll() {
log.WithField("server", s.Id()).Info("loaded configuration for server") log.WithField("server", s.Id()).Info("loaded configuration for server")
} }
@ -217,7 +229,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
// and reboot processes without causing a slow-down due to sequential booting. // and reboot processes without causing a slow-down due to sequential booting.
pool := workerpool.New(4) pool := workerpool.New(4)
for _, serv := range server.GetServers().All() { for _, serv := range serverManager.GetAll() {
s := serv s := serv
pool.Submit(func() { pool.Submit(func() {
@ -302,7 +314,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
}).Info("configuring internal webserver") }).Info("configuring internal webserver")
// Configure the router. // Configure the router.
r := router.Configure() r := router.Configure(serverManager)
s := &http.Server{ s := &http.Server{
Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port), Addr: fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port),
@ -372,7 +384,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
// Cancel the context on all of the running servers at this point, even though the // Cancel the context on all of the running servers at this point, even though the
// program is just shutting down. // program is just shutting down.
for _, s := range server.GetServers().All() { for _, s := range serverManager.GetAll() {
s.CtxCancel() s.CtxCancel()
} }
} }

View File

@ -12,7 +12,9 @@ import (
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
) )
type Middleware struct{} type Middleware struct {
serverManager server.Manager
}
// A custom handler function allowing for errors bubbled up by c.Error() to be returned in a // A custom handler function allowing for errors bubbled up by c.Error() to be returned in a
// standardized format with tracking UUIDs on them for easier log searching. // standardized format with tracking UUIDs on them for easier log searching.
@ -92,14 +94,19 @@ func (m *Middleware) RequireAuthorization() gin.HandlerFunc {
} }
} }
// Helper function to fetch a server out of the servers collection stored in memory. func (m *Middleware) WithServerManager() gin.HandlerFunc {
// return func(c *gin.Context) {
// This function should not be used in new controllers, prefer ExtractServer where c.Set("servermanager", m.serverManager)
// possible. }
func GetServer(uuid string) *server.Server { }
return server.GetServers().Find(func(s *server.Server) bool {
return uuid == s.Id() func ServerManagerFromContext(c *gin.Context) server.Manager {
}) if s, ok := c.Get("servermanager"); ok {
if srvs, ok := s.(server.Manager); ok {
return srvs
}
}
return nil
} }
// Ensure that the requested server exists in this setup. Returns a 404 if we cannot // Ensure that the requested server exists in this setup. Returns a 404 if we cannot
@ -108,7 +115,7 @@ func (m *Middleware) ServerExists() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
u, err := uuid.Parse(c.Param("server")) u, err := uuid.Parse(c.Param("server"))
if err == nil { if err == nil {
if s := GetServer(u.String()); s != nil { if s := m.serverManager.Get(u.String()); s != nil {
c.Set("server", s) c.Set("server", s)
c.Next() c.Next()
return return

View File

@ -3,15 +3,18 @@ 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/server"
) )
// Configures the routing infrastructure for this daemon instance. // Configures the routing infrastructure for this daemon instance.
func Configure() *gin.Engine { func Configure(serverManager server.Manager) *gin.Engine {
gin.SetMode("release") gin.SetMode("release")
m := Middleware{} m := Middleware{
serverManager,
}
router := gin.New() router := gin.New()
router.Use(gin.Recovery(), m.ErrorHandler(), m.SetAccessControlHeaders()) router.Use(gin.Recovery(), m.ErrorHandler(), m.SetAccessControlHeaders(), m.WithServerManager())
// @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

@ -14,13 +14,15 @@ import (
// Handle a download request for a server backup. // Handle a download request for a server backup.
func getDownloadBackup(c *gin.Context) { func getDownloadBackup(c *gin.Context) {
serverManager := ServerManagerFromContext(c)
token := tokens.BackupPayload{} token := tokens.BackupPayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil { if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
NewTrackedError(err).Abort(c) NewTrackedError(err).Abort(c)
return return
} }
s := GetServer(token.ServerUuid) s := serverManager.Get(token.ServerUuid)
if s == nil || !token.IsUniqueRequest() { if s == nil || !token.IsUniqueRequest() {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "The requested resource was not found on this server.", "error": "The requested resource was not found on this server.",
@ -57,13 +59,15 @@ func getDownloadBackup(c *gin.Context) {
// Handles downloading a specific file for a server. // Handles downloading a specific file for a server.
func getDownloadFile(c *gin.Context) { func getDownloadFile(c *gin.Context) {
serverManager := ServerManagerFromContext(c)
token := tokens.FilePayload{} token := tokens.FilePayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil { if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
NewTrackedError(err).Abort(c) NewTrackedError(err).Abort(c)
return return
} }
s := GetServer(token.ServerUuid) s := serverManager.Get(token.ServerUuid)
if s == nil || !token.IsUniqueRequest() { if s == nil || !token.IsUniqueRequest() {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "The requested resource was not found on this server.", "error": "The requested resource was not found on this server.",

View File

@ -22,7 +22,7 @@ type serverProcData struct {
// Returns a single server from the collection of servers. // Returns a single server from the collection of servers.
func getServer(c *gin.Context) { func getServer(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
c.JSON(http.StatusOK, serverProcData{ c.JSON(http.StatusOK, serverProcData{
ResourceUsage: s.Proc(), ResourceUsage: s.Proc(),
@ -32,7 +32,7 @@ func getServer(c *gin.Context) {
// Returns the logs for a given server instance. // Returns the logs for a given server instance.
func getServerLogs(c *gin.Context) { func getServerLogs(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
l, _ := strconv.Atoi(c.DefaultQuery("size", "100")) l, _ := strconv.Atoi(c.DefaultQuery("size", "100"))
if l <= 0 { if l <= 0 {
@ -59,7 +59,7 @@ func getServerLogs(c *gin.Context) {
// things are happening, so theres no reason to sit and wait for a request to finish. We'll // things are happening, so theres no reason to sit and wait for a request to finish. We'll
// just see over the socket if something isn't working correctly. // just see over the socket if something isn't working correctly.
func postServerPower(c *gin.Context) { func postServerPower(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
Action server.PowerAction `json:"action"` Action server.PowerAction `json:"action"`
@ -109,7 +109,7 @@ func postServerPower(c *gin.Context) {
// Sends an array of commands to a running server instance. // Sends an array of commands to a running server instance.
func postServerCommands(c *gin.Context) { func postServerCommands(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
if running, err := s.Environment.IsRunning(); err != nil { if running, err := s.Environment.IsRunning(); err != nil {
NewServerError(err, s).Abort(c) NewServerError(err, s).Abort(c)
@ -140,7 +140,7 @@ func postServerCommands(c *gin.Context) {
// Updates information about a server internally. // Updates information about a server internally.
func patchServer(c *gin.Context) { func patchServer(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
buf := bytes.Buffer{} buf := bytes.Buffer{}
buf.ReadFrom(c.Request.Body) buf.ReadFrom(c.Request.Body)
@ -157,7 +157,7 @@ func patchServer(c *gin.Context) {
// Performs a server installation in a background thread. // Performs a server installation in a background thread.
func postServerInstall(c *gin.Context) { func postServerInstall(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
go func(serv *server.Server) { go func(serv *server.Server) {
if err := serv.Install(true); err != nil { if err := serv.Install(true); err != nil {
@ -170,7 +170,7 @@ func postServerInstall(c *gin.Context) {
// Reinstalls a server. // Reinstalls a server.
func postServerReinstall(c *gin.Context) { func postServerReinstall(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
if s.ExecutingPowerAction() { if s.ExecutingPowerAction() {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{ c.AbortWithStatusJSON(http.StatusConflict, gin.H{
@ -191,6 +191,7 @@ func postServerReinstall(c *gin.Context) {
// Deletes a server from the wings daemon and dissociate it's objects. // Deletes a server from the wings daemon and dissociate it's objects.
func deleteServer(c *gin.Context) { func deleteServer(c *gin.Context) {
s := ExtractServer(c) s := ExtractServer(c)
sm := ServerManagerFromContext(c)
// Immediately suspend the server to prevent a user from attempting // Immediately suspend the server to prevent a user from attempting
// to start it while this process is running. // to start it while this process is running.
@ -234,10 +235,7 @@ func deleteServer(c *gin.Context) {
} }
}(s.Filesystem().Path()) }(s.Filesystem().Path())
uuid := s.Id() sm.Remove(s)
server.GetServers().Remove(func(s2 *server.Server) bool {
return s2.Id() == uuid
})
// Deallocate the reference to this server. // Deallocate the reference to this server.
s = nil s = nil

View File

@ -13,7 +13,7 @@ import (
// Backs up a server. // Backs up a server.
func postServerBackup(c *gin.Context) { func postServerBackup(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
data := &backup.Request{} data := &backup.Request{}
// BindJSON sends 400 if the request fails, all we need to do is return // BindJSON sends 400 if the request fails, all we need to do is return
@ -57,7 +57,7 @@ func postServerBackup(c *gin.Context) {
// a 404 error. The service calling this endpoint can make its own decisions as to how it wants // a 404 error. The service calling this endpoint can make its own decisions as to how it wants
// to handle that response. // to handle that response.
func deleteServerBackup(c *gin.Context) { func deleteServerBackup(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
b, _, err := backup.LocateLocal(c.Param("backup")) b, _, err := backup.LocateLocal(c.Param("backup"))
if err != nil { if err != nil {

View File

@ -76,7 +76,7 @@ type renameFile struct {
// Renames (or moves) files for a server. // Renames (or moves) files for a server.
func putServerRenameFiles(c *gin.Context) { func putServerRenameFiles(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
Root string `json:"root"` Root string `json:"root"`
@ -138,7 +138,7 @@ func putServerRenameFiles(c *gin.Context) {
// Copies a server file. // Copies a server file.
func postServerCopyFile(c *gin.Context) { func postServerCopyFile(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
Location string `json:"location"` Location string `json:"location"`
@ -158,7 +158,7 @@ func postServerCopyFile(c *gin.Context) {
// Deletes files from a server. // Deletes files from a server.
func postServerDeleteFiles(c *gin.Context) { func postServerDeleteFiles(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
Root string `json:"root"` Root string `json:"root"`
@ -203,7 +203,7 @@ func postServerDeleteFiles(c *gin.Context) {
// Writes the contents of the request to a file on a server. // Writes the contents of the request to a file on a server.
func postServerWriteFile(c *gin.Context) { func postServerWriteFile(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
f := c.Query("file") f := c.Query("file")
f = "/" + strings.TrimLeft(f, "/") f = "/" + strings.TrimLeft(f, "/")
@ -300,7 +300,7 @@ func deleteServerPullRemoteFile(c *gin.Context) {
// Create a directory on a server. // Create a directory on a server.
func postServerCreateDirectory(c *gin.Context) { func postServerCreateDirectory(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
Name string `json:"name"` Name string `json:"name"`
@ -327,7 +327,7 @@ func postServerCreateDirectory(c *gin.Context) {
} }
func postServerCompressFiles(c *gin.Context) { func postServerCompressFiles(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
RootPath string `json:"root"` RootPath string `json:"root"`
@ -365,7 +365,7 @@ func postServerCompressFiles(c *gin.Context) {
} }
func postServerDecompressFiles(c *gin.Context) { func postServerDecompressFiles(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
RootPath string `json:"root"` RootPath string `json:"root"`
@ -433,7 +433,7 @@ type chmodFile struct {
var errInvalidFileMode = errors.New("invalid file mode") var errInvalidFileMode = errors.New("invalid file mode")
func postServerChmodFile(c *gin.Context) { func postServerChmodFile(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
var data struct { var data struct {
Root string `json:"root"` Root string `json:"root"`
@ -497,13 +497,15 @@ func postServerChmodFile(c *gin.Context) {
} }
func postServerUploadFiles(c *gin.Context) { func postServerUploadFiles(c *gin.Context) {
serverManager := ServerManagerFromContext(c)
token := tokens.UploadPayload{} token := tokens.UploadPayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil { if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
NewTrackedError(err).Abort(c) NewTrackedError(err).Abort(c)
return return
} }
s := GetServer(token.ServerUuid) s := serverManager.Get(token.ServerUuid)
if s == nil || !token.IsUniqueRequest() { if s == nil || !token.IsUniqueRequest() {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "The requested resource was not found on this server.", "error": "The requested resource was not found on this server.",

View File

@ -12,7 +12,8 @@ import (
// Upgrades a connection to a websocket and passes events along between. // Upgrades a connection to a websocket and passes events along between.
func getServerWebsocket(c *gin.Context) { func getServerWebsocket(c *gin.Context) {
s := GetServer(c.Param("server")) serverManager := ServerManagerFromContext(c)
s := serverManager.Get(c.Param("server"))
handler, err := websocket.GetHandler(s, c.Writer, c.Request) handler, err := websocket.GetHandler(s, c.Writer, c.Request)
if err != nil { if err != nil {
NewServerError(err, s).Abort(c) NewServerError(err, s).Abort(c)

View File

@ -9,7 +9,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer" "github.com/pterodactyl/wings/installer"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
) )
@ -28,7 +27,8 @@ func getSystemInformation(c *gin.Context) {
// Returns all of the servers that are registered and configured correctly on // Returns all of the servers that are registered and configured correctly on
// this wings instance. // this wings instance.
func getAllServers(c *gin.Context) { func getAllServers(c *gin.Context) {
c.JSON(http.StatusOK, server.GetServers().All()) serverManager := ServerManagerFromContext(c)
c.JSON(http.StatusOK, serverManager.GetAll())
} }
// 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
@ -52,7 +52,8 @@ 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.
server.GetServers().Add(install.Server()) serverManager := ServerManagerFromContext(c)
serverManager.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
// cycle. If there are any errors they will be logged and communicated back // cycle. If there are any errors they will be logged and communicated back

View File

@ -323,19 +323,19 @@ func postTransfer(c *gin.Context) {
i.Server().Events().Publish(server.TransferLogsEvent, output) i.Server().Events().Publish(server.TransferLogsEvent, output)
} }
serverManager := ServerManagerFromContext(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)
server.GetServers().Add(i.Server()) serverManager.Add(i.Server())
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(!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")
server.GetServers().Remove(func(s2 *server.Server) bool { serverManager.Remove(s)
return s.Id() == s2.Id()
})
// If the transfer status was successful but the request failed, act like the transfer failed. // If the transfer status was successful but the request failed, act like the transfer failed.
if !hasError && err != nil { if !hasError && err != nil {

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@ -15,26 +16,21 @@ 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/environment/docker" "github.com/pterodactyl/wings/environment/docker"
"github.com/pterodactyl/wings/panelapi"
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
) )
var servers = NewCollection(nil)
func GetServers() *Collection {
return servers
}
// Iterates over a given directory and loads all of the servers listed before returning // Iterates over a given directory and loads all of the servers listed before returning
// them to the calling function. // them to the calling function.
func LoadDirectory() error { func (m *manager) Initialize(serversPerPage int) error {
if len(servers.items) != 0 { if len(m.servers.items) != 0 {
return errors.New("cannot call LoadDirectory with a non-nil collection") return errors.New("cannot call LoadDirectory with a non-nil collection")
} }
log.Info("fetching list of servers from API") log.Info("fetching list of servers from API")
configs, err := api.New().GetServers() assignedServers, err := m.panelClient.GetServers(context.TODO(), serversPerPage)
if err != nil { if err != nil {
if !api.IsRequestError(err) { if !panelapi.IsRequestError(err) {
return err return err
} }
@ -42,13 +38,11 @@ func LoadDirectory() error {
} }
start := time.Now() start := time.Now()
log.WithField("total_configs", len(configs)).Info("processing servers returned by the API") log.WithField("total_configs", len(assignedServers)).Info("processing servers returned by the API")
pool := workerpool.New(runtime.NumCPU()) pool := workerpool.New(runtime.NumCPU())
log.Debugf("using %d workerpools to instantiate server instances", runtime.NumCPU()) log.Debugf("using %d workerpools to instantiate server instances", runtime.NumCPU())
for _, data := range configs { for _, data := range assignedServers {
data := data
pool.Submit(func() { pool.Submit(func() {
// Parse the json.RawMessage into an expected struct value. We do this here so that a single broken // Parse the json.RawMessage into an expected struct value. We do this here so that a single broken
// server does not cause the entire boot process to hang, and allows us to show more useful error // server does not cause the entire boot process to hang, and allows us to show more useful error
@ -69,7 +63,7 @@ func LoadDirectory() error {
return return
} }
servers.Add(s) m.Add(s)
}) })
} }

46
server/manager.go Normal file
View File

@ -0,0 +1,46 @@
package server
import (
"github.com/pterodactyl/wings/panelapi"
)
type Manager interface {
// Initialize fetches all servers assigned to this node from the API.
Initialize(serversPerPage int) error
GetAll() []*Server
Get(uuid string) *Server
Add(s *Server)
Remove(s *Server)
}
type manager struct {
servers Collection
panelClient panelapi.Client
}
// NewManager creates a new server manager.
func NewManager(panelClient panelapi.Client) Manager {
return &manager{panelClient: panelClient}
}
func (m *manager) GetAll() []*Server {
return m.servers.items
}
func (m *manager) Get(uuid string) *Server {
return m.servers.Find(func(s *Server) bool {
return s.Id() == uuid
})
}
func (m *manager) Add(s *Server) {
s.manager = m
m.servers.Add(s)
}
func (m *manager) Remove(s *Server) {
m.servers.Remove(func(sf *Server) bool {
return sf.Id() == s.Id()
})
}

View File

@ -27,6 +27,9 @@ type Server struct {
ctx context.Context ctx context.Context
ctxCancel *context.CancelFunc ctxCancel *context.CancelFunc
// manager holds a reference to the manager responsible for the server
manager *manager
emitterLock sync.Mutex emitterLock sync.Mutex
powerLock *semaphore.Weighted powerLock *semaphore.Weighted
throttleOnce sync.Once throttleOnce sync.Once

View File

@ -36,10 +36,10 @@ func CachedServerStates() (map[string]string, error) {
} }
// saveServerStates . // saveServerStates .
func saveServerStates() error { func (m *manager) saveServerStates() error {
// Get the states of all servers on the daemon. // Get the states of all servers on the daemon.
states := map[string]string{} states := map[string]string{}
for _, s := range GetServers().All() { for _, s := range m.GetAll() {
states[s.Id()] = s.Environment.State() states[s.Id()] = s.Environment.State()
} }
@ -84,7 +84,7 @@ func (s *Server) OnStateChange() {
// We also get the benefit of server status changes always propagating corrected configurations // We also get the benefit of server status changes always propagating corrected configurations
// to the disk should we forget to do it elsewhere. // to the disk should we forget to do it elsewhere.
go func() { go func() {
if err := saveServerStates(); err != nil { if err := s.manager.saveServerStates(); err != nil {
s.Log().WithField("error", err).Warn("failed to write server states to disk") s.Log().WithField("error", err).Warn("failed to write server states to disk")
} }
}() }()

View File

@ -12,12 +12,12 @@ import (
"os" "os"
"path" "path"
"strings" "strings"
"time"
"github.com/apex/log" "github.com/apex/log"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
"github.com/pkg/sftp" "github.com/pkg/sftp"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/server"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -39,6 +39,8 @@ type Server struct {
Settings Settings Settings Settings
User User User User
serverManager server.Manager
PathValidator func(fs FileSystem, p string) (string, error) PathValidator func(fs FileSystem, p string) (string, error)
DiskSpaceValidator func(fs FileSystem) bool DiskSpaceValidator func(fs FileSystem) bool
@ -48,13 +50,6 @@ type Server struct {
CredentialValidator func(r api.SftpAuthRequest) (*api.SftpAuthResponse, error) CredentialValidator func(r api.SftpAuthRequest) (*api.SftpAuthResponse, error)
} }
// Create a new server configuration instance.
func New(c *Server) error {
c.cache = cache.New(5*time.Minute, 10*time.Minute)
return nil
}
// Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections. // Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections.
func (c *Server) Initialize() error { func (c *Server) Initialize() error {
serverConfig := &ssh.ServerConfig{ serverConfig := &ssh.ServerConfig{

View File

@ -1,11 +1,13 @@
package sftp package sftp
import ( import (
"time"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server"
) )
var noMatchingServerError = errors.New("no matching server with that UUID was found") var noMatchingServerError = errors.New("no matching server with that UUID was found")
@ -22,14 +24,11 @@ func Initialize(config config.SystemConfiguration) error {
BindAddress: config.Sftp.Address, BindAddress: config.Sftp.Address,
BindPort: config.Sftp.Port, BindPort: config.Sftp.Port,
}, },
CredentialValidator: validateCredentials, cache: cache.New(5*time.Minute, 10*time.Minute),
PathValidator: validatePath,
DiskSpaceValidator: validateDiskSpace,
}
if err := New(s); err != nil {
return err
} }
s.CredentialValidator = s.validateCredentials
s.PathValidator = s.validatePath
s.DiskSpaceValidator = s.validateDiskSpace
// Initialize the SFTP server in a background thread since this is // Initialize the SFTP server in a background thread since this is
// a long running operation. // a long running operation.
@ -42,33 +41,25 @@ func Initialize(config config.SystemConfiguration) error {
return nil return nil
} }
func validatePath(fs FileSystem, p string) (string, error) { func (s *Server) validatePath(fs FileSystem, p string) (string, error) {
s := server.GetServers().Find(func(server *server.Server) bool { srv := s.serverManager.Get(fs.UUID)
return server.Id() == fs.UUID if srv == nil {
})
if s == nil {
return "", noMatchingServerError return "", noMatchingServerError
} }
return srv.Filesystem().SafePath(p)
return s.Filesystem().SafePath(p)
} }
func validateDiskSpace(fs FileSystem) bool { func (s *Server) validateDiskSpace(fs FileSystem) bool {
s := server.GetServers().Find(func(server *server.Server) bool { srv := s.serverManager.Get(fs.UUID)
return server.Id() == fs.UUID if srv == nil {
})
if s == nil {
return false return false
} }
return srv.Filesystem().HasSpaceAvailable(true)
return s.Filesystem().HasSpaceAvailable(true)
} }
// Validates a set of credentials for a SFTP login against Pterodactyl Panel and returns // Validates a set of credentials for a SFTP login against Pterodactyl Panel and returns
// the server's UUID if the credentials were valid. // the server's UUID if the credentials were valid.
func validateCredentials(c api.SftpAuthRequest) (*api.SftpAuthResponse, error) { func (s *Server) validateCredentials(c api.SftpAuthRequest) (*api.SftpAuthResponse, error) {
f := log.Fields{"subsystem": "sftp", "username": c.User, "ip": c.IP} f := log.Fields{"subsystem": "sftp", "username": c.User, "ip": c.IP}
log.WithFields(f).Debug("validating credentials for SFTP connection") log.WithFields(f).Debug("validating credentials for SFTP connection")
@ -83,15 +74,12 @@ func validateCredentials(c api.SftpAuthRequest) (*api.SftpAuthResponse, error) {
return resp, err return resp, err
} }
s := server.GetServers().Find(func(server *server.Server) bool { srv := s.serverManager.Get(resp.Server)
return server.Id() == resp.Server if srv == nil {
})
if s == nil {
return resp, noMatchingServerError return resp, noMatchingServerError
} }
s.Log().WithFields(f).Debug("credentials successfully validated and matched user to server instance") srv.Log().WithFields(f).Debug("credentials successfully validated and matched user to server instance")
return resp, err return resp, err
} }