From 64928cfed1a03ab3bc5919040c84594a350a6b5c Mon Sep 17 00:00:00 2001 From: zuevav Date: Mon, 5 Oct 2026 19:30:53 +0300 Subject: [PATCH] =?UTF-8?q?Java=2017=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BB?= =?UTF-8?q?=D1=83=D0=B6=D0=B1=D1=8B=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B8:=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D1=89?= =?UTF-8?q?=D0=B8=D0=BA=20=D1=81=D1=82=D0=B0=D0=B2=D0=B8=D1=82=20openjdk-1?= =?UTF-8?q?7-jre-headless=20=D1=82=D0=B0=D0=BC,=20=D0=B3=D0=B4=D0=B5=20Jav?= =?UTF-8?q?a=20=D0=BF=D0=BE=20=D1=83=D0=BC=D0=BE=D0=BB=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8E=20=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B5=20(Ubuntu?= =?UTF-8?q?=2022.04=20=E2=80=94=2011;=20crypto-service=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=BD=20=D0=BF=D0=BE=D0=B4=2017),=20=D0=B2?= =?UTF-8?q?=20=D1=81=D0=BB=D0=BE=D0=B9=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BB=D0=B0=D0=B4=D1=91=D1=82=D1=81=D1=8F=20Jav?= =?UTF-8?q?a=2017;=20=C2=AB=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=C2=BB=20=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B5=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8=D0=BC?= =?UTF-8?q?=D0=B0=D1=8E=D1=82=20=D0=BF=D0=B0=D0=B4=D0=B0=D1=8E=D1=89=D1=83?= =?UTF-8?q?=D1=8E=20=D0=BF=D0=BE=20=D0=BA=D1=80=D1=83=D0=B3=D1=83=20=D1=81?= =?UTF-8?q?=D0=BB=D1=83=D0=B6=D0=B1=D1=83=20=D0=B7=D0=B0=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D1=8E=D1=89=D1=83=D1=8E=20(=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20=E2=89=A5=205=20?= =?UTF-8?q?=D1=81)=20=D0=B8=20=D0=BD=D0=B0=D0=B7=D1=8B=D0=B2=D0=B0=D1=8E?= =?UTF-8?q?=D1=82=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=83=20=E2=80=94?= =?UTF-8?q?=20=D0=B2=20=D1=82=D0=BE=D0=BC=20=D1=87=D0=B8=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=20=C2=AB=D1=81=D0=BB=D0=B8=D1=88=D0=BA=D0=BE=D0=BC=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B0=D1=8F=20Java=C2=BB=20=D1=81=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=BE=D0=B9=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- cmd/bj-installer/main.go | 27 ++++++++-- deploy/iso/build-ubuntu-updates-layer.sh | 4 +- internal/lkgateway/prepare.go | 69 ++++++++++++++++++++++-- internal/lkgateway/readiness.go | 10 ++-- 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/cmd/bj-installer/main.go b/cmd/bj-installer/main.go index 7a13c53..eecfee1 100644 --- a/cmd/bj-installer/main.go +++ b/cmd/bj-installer/main.go @@ -262,23 +262,44 @@ func memTotal() uint64 { return 0 } +// javaPkg — Java для службы подписи (crypto-service собран под Java 17): где Java по умолчанию старше +// не бывает (Ubuntu 22.04, Astra 1.7 — Java 11), ставится openjdk-17; где по умолчанию 17 и новее +// (Debian 13 — Java 21), — пакет по умолчанию. +func javaPkg(debs []string) string { + if len(debs) > 0 { // пакеты с образа: какая Java положена в образ + for _, d := range debs { + if strings.HasPrefix(filepath.Base(d), "openjdk-17-jre-headless_") { + return "openjdk-17-jre-headless" + } + } + return "default-jre-headless" + } + out, err := run("apt-cache", "policy", "openjdk-17-jre-headless") + if err == nil && strings.Contains(out, "Candidate:") && !strings.Contains(out, "Candidate: (none)") { + return "openjdk-17-jre-headless" + } + return "default-jre-headless" +} + func (in *installer) stepPackages() (string, error) { - pkgs := []string{"ca-certificates", "curl", "postgresql", "postgresql-client", "default-jre-headless", "pcscd", "python3", "smbclient", + pkgs := []string{"ca-certificates", "curl", "postgresql", "postgresql-client", "pcscd", "python3", "smbclient", // для интеграционного шлюза НРД без графики (ИШ 107: пароли в gnome-keyring, движок под виртуальным дисплеем) "gnome-keyring", "dbus", "xvfb", "xauth", "libsecret-tools", "unzip"} env := append(os.Environ(), "DEBIAN_FRONTEND=noninteractive") // Образ кладёт пакеты со всеми зависимостями в /var/lib/bj/debs — ставим с диска, без сети. // Не вышло (другая система, не хватает зависимости) — обычная установка из репозитория. if debs, _ := filepath.Glob(in.P(debsDir) + "/*.deb"); len(debs) > 0 && in.root == "" { + withJava := append(pkgs, javaPkg(debs)) args := append([]string{"install", "-y", "-qq", "--no-install-recommends"}, debs...) - if _, err := runEnv(env, "apt-get", append(args, pkgs...)...); err == nil { + if _, err := runEnv(env, "apt-get", append(args, withJava...)...); err == nil { _ = os.RemoveAll(in.P(debsDir)) - return strings.Join(pkgs, ", ") + " (с образа, без загрузки)", nil + return strings.Join(withJava, ", ") + " (с образа, без загрузки)", nil } } if out, err := runEnv(env, "apt-get", "update", "-qq"); err != nil { return "", fmt.Errorf("apt-get update: %v\n%s", err, tail(out, 8)) } + pkgs = append(pkgs, javaPkg(nil)) args := append([]string{"install", "-y", "-qq", "--no-install-recommends"}, pkgs...) if out, err := runEnv(env, "apt-get", args...); err != nil { return "", fmt.Errorf("apt-get install: %v\n%s", err, tail(out, 12)) diff --git a/deploy/iso/build-ubuntu-updates-layer.sh b/deploy/iso/build-ubuntu-updates-layer.sh index 9146ffc..293014b 100755 --- a/deploy/iso/build-ubuntu-updates-layer.sh +++ b/deploy/iso/build-ubuntu-updates-layer.sh @@ -18,8 +18,8 @@ SRC=${1:?ubuntu-22.04.x-live-server-amd64.iso} OUT=${2:?выходной каталог} [ "$(id -u)" = 0 ] || { echo "нужен root"; exit 1; } for c in mksquashfs xorriso; do command -v $c >/dev/null || { echo "нужен $c"; exit 1; }; done -# пакеты — как в bj-installer (stepPackages) -BJ_PKGS="ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3 smbclient gnome-keyring dbus xvfb xauth libsecret-tools unzip" +# пакеты — как в bj-installer (stepPackages); Java 17 — служба подписи собрана под неё (в 22.04 по умолчанию 11) +BJ_PKGS="ca-certificates curl postgresql postgresql-client openjdk-17-jre-headless pcscd python3 smbclient gnome-keyring dbus xvfb xauth libsecret-tools unzip" LAYER=ubuntu-server-minimal.ubuntu-server.bj.squashfs mkdir -p "$OUT" diff --git a/internal/lkgateway/prepare.go b/internal/lkgateway/prepare.go index de1b982..e423182 100644 --- a/internal/lkgateway/prepare.go +++ b/internal/lkgateway/prepare.go @@ -9,6 +9,8 @@ import ( "context" "net/http" "os" + "os/exec" + "strconv" "strings" "sync" "time" @@ -47,17 +49,19 @@ func prepareItems(rc *RuntimeConfig, running, finished bool, runErr string) []Pr it := PrepareItem{Title: prepareTitle(c)} _, statErr := os.Stat(c.Path) present := statErr == nil - us := "" + var u unitRun if present && c.Unit != "" { - us = unitState(c.Unit).State + u = unitRunState(c.Unit) } switch { - case us == "active": + case u.State == "active" && u.Up >= 5*time.Second: it.State, it.Detail = "ok", "установлен, служба работает" - case us == "activating" || us == "reloading": + case (u.State == "activating" || u.State == "active") && u.Restarts >= 3: + it.State, it.Detail = "err", "установлен, но служба падает при запуске: "+unitFailCause(c) + case u.State == "activating" || u.State == "reloading" || u.State == "active": it.State, it.Detail = "run", "установлен, служба запускается…" case present && c.Unit == "bj-crypto": - it.State, it.Detail = "warn", "установлен, служба не запущена ("+us+") — обмену не мешает: пакеты для НРД подписывает шлюз" + it.State, it.Detail = "warn", "установлен, служба не запущена ("+u.State+") — обмену не мешает: пакеты для НРД подписывает шлюз" case present: it.State, it.Detail = "ok", "установлен; служба заработает, когда модуль будет настроен (глава «Запуск»)" case running: @@ -77,6 +81,61 @@ func prepareItems(rc *RuntimeConfig, running, finished bool, runErr string) []Pr return out } +// unitRun — служба: состояние, сколько работает после последнего запуска, сколько раз systemd её перезапускал. +type unitRun struct { + State string + Up time.Duration + Restarts int +} + +func unitRunState(unit string) unitRun { + r := unitRun{State: "unknown"} + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + out, err := exec.CommandContext(ctx, "systemctl", "show", unit, "--property=ActiveState,NRestarts,ActiveEnterTimestampMonotonic").Output() + if err != nil { + return r + } + var since int64 + for _, line := range strings.Split(string(out), "\n") { + k, v, _ := strings.Cut(strings.TrimSpace(line), "=") + switch k { + case "ActiveState": + r.State = v + case "NRestarts": + r.Restarts, _ = strconv.Atoi(v) + case "ActiveEnterTimestampMonotonic": + since, _ = strconv.ParseInt(v, 10, 64) + } + } + if b, err := os.ReadFile("/proc/uptime"); err == nil && since > 0 { + if f := strings.Fields(string(b)); len(f) > 0 { + if up, err := strconv.ParseFloat(f[0], 64); err == nil { + r.Up = time.Duration(up*float64(time.Second)) - time.Duration(since)*time.Microsecond + } + } + } + return r +} + +// unitFailCause — причина падения службы модуля по последней строке её журнала ошибок — понятными словами. +func unitFailCause(c release.Component) string { + logs := map[string]string{"bj-crypto": "/var/log/bj/crypto-service.err", "bj-npr": "/var/log/bj/bj-npr.err", "bj-quikexport": "/var/log/bj/bj-quikexport.err"} + b, err := os.ReadFile(logs[c.Unit]) + if err != nil { + return "подробности — в журнале службы " + c.Unit + } + lines := strings.Split(strings.TrimSpace(string(b)), "\n") + last := strings.TrimSpace(lines[len(lines)-1]) + if strings.Contains(string(b[max(0, len(b)-4000):]), "UnsupportedClassVersionError") { + return "на сервере слишком старая Java (нужна 17 или новее): sudo apt-get install openjdk-17-jre-headless — служба запустится сама" + } + if len(last) > 240 { + last = last[:240] + "…" + } + return last +} + // prepareTitle — название модуля для экрана. func prepareTitle(c release.Component) string { switch c.Name { diff --git a/internal/lkgateway/readiness.go b/internal/lkgateway/readiness.go index 921b8a3..aeb4a32 100644 --- a/internal/lkgateway/readiness.go +++ b/internal/lkgateway/readiness.go @@ -17,6 +17,8 @@ import ( "strings" "sync" "time" + + "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release" ) // cryptoJarPath — модуль службы пробной подписи bj-crypto (компонент crypto-service.jar). @@ -134,10 +136,12 @@ func readinessChecks(ctx context.Context, rc *RuntimeConfig) []ReadyCheck { // Служба 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" { + if u := unitRunState("bj-crypto"); !(u.State == "active" && u.Up >= 5*time.Second) { c.State, c.Link = "warn", "/admin/setup#sec-crypto" - c.Detail += "; служба пробной подписи bj-crypto не запущена (" + pickStr(svc, "нет") + ") — обмену не мешает: пакеты для НРД подписывает шлюз" + c.Detail += "; служба пробной подписи bj-crypto не работает (" + pickStr(u.State, "нет") + ") — обмену не мешает: пакеты для НРД подписывает шлюз" + if cc, ok := release.ComponentByName("crypto-service.jar"); ok && (u.Restarts >= 3 || u.State == "failed") { + c.Detail += ". Причина: " + unitFailCause(cc) + } } else { c.Detail += ", служба пробной подписи bj-crypto работает" }