159 lines
3.3 KiB
Go
159 lines
3.3 KiB
Go
package codeserver
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"zlh-agent/internal/provision/executil"
|
|
"zlh-agent/internal/state"
|
|
)
|
|
|
|
const (
|
|
marker = "/opt/zlh/.zlh/addons/code-server.installed"
|
|
pidFile = "/opt/zlh/.zlh/addons/code-server.pid"
|
|
binPath = "/opt/zlh/services/code-server/bin/code-server"
|
|
)
|
|
|
|
func Installed() bool {
|
|
_, err := os.Stat(marker)
|
|
return err == nil
|
|
}
|
|
|
|
func Running() bool {
|
|
raw, err := os.ReadFile(pidFile)
|
|
if err == nil {
|
|
pid, convErr := strconv.Atoi(strings.TrimSpace(string(raw)))
|
|
if convErr == nil && pid > 0 {
|
|
process, findErr := os.FindProcess(pid)
|
|
if findErr == nil && process.Signal(syscall.Signal(0)) == nil {
|
|
return true
|
|
}
|
|
}
|
|
_ = os.Remove(pidFile)
|
|
}
|
|
|
|
pid, err := findRunningPID()
|
|
if err != nil || pid <= 0 {
|
|
return false
|
|
}
|
|
_ = os.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0o644)
|
|
return true
|
|
}
|
|
|
|
func Verify() error {
|
|
|
|
if !Installed() {
|
|
return fmt.Errorf("code-server addon marker missing")
|
|
}
|
|
|
|
if _, err := os.Stat(binPath); err != nil {
|
|
return fmt.Errorf("code-server binary missing at %s", binPath)
|
|
}
|
|
|
|
if _, err := exec.LookPath("code-server"); err != nil {
|
|
return fmt.Errorf("code-server binary not found in PATH")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func findRunningPID() (int, error) {
|
|
matches, err := filepath.Glob("/proc/[0-9]*/cmdline")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
for _, match := range matches {
|
|
raw, err := os.ReadFile(match)
|
|
if err != nil || len(raw) == 0 {
|
|
continue
|
|
}
|
|
|
|
cmdline := strings.ReplaceAll(string(raw), "\x00", " ")
|
|
if !strings.Contains(cmdline, "code-server") {
|
|
continue
|
|
}
|
|
if !strings.Contains(cmdline, "--bind-addr 0.0.0.0:8080") {
|
|
continue
|
|
}
|
|
|
|
pidStr := filepath.Base(filepath.Dir(match))
|
|
pid, err := strconv.Atoi(pidStr)
|
|
if err != nil || pid <= 0 {
|
|
continue
|
|
}
|
|
return pid, nil
|
|
}
|
|
|
|
return 0, fmt.Errorf("code-server process not found")
|
|
}
|
|
|
|
func Start(cfg state.Config) error {
|
|
if !Installed() {
|
|
return fmt.Errorf("code-server addon not installed")
|
|
}
|
|
if Running() {
|
|
if err := Stop(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return executil.RunEmbeddedScript("addons/codeserver/install.sh", launchEnv(cfg)...)
|
|
}
|
|
|
|
func Stop() error {
|
|
raw, err := os.ReadFile(pidFile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("read code-server pid: %w", err)
|
|
}
|
|
|
|
pid, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
|
if err != nil || pid <= 0 {
|
|
_ = os.Remove(pidFile)
|
|
return fmt.Errorf("invalid code-server pid")
|
|
}
|
|
|
|
process, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
_ = os.Remove(pidFile)
|
|
return fmt.Errorf("find code-server process: %w", err)
|
|
}
|
|
|
|
if err := process.Signal(syscall.SIGTERM); err != nil && !errorsIsProcessDone(err) {
|
|
return fmt.Errorf("stop code-server: %w", err)
|
|
}
|
|
|
|
for i := 0; i < 20; i++ {
|
|
if !Running() {
|
|
_ = os.Remove(pidFile)
|
|
return nil
|
|
}
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
|
|
if err := process.Signal(syscall.SIGKILL); err != nil && !errorsIsProcessDone(err) {
|
|
return fmt.Errorf("kill code-server: %w", err)
|
|
}
|
|
_ = os.Remove(pidFile)
|
|
return nil
|
|
}
|
|
|
|
func Restart(cfg state.Config) error {
|
|
if err := Stop(); err != nil {
|
|
return err
|
|
}
|
|
return Start(cfg)
|
|
}
|
|
|
|
func errorsIsProcessDone(err error) bool {
|
|
return err == os.ErrProcessDone || strings.Contains(strings.ToLower(err.Error()), "process already finished")
|
|
}
|