Files
pzmanager/internal/api/handlers_update.go
T
mrleo1nidandClaude Opus 5 1e0b0a8d2b Кнопка обновления обновляет всё, а не только бинарник
pzmanager-admin update больше не повторяет логику установки: он забирает
из релиза get.sh (сверив sha256) и отдаёт работу ему. Поэтому обновление
из панели теперь приводит в порядок и скрипты установки, и systemd-юнит —
раньше они застывали на версии первой установки, а вместе с ними
терялась, например, локальная копия установщика для удаления.

Работа уходит в разовый юнит через systemd-run. Иначе она не пережила бы
сама себя: панель вызывает скрипт через sudo, тот живёт в cgroup её юнита,
и остановка службы установщиком убила бы его на середине. Команда restart
по той же причине получила --no-block.

Установщик стал понятнее в двух местах, о которые легко споткнуться:
- повторная установка той же сборки больше не роняет игровой сервер —
  если sha256 бинарника совпал, служба не останавливается вовсе;
- после обновления он явно пишет, что запускает службу, и проверяет, что
  она поднялась. Раньше systemctl start отрабатывал молча, и по выводу
  нельзя было понять, работает панель или нет. Службу, остановленную не
  установщиком, он по-прежнему не поднимает самовольно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 20:28:42 +03:00

82 lines
3.1 KiB
Go

package api
import (
"context"
"errors"
"net/http"
"time"
"github.com/mrleo1nid/pzmanager/internal/pzserver"
"github.com/mrleo1nid/pzmanager/internal/updater"
)
// handleCheckUpdate спрашивает у репозитория последнюю версию.
func (s *Server) handleCheckUpdate(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, s.updater.Check(r.Context()))
}
// handleApplyUpdate качает новую версию и перезапускает панель. Ответ уходит
// раньше, чем работа заканчивается: обновление убивает текущий процесс.
func (s *Server) handleApplyUpdate(w http.ResponseWriter, r *http.Request) {
var body struct {
Version string `json:"version"`
}
if r.ContentLength > 0 && !decodeJSON(w, r, &body) {
return
}
if !s.updater.CanApply(r.Context()) {
writeError(w, http.StatusPreconditionFailed, updater.ErrNoAdmin.Error())
return
}
name := "Обновление панели"
err := s.jobs.Start(name, func(ctx context.Context, log func(string)) error {
// Игровой сервер останавливаем сами: иначе это сделает systemd уже в
// момент перезапуска, и ожидание растянется на таймаут остановки.
s.stopServerBefore(log, "обновлением")
log("Запускаю обновление — оно идёт отдельной службой и переживёт перезапуск панели")
log("Подробности: journalctl -u pzmanager-update -f")
return s.updater.Apply(ctx, log, body.Version)
})
if err != nil {
writeOpError(w, err)
return
}
writeJSON(w, http.StatusAccepted, map[string]any{"task": name})
}
// handleRestartManager перезапускает саму панель.
func (s *Server) handleRestartManager(w http.ResponseWriter, r *http.Request) {
if !s.updater.CanApply(r.Context()) {
writeError(w, http.StatusPreconditionFailed, updater.ErrNoAdmin.Error())
return
}
name := "Перезапуск панели"
err := s.jobs.Start(name, func(ctx context.Context, log func(string)) error {
s.stopServerBefore(log, "перезапуском")
log("Перезапускаю панель")
return s.updater.Restart(ctx, log)
})
if err != nil {
writeOpError(w, err)
return
}
writeJSON(w, http.StatusAccepted, map[string]any{"task": name})
}
// stopServerBefore корректно гасит игровой сервер перед тем, как панель уйдёт
// в перезапуск: мир сохраняется, а systemd не ждёт таймаут остановки.
func (s *Server) stopServerBefore(log func(string), what string) {
if s.sup.State() == pzserver.StateStopped {
return
}
log("Останавливаю игровой сервер перед " + what)
if err := s.sup.Stop(); err != nil && !errors.Is(err, pzserver.ErrNotRunning) {
log("Остановить сервер не удалось: " + err.Error())
return
}
s.sup.WaitStopped(30 * time.Second)
}