From a9cb055504770f9e3b78d0e0d212642d3c0d3864 Mon Sep 17 00:00:00 2001 From: zuevav Date: Tue, 6 Oct 2026 23:44:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5:=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D1=8B,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BD=D0=B5=20=D0=B7=D0=BD=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B6=D0=BD=D0=B8=D0=B9=20root-=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D1=89=D0=B8=D0=BA=20(igate-dist)?= =?UTF-8?q?,=20=E2=80=94=20=D0=B2=D1=82=D0=BE=D1=80=D1=8B=D0=BC=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=85=D0=BE=D0=B4=D0=BE=D0=BC,=20=D1=81=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D1=83=20=D0=B8=D0=BB=D0=B8=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Старый bj-component-install отклонял обновление целиком из-за незнакомого igate-dist — вместе со своей новой версией (рабочий сервер: установщик .06.2). installerSince: с какой версии установщик знает компонент; отложенное ставится тем же нажатием после нового установщика, а если перезапускался bj-server — само после старта (Update.Pending). Тест TestLKAgentEndToEnd: ждёт счётчик агента (гонка). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- internal/lkgateway/lkagent_test.go | 8 ++- internal/lkgateway/notifyadmin_test.go | 22 ++++++ internal/lkgateway/runtimeconfig.go | 2 + internal/lkgateway/updater.go | 95 +++++++++++++++++++------- 4 files changed, 103 insertions(+), 24 deletions(-) diff --git a/internal/lkgateway/lkagent_test.go b/internal/lkgateway/lkagent_test.go index b9d8387..a7326a8 100644 --- a/internal/lkgateway/lkagent_test.go +++ b/internal/lkgateway/lkagent_test.go @@ -63,7 +63,13 @@ func TestLKAgentEndToEnd(t *testing.T) { if resp.StatusCode != http.StatusOK || !out.OK || out.Mode != "agent" || out.BJ["service"] != "bj-server" { t.Fatalf("ответ ядра через очередь: код %d, %+v", resp.StatusCode, out) } - if st := lkAgentStatus(rc.Snapshot()); !st.Connected || st.Served == 0 { + // счётчик агент увеличивает после того, как отдал ответ кабинету, — клиент может получить ответ раньше + st := lkAgentStatus(rc.Snapshot()) + for i := 0; i < 40 && (!st.Connected || st.Served == 0); i++ { + time.Sleep(50 * time.Millisecond) + st = lkAgentStatus(rc.Snapshot()) + } + if !st.Connected || st.Served == 0 { t.Fatalf("состояние канала: %+v", st) } diff --git a/internal/lkgateway/notifyadmin_test.go b/internal/lkgateway/notifyadmin_test.go index 575e4f8..3ff0066 100644 --- a/internal/lkgateway/notifyadmin_test.go +++ b/internal/lkgateway/notifyadmin_test.go @@ -1,6 +1,7 @@ package lkgateway import ( + "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release" "net/http" "net/http/httptest" "path/filepath" @@ -184,3 +185,24 @@ func TestCreateUserLoginTaken(t *testing.T) { t.Fatal("один администратор — шаг не выполнен") } } + +// Старый root-установщик не знает igate-dist (рабочий сервер 06.10.2026: установщик .06.2) — сначала ставится +// новый установщик, igate-dist откладывается на второй заход; новый установщик — всё одним заходом. +func TestDeferForInstaller(t *testing.T) { + st := func(name, inst string, upd, isNew bool) release.ComponentState { + return release.ComponentState{Component: release.Component{Name: name}, Installed: inst, Update: upd, New: isNew} + } + plan := []release.ComponentState{st("bj-component-install", "2026.10.06.2", true, false), st("igate-dist", "", true, true), st("bj-server", "2026.10.06.26", true, false)} + kept, deferred := deferForInstaller(plan, plan) + if len(kept) != 2 || len(deferred) != 1 || deferred[0] != "igate-dist" { + t.Fatalf("старый установщик: оставлено %v, отложено %v", kept, deferred) + } + plan[0] = st("bj-component-install", "2026.10.06.15", true, false) + if kept, deferred := deferForInstaller(plan, plan); len(kept) != 3 || len(deferred) != 0 { + t.Fatalf("установщик знает igate-dist: оставлено %d, отложено %v", len(kept), deferred) + } + plan[0] = st("bj-component-install", "2026.10.06.2", false, false) // установщик не обновляется — откладывать нечем + if kept, deferred := deferForInstaller(plan, plan); len(kept) != 3 || len(deferred) != 0 { + t.Fatalf("без нового установщика: оставлено %d, отложено %v", len(kept), deferred) + } +} diff --git a/internal/lkgateway/runtimeconfig.go b/internal/lkgateway/runtimeconfig.go index 458b5fd..e53b319 100644 --- a/internal/lkgateway/runtimeconfig.go +++ b/internal/lkgateway/runtimeconfig.go @@ -113,6 +113,8 @@ type UpdateSettings struct { ComponentsChannel string `json:"components_channel,omitempty"` // Notified — выпуск, о котором уже написали администратору («Доступно обновление bj»): по разу на выпуск. Notified string `json:"notified,omitempty"` + // Pending — компоненты, отложенные до нового установщика обновлений (ставятся после перезапуска bj-server). + Pending []string `json:"pending,omitempty"` } // NewsSettings — лента новостей (события системы, окна техработ, обновления diff --git a/internal/lkgateway/updater.go b/internal/lkgateway/updater.go index b741358..c959b43 100644 --- a/internal/lkgateway/updater.go +++ b/internal/lkgateway/updater.go @@ -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