diff --git a/config/config.go b/config/config.go index c0c613e..dbe92f4 100644 --- a/config/config.go +++ b/config/config.go @@ -17,6 +17,12 @@ import ( ) type Configuration struct { + sync.RWMutex `json:"-" yaml:"-"` + + // Locker specific to writing the configuration to the disk, this happens + // in areas that might already be locked so we don't want to crash the process. + writeLock sync.Mutex + // Determines if wings should be running in debug mode. This value is ignored // if the debug flag is passed through the command line arguments. Debug bool @@ -207,7 +213,7 @@ func ReadConfiguration(path string) (*Configuration, error) { c := new(Configuration) // Configures the default values for many of the configuration options present - // in the structs. Valkues set in the configuration file take priority over the + // in the structs. Values set in the configuration file take priority over the // default values. if err := defaults.Set(c); err != nil { return nil, err @@ -224,20 +230,29 @@ func ReadConfiguration(path string) (*Configuration, error) { return c, nil } +var Mutex sync.RWMutex var _config *Configuration var _debugViaFlag bool -// Set the global configuration instance. +// Set the global configuration instance. This is a blocking operation such that +// anything trying to set a different configuration value, or read the configuration +// will be paused until it is complete. func Set(c *Configuration) { + Mutex.Lock() _config = c + Mutex.Unlock() } func SetDebugViaFlag(d bool) { _debugViaFlag = d } -// Get the global configuration instance. +// Get the global configuration instance. This is a read-safe operation that will block +// if the configuration is presently being modified. func Get() *Configuration { + Mutex.RLock() + defer Mutex.RUnlock() + return _config } @@ -294,6 +309,9 @@ func (c *Configuration) setSystemUser(u *user.User) error { uid, _ := strconv.Atoi(u.Uid) gid, _ := strconv.Atoi(u.Gid) + c.Lock() + defer c.Unlock() + c.System.Username = u.Username c.System.User.Uid = uid c.System.User.Gid = gid @@ -369,6 +387,10 @@ func (c *Configuration) WriteToDisk() error { return err } + // Obtain an exclusive write against the configuration file. + c.writeLock.Lock() + defer c.writeLock.Unlock() + if err := ioutil.WriteFile("config.yml", b, 0644); err != nil { return err } diff --git a/router/router.go b/router/router.go index 6dffa99..93af8b2 100644 --- a/router/router.go +++ b/router/router.go @@ -30,6 +30,7 @@ func Configure() *gin.Engine { // All of the routes beyond this mount will use an authorization middleware // and will not be accessible without the correct Authorization header provided. protected := router.Use(AuthorizationMiddleware) + protected.POST("/api/update", postUpdateConfiguration) protected.GET("/api/system", getSystemInformation) protected.GET("/api/servers", getAllServers) protected.POST("/api/servers", postCreateServer) diff --git a/router/router_system.go b/router/router_system.go index aa560d5..3b3ddcd 100644 --- a/router/router_system.go +++ b/router/router_system.go @@ -3,6 +3,7 @@ package router import ( "bytes" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/installer" "github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/system" @@ -63,3 +64,25 @@ func postCreateServer(c *gin.Context) { c.Status(http.StatusAccepted) } + +// Updates the running configuration for this daemon instance. +func postUpdateConfiguration(c *gin.Context) { + // A backup of the configuration for error purposes. + ccopy := *config.Get() + // A copy of the configuration we're using to bind the data recevied into. + cfg := *config.Get() + + c.BindJSON(&cfg) + + config.Set(&cfg) + if err := config.Get().WriteToDisk(); err != nil { + // If there was an error writing to the disk, revert back to the configuration we had + // before this code was run. + config.Set(&ccopy) + + TrackedError(err).AbortWithServerError(c) + return + } + + c.Status(http.StatusNoContent) +} \ No newline at end of file diff --git a/server/collection.go b/server/collection.go index 2cb0344..c675024 100644 --- a/server/collection.go +++ b/server/collection.go @@ -16,6 +16,9 @@ func NewCollection(servers []*Server) *Collection { // Return all of the items in the collection. func (c *Collection) All() []*Server { + c.RLock() + defer c.RUnlock() + return c.items } diff --git a/server/update.go b/server/update.go index 1f0f5f8..296f106 100644 --- a/server/update.go +++ b/server/update.go @@ -69,10 +69,6 @@ func (s *Server) UpdateDataStructure(data []byte, background bool) error { s.Allocations.Mappings = src.Allocations.Mappings } - /*if _, err := s.WriteConfigurationToDisk(); err != nil { - return errors.WithStack(err) - }*/ - if background { s.runBackgroundActions() } @@ -105,14 +101,6 @@ func (s *Server) runBackgroundActions() { if server.Suspended && server.GetState() != ProcessOfflineState { zap.S().Infow("server suspended with running process state, terminating now", zap.String("server", server.Uuid)) - /*if err := server.Environment.Terminate(os.Kill); err != nil { - zap.S().Warnw( - "failed to terminate server environment after seeing suspension", - zap.String("server", server.Uuid), - zap.Error(err), - ) - }*/ - if err := server.Environment.WaitForStop(10, true); err != nil { zap.S().Warnw( "failed to stop server environment after seeing suspension",