«Подготовка модулей» между ключом продукта и мастером: bj узнаёт состав лицензии, ставит модули, которые в неё входят (из комплекта образа, если там тот же выпуск, — без скачивания), запускает их службы и открывает мастер; после активации ключа и при изменении состава лицензии модули ставятся сразу, а не при следующем запуске bj

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-05 19:25:17 +03:00
parent 64eb63efc2
commit 0dd38bbae4
8 changed files with 413 additions and 9 deletions
+42 -3
View File
@@ -20,6 +20,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release"
@@ -340,6 +341,10 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
skip := false
for _, a := range m.Artifacts {
if a.Name == c.Name {
if fromLocalBundle(a, staging) {
log.Printf("lk-gateway: %s %s — из комплекта образа, без скачивания", c.Name, pickVersion(c.Available))
continue
}
if _, err := cl.DownloadArtifact(ctx, a, staging); err != nil {
// Модуль сняли с лицензии, а bj ещё помнит прежний её состав (узнаёт при продлении):
// сервер обновлений файл не отдаёт — пропускаем модуль, остальное ставим.
@@ -408,10 +413,14 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
// allowed — разрешён ли лицензией признак компонента.
func (u *Updater) allowed(feature string) bool { return moduleAllowed(u.rc, feature) }
var splitModulesMu sync.Mutex
// ensureSplitModules — выделенные из ядра модули, которые входят в лицензию, но ещё не стоят на этом
// сервере (например, ядро обновилось прежним установщиком), ставятся сами: без своих служб разделы
// этих модулей в bj не работают. Раз за запуск bj-server; ошибки — только в журнал и «Последнюю проверку».
func (u *Updater) ensureSplitModules(ctx context.Context) {
func (u *Updater) ensureSplitModules(ctx context.Context) (string, error) {
splitModulesMu.Lock() // запуск bj, активация ключа и «Подготовка модулей» — по очереди: второй увидит, что всё уже стоит
defer splitModulesMu.Unlock()
var names []string
installed := release.LoadInstalled(release.InstalledFile)
for _, c := range release.Plan("", nil, installed, BuildVersion) {
@@ -432,7 +441,7 @@ func (u *Updater) ensureSplitModules(ctx context.Context) {
}
}
if len(names) == 0 {
return
return "", nil
}
cctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
@@ -446,6 +455,36 @@ func (u *Updater) ensureSplitModules(ctx context.Context) {
cfg := u.rc.Snapshot().Update
cfg.LastResult, cfg.LastCheck = msg, time.Now().UTC()
_ = u.rc.SaveUpdateSettings(cfg)
return msg, err
}
// localBundleDir — комплект компонентов, с которого сервер ставился с образа (bj-firstboot).
var localBundleDir = "/var/lib/bj/bundle"
// fromLocalBundle — файл компонента из комплекта образа, если это тот же файл, что в выпуске (совпадает
// sha256): модуль, который уже лежит на диске, не качается заново — после ввода ключа он ставится сразу.
func fromLocalBundle(a release.Artifact, staging string) bool {
src := filepath.Join(localBundleDir, filepath.Base(a.File))
sum, _, err := release.HashFile(src)
if err != nil || !strings.EqualFold(sum, a.SHA256) {
return false
}
in, err := os.Open(src)
if err != nil {
return false
}
defer in.Close()
dst := filepath.Join(staging, filepath.Base(a.File))
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o640)
if err != nil {
return false
}
if _, err := io.Copy(out, in); err != nil {
out.Close()
_ = os.Remove(dst)
return false
}
return out.Close() == nil
}
// updateComponentRows — компоненты для раздела «Обновления» без обращения к артефактории:
@@ -536,7 +575,7 @@ func (u *Updater) updateLoop(ctx context.Context) {
case <-time.After(time.Minute):
cleanupStaging(time.Hour)
enforceModuleLicense(ctx, u.rc)
u.ensureSplitModules(ctx)
_, _ = u.ensureSplitModules(ctx)
check()
}
for {