Проверка на чистой ВМ: служба впервые установленного модуля включается в автозапуск, даже если 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
+41 -13
View File
@@ -11,6 +11,7 @@ import (
"fmt"
"net"
"net/http"
"os"
"os/exec"
"sort"
"strings"
@@ -18,6 +19,23 @@ import (
"time"
)
// cryptoJarPath — модуль службы пробной подписи bj-crypto (компонент crypto-service.jar).
const cryptoJarPath = "/opt/bj/crypto-service.jar"
// ruPlural — форма слова для числа: 1 участник, 2 участника, 5 участников.
func ruPlural(n int, one, few, many string) string {
n %= 100
switch {
case n >= 11 && n <= 14:
return many
case n%10 == 1:
return one
case n%10 >= 2 && n%10 <= 4:
return few
}
return many
}
// ReadyCheck — одна строка итоговой проверки.
type ReadyCheck struct {
Order int `json:"-"`
@@ -107,16 +125,22 @@ func readinessChecks(ctx context.Context, rc *RuntimeConfig) []ReadyCheck {
cctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel()
ver, _, _ := validataVersion(cctx)
out, _ := exec.CommandContext(cctx, "systemctl", "is-active", "bj-crypto.service").Output()
svc := strings.TrimSpace(string(out))
c := ReadyCheck{Order: 6, Title: "СКЗИ «Валидата» и служба криптографии", Link: "/admin/wizard?step=6", Required: true}
switch {
case ver == "":
c.State, c.Detail = "err", "Валидата на сервере не найдена"
case svc != "active":
c.State, c.Detail = "err", "Валидата "+ver+", но служба bj-crypto не запущена ("+pickStr(svc, "нет")+")"
default:
c.State, c.Detail = "ok", "Валидата "+ver+", служба bj-crypto работает"
c := ReadyCheck{Order: 6, Title: "СКЗИ «Валидата»", Link: "/admin/wizard?step=6", Required: true}
if ver == "" {
c.State, c.Detail = "err", "Валидата на сервере не найдена — без неё шлюз не подпишет пакеты для НРД"
return c
}
c.State, c.Detail = "ok", "Валидата "+ver
// Служба bj-crypto — пробная подпись и проверка ключа из bj; пакеты для НРД подписывает шлюз, поэтому
// её остановка обмену не мешает. Модуль приезжает с обновлениями — до них службы нет, это не ошибка.
if _, err := os.Stat(cryptoJarPath); err == nil {
out, _ := exec.CommandContext(cctx, "systemctl", "is-active", "bj-crypto.service").Output()
if svc := strings.TrimSpace(string(out)); svc != "active" {
c.State, c.Link = "warn", "/admin/setup#sec-crypto"
c.Detail += "; служба пробной подписи bj-crypto не запущена (" + pickStr(svc, "нет") + ") — обмену не мешает: пакеты для НРД подписывает шлюз"
} else {
c.Detail += ", служба пробной подписи bj-crypto работает"
}
}
return c
})
@@ -176,7 +200,11 @@ func readinessChecks(ctx context.Context, rc *RuntimeConfig) []ReadyCheck {
case s.Alerts.Zabbix.Enabled:
c.State, c.Detail = "ok", "Zabbix "+s.Alerts.Zabbix.Server
default:
c.State, c.Detail = "warn", "не настроены: о сбоях (НРД не отвечает, недоставленные переводы, ошибки DEPO, срок сертификата) узнают только открыв bj"
depo := ""
if moduleAllowed(rc, "quik-mnp") {
depo = ", ошибки DEPO"
}
c.State, c.Detail = "warn", "не настроены: о сбоях (НРД не отвечает, недоставленные переводы"+depo+", срок сертификата) узнают только открыв bj"
}
return c
})
@@ -241,9 +269,9 @@ func readinessChecks(ctx context.Context, rc *RuntimeConfig) []ReadyCheck {
case len(hb.Participants) == 0:
c.State, c.Detail = "err", "справочника нет — получателя перевода не определить"
case !at.IsZero() && time.Since(at) > 14*24*time.Hour:
c.State, c.Detail = "warn", fmt.Sprintf("%d участников, от %s — давно не обновлялся (обновляется по связи с НРД)", len(hb.Participants), at.Format("02.01.2006"))
c.State, c.Detail = "warn", fmt.Sprintf("%d %s, от %s — давно не обновлялся (обновляется по связи с НРД)", len(hb.Participants), ruPlural(len(hb.Participants), "участник", "участника", "участников"), at.Format("02.01.2006"))
default:
c.State, c.Detail = "ok", fmt.Sprintf("%d участников, от %s", len(hb.Participants), at.Format("02.01.2006"))
c.State, c.Detail = "ok", fmt.Sprintf("%d %s, от %s", len(hb.Participants), ruPlural(len(hb.Participants), "участник", "участника", "участников"), at.Format("02.01.2006"))
}
return c
})