269 lines
5.1 KiB
Go
Executable File
269 lines
5.1 KiB
Go
Executable File
package system
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"zlh-agent/internal/provision"
|
|
"zlh-agent/internal/runtime"
|
|
"zlh-agent/internal/state"
|
|
)
|
|
|
|
/* --------------------------------------------------------------------------
|
|
GLOBAL PROCESS STATE
|
|
----------------------------------------------------------------------------*/
|
|
|
|
var (
|
|
mu sync.Mutex
|
|
serverCmd *exec.Cmd
|
|
serverPTY *os.File
|
|
|
|
devCmd *exec.Cmd
|
|
devPTY *os.File
|
|
)
|
|
|
|
/* --------------------------------------------------------------------------
|
|
StartServer (fixed)
|
|
----------------------------------------------------------------------------*/
|
|
|
|
func StartServer(cfg *state.Config) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
// Already running?
|
|
if serverCmd != nil {
|
|
return fmt.Errorf("server already running")
|
|
}
|
|
|
|
dir := provision.ServerDir(*cfg)
|
|
startScript := filepath.Join(dir, "start.sh")
|
|
|
|
cmd := exec.Command("/bin/bash", startScript)
|
|
cmd.Dir = dir
|
|
|
|
ptmx, err := runtime.CreatePTY(cmd)
|
|
if err != nil {
|
|
return fmt.Errorf("start server: %w", err)
|
|
}
|
|
|
|
serverCmd = cmd
|
|
serverPTY = ptmx
|
|
|
|
state.SetState(state.StateRunning)
|
|
state.SetError(nil)
|
|
|
|
go func() {
|
|
err := cmd.Wait()
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if serverPTY != nil {
|
|
_ = serverPTY.Close()
|
|
}
|
|
|
|
if err != nil {
|
|
state.RecordCrash(err)
|
|
} else {
|
|
state.SetState(state.StateIdle)
|
|
}
|
|
|
|
serverCmd = nil
|
|
serverPTY = nil
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
StopServer
|
|
----------------------------------------------------------------------------*/
|
|
|
|
func StopServer() error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if serverCmd == nil {
|
|
return fmt.Errorf("server not running")
|
|
}
|
|
|
|
state.SetState(state.StateStopping)
|
|
|
|
// Try graceful stop
|
|
if serverPTY != nil {
|
|
_ = runtime.Write(serverPTY, []byte("save-all\n"))
|
|
time.Sleep(2 * time.Second)
|
|
_ = runtime.Write(serverPTY, []byte("stop\n"))
|
|
}
|
|
|
|
// Wait a moment
|
|
time.Sleep(4 * time.Second)
|
|
|
|
// If still running, force kill
|
|
if serverCmd.Process != nil {
|
|
_ = serverCmd.Process.Kill()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
RestartServer
|
|
----------------------------------------------------------------------------*/
|
|
|
|
func RestartServer(cfg *state.Config) error {
|
|
if err := StopServer(); err != nil {
|
|
// ignore if not running
|
|
}
|
|
|
|
return StartServer(cfg)
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
SendConsoleCommand
|
|
----------------------------------------------------------------------------*/
|
|
|
|
func SendConsoleCommand(cmd string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if serverPTY == nil {
|
|
return fmt.Errorf("server console not available")
|
|
}
|
|
|
|
return runtime.Write(serverPTY, []byte(cmd+"\n"))
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Dev Shell PTY
|
|
----------------------------------------------------------------------------*/
|
|
|
|
func StartDevShell() (*os.File, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if devPTY != nil && devCmd != nil {
|
|
return devPTY, nil
|
|
}
|
|
|
|
shell := "/bin/bash"
|
|
if _, err := os.Stat(shell); err != nil {
|
|
shell = "/bin/sh"
|
|
}
|
|
|
|
var cmd *exec.Cmd
|
|
if shell == "/bin/bash" {
|
|
cmd = exec.Command(shell, "-l", "-i")
|
|
} else {
|
|
cmd = exec.Command(shell, "-i")
|
|
}
|
|
cmd.Dir = "/opt"
|
|
|
|
ptmx, err := runtime.CreatePTY(cmd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("start dev shell: %w", err)
|
|
}
|
|
|
|
devCmd = cmd
|
|
devPTY = ptmx
|
|
|
|
state.SetState(state.StateRunning)
|
|
state.SetError(nil)
|
|
|
|
go func() {
|
|
err := cmd.Wait()
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if devPTY != nil {
|
|
_ = devPTY.Close()
|
|
}
|
|
|
|
if err != nil {
|
|
state.RecordCrash(err)
|
|
} else {
|
|
state.SetState(state.StateIdle)
|
|
}
|
|
|
|
devCmd = nil
|
|
devPTY = nil
|
|
}()
|
|
|
|
return devPTY, nil
|
|
}
|
|
|
|
func GetConsolePTY(cfg *state.Config) (*os.File, error) {
|
|
if cfg.ContainerType == "dev" {
|
|
return StartDevShell()
|
|
}
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if serverPTY == nil {
|
|
return nil, fmt.Errorf("server console not available")
|
|
}
|
|
return serverPTY, nil
|
|
}
|
|
|
|
func WriteConsoleInput(cfg *state.Config, input string) error {
|
|
if strings.HasSuffix(input, "\n") {
|
|
input = strings.TrimSuffix(input, "\n")
|
|
}
|
|
payload := []byte(input + "\n")
|
|
|
|
if cfg.ContainerType == "dev" {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if devPTY == nil {
|
|
return fmt.Errorf("dev shell not available")
|
|
}
|
|
return runtime.Write(devPTY, payload)
|
|
}
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if serverPTY == nil {
|
|
return fmt.Errorf("server console not available")
|
|
}
|
|
return runtime.Write(serverPTY, payload)
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Stop Dev Shell
|
|
----------------------------------------------------------------------------*/
|
|
|
|
func StopDevShell() error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if devCmd == nil {
|
|
return nil
|
|
}
|
|
|
|
if devPTY != nil {
|
|
_ = runtime.Write(devPTY, []byte("exit\n"))
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
if devCmd.Process != nil {
|
|
_ = devCmd.Process.Kill()
|
|
}
|
|
|
|
if devPTY != nil {
|
|
_ = devPTY.Close()
|
|
devPTY = nil
|
|
}
|
|
|
|
devCmd = nil
|
|
state.SetState(state.StateIdle)
|
|
return nil
|
|
}
|