Обновление: модули вне лицензии пропускаются с пояснением «не входит в лицензию — свяжитесь с ZETIT», а не срывают обновление

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-09-24 22:17:29 +03:00
parent f1df6b529b
commit 86c48288a0
3 changed files with 50 additions and 2 deletions
+40
View File
@@ -97,6 +97,7 @@ func (u *Updater) CheckForUpdate(ctx context.Context) (UpdateStatus, error) {
st.Available, st.Notes = m.Version, m.Notes st.Available, st.Notes = m.Version, m.Notes
st.Components = release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion) st.Components = release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion)
release.MarkSplitModules(st.Components, u.allowed) release.MarkSplitModules(st.Components, u.allowed)
off := markUnlicensed(st.Components, u.allowed)
var upd []string var upd []string
avail := map[string]string{} avail := map[string]string{}
for _, c := range st.Components { for _, c := range st.Components {
@@ -115,10 +116,39 @@ func (u *Updater) CheckForUpdate(ctx context.Context) (UpdateStatus, error) {
} else { } else {
st.Message = "все установленные компоненты актуальны (релиз " + m.Version + ")" st.Message = "все установленные компоненты актуальны (релиз " + m.Version + ")"
} }
st.Message += unlicensedNote(off)
u.saveCheckResult(st, avail) u.saveCheckResult(st, avail)
return st, nil return st, nil
} }
// markUnlicensed — модули, установленные на сервере, но снятые с лицензии: не обновляются (сервер
// обновлений их файлы не отдаёт), в списке — с пояснением. Возвращает названия таких модулей.
func markUnlicensed(states []release.ComponentState, allowed func(string) bool) []string {
var off []string
for i := range states {
c := &states[i]
if c.Core || c.Feature == "" || c.Feature == "updates" || allowed(c.Feature) {
continue
}
c.Update, c.New = false, false
if c.Present {
c.Blocked = "не входит в лицензию — модуль отключён и не обновляется; чтобы подключить, свяжитесь с ZETIT"
off = append(off, "«"+c.Title+"»")
}
}
return off
}
func unlicensedNote(off []string) string {
if len(off) == 0 {
return ""
}
if len(off) == 1 {
return "; модуль " + off[0] + " не входит в лицензию — отключён и не обновлялся; чтобы подключить, свяжитесь с ZETIT"
}
return "; модули " + strings.Join(off, ", ") + " не входят в лицензию — отключены и не обновлялись; чтобы подключить, свяжитесь с ZETIT"
}
// updateChannel — канал обновлений установки (пусто = stable). // updateChannel — канал обновлений установки (пусто = stable).
func updateChannel(s UpdateSettings) string { func updateChannel(s UpdateSettings) string {
if c := strings.TrimSpace(s.Channel); c != "" { if c := strings.TrimSpace(s.Channel); c != "" {
@@ -174,6 +204,7 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
} }
plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion) plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion)
release.MarkSplitModules(plan, u.allowed) release.MarkSplitModules(plan, u.allowed)
off := markUnlicensed(plan, u.allowed)
want := map[string]bool{} want := map[string]bool{}
for _, n := range names { for _, n := range names {
want[n] = true want[n] = true
@@ -184,11 +215,16 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
case len(want) > 0 && !want[c.Name]: case len(want) > 0 && !want[c.Name]:
case c.Update: case c.Update:
selected = append(selected, c) selected = append(selected, c)
case want[c.Name] && c.Blocked != "":
return "", fmt.Errorf("%s: %s", c.Title, c.Blocked)
case want[c.Name]: case want[c.Name]:
return "", fmt.Errorf("%s: обновление не требуется (установлена %s, в релизе %s)", c.Title, pickVersion(c.Installed), pickVersion(c.Available)) return "", fmt.Errorf("%s: обновление не требуется (установлена %s, в релизе %s)", c.Title, pickVersion(c.Installed), pickVersion(c.Available))
} }
} }
if len(selected) == 0 { if len(selected) == 0 {
if len(off) > 0 {
return "обновлять нечего: компоненты лицензии актуальны (релиз " + m.Version + ")" + unlicensedNote(off), nil
}
return "", fmt.Errorf("нечего обновлять: все установленные компоненты актуальны (релиз %s)", m.Version) return "", fmt.Errorf("нечего обновлять: все установленные компоненты актуальны (релиз %s)", m.Version)
} }
// Прежний root-установщик не умеет впервые ставить выделенные из ядра модули: пока он сам не // Прежний root-установщик не умеет впервые ставить выделенные из ядра модули: пока он сам не
@@ -240,6 +276,7 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
progressTarget(c.Available) progressTarget(c.Available)
} }
} }
progressNote(strings.TrimPrefix(unlicensedNote(off), "; "))
progressStage(2, "Скачивание файлов") progressStage(2, "Скачивание файлов")
stopWatch := progressWatchDir(staging, total) stopWatch := progressWatchDir(staging, total)
for _, c := range selected { for _, c := range selected {
@@ -282,6 +319,7 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
if len(deferred) > 0 { if len(deferred) > 0 {
msg += "; модули " + strings.Join(deferred, ", ") + " будут установлены после перезапуска bj-server новым установщиком" msg += "; модули " + strings.Join(deferred, ", ") + " будут установлены после перезапуска bj-server новым установщиком"
} }
msg += unlicensedNote(off)
return msg, nil return msg, nil
} }
@@ -355,6 +393,7 @@ func updateComponentRows(rc *RuntimeConfig) ([]release.ComponentState, bool) {
} }
} }
release.MarkSplitModules(out, func(f string) bool { return moduleAllowed(rc, f) }) release.MarkSplitModules(out, func(f string) bool { return moduleAllowed(rc, f) })
markUnlicensed(out, func(f string) bool { return moduleAllowed(rc, f) })
// Последняя проверка не удалась — предлагать установку по старым данным нельзя: // Последняя проверка не удалась — предлагать установку по старым данным нельзя:
// кнопка ушла бы в тот же отказ. Показываем причину и просим проверить заново. // кнопка ушла бы в тот же отказ. Показываем причину и просим проверить заново.
if updateResultFailed(set.LastResult) { if updateResultFailed(set.LastResult) {
@@ -475,6 +514,7 @@ func (u *Updater) applyBundle(ctx context.Context, zipPath string) (string, erro
} }
plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion) plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion)
release.MarkSplitModules(plan, u.allowed) release.MarkSplitModules(plan, u.allowed)
markUnlicensed(plan, u.allowed)
var req struct { var req struct {
Names []string `json:"names"` Names []string `json:"names"`
New []string `json:"new,omitempty"` New []string `json:"new,omitempty"`
+7
View File
@@ -25,6 +25,7 @@ type updateProgressState struct {
Total int64 `json:"total"` Total int64 `json:"total"`
Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется) Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется)
Result string `json:"result,omitempty"` Result string `json:"result,omitempty"`
Note string `json:"note,omitempty"` // пояснение, которое нужно показать и после перезапуска ядра
Err string `json:"error,omitempty"` Err string `json:"error,omitempty"`
Started time.Time `json:"started"` Started time.Time `json:"started"`
Version string `json:"version"` // версия работающего bj-server Version string `json:"version"` // версия работающего bj-server
@@ -59,6 +60,12 @@ func progressTarget(v string) {
updProg.s.Target = v updProg.s.Target = v
} }
func progressNote(n string) {
updProg.Lock()
defer updProg.Unlock()
updProg.s.Note = n
}
func progressEnd(result string, err error) { func progressEnd(result string, err error) {
updProg.Lock() updProg.Lock()
defer updProg.Unlock() defer updProg.Unlock()
+3 -2
View File
@@ -599,7 +599,7 @@ label { font-size:13px; font-weight:500; }
doneEl.style.display='block'; doneEl.style.display='block';
} }
function run(form){ function run(form){
var startVer=null, target='', seenActive=false, postDone=false, lost=false, t0=Date.now(), seq0=null; var note='', startVer=null, target='', seenActive=false, postDone=false, lost=false, t0=Date.now(), seq0=null;
show(); stageEl.textContent='Отправка запроса…'; bar.style.width='3%'; detail.textContent=''; show(); stageEl.textContent='Отправка запроса…'; bar.style.width='3%'; detail.textContent='';
var xhr=new XMLHttpRequest(); xhr.open('POST', form.action); var xhr=new XMLHttpRequest(); xhr.open('POST', form.action);
var file=form.querySelector('input[type=file]'); var file=form.querySelector('input[type=file]');
@@ -613,8 +613,9 @@ label { font-size:13px; font-weight:500; }
fetch('/admin/setup/update/progress',{cache:'no-store',credentials:'same-origin'}).then(function(r){ if(!r.ok) throw 0; return r.json(); }).then(function(p){ fetch('/admin/setup/update/progress',{cache:'no-store',credentials:'same-origin'}).then(function(r){ if(!r.ok) throw 0; return r.json(); }).then(function(p){
if(startVer===null){ startVer=p.version; seq0=p.seq; } if(startVer===null){ startVer=p.version; seq0=p.seq; }
if(p.target) target=p.target; if(p.target) target=p.target;
if(p.note) note=p.note;
if(lost){ // bj вернулся после перезапуска if(lost){ // bj вернулся после перезапуска
if(!target || p.version===target || p.version!==startVer){ finish('Готово: bj перезапущен, работает версия '+p.version+'.', true); detail.textContent=''; return; } if(!target || p.version===target || p.version!==startVer){ finish('Готово: bj перезапущен, работает версия '+p.version+'.', true); detail.textContent=note ? note.charAt(0).toUpperCase()+note.slice(1)+'.' : ''; return; }
} }
var fresh = p.seq!==seq0 || seenActive || postDone; var fresh = p.seq!==seq0 || seenActive || postDone;
if(p.active){ seenActive=true; if(p.active){ seenActive=true;