Проверка на чистой ВМ: служба впервые установленного модуля включается в автозапуск, даже если unit-файл уже положил образ (иначе bj-crypto после перезагрузки не стартовал); bj-crypto в готовности — предупреждение, а не блокер (пакеты для НРД подписывает шлюз), до установки модуля не проверяется; в мастере без ссылки «Открыть мастер» на самом мастере, без двойного заголовка «Наша организация», без QUIK и DEPO в текстах и маршрутах уведомлений, если их нет в лицензии; «162 участника», понятная сводка готовности; адрес SMTP через JoinHostPort

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-10-05 19:04:24 +03:00
parent 1b58b07e02
commit ba1aae705d
11 changed files with 137 additions and 26 deletions
+7 -1
View File
@@ -40,6 +40,7 @@ type InstallResult struct {
Release string `json:"release,omitempty"`
Installed []map[string]string `json:"installed,omitempty"`
Restarted []string `json:"restarted,omitempty"`
Enabled []string `json:"enabled,omitempty"` // службы, включённые в автозапуск (впервые установленные модули)
}
// InstallStaged — установить компоненты из каталога подготовки: manifest.json (подписанный),
@@ -129,6 +130,10 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) {
ts := time.Now().Format("20060102-150405")
var units, newUnits []string
for _, j := range jobs {
// Модуля на сервере ещё не было — его службу включаем, даже если unit-файл уже стоит: установка
// с образа кладёт unit-файлы всех служб сразу (выключенными), а файл модуля приезжает обновлением.
_, statErr := os.Stat(j.c.PathAt(opt.Root))
fresh := statErr != nil
if err := installFile(j.file, j.c.PathAt(opt.Root), j.c.Exec, ts); err != nil {
return fail("установка %s: %v", j.c.Name, err)
}
@@ -140,7 +145,7 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) {
if err != nil {
return fail("служба %s: %v", j.c.Unit, err)
}
if added {
if added || fresh {
newUnits = append(newUnits, j.c.Unit)
}
}
@@ -153,6 +158,7 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) {
return fail("включение служб %s: %v: %s", strings.Join(newUnits, ", "), err, strings.TrimSpace(string(out)))
}
}
res.Enabled = newUnits
for _, j := range jobs {
if j.c.Name == "bj-installer" && opt.Root == "" {
// новые службы и скрипты из обновлённого установщика; перезапуск служб — отдельно ниже
+23
View File
@@ -66,6 +66,29 @@ func TestInstallSplitModuleWithUnit(t *testing.T) {
if inst := LoadInstalled(filepath.Join(root, InstalledFile)); inst["bj-npr"].Version != "0.1.0" {
t.Fatalf("components.json: %+v", inst)
}
if len(res.Enabled) != 1 || res.Enabled[0] != "bj-npr" {
t.Fatalf("служба впервые установленного модуля должна включаться: %+v", res.Enabled)
}
// Повторная установка того же модуля: служба уже стоит и модуль был — автозапуск не трогаем
// (администратор мог выключить службу сам).
res = InstallStaged(stageNPR(t, priv, `{"names":["bj-npr"]}`), InstallOptions{Root: root, PubKey: pub, Units: units})
if !res.OK || len(res.Enabled) != 0 {
t.Fatalf("повторная установка не должна включать службу: %+v", res)
}
// Установка с образа: unit-файл уже положен (выключенным), файла модуля нет — служба включается.
root2 := t.TempDir()
if err := os.MkdirAll(filepath.Join(root2, "/etc/systemd/system"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root2, "/etc/systemd/system/bj-npr.service"), []byte("[Service]\n"), 0o644); err != nil {
t.Fatal(err)
}
res = InstallStaged(stageNPR(t, priv, `{"names":["bj-npr"],"new":["bj-npr"]}`), InstallOptions{Root: root2, PubKey: pub, Units: units})
if !res.OK || len(res.Enabled) != 1 || res.Enabled[0] != "bj-npr" {
t.Fatalf("unit-файл из образа: служба должна включиться при первой установке модуля: %+v", res)
}
// Самостоятельный модуль (не FromCore) по new не ставится.
c, _ := ComponentByName("bj-lk")