diff --git a/http.go b/http.go index 344f638..a97fd3f 100644 --- a/http.go +++ b/http.go @@ -152,6 +152,17 @@ func (rt *Router) routeServerPower(w http.ResponseWriter, r *http.Request, ps ht return } + // Because we route all of the actual bootup process to a seperate thread we need to + // check the suspension status here, otherwise the user will hit the endpoint and then + // just sit there wondering why it returns a success but nothing actually happens. + // + // We don't really care about any of the other actions at this point, they'll all result + // in the process being stopped, which should have happened anyways if the server is suspended. + if action.Action == "start" && s.Suspended { + http.Error(w, "server is suspended", http.StatusBadRequest) + return + } + // Pass the actual heavy processing off to a seperate thread to handle so that // we can immediately return a response from the server. go func(a string, s *server.Server) { diff --git a/server/environment_docker.go b/server/environment_docker.go index 8019cb8..4d3a513 100644 --- a/server/environment_docker.go +++ b/server/environment_docker.go @@ -215,6 +215,16 @@ func (d *DockerEnvironment) Start() error { } }() + // If the server is suspended the user shouldn't be able to boot it, in those cases + // return a suspension error and let the calling area handle the issue. + // + // Theoretically you'd have the Panel handle all of this logic, but we cannot do that + // because we allow the websocket to control the server power state as well, so we'll + // need to handle that action in here. + if d.Server.Suspended { + return &suspendedError{} + } + c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid) if err != nil && !client.IsErrNotFound(err) { return errors.WithStack(err) diff --git a/server/errors.go b/server/errors.go new file mode 100644 index 0000000..fd39685 --- /dev/null +++ b/server/errors.go @@ -0,0 +1,14 @@ +package server + +type suspendedError struct { +} + +func (e *suspendedError) Error() string { + return "server is currently in a suspended state" +} + +func IsSuspendedError(err error) bool { + _, ok := err.(*suspendedError) + + return ok +} \ No newline at end of file diff --git a/server/update.go b/server/update.go index 6332929..e7cf62e 100644 --- a/server/update.go +++ b/server/update.go @@ -5,6 +5,8 @@ import ( "github.com/buger/jsonparser" "github.com/imdario/mergo" "github.com/pkg/errors" + "go.uber.org/zap" + "os" ) // Merges data passed through in JSON form into the existing server object. @@ -59,5 +61,41 @@ func (s *Server) UpdateDataStructure(data []byte) error { return errors.WithStack(err) } - return s.Environment.InSituUpdate() + s.runBackgroundActions() + + return nil +} + +// Runs through different actions once a server's configuration has been persisted +// to the disk. This function does not return anything as any failures should be logged +// but have no effect on actually updating the server itself. +// +// These tasks run in independent threads where relevant to speed up any updates +// that need to happen. +func (s *Server) runBackgroundActions() { + // Update the environment in place, allowing memory and CPU usage to be adjusted + // on the fly without the user needing to reboot (theoretically). + go func(server *Server) { + if err := server.Environment.InSituUpdate(); err != nil { + zap.S().Warnw( + "failed to perform in-situ update of server environment", + zap.String("server", server.Uuid), + zap.Error(err), + ) + } + }(s) + + // Check if the server is now suspended, and if so and the process is not terminated + // yet, do it immediately. + go func(server *Server) { + if server.Suspended && server.State != ProcessOfflineState { + 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), + ) + } + } + }(s) } diff --git a/websocket.go b/websocket.go index 28e1806..1254591 100644 --- a/websocket.go +++ b/websocket.go @@ -276,8 +276,10 @@ func (wsh *WebsocketHandler) SendErrorJson(err error) error { defer wsh.Mutex.Unlock() message := "an unexpected error was encountered while handling this request" - if wsh.JWT != nil && wsh.JWT.HasPermission(PermissionReceiveErrors) { - message = err.Error() + if wsh.JWT != nil { + if server.IsSuspendedError(err) || wsh.JWT.HasPermission(PermissionReceiveErrors) { + message = err.Error() + } } m, u := wsh.GetErrorMessage(message)