Агенты подхватывают изменения с сервера: каталог и самообновление
CI / test (push) Successful in 1m29s

- сервер отдаёт агенту итоговый каталог (встроенный каталог сервера +
  переопределения клиента); если каталог не подходит версии агента —
  встроенный каталог агента и предупреждение
- самообновление перед бэкапом: версия сервера новее → скачивание с того же
  сервера (только /dl/...), проверка sha256 и запуска новой версии, подмена
  с откатом, перезапуск через exec; при ошибке бэкап идёт текущей версией
- галочка «Автообновление» у клиента (по умолчанию включена), версия агента
  в списке и карточке, отставшие подсвечены; версия сервера в шапке
- миграция существующей базы: колонки auto_update и agent_version
- backupctl backup --no-update

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-17 15:27:31 +03:00
1 parent 1947b26913
commit 488abc2d34
21 files changed
+674 -51

No files matched your search

+48 -1
View File
@@ -15,10 +15,14 @@ import (
"regexp"
"slices"
"strconv"
"strings"
"time"
"gitea.hsrv.site/mrleo1nid/backup-master/internal/api"
"gitea.hsrv.site/mrleo1nid/backup-master/internal/catalog"
"gitea.hsrv.site/mrleo1nid/backup-master/internal/manifest"
"gitea.hsrv.site/mrleo1nid/backup-master/internal/version"
"gopkg.in/yaml.v3"
)
type ctxKey int
@@ -79,7 +83,50 @@ func (s *Server) apiAgentConfig(w http.ResponseWriter, r *http.Request) {
c := clientFrom(r)
due := c.HeavyEveryDays >= 0 &&
time.Since(time.Unix(c.LastHeavyAt, 0)) >= time.Duration(c.HeavyEveryDays)*24*time.Hour-time.Hour
jsonResp(w, http.StatusOK, AgentConfigResponse{Domain: c.Domain, ConfigYAML: c.ConfigYAML, HeavyDue: due})
resp := AgentConfigResponse{Domain: c.Domain, ConfigYAML: c.ConfigYAML, HeavyDue: due, ServerVersion: s.cfg.Version}
agentVer := r.URL.Query().Get("version")
if agentVer != "" && agentVer != c.AgentVersion {
_ = s.st.SetAgentVersion(c.ID, agentVer)
}
// итоговый каталог: встроенный каталог сервера + переопределения клиента
if cat, err := s.clientCatalog(c); err != nil {
log.Printf("каталог %s: %v", c.Domain, err)
} else {
resp.CatalogYAML = cat
}
arch := r.URL.Query().Get("arch")
if c.AutoUpdate && agentVer != "" && version.ShouldUpdate(s.cfg.Version, agentVer) {
if p, ok := s.binaryPath(arch); ok {
if sum, err := s.binarySHA256(p); err == nil {
resp.Update = &api.AgentUpdate{Version: s.cfg.Version, Path: "/dl/backupctl-linux-" + arch, SHA256: sum}
} else {
log.Printf("sha256 %s: %v", p, err)
}
}
}
jsonResp(w, http.StatusOK, resp)
}
func (s *Server) clientCatalog(c *Client) (string, error) {
cat, err := catalog.Default()
if err != nil {
return "", err
}
if strings.TrimSpace(c.ConfigYAML) != "" {
over, err := catalog.Parse([]byte(c.ConfigYAML))
if err != nil {
return "", err
}
cat = catalog.Merge(cat, over)
}
if err := cat.Validate(); err != nil {
return "", err
}
data, err := yaml.Marshal(cat)
return string(data), err
}
var partNameRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`)