Агенты подхватывают изменения с сервера: каталог и самообновление
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 -5
View File
@@ -94,7 +94,38 @@ func OpenStore(path string) (*Store, error) {
db.Close()
return nil, fmt.Errorf("миграция: %w", err)
}
return &Store{db: db}, nil
st := &Store{db: db}
// колонки, добавленные после первых релизов: у существующих баз их нет
for _, col := range []struct{ table, name, def string }{
{"clients", "auto_update", "INTEGER NOT NULL DEFAULT 1"},
{"clients", "agent_version", "TEXT NOT NULL DEFAULT ''"},
} {
if err := st.ensureColumn(col.table, col.name, col.def); err != nil {
db.Close()
return nil, fmt.Errorf("миграция %s.%s: %w", col.table, col.name, err)
}
}
return st, nil
}
func (s *Store) ensureColumn(table, name, def string) error {
rows, err := s.db.Query(`SELECT name FROM pragma_table_info(?)`, table)
if err != nil {
return err
}
found := false
for rows.Next() {
var n string
if rows.Scan(&n) == nil && n == name {
found = true
}
}
rows.Close()
if found {
return nil
}
_, err = s.db.Exec(fmt.Sprintf(`ALTER TABLE %s ADD COLUMN %s %s`, table, name, def))
return err
}
func (s *Store) Close() error { return s.db.Close() }
@@ -119,17 +150,20 @@ type Client struct {
LastErrorAt int64
StaleNotified bool
Disabled bool
AutoUpdate bool // агент сам обновляется до версии сервера
AgentVersion string // версия агента при последнем обращении
}
const clientCols = `id, name, domain, token_hash, config_yaml, keep_daily, keep_weekly, keep_monthly,
heavy_keep, heavy_every_days, stale_hours, created_at, last_backup_at, last_heavy_at, last_seen_at,
last_error, last_error_at, stale_notified, disabled`
last_error, last_error_at, stale_notified, disabled, auto_update, agent_version`
func scanClient(row interface{ Scan(...any) error }) (*Client, error) {
var c Client
err := row.Scan(&c.ID, &c.Name, &c.Domain, &c.TokenHash, &c.ConfigYAML, &c.KeepDaily, &c.KeepWeekly,
&c.KeepMonthly, &c.HeavyKeep, &c.HeavyEveryDays, &c.StaleHours, &c.CreatedAt, &c.LastBackupAt,
&c.LastHeavyAt, &c.LastSeenAt, &c.LastError, &c.LastErrorAt, &c.StaleNotified, &c.Disabled)
&c.LastHeavyAt, &c.LastSeenAt, &c.LastError, &c.LastErrorAt, &c.StaleNotified, &c.Disabled,
&c.AutoUpdate, &c.AgentVersion)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
@@ -172,9 +206,10 @@ func (s *Store) ListClients() ([]*Client, error) {
func (s *Store) UpdateClientSettings(c *Client) error {
_, err := s.db.Exec(`UPDATE clients SET name = ?, config_yaml = ?, keep_daily = ?, keep_weekly = ?,
keep_monthly = ?, heavy_keep = ?, heavy_every_days = ?, stale_hours = ?, disabled = ? WHERE id = ?`,
keep_monthly = ?, heavy_keep = ?, heavy_every_days = ?, stale_hours = ?, disabled = ?, auto_update = ?
WHERE id = ?`,
c.Name, c.ConfigYAML, c.KeepDaily, c.KeepWeekly, c.KeepMonthly, c.HeavyKeep, c.HeavyEveryDays,
c.StaleHours, c.Disabled, c.ID)
c.StaleHours, c.Disabled, c.AutoUpdate, c.ID)
return err
}
@@ -193,6 +228,11 @@ func (s *Store) TouchClient(id int64) error {
return err
}
func (s *Store) SetAgentVersion(id int64, version string) error {
_, err := s.db.Exec(`UPDATE clients SET agent_version = ? WHERE id = ?`, version, id)
return err
}
func (s *Store) MarkClientSuccess(id int64, at int64, heavy bool) error {
q := `UPDATE clients SET last_backup_at = ?1, last_error = '', last_error_at = 0, stale_notified = 0`
if heavy {