feat(update): независимые обновления компонентов bj

- internal/release/components.go: список компонентов (ядро bj-server, криптосервис, bj-lk,
  bj-npr, bj-quikexport) с путём и службой, установленные версии в components.json,
  расчёт обновлений по версии каждого файла и sha256 (без отката, модули — только если
  уже установлены на сервере)
- cmd/bj-component-install: root-установщик — сам перепроверяет подпись манифеста ключом
  /etc/bj/update.pub и sha256, копия прежнего файла, перезапуск только затронутых служб
- updater: bj-server (без прав root) скачивает в update-staging и передаёт установку
  root-воркеру (действие component-install); установка в фоне
- настройки: таблица компонентов с версиями, «Обновить» и «Обновить всё»
- bj-release: версия файла из <файл>.version; bj-keymedia-worker: component-install

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-15 20:16:36 +03:00
parent 5115ed7223
commit 2908697363
11 changed files with 720 additions and 114 deletions
+17 -8
View File
@@ -48,6 +48,7 @@ func main() {
func usage() {
fmt.Fprintln(os.Stderr, "bj-release keygen -out <prefix>")
fmt.Fprintln(os.Stderr, "bj-release build -dir <artifacts> -version <v> -channel <c> -key <priv> -keyid <id> -out <manifest.json> [-notes <txt>]")
fmt.Fprintln(os.Stderr, " версия отдельного файла — в <файл>.version рядом (иначе версия релиза -version)")
os.Exit(2)
}
@@ -97,11 +98,11 @@ func build(args []string) {
// Имена артефактов, которые издаём (логическое имя → ставить +x).
known := map[string]bool{
"bj-server": true, // Go-бинарь
"crypto-service.jar": false, // Java сайдкар
"install-validata.sh": true,
"install.sh": true,
"configure-ish.sql": false,
"bj-server": true, // Go-бинарь
"crypto-service.jar": false, // Java сайдкар
"install-validata.sh": true,
"install.sh": true,
"configure-ish.sql": false,
}
entries, err := os.ReadDir(*dir)
@@ -110,7 +111,7 @@ func build(args []string) {
}
var arts []release.Artifact
for _, e := range entries {
if e.IsDir() || e.Name() == "manifest.json" {
if e.IsDir() || e.Name() == "manifest.json" || strings.HasSuffix(e.Name(), ".version") {
continue
}
full := filepath.Join(*dir, e.Name())
@@ -119,6 +120,14 @@ func build(args []string) {
fatal("hash %s: %v", e.Name(), err)
}
exec, ok := known[e.Name()]
if c, isComp := release.ComponentByName(e.Name()); isComp {
exec, ok = c.Exec, true
}
// Своя версия компонента: <файл>.version рядом с файлом (независимые обновления).
ver := *version
if vb, err := os.ReadFile(full + ".version"); err == nil && strings.TrimSpace(string(vb)) != "" {
ver = strings.TrimSpace(string(vb))
}
if !ok {
// неизвестный файл — включаем, +x по расширению
exec = strings.HasSuffix(e.Name(), ".sh")
@@ -126,7 +135,7 @@ func build(args []string) {
arts = append(arts, release.Artifact{
Name: e.Name(),
File: e.Name(),
Version: *version,
Version: ver,
SHA256: sha,
Size: size,
Exec: exec,
@@ -159,7 +168,7 @@ func build(args []string) {
fmt.Printf("Манифест %s: версия %s, канал %s, артефактов %d, подписан ключом %s\n",
*out, *version, *channel, len(arts), *keyID)
for _, a := range arts {
fmt.Printf(" %-22s %10d B %s\n", a.Name, a.Size, a.SHA256[:16])
fmt.Printf(" %-22s %-10s %10d B %s\n", a.Name, a.Version, a.Size, a.SHA256[:16])
}
}