Обновления из файла для закрытого контура: пакет с портала → проверка подписи → ядро, потом модули
- release: пакет обновления (ExtractBundle/ReadStagedManifest/WriteBundle), Artifact.MinCore, план блокирует модуль, которому нужно ядро новее, чем будет после установки - bj-release: модули получают min_core = версия релиза - настройки → Обновления: «Обновить из файла» (zip до 2 ГБ), фон как у сетевого обновления, причина блокировки в таблице компонентов Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
@@ -11,6 +11,7 @@ package lkgateway
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -356,3 +357,106 @@ func (u *Updater) updateLoop(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyBundle — обновление из файла для закрытого контура: пакет с портала клиента
|
||||
// (подписанный манифест + файлы компонентов). Что ставить, решает тот же план, что и при
|
||||
// обновлении по сети: всё, что новее установленного, в порядке компонентов — ядро первым,
|
||||
// модули за ним. Модуль, которому нужно более новое ядро, чем будет после установки,
|
||||
// пропускается с объяснением.
|
||||
func (u *Updater) ApplyBundle(ctx context.Context, zipPath string) (string, error) {
|
||||
defer os.Remove(zipPath)
|
||||
if licensingEnabled(u.rc) {
|
||||
ls := licenseStatus(u.rc)
|
||||
if !ls.Valid {
|
||||
return "", fmt.Errorf("обновления заблокированы — лицензия: %s", ls.Message)
|
||||
}
|
||||
if !ls.AllowsUpdates {
|
||||
return "", fmt.Errorf("обновления не входят в план %q", ls.Plan)
|
||||
}
|
||||
}
|
||||
pubB64 := u.rc.Snapshot().Update.PublicKey
|
||||
if pubB64 == "" {
|
||||
pubB64 = DefaultUpdatePublicKey
|
||||
}
|
||||
pub, err := release.ParsePublicKey(pubB64)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("открытый ключ подписи релизов: %w", err)
|
||||
}
|
||||
cleanupStaging(24 * time.Hour)
|
||||
staging := filepath.Join(updateStagingDir, fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||
files, err := release.ExtractBundle(zipPath, staging)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(staging)
|
||||
return "", err
|
||||
}
|
||||
sm, m, err := release.ReadStagedManifest(staging, pub)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(staging)
|
||||
return "", err
|
||||
}
|
||||
_ = sm
|
||||
have := map[string]bool{}
|
||||
for _, f := range files {
|
||||
have[f] = true
|
||||
}
|
||||
plan := release.Plan("", m, release.LoadInstalled(release.InstalledFile), BuildVersion)
|
||||
release.MarkSplitModules(plan, u.allowed)
|
||||
var req struct {
|
||||
Names []string `json:"names"`
|
||||
New []string `json:"new,omitempty"`
|
||||
}
|
||||
var list, skipped []string
|
||||
for _, c := range plan {
|
||||
if c.Blocked != "" {
|
||||
skipped = append(skipped, c.Title+": "+c.Blocked)
|
||||
continue
|
||||
}
|
||||
if !c.Update {
|
||||
continue
|
||||
}
|
||||
if !have[c.Name] { // в пакете может не быть файла модуля (не входит в лицензию клиента)
|
||||
continue
|
||||
}
|
||||
if !u.allowed(c.Feature) && c.Feature != "updates" {
|
||||
skipped = append(skipped, c.Title+": не входит в лицензию")
|
||||
continue
|
||||
}
|
||||
req.Names = append(req.Names, c.Name)
|
||||
if c.New {
|
||||
req.New = append(req.New, c.Name)
|
||||
}
|
||||
list = append(list, c.Name+" "+c.Available)
|
||||
}
|
||||
if len(req.Names) == 0 {
|
||||
_ = os.RemoveAll(staging)
|
||||
msg := fmt.Sprintf("в пакете релиз %s: нечего обновлять, все компоненты актуальны", m.Version)
|
||||
if len(skipped) > 0 {
|
||||
msg += "; пропущено: " + strings.Join(skipped, "; ")
|
||||
}
|
||||
return "", errors.New(msg)
|
||||
}
|
||||
rb, _ := json.Marshal(req)
|
||||
if err := os.WriteFile(filepath.Join(staging, "request.json"), rb, 0o640); err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("lk-gateway: обновление из файла (релиз %s): %s — передано root-установщику", m.Version, strings.Join(list, ", "))
|
||||
wctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||
defer cancel()
|
||||
res, err := runKeymediaOp(wctx, "component-install", map[string]string{"staging": staging})
|
||||
_ = os.RemoveAll(staging)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("установка: %w", err)
|
||||
}
|
||||
msg := fmt.Sprintf("из файла (релиз %s) установлено: %s", m.Version, strings.Join(list, ", "))
|
||||
if r, ok := res["restarted"].([]any); ok && len(r) > 0 {
|
||||
var units []string
|
||||
for _, x := range r {
|
||||
units = append(units, fmt.Sprint(x))
|
||||
}
|
||||
msg += "; перезапущены службы: " + strings.Join(units, ", ")
|
||||
}
|
||||
if len(skipped) > 0 {
|
||||
msg += "; пропущено: " + strings.Join(skipped, "; ")
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user