Обновления несут установщик: bj-installer — компонент ядра, refresh-system переписывает службы и скрипты; мастер предлагает обновиться сразу после ввода ключа

Правки установки приезжают обновлением с сервера лицензий, образ ISO пересобирать не нужно.
Прежний установщик обновлений bj-installer не знает — он откладывается и ставится после
перезапуска bj-server (ensureSplitModules).

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 17:44:28 +03:00
parent 222699168d
commit fdd70c06dd
6 changed files with 74 additions and 6 deletions
+23
View File
@@ -81,6 +81,8 @@ func main() {
switch os.Args[1] {
case "install":
cmdInstall(os.Args[2:])
case "refresh-system":
os.Exit(refreshSystem())
case "first-run-code":
cmdFirstRunCode(os.Args[2:])
case "version":
@@ -93,6 +95,7 @@ func main() {
func usage() {
fmt.Fprintln(os.Stderr, "bj-installer install --code BJ-XXXX-XXXX-XXXX [--server https://lic.zetit.ru] [--channel stable] [--dry-run]")
fmt.Fprintln(os.Stderr, "bj-installer first-run-code")
fmt.Fprintln(os.Stderr, "bj-installer refresh-system (переписать службы и скрипты из этой версии установщика)")
fmt.Fprintln(os.Stderr, "bj-installer version")
os.Exit(2)
}
@@ -742,6 +745,26 @@ func (in *installer) stepServices() (string, error) {
return strings.Join(files, ", ") + "; криптосервис включается после настройки ключей в bj", nil
}
// refreshSystem — «bj-installer refresh-system»: переписать службы systemd и служебные скрипты
// из этой версии установщика (вызывается root-установщиком обновлений после обновления самого
// bj-installer). Работающие службы не перезапускаются: ядро перезапустит обновление, а служба
// root-операций bj-keymedia — через минуту, когда обновление, которое она выполняет, закончится.
func refreshSystem() int {
if os.Geteuid() != 0 {
fmt.Fprintln(os.Stderr, "refresh-system: нужны права root")
return 1
}
in := &installer{}
msg, err := in.stepServices()
if err != nil {
fmt.Fprintln(os.Stderr, "refresh-system:", err)
return 1
}
_, _ = run("systemd-run", "--on-active=60", "--unit=bj-keymedia-refresh", "systemctl", "try-restart", "bj-keymedia.service")
fmt.Println("системные файлы обновлены:", msg)
return 0
}
func (in *installer) stepFirstRun() (string, error) {
code, err := in.newFirstRunCode()
if err != nil {
+14 -4
View File
@@ -169,10 +169,11 @@ type WizardData struct {
Lic LicenseStatus
DepoLicensed bool
DepoDB string
ValidataVer string // версия установленной «Валидаты Клиент L» (пусто — не установлена)
CryptoSvc string // состояние службы bj-crypto (systemctl is-active)
PGSummary string // куда подключена база bj, без пароля
AutoDone []string // шаги до текущего, уже выполненные без участия пользователя, — с пояснением
ValidataVer string // версия установленной «Валидаты Клиент L» (пусто — не установлена)
CryptoSvc string // состояние службы bj-crypto (systemctl is-active)
PGSummary string // куда подключена база bj, без пароля
Update *UpdateStatus // шаг 1: есть ли на сервере обновлений версия новее, чем в образе
AutoDone []string // шаги до текущего, уже выполненные без участия пользователя, — с пояснением
Certs []cryptocli.Certificate
Flash string
Done struct {
@@ -263,6 +264,15 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
}
}
d.Step = step
if step == 1 && d.Done.License {
// Образ ставит ядро той версии, с которой собран; всё, что вышло позже (исправления ядра,
// установщика и служб), приезжает обновлением — предлагаем его сразу после ввода ключа.
ctx, cancel := context.WithTimeout(r.Context(), 8*time.Second)
if st, err := NewUpdater(h.rc).CheckForUpdate(ctx); err == nil {
d.Update = &st
}
cancel()
}
d.AutoDone = wizardAutoDone(&d)
render(w, h.tpl.a.wizard, d)
}
+15 -2
View File
@@ -195,7 +195,7 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro
if installerOutdated {
kept := selected[:0]
for _, c := range selected {
if c.New {
if c.New || c.Name == "bj-installer" { // bj-installer прежний установщик обновлений не знает
deferred = append(deferred, c.Name)
continue
}
@@ -269,11 +269,24 @@ func (u *Updater) allowed(feature string) bool { return moduleAllowed(u.rc, feat
// этих модулей в bj не работают. Раз за запуск bj-server; ошибки — только в журнал и «Последнюю проверку».
func (u *Updater) ensureSplitModules(ctx context.Context) {
var names []string
for _, c := range release.Plan("", nil, release.LoadInstalled(release.InstalledFile), BuildVersion) {
installed := release.LoadInstalled(release.InstalledFile)
for _, c := range release.Plan("", nil, installed, BuildVersion) {
if c.FromCore && !c.Present && u.allowed(c.Feature) {
names = append(names, c.Name)
}
}
// bj-installer обновлением ещё не ставился (сервер с образа или обновлён прежним установщиком
// обновлений): ставим сразу — с ним приезжают свежие службы и скрипты. Только если на сервере
// обновлений он новее, иначе ApplyUpdate вернёт «обновление не требуется» — это не ошибка.
if _, ok := installed["bj-installer"]; !ok && licenseStatus(u.rc).AllowsUpdates {
if st, err := u.CheckForUpdate(ctx); err == nil {
for _, c := range st.Components {
if c.Name == "bj-installer" && c.Update {
names = append(names, c.Name)
}
}
}
}
if len(names) == 0 {
return
}
@@ -55,6 +55,16 @@
<strong>Зачем.</strong> От лицензии зависят модули (обмен с DEPO и QUIK, личный кабинет, НПР/УДС) и получение обновлений: bj скачает и поставит модули, входящие в лицензию, автоматически.<br>
<strong>Где взять.</strong> Портал клиента <a href="https://lic.zetit.ru/portal/" target="_blank">lic.zetit.ru/portal</a> или письмо от ZETIT.</div>
{{if .Done.License}}<p>✓ Лицензия активна: <strong>{{.Lic.Tenant}}</strong>, план {{.Lic.Plan}}, модули: {{if .Lic.Features}}{{range $i, $f := .Lic.Features}}{{if $i}}, {{end}}{{$f}}{{end}}{{else}}только ядро{{end}}; {{.Lic.Message}}</p>{{end}}
{{with .Update}}{{if .HasUpdate}}
<div style="border:1px solid var(--accent);border-radius:8px;padding:12px 14px;margin-top:12px;background:var(--accent-weak)">
<p style="margin:0 0 6px;font-weight:600">Есть обновление: версия {{.Available}}</p>
<p class="muted" style="margin:0 0 10px;font-size:13px">Образ установил ту версию bj, с которой был собран. На сервере лицензий есть более новая — с исправлениями ядра, установщика и служб. Поставьте её сейчас, до настройки: bj перезапустится примерно на 10 секунд, затем обновите страницу.<br>{{.Message}}</p>
<form method="post" action="/admin/setup/update/apply" style="margin:0">
<input type="hidden" name="return_to" value="/admin/wizard?step=1">
<button type="submit" class="btn">Обновить сейчас</button>
</form>
</div>
{{else}}<p class="muted" style="font-size:13px">✓ Установлена последняя версия bj ({{.Available}}).</p>{{end}}{{end}}
<form method="post" action="/admin/setup/license/activate" style="margin-top:12px;display:grid;gap:10px">
<input type="hidden" name="return_to" value="/admin/wizard?step=1">
<label>Ключ продукта</label>
+4
View File
@@ -32,6 +32,10 @@ type Component struct {
var Components = []Component{
{Name: "bj-server", Title: "Ядро bj: переводы M2M, DEPO, QUIK МНП, ЛК ЕСИА", Path: "/opt/bj/bj-server", Unit: "bj-server", Exec: true, Core: true, Feature: "updates"},
{Name: "bj-component-install", Title: "Установщик обновлений", Path: "/usr/local/sbin/bj-component-install", Exec: true, Core: true, Feature: "updates"},
// Установщик несёт системные файлы (службы systemd, служебные скрипты): после его обновления
// они переписываются командой «bj-installer refresh-system» — правки установки приезжают
// обновлением, образ ISO пересобирать не нужно.
{Name: "bj-installer", Title: "Установщик и системные файлы (службы, скрипты)", Path: "/usr/local/sbin/bj-installer", Exec: true, Core: true, Feature: "updates"},
{Name: "crypto-service.jar", Title: "Криптосервис", Path: "/opt/bj/crypto-service.jar", Unit: "bj-crypto", Feature: "updates"},
{Name: "bj-lk", Title: "Личный кабинет клиента", Path: "/opt/bj/bj-lk", Unit: "bj-lk", Exec: true, Feature: "web-cabinet"},
{Name: "bj-npr", Title: "Риск-монитор НПР / УДС", Path: "/opt/bj/bj-npr", Unit: "bj-npr", Exec: true, FromCore: true, Feature: "npr"},
+8
View File
@@ -153,6 +153,14 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) {
return fail("включение служб %s: %v: %s", strings.Join(newUnits, ", "), err, strings.TrimSpace(string(out)))
}
}
for _, j := range jobs {
if j.c.Name == "bj-installer" && opt.Root == "" {
// новые службы и скрипты из обновлённого установщика; перезапуск служб — отдельно ниже
if out, err := exec.Command(j.c.Path, "refresh-system").CombinedOutput(); err != nil {
return fail("обновление системных файлов (bj-installer refresh-system): %v: %s", err, strings.TrimSpace(string(out)))
}
}
}
if err := SaveInstalled(installedPath, installed); err != nil {
return fail("запись установленных версий: %v", err)
}