Java 17 для службы подписи: установщик ставит openjdk-17-jre-headless там, где Java по умолчанию старее (Ubuntu 22.04 — 11; crypto-service собран под 17), в слой образа кладётся Java 17; «Подготовка модулей» и проверка готовности не принимают падающую по кругу службу за работающую (работает ≥ 5 с) и называют причину — в том числе «слишком старая Java» с командой исправления

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:30:53 +03:00
parent 2c22974141
commit 64928cfed1
4 changed files with 97 additions and 13 deletions
+64 -5
View File
@@ -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 {
+7 -3
View File
@@ -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 работает"
}