diff --git a/Makefile b/Makefile index 5723b8e..190d16e 100644 --- a/Makefile +++ b/Makefile @@ -4,16 +4,18 @@ NODE_ENV:=production build: +# make sure this project is located within GOPATH, I.E. $GOPATH/src/factorio-server-manager + # Build Linux release mkdir build - GOOS=linux GOARCH=amd64 go build -o factorio-server-manager/factorio-server-manager src/* + GOOS=linux GOARCH=amd64 go build -o factorio-server-manager/factorio-server-manager factorio-server-manager/src # ui/node_modules/webpack/bin/webpack.js ui/webpack.config.js app/bundle.js --progress --profile --colors cp -r app/ factorio-server-manager/ cp conf.json.example factorio-server-manager/conf.json zip -r build/factorio-server-manager-linux-x64.zip factorio-server-manager rm -rf factorio-server-manager # Build Windows release - GOOS=windows GOARCH=386 go build -o factorio-server-manager/factorio-server-manager.exe src/* + GOOS=windows GOARCH=386 go build -o factorio-server-manager/factorio-server-manager.exe factorio-server-manager/src cp -r app/ factorio-server-manager/ cp conf.json.example factorio-server-manager/conf.json zip -r build/factorio-server-manager-windows.zip factorio-server-manager @@ -21,7 +23,7 @@ build: dev: mkdir dev - GOOS=linux GOARCH=amd64 go build -o factorio-server-linux/factorio-server-manager src/* + GOOS=linux GOARCH=amd64 go build -o factorio-server-linux/factorio-server-manager factorio-server-manager/src cp -r app/ dev/ cp conf.json.example dev/conf.json mv factorio-server-linux/factorio-server-manager dev/factorio-server-manager diff --git a/src/factorio_server.go b/src/factorio_server.go index 241c907..60d55b9 100644 --- a/src/factorio_server.go +++ b/src/factorio_server.go @@ -14,6 +14,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/majormjr/rcon" ) @@ -225,21 +226,27 @@ func (f *FactorioServer) checkLogError(logline []string) error { } func (f *FactorioServer) Stop() error { - // TODO: Find an alternative to os.Kill on Windows. os.Interupt - // is not implemented. Maps will not be saved. if runtime.GOOS == "windows" { - err := f.Cmd.Process.Signal(os.Kill) - if err != nil { - if err.Error() == "os: process already finished" { - f.Running = false - return err - } - log.Printf("Error sending SIGKILLL to Factorio process: %s", err) - return err - } - f.Running = false - log.Println("Sent SIGKILL to Factorio process. Factorio forced to exit.") + // Disable our own handling of CTRL+C, so we don't close when we send it to the console. + setCtrlHandlingIsDisabledForThisProcess(true) + + // Send CTRL+C to all processes attached to the console (ourself, and the factorio server instance) + sendCtrlCToPid(0) + log.Println("Sent SIGINT to Factorio process. Factorio shutting down...") + + // Somehow, the Factorio devs managed to code the game to react appropriately to CTRL+C, including + // saving the game, but not actually exit. So, we still have to manually kill the process, and + // for extra fun, there's no way to know when the server save has actually completed (unless we want + // to inject filesystem logic into what should be a process-level Stop() routine), so our best option + // is to just wait an arbitrary amount of time and hope that the save is successful in that time. + time.Sleep(2 * time.Second) + f.Cmd.Process.Signal(os.Kill) + + // Re-enable handling of CTRL+C after we're sure that the factrio server is shut down. + setCtrlHandlingIsDisabledForThisProcess(false) + + f.Running = false return nil } @@ -262,3 +269,43 @@ func (f *FactorioServer) Stop() error { return nil } + +func (f *FactorioServer) Kill() error { + if runtime.GOOS == "windows" { + + err := f.Cmd.Process.Signal(os.Kill) + if err != nil { + if err.Error() == "os: process already finished" { + f.Running = false + return err + } + log.Printf("Error sending SIGKILL to Factorio process: %s", err) + return err + } + f.Running = false + log.Println("Sent SIGKILL to Factorio process. Factorio forced to exit.") + + return nil + } + + err := f.Cmd.Process.Signal(os.Kill) + if err != nil { + if err.Error() == "os: process already finished" { + f.Running = false + return err + } + log.Printf("Error sending SIGKILL to Factorio process: %s", err) + return err + } + f.Running = false + log.Printf("Sent SIGKILL to Factorio process. Factorio forced to exit.") + + err = f.Rcon.Close() + if err != nil { + log.Printf("Error close rcon connection: %s", err) + } + + return nil +} + + diff --git a/src/factorio_server_linux.go b/src/factorio_server_linux.go new file mode 100644 index 0000000..0bfb230 --- /dev/null +++ b/src/factorio_server_linux.go @@ -0,0 +1,10 @@ +package main + + +// Stubs for windows-only functions + +func sendCtrlCToPid(pid int) { +} + +func setCtrlHandlingIsDisabledForThisProcess(disabled bool) { +} diff --git a/src/factorio_server_windows.go b/src/factorio_server_windows.go new file mode 100644 index 0000000..a6ea5f5 --- /dev/null +++ b/src/factorio_server_windows.go @@ -0,0 +1,41 @@ +package main + +import ( + "log" + "syscall" +) + +func sendCtrlCToPid(pid int) { + d, e := syscall.LoadDLL("kernel32.dll") + if e != nil { + log.Fatalf("LoadDLL: %v\n", e) + } + p, e := d.FindProc("GenerateConsoleCtrlEvent") + if e != nil { + log.Fatalf("FindProc: %v\n", e) + } + r, _, e := p.Call(uintptr(syscall.CTRL_C_EVENT), uintptr(pid)) + if r == 0 { + log.Fatalf("GenerateConsoleCtrlEvent: %v\n", e) + } +} + +func setCtrlHandlingIsDisabledForThisProcess(disabled bool) { + disabledInt := 0 + if(disabled){ + disabledInt = 1 + } + + d, e := syscall.LoadDLL("kernel32.dll") + if e != nil { + log.Fatalf("LoadDLL: %v\n", e) + } + p, e := d.FindProc("SetConsoleCtrlHandler") + if e != nil { + log.Fatalf("FindProc: %v\n", e) + } + r, _, e := p.Call(uintptr(0), uintptr(disabledInt)) + if r == 0 { + log.Fatalf("SetConsoleCtrlHandler: %v\n", e) + } +} diff --git a/src/handlers.go b/src/handlers.go index 88a09c6..10e0355 100644 --- a/src/handlers.go +++ b/src/handlers.go @@ -667,6 +667,41 @@ func StopServer(w http.ResponseWriter, r *http.Request) { } } + +func KillServer(w http.ResponseWriter, r *http.Request) { + resp := JSONResponse{ + Success: false, + } + + w.Header().Set("Content-Type", "application/json;charset=UTF-8") + + if FactorioServ.Running { + err := FactorioServ.Kill() + if err != nil { + log.Printf("Error in kill server handler: %s", err) + resp.Data = fmt.Sprintf("Error in kill server handler: %s", err) + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Error encoding config file JSON reponse: ", err) + } + return + } + + log.Printf("Killed Factorio server.") + resp.Success = true + resp.Data = fmt.Sprintf("Factorio server killed") + } else { + resp.Data = "Factorio server is not running" + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Error encoding config file JSON reponse: ", err) + } + return + } + + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Error encoding config file JSON reponse: ", err) + } +} + func CheckServer(w http.ResponseWriter, r *http.Request) { resp := JSONResponse{ Success: false, diff --git a/src/routes.go b/src/routes.go index 03779ef..2dd2174 100644 --- a/src/routes.go +++ b/src/routes.go @@ -228,6 +228,11 @@ var apiRoutes = Routes{ "GET", "/server/stop", StopServer, + }, { + "KillServer", + "GET", + "/server/kill", + KillServer, }, { "RunningServer", "GET", diff --git a/ui/App/components/ServerCtl/ServerCtl.jsx b/ui/App/components/ServerCtl/ServerCtl.jsx index 04f97a5..6194fb5 100644 --- a/ui/App/components/ServerCtl/ServerCtl.jsx +++ b/ui/App/components/ServerCtl/ServerCtl.jsx @@ -5,6 +5,7 @@ class ServerCtl extends React.Component { super(props); this.startServer = this.startServer.bind(this); this.stopServer = this.stopServer.bind(this); + this.killServer = this.killServer.bind(this); this.incrementPort = this.incrementPort.bind(this); this.decrementPort = this.decrementPort.bind(this); @@ -58,6 +59,21 @@ class ServerCtl extends React.Component { e.preventDefault(); } + killServer(e) { + $.ajax({ + type: "GET", + url: "/api/server/kill", + dataType: "json", + success: (resp) => { + this.props.facServStatus(); + this.props.getStatus(); + console.log(resp) + swal(resp.data) + } + }); + e.preventDefault(); + } + incrementPort() { let port = this.state.port + 1; this.setState({port: port}) @@ -85,7 +101,11 @@ class ServerCtl extends React.Component {