Агенты подхватывают изменения с сервера: каталог и самообновление
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

+45 -3
View File
@@ -9,6 +9,7 @@ import (
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
@@ -17,8 +18,20 @@ import (
type AgentConfig struct {
Domain string `json:"domain"`
ConfigYAML string `json:"config_yaml"`
ConfigYAML string `json:"config_yaml"` // только переопределения клиента (для старых агентов)
HeavyDue bool `json:"heavy_due"`
// CatalogYAML — итоговый каталог: встроенный каталог сервера + переопределения клиента.
// Агент берёт его вместо своего встроенного, поэтому новые категории приходят без обновления агента.
CatalogYAML string `json:"catalog_yaml,omitempty"`
ServerVersion string `json:"server_version,omitempty"`
Update *AgentUpdate `json:"update,omitempty"` // есть, если агенту пора обновиться
}
// AgentUpdate — новая версия агента. Path — путь на том же сервере (/dl/...), чужие адреса агент не принимает.
type AgentUpdate struct {
Version string `json:"version"`
Path string `json:"path"`
SHA256 string `json:"sha256"`
}
type PartInfo struct {
@@ -100,9 +113,38 @@ func jsonBody(v any) io.Reader {
return bytes.NewReader(data)
}
func (c *Client) AgentConfig(ctx context.Context) (*AgentConfig, error) {
// AgentConfig запрашивает настройки агента; arch и version нужны серверу, чтобы предложить обновление.
func (c *Client) AgentConfig(ctx context.Context, arch, version string) (*AgentConfig, error) {
var out AgentConfig
return &out, c.do(ctx, http.MethodGet, "/api/v1/agent/config", nil, &out)
q := url.Values{}
if arch != "" {
q.Set("arch", arch)
}
if version != "" {
q.Set("version", version)
}
path := "/api/v1/agent/config"
if len(q) > 0 {
path += "?" + q.Encode()
}
return &out, c.do(ctx, http.MethodGet, path, nil, &out)
}
// Download открывает поток файла с этого же сервера (path вида /dl/...).
func (c *Client) Download(ctx context.Context, path string) (io.ReadCloser, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.Server+path, nil)
if err != nil {
return nil, err
}
resp, err := c.HTTP.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, &Error{Status: resp.StatusCode, Msg: resp.Status}
}
return resp.Body, nil
}
func (c *Client) CreateBackup(ctx context.Context, m *manifest.Manifest) (int64, error) {