Merge branch 'develop' of github.com:pterodactyl/wings into develop
This commit is contained in:
commit
354e69b976
|
@ -6,11 +6,11 @@ import (
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/remote"
|
"github.com/pterodactyl/wings/remote"
|
||||||
"github.com/pterodactyl/wings/router/middleware"
|
"github.com/pterodactyl/wings/router/middleware"
|
||||||
"github.com/pterodactyl/wings/server"
|
wserver "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, client remote.Client) *gin.Engine {
|
func Configure(m *wserver.Manager, client remote.Client) *gin.Engine {
|
||||||
gin.SetMode("release")
|
gin.SetMode("release")
|
||||||
|
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
|
@ -63,7 +63,6 @@ func Configure(m *server.Manager, client remote.Client) *gin.Engine {
|
||||||
server.Use(middleware.RequireAuthorization(), middleware.ServerExists())
|
server.Use(middleware.RequireAuthorization(), middleware.ServerExists())
|
||||||
{
|
{
|
||||||
server.GET("", getServer)
|
server.GET("", getServer)
|
||||||
server.PATCH("", patchServer)
|
|
||||||
server.DELETE("", deleteServer)
|
server.DELETE("", deleteServer)
|
||||||
|
|
||||||
server.GET("/logs", getServerLogs)
|
server.GET("/logs", getServerLogs)
|
||||||
|
@ -71,6 +70,7 @@ func Configure(m *server.Manager, client remote.Client) *gin.Engine {
|
||||||
server.POST("/commands", postServerCommands)
|
server.POST("/commands", postServerCommands)
|
||||||
server.POST("/install", postServerInstall)
|
server.POST("/install", postServerInstall)
|
||||||
server.POST("/reinstall", postServerReinstall)
|
server.POST("/reinstall", postServerReinstall)
|
||||||
|
server.POST("/sync", postServerSync)
|
||||||
server.POST("/ws/deny", postServerDenyWSTokens)
|
server.POST("/ws/deny", postServerDenyWSTokens)
|
||||||
|
|
||||||
// This archive request causes the archive to start being created
|
// This archive request causes the archive to start being created
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -10,7 +9,6 @@ import (
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/router/downloader"
|
"github.com/pterodactyl/wings/router/downloader"
|
||||||
"github.com/pterodactyl/wings/router/middleware"
|
"github.com/pterodactyl/wings/router/middleware"
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
|
@ -130,21 +128,18 @@ func postServerCommands(c *gin.Context) {
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates information about a server internally.
|
// postServerSync will accept a POST request and trigger a re-sync of the given
|
||||||
func patchServer(c *gin.Context) {
|
// server against the Panel. This can be manually triggered when needed by an
|
||||||
|
// external system, or triggered by the Panel itself when modifications are made
|
||||||
|
// to the build of a server internally.
|
||||||
|
func postServerSync(c *gin.Context) {
|
||||||
s := ExtractServer(c)
|
s := ExtractServer(c)
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
if err := s.Sync(); err != nil {
|
||||||
buf.ReadFrom(c.Request.Body)
|
WithError(c, err)
|
||||||
|
} else {
|
||||||
if err := s.UpdateDataStructure(buf.Bytes()); err != nil {
|
c.Status(http.StatusNoContent)
|
||||||
NewServerError(err, s).Abort(c)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.SyncWithEnvironment()
|
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a server installation in a background thread.
|
// Performs a server installation in a background thread.
|
||||||
|
|
|
@ -172,8 +172,11 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.UpdateDataStructure(data.Settings); err != nil {
|
|
||||||
return nil, err
|
// Setup the base server configuration data which will be used for all of the
|
||||||
|
// remaining functionality in this call.
|
||||||
|
if err := s.SyncWithConfiguration(data); err != nil {
|
||||||
|
return nil, errors.WithStackIf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.ID()), s.DiskSpace(), s.Config().Egg.FileDenylist)
|
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.ID()), s.DiskSpace(), s.Config().Egg.FileDenylist)
|
||||||
|
@ -200,11 +203,6 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
|
||||||
s.Throttler().StartTimer(s.Context())
|
s.Throttler().StartTimer(s.Context())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forces the configuration to be synced with the panel.
|
|
||||||
if err := s.SyncWithConfiguration(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the server's data directory exists, force disk usage calculation.
|
// If the server's data directory exists, force disk usage calculation.
|
||||||
if _, err := os.Stat(s.Filesystem().Path()); err == nil {
|
if _, err := os.Stat(s.Filesystem().Path()); err == nil {
|
||||||
s.Filesystem().HasSpaceAvailable(true)
|
s.Filesystem().HasSpaceAvailable(true)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -15,7 +16,6 @@ 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/events"
|
"github.com/pterodactyl/wings/events"
|
||||||
"github.com/pterodactyl/wings/remote"
|
"github.com/pterodactyl/wings/remote"
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
|
@ -167,31 +167,48 @@ func (s *Server) Sync() error {
|
||||||
}
|
}
|
||||||
return errors.WithStackIf(err)
|
return errors.WithStackIf(err)
|
||||||
}
|
}
|
||||||
return s.SyncWithConfiguration(cfg)
|
|
||||||
|
if err := s.SyncWithConfiguration(cfg); err != nil {
|
||||||
|
return errors.WithStackIf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the disk space limits for the server whenever the configuration for
|
||||||
|
// it changes.
|
||||||
|
s.fs.SetDiskLimit(s.DiskSpace())
|
||||||
|
|
||||||
|
s.SyncWithEnvironment()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncWithConfiguration accepts a configuration object for a server and will
|
||||||
|
// sync all of the values with the existing server state. This only replaces the
|
||||||
|
// existing configuration and process configuration for the server. The
|
||||||
|
// underlying environment will not be affected. This is because this function
|
||||||
|
// can be called from scoped where the server may not be fully initialized,
|
||||||
|
// therefore other things like the filesystem and environment may not exist yet.
|
||||||
func (s *Server) SyncWithConfiguration(cfg remote.ServerConfigurationResponse) error {
|
func (s *Server) SyncWithConfiguration(cfg remote.ServerConfigurationResponse) error {
|
||||||
// Update the data structure and persist it to the disk.
|
c := Configuration{}
|
||||||
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
if err := json.Unmarshal(cfg.Settings, &c); err != nil {
|
||||||
return err
|
return errors.WithStackIf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.cfg.mu.Lock()
|
||||||
|
defer s.cfg.mu.Unlock()
|
||||||
|
|
||||||
|
// Lock the new configuration. Since we have the defered Unlock above we need
|
||||||
|
// to make sure that the NEW configuration object is already locked since that
|
||||||
|
// defer is running on the memory address for "s.cfg.mu" which we're explcitly
|
||||||
|
// changing on the next line.
|
||||||
|
c.mu.Lock()
|
||||||
|
|
||||||
|
//goland:noinspection GoVetCopyLock
|
||||||
|
s.cfg = c
|
||||||
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
s.procConfig = cfg.ProcessConfiguration
|
s.procConfig = cfg.ProcessConfiguration
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
|
|
||||||
// Update the disk space limits for the server whenever the configuration
|
|
||||||
// for it changes.
|
|
||||||
s.fs.SetDiskLimit(s.DiskSpace())
|
|
||||||
|
|
||||||
// If this is a Docker environment we need to sync the stop configuration with it so that
|
|
||||||
// the process isn't just terminated when a user requests it be stopped.
|
|
||||||
if e, ok := s.Environment.(*docker.Environment); ok {
|
|
||||||
s.Log().Debug("syncing stop configuration with configured docker environment")
|
|
||||||
e.SetImage(s.Config().Container.Image)
|
|
||||||
e.SetStopConfiguration(cfg.ProcessConfiguration.Stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
152
server/update.go
152
server/update.go
|
@ -1,149 +1,41 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/pterodactyl/wings/environment/docker"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/buger/jsonparser"
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateDataStructure merges data passed through in JSON form into the existing
|
// SyncWithEnvironment updates the environment for the server to match any of
|
||||||
// server object. Any changes to the build settings will apply immediately in
|
// the changed data. This pushes new settings and environment variables to the
|
||||||
// the environment if the environment supports it.
|
// environment. In addition, the in-situ update method is called on the
|
||||||
|
// environment which will allow environments that make use of it (such as Docker)
|
||||||
|
// to immediately apply some settings without having to wait on a server to
|
||||||
|
// restart.
|
||||||
//
|
//
|
||||||
// The server will be marked as requiring a rebuild on the next boot sequence,
|
// This functionality allows a server's resources limits to be modified on the
|
||||||
// it is up to the specific environment to determine what needs to happen when
|
// fly and have them apply right away allowing for dynamic resource allocation
|
||||||
// that is the case.
|
// and responses to abusive server processes.
|
||||||
func (s *Server) UpdateDataStructure(data []byte) error {
|
|
||||||
src := new(Configuration)
|
|
||||||
if err := json.Unmarshal(data, src); err != nil {
|
|
||||||
return errors.Wrap(err, "server/update: could not unmarshal source data into Configuration struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't allow obviously corrupted data to pass through into this function. If the UUID
|
|
||||||
// doesn't match something has gone wrong and the API is attempting to meld this server
|
|
||||||
// instance into a totally different one, which would be bad.
|
|
||||||
if src.Uuid != "" && s.ID() != "" && src.Uuid != s.ID() {
|
|
||||||
return errors.New("server/update: attempting to merge a data stack with an invalid UUID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab a copy of the configuration to work on.
|
|
||||||
c := *s.Config()
|
|
||||||
|
|
||||||
// Lock our copy of the configuration since the deferred unlock will end up acting upon this
|
|
||||||
// new memory address rather than the old one. If we don't lock this, the deferred unlock will
|
|
||||||
// cause a panic when it goes to run. However, since we only update s.cfg at the end, if there
|
|
||||||
// is an error before that point we'll still properly unlock the original configuration for the
|
|
||||||
// server.
|
|
||||||
c.mu.Lock()
|
|
||||||
|
|
||||||
// Lock the server configuration while we're doing this merge to avoid anything
|
|
||||||
// trying to overwrite it or make modifications while we're sorting out what we
|
|
||||||
// need to do.
|
|
||||||
s.cfg.mu.Lock()
|
|
||||||
defer s.cfg.mu.Unlock()
|
|
||||||
|
|
||||||
// Merge the new data object that we have received with the existing server data object
|
|
||||||
// and then save it to the disk so it is persistent.
|
|
||||||
if err := mergo.Merge(&c, src, mergo.WithOverride); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't explode if we're setting CPU limits to 0. Mergo sees that as an empty value
|
|
||||||
// so it won't override the value we've passed through in the API call. However, we can
|
|
||||||
// safely assume that we're passing through valid data structures here. I foresee this
|
|
||||||
// backfiring at some point, but until then...
|
|
||||||
c.Build = src.Build
|
|
||||||
|
|
||||||
// Yee haw.
|
|
||||||
c.Egg = src.Egg
|
|
||||||
|
|
||||||
// Mergo can't quite handle this boolean value correctly, so for now we'll just
|
|
||||||
// handle this edge case manually since none of the other data passed through in this
|
|
||||||
// request is going to be boolean. Allegedly.
|
|
||||||
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
|
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.Build.OOMDisabled = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mergo also cannot handle this boolean value.
|
|
||||||
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
|
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.Suspended = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, err := jsonparser.GetBoolean(data, "skip_egg_scripts"); err != nil {
|
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.SkipEggScripts = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, err := jsonparser.GetBoolean(data, "start_on_completion"); err != nil {
|
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.StartOnCompletion = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, err := jsonparser.GetBoolean(data, "crash_detection_enabled"); err != nil {
|
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
// Enable crash detection by default.
|
|
||||||
c.CrashDetectionEnabled = true
|
|
||||||
} else {
|
|
||||||
c.CrashDetectionEnabled = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment and Mappings should be treated as a full update at all times, never a
|
|
||||||
// true patch, otherwise we can't know what we're passing along.
|
|
||||||
if src.EnvVars != nil && len(src.EnvVars) > 0 {
|
|
||||||
c.EnvVars = src.EnvVars
|
|
||||||
}
|
|
||||||
|
|
||||||
if src.Allocations.Mappings != nil && len(src.Allocations.Mappings) > 0 {
|
|
||||||
c.Allocations.Mappings = src.Allocations.Mappings
|
|
||||||
}
|
|
||||||
|
|
||||||
if src.Mounts != nil && len(src.Mounts) > 0 {
|
|
||||||
c.Mounts = src.Mounts
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the configuration once we have a lock on the configuration object.
|
|
||||||
s.cfg = c
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates the environment for the server to match any of the changed data. This pushes new settings and
|
|
||||||
// environment variables to the environment. In addition, the in-situ update method is called on the
|
|
||||||
// environment which will allow environments that make use of it (such as Docker) to immediately apply
|
|
||||||
// some settings without having to wait on a server to restart.
|
|
||||||
//
|
|
||||||
// This functionality allows a server's resources limits to be modified on the fly and have them apply
|
|
||||||
// right away allowing for dynamic resource allocation and responses to abusive server processes.
|
|
||||||
func (s *Server) SyncWithEnvironment() {
|
func (s *Server) SyncWithEnvironment() {
|
||||||
s.Log().Debug("syncing server settings with environment")
|
s.Log().Debug("syncing server settings with environment")
|
||||||
|
|
||||||
|
cfg := s.Config()
|
||||||
|
|
||||||
// Update the environment settings using the new information from this server.
|
// Update the environment settings using the new information from this server.
|
||||||
s.Environment.Config().SetSettings(environment.Settings{
|
s.Environment.Config().SetSettings(environment.Settings{
|
||||||
Mounts: s.Mounts(),
|
Mounts: s.Mounts(),
|
||||||
Allocations: s.Config().Allocations,
|
Allocations: cfg.Allocations,
|
||||||
Limits: s.Config().Build,
|
Limits: cfg.Build,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// For Docker specific environments we also want to update the configured image
|
||||||
|
// and stop configuration.
|
||||||
|
if e, ok := s.Environment.(*docker.Environment); ok {
|
||||||
|
s.Log().Debug("syncing stop configuration with configured docker environment")
|
||||||
|
e.SetImage(cfg.Container.Image)
|
||||||
|
e.SetStopConfiguration(s.ProcessConfiguration().Stop)
|
||||||
|
}
|
||||||
|
|
||||||
// If build limits are changed, environment variables also change. Plus, any modifications to
|
// If build limits are changed, environment variables also change. Plus, any modifications to
|
||||||
// the startup command also need to be properly propagated to this environment.
|
// the startup command also need to be properly propagated to this environment.
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue
Block a user