Обновление: компоненты, которых не знает прежний root-установщик (igate-dist), — вторым заходом, сразу или после перезапуска

Старый bj-component-install отклонял обновление целиком из-за незнакомого igate-dist — вместе со своей
новой версией (рабочий сервер: установщик .06.2). installerSince: с какой версии установщик знает
компонент; отложенное ставится тем же нажатием после нового установщика, а если перезапускался
bj-server — само после старта (Update.Pending). Тест TestLKAgentEndToEnd: ждёт счётчик агента (гонка).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-06 23:44:55 +03:00
parent efa135b513
commit a9cb055504
4 changed files with 103 additions and 24 deletions
+72 -23
View File
@@ -45,6 +45,12 @@ const DefaultUpdateURL = "https://lic.zetit.ru/updates"
// installerKnowsAll — с этой версии root-установщик обновлений знает bj-installer и ставит выделенные модули.
const installerKnowsAll = "2026.09.25"
// installerSince — с какой версии root-установщик (bj-component-install) знает компонент. Старый установщик
// отклоняет обновление целиком, если в нём есть незнакомый компонент — вместе с собственной новой версией
// (06.10.2026 на рабочем сервере: установщик .06.2, «неизвестный компонент "igate-dist"»). Такие компоненты
// ставятся вторым заходом, уже новым установщиком — сразу или после перезапуска bj-server.
var installerSince = map[string]string{"bj-installer": installerKnowsAll, "igate-dist": "2026.10.06.15"}
type Updater struct{ rc *RuntimeConfig }
func NewUpdater(rc *RuntimeConfig) *Updater { return &Updater{rc: rc} }
@@ -294,26 +300,9 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
// из ядра модули: пока он сам не обновлён, такие компоненты откладываются (второй заход). Установщик
// новее этого умеет всё — обновление одним заходом, даже если вместе с ним обновляется и он сам
// (раньше любое его обновление делило установку на два раза: «скачал 24 МБ — снова „Обновить“ — ещё 6»).
installerOutdated := false
for _, c := range plan {
if c.Name == "bj-component-install" && c.Update && (c.Installed == "" || release.IsNewer(installerKnowsAll, c.Installed)) {
installerOutdated = true
}
}
var deferred []string
if installerOutdated {
kept := selected[:0]
for _, c := range selected {
if c.New || c.Name == "bj-installer" { // bj-installer прежний установщик обновлений не знает
deferred = append(deferred, c.Name)
continue
}
kept = append(kept, c)
}
selected = kept
if len(selected) == 0 {
return "", fmt.Errorf("сначала обновите установщик обновлений (bj-component-install): текущий не ставит модули %s", strings.Join(deferred, ", "))
}
selected, deferred := deferForInstaller(plan, selected)
if len(selected) == 0 {
return "", fmt.Errorf("сначала обновите установщик обновлений (bj-component-install): текущий не ставит %s", strings.Join(deferred, ", "))
}
cleanupStaging(24 * time.Hour)
@@ -411,10 +400,25 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
}
msg += "; перезапущены службы: " + strings.Join(units, ", ")
}
if len(deferred) > 0 {
msg += "; модули " + strings.Join(deferred, ", ") + " будут установлены после перезапуска bj-server новым установщиком"
}
msg += unlicensedNote(off)
if len(deferred) > 0 {
core := false
for _, n := range req.Names {
core = core || n == "bj-server"
}
if core { // bj-server перезапускается — доставит отложенное сам после старта (updateLoop)
cfg := u.rc.Snapshot().Update
cfg.Pending = deferred
_ = u.rc.SaveUpdateSettings(cfg)
return msg + "; " + strings.Join(deferred, ", ") + " — сразу после перезапуска bj-server, новым установщиком", nil
}
log.Printf("lk-gateway: установщик обновлён — второй заход: %s", strings.Join(deferred, ", "))
more, err := u.applyUpdate(ctx, deferred)
if err != nil {
return msg + "; " + strings.Join(deferred, ", ") + " не установлены: " + err.Error(), nil
}
return msg + "; затем " + more, nil
}
return msg, nil
}
@@ -585,6 +589,7 @@ func (u *Updater) updateLoop(ctx context.Context) {
cleanupStaging(time.Hour)
enforceModuleLicense(ctx, u.rc)
_, _ = u.ensureSplitModules(ctx)
u.applyPending(ctx)
check()
}
for {
@@ -597,6 +602,50 @@ func (u *Updater) updateLoop(ctx context.Context) {
}
}
// deferForInstaller — что из выбранного прежний root-установщик не поставит (installerSince, впервые
// выделенные модули): откладывается до второго захода, когда в первом обновится сам установщик.
func deferForInstaller(plan, selected []release.ComponentState) (kept []release.ComponentState, deferred []string) {
installerUpd, installerVer := false, ""
for _, c := range plan {
if c.Name == "bj-component-install" {
installerUpd, installerVer = c.Update, c.Installed
}
}
if !installerUpd {
return selected, nil
}
older := func(since string) bool { return installerVer == "" || release.IsNewer(since, installerVer) }
for _, c := range selected {
since, known := installerSince[c.Name]
if (known && older(since)) || (c.New && older(installerKnowsAll)) {
deferred = append(deferred, c.Name)
continue
}
kept = append(kept, c)
}
return kept, deferred
}
// applyPending — компоненты, отложенные прошлым обновлением до нового установщика (обновление шло вместе с
// bj-server и перезапустило его): ставятся сами, без второго нажатия «Обновить».
func (u *Updater) applyPending(ctx context.Context) {
cfg := u.rc.Snapshot().Update
if len(cfg.Pending) == 0 {
return
}
names := cfg.Pending
cfg.Pending = nil // один заход: не получилось — человек увидит компонент в списке обновлений
_ = u.rc.SaveUpdateSettings(cfg)
actx, cancel := context.WithTimeout(ctx, 30*time.Minute)
defer cancel()
msg, err := u.ApplyUpdate(actx, names)
if err != nil {
log.Printf("lk-gateway: отложенные компоненты %s не установлены: %v", strings.Join(names, ", "), err)
return
}
log.Printf("lk-gateway: отложенные компоненты: %s", msg)
}
// notifyUpdate — администратору системы: вышел выпуск, который на этом сервере ещё не установлен (по разу на выпуск).
func (u *Updater) notifyUpdate(st UpdateStatus) {
cfg := u.rc.Snapshot().Update