package handlers import ( "log" "net/http" "os" "os/exec" "runtime" "strings" "time" "github.com/gin-gonic/gin" ) const defaultSystemdUnit = "sproutclaw-web.service" func handleRestart() gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) go func() { time.Sleep(300 * time.Millisecond) if unit, underSystemd := systemdServiceUnit(); underSystemd { log.Printf("[restart] restarting via systemctl: %s", unit) cmd := exec.Command("systemctl", "restart", unit) out, err := cmd.CombinedOutput() if err != nil { log.Printf("[restart] systemctl restart failed: %v: %s", err, strings.TrimSpace(string(out))) } return } restartByReexec() }() } } func restartByReexec() { exe, err := os.Executable() if err != nil { log.Printf("[restart] get executable: %v", err) os.Exit(1) } cmd := exec.Command(exe, os.Args[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { log.Printf("[restart] start new process: %v", err) os.Exit(1) } os.Exit(0) } func systemdServiceUnit() (string, bool) { if runtime.GOOS != "linux" { return "", false } if os.Getenv("INVOCATION_ID") == "" && !inSystemdCgroup() { return "", false } if unit := strings.TrimSpace(os.Getenv("SPROUTCLAW_WEB_SYSTEMD_UNIT")); unit != "" { return normalizeSystemdUnit(unit), true } if unit := readSystemdUnitFromCgroup(); unit != "" { return unit, true } return defaultSystemdUnit, true } func normalizeSystemdUnit(name string) string { if strings.HasSuffix(name, ".service") { return name } return name + ".service" } func inSystemdCgroup() bool { data, err := os.ReadFile("/proc/self/cgroup") if err != nil { return false } return strings.Contains(string(data), ".service") } func readSystemdUnitFromCgroup() string { data, err := os.ReadFile("/proc/self/cgroup") if err != nil { return "" } for _, line := range strings.Split(string(data), "\n") { idx := strings.LastIndex(line, "/") if idx < 0 { continue } name := line[idx+1:] if strings.HasSuffix(name, ".service") { return name } } return "" }