zlh-agent/internal/provision/addons/codeserver/verify.go
2026-03-20 23:17:19 +00:00

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")
}