From fca264a146efb4e3d773d6a1cfa15b7050f88b7d Mon Sep 17 00:00:00 2001 From: gekigek99 <53654579+gekigek99@users.noreply.github.com> Date: Thu, 18 Feb 2021 20:05:24 +0100 Subject: [PATCH] servctrl: serv status is updated from terminal cmd config: removed MinecraftServerStartupTime servctrl-cmd: fixed cmd child process receiving SIGINT servctrl-cmd: servctrl.Execute() now returns the first line of the output servctrl-cmd: simplified servctrl.waitForExit() --- config.json | 11 ++-- lib/confctrl/confctrl.go | 1 - lib/connctrl/connctrl.go | 4 +- lib/servctrl/cmd.go | 121 ++++++++++++++++++++++++++++---------- lib/servctrl/servctrl.go | 83 ++++++++++---------------- lib/servctrl/servstats.go | 7 ++- 6 files changed, 132 insertions(+), 95 deletions(-) diff --git a/config.json b/config.json index 45c1851d..2753f6c9 100644 --- a/config.json +++ b/config.json @@ -1,23 +1,22 @@ { - "basic": { - "serverDirPath": "{path/to/server/folder}", - "serverFileName": "{server.jar}", + "Basic": { + "ServerDirPath": "{path/to/server/folder}", + "ServerFileName": "{server.jar}", "StartMinecraftServer": "java {-Xmx1024M} {-Xms1024M} -jar serverFileName nogui", "StopMinecraftServer": "stop", "StopMinecraftServerForce": "", "HibernationInfo": " \u0026fserver status:\n \u0026b\u0026lHIBERNATING", "StartingInfo": " \u0026fserver status:\n \u00266\u0026lWARMING UP", - "MinecraftServerStartupTime": 20, "TimeBeforeStoppingEmptyServer": 60, "CheckForUpdates": true }, - "advanced": { + "Advanced": { "ListenHost": "0.0.0.0", "ListenPort": "25555", "TargetHost": "127.0.0.1", "TargetPort": "25565", "Debug": false, - "ServerVersion": "1.16.4", + "ServerVersion": "1.16.5", "ServerProtocol": "754" } } \ No newline at end of file diff --git a/lib/confctrl/confctrl.go b/lib/confctrl/confctrl.go index a46fe459..35ed39ca 100644 --- a/lib/confctrl/confctrl.go +++ b/lib/confctrl/confctrl.go @@ -30,7 +30,6 @@ type basic struct { StopMinecraftServerForce string HibernationInfo string StartingInfo string - MinecraftServerStartupTime int TimeBeforeStoppingEmptyServer int CheckForUpdates bool } diff --git a/lib/connctrl/connctrl.go b/lib/connctrl/connctrl.go index af5ae5bf..8fc1458a 100644 --- a/lib/connctrl/connctrl.go +++ b/lib/connctrl/connctrl.go @@ -91,12 +91,12 @@ func HandleClientSocket(clientSocket net.Conn) { servctrl.StartMinecraftServer() log.Printf("*** %s tried to join from %s:%s to %s:%s\n", playerName, clientAddress, confctrl.Config.Advanced.ListenPort, confctrl.Config.Advanced.TargetHost, confctrl.Config.Advanced.TargetPort) // answer to client with text in the loadscreen - clientSocket.Write(servprotocol.BuildMessage("txt", fmt.Sprintf("Server start command issued. Please wait... Time left: %d seconds", servctrl.ServStats.TimeLeftUntilUp))) + clientSocket.Write(servprotocol.BuildMessage("txt", fmt.Sprintf("Server start command issued. Please wait..."))) } else if servctrl.ServStats.Status == "starting" { log.Printf("*** %s tried to join from %s:%s to %s:%s during server startup\n", playerName, clientAddress, confctrl.Config.Advanced.ListenPort, confctrl.Config.Advanced.TargetHost, confctrl.Config.Advanced.TargetPort) // answer to client with text in the loadscreen - clientSocket.Write(servprotocol.BuildMessage("txt", fmt.Sprintf("Server is starting. Please wait... Time left: %d seconds", servctrl.ServStats.TimeLeftUntilUp))) + clientSocket.Write(servprotocol.BuildMessage("txt", fmt.Sprintf("Server is starting. Please wait..."))) } } diff --git a/lib/servctrl/cmd.go b/lib/servctrl/cmd.go index b4ff92f7..72f5f68e 100644 --- a/lib/servctrl/cmd.go +++ b/lib/servctrl/cmd.go @@ -9,13 +9,17 @@ import ( "os/exec" "strings" "sync" + "syscall" + "time" + "msh/lib/asyncctrl" + "msh/lib/confctrl" "msh/lib/debugctrl" ) // ServTerm is the minecraft server terminal type ServTerm struct { - IsActive bool + isActive bool Wg sync.WaitGroup cmd *exec.Cmd out readcl @@ -34,6 +38,9 @@ type writecl struct { io.WriteCloser } +// lastLine is a channel used to communicate the last line got from the printer function +var lastLine = make(chan string) + var colRes string = "\033[0m" var colCya string = "\033[36m" @@ -48,9 +55,10 @@ func CmdStart(dir, command string) (*ServTerm, error) { return nil, err } - go term.out.printer() - go term.err.printer() - go term.in.scanner() + term.Wg.Add(2) + go term.out.printer(term) + go term.err.printer(term) + go term.scanner() err = term.cmd.Start() if err != nil { @@ -59,31 +67,38 @@ func CmdStart(dir, command string) (*ServTerm, error) { go term.waitForExit() + // initialization + ServStats.Status = "starting" + ServStats.LoadProgress = "0%" + ServStats.Players = 0 + log.Print("*** MINECRAFT SERVER IS STARTING!") + return term, nil } // Execute executes a command on the specified term -func (term *ServTerm) Execute(command string) error { - if !term.IsActive { - return fmt.Errorf("terminal is not active") +func (term *ServTerm) Execute(command string) (string, error) { + if !term.isActive { + return "", fmt.Errorf("servctrl-cmd: Execute: terminal not active") } commands := strings.Split(command, "\n") for _, com := range commands { - // needs to be added otherwise the virtual "enter" button is not pressed - com += "\n" - - log.Print("terminal execute: ", com) - - // write to cmd - _, err := term.in.Write([]byte(com)) - if err != nil { - return err + if ServStats.Status == "online" { + debugctrl.Logger("sending", com, "to terminal") + + // write to cmd (\n indicates the enter key) + _, err := term.in.Write([]byte(com + "\n")) + if err != nil { + return "", err + } + } else { + return "", fmt.Errorf("servctrl-cmd: Execute: server not online") } } - return nil + return <-lastLine, nil } func (term *ServTerm) loadCmd(dir, command string) { @@ -91,6 +106,14 @@ func (term *ServTerm) loadCmd(dir, command string) { term.cmd = exec.Command(cSplit[0], cSplit[1:]...) term.cmd.Dir = dir + + // launch as new process group so that signals (ex: SIGINT) are not sent also the the child process + term.cmd.SysProcAttr = &syscall.SysProcAttr{ + // windows // + CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, + // linux // + // Setpgid: true, + } } func (term *ServTerm) loadStdPipes() error { @@ -114,28 +137,29 @@ func (term *ServTerm) loadStdPipes() error { return nil } +// waitForExit manages term.isActive parameter and set ServStats.Status = "offline" when it exits func (term *ServTerm) waitForExit() { - term.IsActive = true + term.isActive = true - term.Wg.Add(1) - err := term.cmd.Wait() - if err != nil { - debugctrl.Logger("waitForExit: error while waiting for cmd exit") - } - term.Wg.Done() - - term.IsActive = false + // wait for printer (out-err) to exit + term.Wg.Wait() term.out.Close() term.err.Close() term.in.Close() - fmt.Println("terminal exited correctly") + term.isActive = false + debugctrl.Logger("cmd: waitForExit: terminal exited") + + ServStats.Status = "offline" + log.Print("*** MINECRAFT SERVER IS OFFLINE!") } -func (cmdOutErrReader *readcl) printer() { +func (cmdOutErrReader *readcl) printer(term *ServTerm) { var line string + defer term.Wg.Done() + scanner := bufio.NewScanner(cmdOutErrReader) for scanner.Scan() { @@ -144,12 +168,41 @@ func (cmdOutErrReader *readcl) printer() { fmt.Println(colCya + line + colRes) if cmdOutErrReader.typ == "out" { - // look for flag strings in stdout + + // case where the server is starting + if ServStats.Status == "starting" { + if strings.Contains(line, "Preparing spawn area: ") { + ServStats.LoadProgress = strings.Split(strings.Split(line, "Preparing spawn area: ")[1], "\n")[0] + } + if strings.Contains(line, "[Server thread/INFO]: Done") { + ServStats.Status = "online" + log.Print("*** MINECRAFT SERVER IS ONLINE!") + + // launch a stopInstance so that if no players connect the server will shutdown + asyncctrl.WithLock(func() { ServStats.StopInstances++ }) + time.AfterFunc(time.Duration(confctrl.Config.Basic.TimeBeforeStoppingEmptyServer)*time.Second, func() { StopEmptyMinecraftServer(false) }) + } + } + + // case where the server is online + if ServStats.Status == "online" { + // if the terminal contains "Stopping" this means that the minecraft server is stopping + if strings.Contains(line, "[Server thread/INFO]: Stopping") { + ServStats.Status = "stopping" + log.Print("*** MINECRAFT SERVER IS STOPPING!") + } + } + } + + // communicate to lastLine so that Execute function can return the first line after the command + select { + case lastLine <- line: + default: } } } -func (cmdInWriter *writecl) scanner() { +func (term *ServTerm) scanner() { var line string var err error @@ -158,10 +211,14 @@ func (cmdInWriter *writecl) scanner() { for { line, err = reader.ReadString('\n') if err != nil { - debugctrl.Logger("cmdInWriter scanner:", err.Error()) + debugctrl.Logger("servTerm scanner:", err.Error()) continue } - cmdInWriter.Write([]byte(line)) + _, err = term.Execute(line) + if err != nil { + debugctrl.Logger("servTerm scanner:", err.Error()) + continue + } } } diff --git a/lib/servctrl/servctrl.go b/lib/servctrl/servctrl.go index 2e10d5f9..29b4cb2c 100644 --- a/lib/servctrl/servctrl.go +++ b/lib/servctrl/servctrl.go @@ -25,75 +25,56 @@ func StartMinecraftServer() { } // initialization - ServStats.Status = "starting" - ServStats.TimeLeftUntilUp = confctrl.Config.Basic.MinecraftServerStartupTime ServStats.Players = 0 - - log.Print("*** MINECRAFT SERVER IS STARTING!") - - // sets serverStatus == "online". - // After {TimeBeforeStoppingEmptyServer} executes stopEmptyMinecraftServer(false) - var setServerStatusOnline = func() { - ServStats.Status = "online" - log.Print("*** MINECRAFT SERVER IS UP!") - - asyncctrl.WithLock(func() { ServStats.StopInstances++ }) - time.AfterFunc(time.Duration(confctrl.Config.Basic.TimeBeforeStoppingEmptyServer)*time.Second, func() { StopEmptyMinecraftServer(false) }) - } - // updates TimeLeftUntilUp each second. if TimeLeftUntilUp == 0 it executes setServerStatusOnline() - var updateTimeleft func() - updateTimeleft = func() { - if ServStats.TimeLeftUntilUp > 0 { - ServStats.TimeLeftUntilUp-- - time.AfterFunc(1*time.Second, func() { updateTimeleft() }) - } else if ServStats.TimeLeftUntilUp == 0 { - setServerStatusOnline() - } - } - - time.AfterFunc(1*time.Second, func() { updateTimeleft() }) } // StopEmptyMinecraftServer stops the minecraft server func StopEmptyMinecraftServer(force bool) { - if force && ServStats.Status != "offline" { - // skip some checks to issue the stop server command forcefully + // wait for the starting server to become online + for ServStats.Status != "starting" { + time.Sleep(1 * time.Second) + } + // if server is not online return + if ServStats.Status != "online" { + debugctrl.Logger("servctrl: StopEmptyMinecraftServer: server is not online") + return + } + + if force { + // skip checks to issue the stop server command forcefully } else { - // check that there is only one "stop server command" instance running and players <= 0 and ServerStatus != "offline". + // check that there is only one "stop server command" instance running and players <= 0. // on the contrary the server won't be stopped asyncctrl.WithLock(func() { ServStats.StopInstances-- }) - if asyncctrl.WithLock(func() interface{} { - return ServStats.StopInstances > 0 || ServStats.Players > 0 || ServStats.Status == "offline" - }).(bool) { + if asyncctrl.WithLock(func() interface{} { return ServStats.StopInstances > 0 || ServStats.Players > 0 }).(bool) { return } } // execute stop command - if force && confctrl.Config.Basic.StopMinecraftServerForce != "" { - err := servTerm.Execute(confctrl.Config.Basic.StopMinecraftServerForce) - if err != nil { - log.Printf("stopEmptyMinecraftServer: error stopping minecraft server: %v\n", err) - return - } - // waits for the terminal to exit - debugctrl.Logger("waiting for server terminal to exit") - servTerm.Wg.Wait() - debugctrl.Logger("server terminal exited") - } else { - err := servTerm.Execute(confctrl.Config.Basic.StopMinecraftServer) - if err != nil { - log.Printf("stopEmptyMinecraftServer: error stopping minecraft server: %v\n", err) - return + var stopCom string + stopCom = confctrl.Config.Basic.StopMinecraftServer + if force { + if confctrl.Config.Basic.StopMinecraftServerForce != "" { + stopCom = confctrl.Config.Basic.StopMinecraftServerForce } } - ServStats.Status = "offline" + _, err := servTerm.Execute(stopCom) + if err != nil { + log.Printf("stopEmptyMinecraftServer: error stopping minecraft server: %s\n", err.Error()) + return + } if force { - log.Print("*** MINECRAFT SERVER IS FORCEFULLY SHUTTING DOWN!") - } else { - log.Print("*** MINECRAFT SERVER IS SHUTTING DOWN!") + if ServStats.Status == "stopping" { + // wait for the terminal to exit + debugctrl.Logger("waiting for server terminal to exit") + servTerm.Wg.Wait() + } else { + log.Println() + debugctrl.Logger("server does not seem to be stopping, is the stopForce command correct?") + } } } diff --git a/lib/servctrl/servstats.go b/lib/servctrl/servstats.go index c78f17dd..cb052b5b 100644 --- a/lib/servctrl/servstats.go +++ b/lib/servctrl/servstats.go @@ -1,14 +1,14 @@ package servctrl type serverStats struct { - // ServerStatus represent the status of the minecraft server ("offline", "starting", "online") + // ServerStatus represent the status of the minecraft server ("offline", "starting", "online", "stopping") Status string // Players keeps track of players connected to the server Players int // StopInstances keeps track of how many times stopEmptyMinecraftServer() has been called in the last {TimeBeforeStoppingEmptyServer} seconds StopInstances int - // TimeLeftUntilUp keeps track of how many seconds are still needed to reach serverStatus == "online" - TimeLeftUntilUp int + // LoadProgress indicates the loading percentage while the server is starting + LoadProgress string } // ServStats contains the info relative to server @@ -19,5 +19,6 @@ func init() { Status: "offline", Players: 0, StopInstances: 0, + LoadProgress: "0%", } }