package agent import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "runtime" "time" "github.com/x-grk/longpolling-remote-access/internal/proto" ) // doUpdate downloads the latest agent binary for this platform, atomically // replaces the running executable, and schedules a service restart. It never // uninstalls — only updates. func (a *Agent) doUpdate() proto.TaskResult { exe, err := os.Executable() if err != nil { return proto.TaskResult{Error: "не удалось определить путь бинарника: " + err.Error()} } exe, _ = filepath.EvalSymlinks(exe) url := fmt.Sprintf("%s/download/agent-%s-%s", a.cfg.Host, runtime.GOOS, runtime.GOARCH) req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) resp, err := a.http.Do(req) if err != nil { return proto.TaskResult{Error: "скачивание: " + err.Error()} } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return proto.TaskResult{Error: "скачивание: сервер вернул " + resp.Status} } tmp := exe + ".new" f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) if err != nil { return proto.TaskResult{Error: "создание файла: " + err.Error()} } if _, err := io.Copy(f, resp.Body); err != nil { f.Close() os.Remove(tmp) return proto.TaskResult{Error: "запись: " + err.Error()} } f.Close() // Atomic replace: renaming over a running executable is allowed on Linux; // the live process keeps the old inode until it restarts. if err := os.Rename(tmp, exe); err != nil { os.Remove(tmp) return proto.TaskResult{Error: "замена бинарника: " + err.Error()} } // Restart shortly, after the result has been reported. go func() { time.Sleep(1500 * time.Millisecond) restartSelf() }() data, _ := json.Marshal(map[string]string{"status": "обновлено, перезапуск"}) return proto.TaskResult{OK: true, Data: data} } // restartSelf restarts the agent so the new binary takes effect. func restartSelf() { if path, err := exec.LookPath("systemctl"); err == nil { _ = exec.Command(path, "restart", "lpagent").Start() return } // No systemd (e.g. launchd/manual run): exit so the supervisor restarts us. os.Exit(0) }