diff --git a/server/environment_docker.go b/server/environment_docker.go index 5383d3b..2e20233 100644 --- a/server/environment_docker.go +++ b/server/environment_docker.go @@ -120,6 +120,7 @@ func (d *DockerEnvironment) Start() error { // No reason to try starting a container that is already running. if c.State.Running { + d.Server.Emit(StatusEvent, ProcessRunningState) if !d.attached { return d.Attach() } @@ -129,7 +130,9 @@ func (d *DockerEnvironment) Start() error { opts := types.ContainerStartOptions{} + d.Server.Emit(StatusEvent, ProcessStartingState) if err := d.Client.ContainerStart(context.Background(), d.Server.Uuid, opts); err != nil { + d.Server.Emit(StatusEvent, ProcessOfflineState) return err } @@ -142,6 +145,7 @@ func (d *DockerEnvironment) Start() error { func (d *DockerEnvironment) Stop() error { t := time.Second * 10 + d.Server.Emit(StatusEvent, ProcessStoppingState) return d.Client.ContainerStop(context.Background(), d.Server.Uuid, &t) } @@ -158,6 +162,7 @@ func (d *DockerEnvironment) Terminate(signal os.Signal) error { return nil } + d.Server.Emit(StatusEvent, ProcessStoppingState) return d.Client.ContainerKill(ctx, d.Server.Uuid, signal.String()) } @@ -193,6 +198,7 @@ func (d *DockerEnvironment) Attach() error { go func() { defer d.stream.Close() defer func() { + d.Server.Emit(StatusEvent, ProcessOfflineState) d.attached = false }() @@ -232,7 +238,7 @@ func (d *DockerEnvironment) FollowConsoleOutput() error { } if err := s.Err(); err != nil { - zap.S().Errorw("error in scanner", zap.Error(err)) + zap.S().Warnw("error processing scanner line in console output", zap.String("server", d.Server.Uuid), zap.Error(err)) } }(reader) diff --git a/server/events.go b/server/events.go index 75f906b..f73ad73 100644 --- a/server/events.go +++ b/server/events.go @@ -7,6 +7,7 @@ type EventListenerFunction *func(string) // Defines all of the possible output events for a server. const ( ConsoleOutputEvent = "console" + StatusEvent = "status" ) // Adds an event listener for the server instance. diff --git a/server/server.go b/server/server.go index 120ae98..888286d 100644 --- a/server/server.go +++ b/server/server.go @@ -1,8 +1,9 @@ package server import ( + "errors" + "fmt" "github.com/patrickmn/go-cache" - "github.com/pkg/errors" "github.com/pterodactyl/wings/config" "github.com/remeh/sizedwaitgroup" "go.uber.org/zap" @@ -14,16 +15,6 @@ import ( "time" ) -// Defines states that identify if the server is running or not. -const ( - StateOffline = "off" - StateStarting = "starting" - StateRunning = "running" - StateStopping = "stopping" -) - -type ProcessState string - // High level definition for a server instance being controlled by Wings. type Server struct { // The unique identifier for the server that should be used when referencing @@ -36,7 +27,7 @@ type Server struct { Suspended bool `json:"suspended"` // The power state of the server. - State ProcessState `json:"state"` + State string `json:"state"` // The command that should be used when booting up the server instance. Invocation string `json:"invocation"` @@ -190,7 +181,7 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e return nil, err } - withConfiguration := func (e *DockerEnvironment) { + withConfiguration := func(e *DockerEnvironment) { e.User = cfg.User.Uid e.TimezonePath = cfg.TimezonePath e.Server = s @@ -207,7 +198,7 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e } s.Environment = env - s.Cache = cache.New(time.Minute * 10, time.Minute * 15) + s.Cache = cache.New(time.Minute*10, time.Minute*15) s.Filesystem = &Filesystem{ Root: cfg.Data, Server: s, @@ -221,22 +212,6 @@ func (s *Server) ReadLogfile(len int64) ([]string, error) { return s.Environment.Readlog(len) } -// Sets the state of the server process. -func (s *Server) SetState(state ProcessState) error { - switch state { - case StateOffline: - case StateRunning: - case StateStarting: - case StateStopping: - s.State = state - break - default: - return errors.New("invalid state provided") - } - - return nil -} - // Determine if the server is bootable in it's current state or not. This will not // indicate why a server is not bootable, only if it is. func (s *Server) IsBootable() bool { @@ -249,4 +224,35 @@ func (s *Server) IsBootable() bool { // for the server is setup, and that all of the necessary files are created. func (s *Server) CreateEnvironment() error { return s.Environment.Create() -} \ No newline at end of file +} + +const ( + ProcessOfflineState = "offline" + ProcessStartingState = "starting" + ProcessRunningState = "running" + ProcessStoppingState = "stopping" +) + +// Sets the state of the server internally. This function handles crash detection as +// well as reporting to event listeners for the server. +func (s *Server) SetState(state string) error { + switch state { + case ProcessOfflineState: + case ProcessStartingState: + case ProcessRunningState: + case ProcessStoppingState: + s.State = state + break + default: + return errors.New(fmt.Sprintf("invalid server state received: %s", state)) + } + + // Emit the event to any listeners that are currently registered. + s.Emit(StatusEvent, state) + + // @todo handle a crash event here. Need to port the logic from the Nodejs daemon + // into this daemon. I believe its basically just if state != stopping && newState = stopped + // then crashed. + + return nil +} diff --git a/websocket.go b/websocket.go index eeb5564..4f04093 100644 --- a/websocket.go +++ b/websocket.go @@ -12,6 +12,14 @@ import ( "sync" ) +const ( + SetStateEvent = "set state" + SendServerLogsEvent = "send logs" + SendCommandEvent = "send command" + ConsoleOutputEvent = "console output" + ServerStatusEvent = "status" +) + type WebsocketMessage struct { // The event to perform. Should be one of the following that are supported: // @@ -46,18 +54,28 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http s := rt.Servers.Get(ps.ByName("server")) handler := WebsocketHandler{ - Server: s, - Mutex: sync.Mutex{}, + Server: s, + Mutex: sync.Mutex{}, Connection: c, } handleOutput := func(data string) { handler.SendJson(&WebsocketMessage{ - Event: "console output", + Event: ConsoleOutputEvent, Args: []string{data}, }) } + handleServerStatus := func(data string) { + handler.SendJson(&WebsocketMessage{ + Event: ServerStatusEvent, + Args: []string{data}, + }) + } + + s.AddListener(server.StatusEvent, &handleServerStatus) + defer s.RemoveListener(server.StatusEvent, &handleServerStatus) + s.AddListener(server.ConsoleOutputEvent, &handleOutput) defer s.RemoveListener(server.ConsoleOutputEvent, &handleOutput) @@ -102,7 +120,7 @@ func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error { } switch m.Event { - case "set state": + case SetStateEvent: { var err error switch strings.Join(m.Args, "") { @@ -121,7 +139,7 @@ func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error { return err } } - case "send logs": + case SendServerLogsEvent: { logs, err := wsh.Server.Environment.Readlog(1024 * 5) if err != nil { @@ -130,14 +148,14 @@ func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error { for _, line := range logs { wsh.SendJson(&WebsocketMessage{ - Event: "console output", + Event: ConsoleOutputEvent, Args: []string{line}, }) } return nil } - case "send command": + case SendCommandEvent: { return wsh.Server.Environment.SendCommand(strings.Join(m.Args, "")) }