Экспорт QUIK — отдельная служба bj-quikexport; модули, выделенные из ядра, ставятся с обновлением

bj-quikexport (modules/quikexport + cmd/bj-quikexport, 127.0.0.1:8084): управление ARQA
quikexport перенесено из bj-server. Настройки у службы в /var/lib/bj/.bj/quik-export/settings.json
(при первом запуске переносятся из setup.json), раздел отдаётся фрагментом с индикатором меню,
действия отвечают X-BJ-Flash. bj-server встраивает раздел в «Настройку», проксирует формы
/admin/setup/quikexport/… и передаёт лицензию (X-BJ-Licensed). Общая проверка служебного
ключа — modules/modkit (им пользуется и bj-npr), общее для прокси — lkgateway/modules.go.

Выделенные из ядра модули (bj-npr, bj-quikexport: Component.FromCore) при обновлении ставятся и
там, где их ещё нет, если входят в лицензию: release.MarkSplitModules, request.json "new",
root-установщик ставит и включает unit-файл из комплекта. Если в обновлении меняется сам
установщик, модули ставятся после перезапуска bj-server (ensureSplitModules).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-16 01:15:27 +03:00
parent 5527a464ca
commit 97e99b72c7
26 changed files with 1095 additions and 303 deletions
+70 -2
View File
@@ -95,13 +95,16 @@ func (u *Updater) CheckForUpdate(ctx context.Context) (UpdateStatus, error) {
}
st.Available, st.Notes = m.Version, m.Notes
st.Components = release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion)
release.MarkSplitModules(st.Components, u.allowed)
var upd []string
avail := map[string]string{}
for _, c := range st.Components {
if c.Available != "" {
avail[c.Name] = c.Available
}
if c.Update {
if c.New {
upd = append(upd, c.Name+" "+c.Available+" (установка модуля)")
} else if c.Update {
upd = append(upd, c.Name+" "+c.Available)
}
}
@@ -150,6 +153,7 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro
return "", fmt.Errorf("манифест: %w", err)
}
plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion)
release.MarkSplitModules(plan, u.allowed)
want := map[string]bool{}
for _, n := range names {
want[n] = true
@@ -167,6 +171,29 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro
if len(selected) == 0 {
return "", fmt.Errorf("нечего обновлять: все установленные компоненты актуальны (релиз %s)", m.Version)
}
// Прежний root-установщик не умеет впервые ставить выделенные из ядра модули: пока он сам не
// обновлён, такие модули откладываются — их поставит ensureSplitModules после перезапуска.
installerOutdated := false
for _, c := range plan {
if c.Name == "bj-component-install" && c.Update {
installerOutdated = true
}
}
var deferred []string
if installerOutdated {
kept := selected[:0]
for _, c := range selected {
if c.New {
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, ", "))
}
}
cleanupStaging(24 * time.Hour)
staging := filepath.Join(updateStagingDir, fmt.Sprintf("%d", time.Now().UnixNano()))
@@ -180,6 +207,7 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro
var list []string
var req struct {
Names []string `json:"names"`
New []string `json:"new,omitempty"`
}
for _, c := range selected {
for _, a := range m.Artifacts {
@@ -190,6 +218,9 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro
}
}
req.Names = append(req.Names, c.Name)
if c.New {
req.New = append(req.New, c.Name)
}
list = append(list, c.Name+" "+c.Available)
}
rb, _ := json.Marshal(req)
@@ -212,9 +243,42 @@ 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 новым установщиком"
}
return msg, nil
}
// allowed — разрешён ли лицензией признак компонента.
func (u *Updater) allowed(feature string) bool { return moduleAllowed(u.rc, feature) }
// ensureSplitModules — выделенные из ядра модули, которые входят в лицензию, но ещё не стоят на этом
// сервере (например, ядро обновилось прежним установщиком), ставятся сами: без своих служб разделы
// этих модулей в bj не работают. Раз за запуск bj-server; ошибки — только в журнал и «Последнюю проверку».
func (u *Updater) ensureSplitModules(ctx context.Context) {
var names []string
for _, c := range release.Plan("", nil, release.LoadInstalled(release.InstalledFile), BuildVersion) {
if c.FromCore && !c.Present && u.allowed(c.Feature) {
names = append(names, c.Name)
}
}
if len(names) == 0 {
return
}
cctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
msg, err := u.ApplyUpdate(cctx, names)
if err != nil {
msg = "Установка модулей " + strings.Join(names, ", ") + " не выполнена: " + err.Error()
} else {
msg = "Установка модулей: " + msg
}
log.Printf("lk-gateway: %s", msg)
cfg := u.rc.Snapshot().Update
cfg.LastResult, cfg.LastCheck = msg, time.Now().UTC()
_ = u.rc.SaveUpdateSettings(cfg)
}
// updateComponentRows — компоненты для раздела «Обновления» без обращения к артефактории:
// установленное на этом сервере и версии из последней проверки.
func updateComponentRows(rc *RuntimeConfig) ([]release.ComponentState, bool) {
@@ -227,9 +291,12 @@ func updateComponentRows(rc *RuntimeConfig) ([]release.ComponentState, bool) {
r.Update = r.Available != "" && (r.Present || r.Core) && (r.Installed == "" || release.IsNewer(r.Available, r.Installed))
if r.Present || r.Core || r.Available != "" {
out = append(out, r)
any = any || r.Update
}
}
release.MarkSplitModules(out, func(f string) bool { return moduleAllowed(rc, f) })
for _, r := range out {
any = any || r.Update
}
return out, any
}
@@ -273,6 +340,7 @@ func (u *Updater) updateLoop(ctx context.Context) {
return
case <-time.After(time.Minute):
cleanupStaging(time.Hour)
u.ensureSplitModules(ctx)
check()
}
for {