f50b338294
Службы (с перезапуском), здоровье фоновых компонентов, запрос справочника участников и тестовые прогоны переехали из «Настройка → Диагностика» в «Статус системы» — одно место про состояние bj. Раздел «Диагностика» и пункт меню убраны, старые ссылки (#tests, #units) ведут в «Статус». «Настройка» больше не опрашивает systemctl при каждом открытии. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
127 lines
5.1 KiB
Go
127 lines
5.1 KiB
Go
package lkgateway
|
||
|
||
// unitswatch.go — сторож служб (06.10.2026): администратор системы узнаёт письмом, что служба модуля
|
||
// (bj-crypto, bj-npr, bj-quikexport, bj-lk) или служба ключей Валидаты не работает, — раньше это было видно
|
||
// только в карточке «Службы» или по ошибкам раздела. ИШ НРД сторожит igwwatch, сбои внутри bj-server — health.
|
||
// Служба должна лежать две проверки подряд: перезапуск при обновлении письма не вызывает.
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
const unitsCheckEvery = 2 * time.Minute
|
||
|
||
var unitsWatch = struct {
|
||
sync.Mutex
|
||
down map[string]int // служба → проверок подряд, когда она не работала
|
||
alerted map[string]bool // о службе уже написали
|
||
}{down: map[string]int{}, alerted: map[string]bool{}}
|
||
|
||
// watchedUnit — служба под присмотром и её состояние сейчас.
|
||
type watchedUnit struct {
|
||
Key, Title, Unit string
|
||
OK bool
|
||
State string // как она лежит — для письма
|
||
Hint string // что из-за этого не работает и как поднять
|
||
}
|
||
|
||
func (s *Server) unitsWatchLoop(ctx context.Context) {
|
||
healthRegister("units-watch", 2*unitsCheckEvery)
|
||
t := time.NewTicker(unitsCheckEvery)
|
||
defer t.Stop()
|
||
for {
|
||
unitsWatchTick(s.rc, watchedUnits(s.rc))
|
||
healthBeat("units-watch")
|
||
select {
|
||
case <-ctx.Done():
|
||
return
|
||
case <-t.C:
|
||
}
|
||
}
|
||
}
|
||
|
||
// watchedUnits — службы модулей этого сервера (кроме самого bj-server) и служба ключей Валидаты.
|
||
func watchedUnits(rc *RuntimeConfig) []watchedUnit {
|
||
var out []watchedUnit
|
||
for _, v := range unitViews(rc) {
|
||
if v.Unit == "bj-server" {
|
||
continue
|
||
}
|
||
st := v.State + "/" + v.Sub
|
||
if v.Result != "" {
|
||
st += ", причина: " + v.Result
|
||
}
|
||
if v.Since != "" {
|
||
st += ", с " + v.Since
|
||
}
|
||
out = append(out, watchedUnit{Key: v.Unit, Title: v.Title, Unit: v.Unit, OK: v.OK(), State: st,
|
||
Hint: "Перезапустить — Статус системы → «Службы», кнопка «Перезапустить»; если не поднимается — journalctl -u " + v.Unit + "."})
|
||
}
|
||
// служба ключей — когда она нужна: включена в автозапуск (bj включает её при установке ключа) или уже работала;
|
||
// на свежем сервере до установки ключа её может и не быть — это не сбой
|
||
if _, err := os.Stat("/opt/Validata/VDCSP/bin/amd64/vdcrysvc"); err == nil {
|
||
ok := procRunning("vdcrysvc")
|
||
unitsWatch.Lock()
|
||
seen := unitsWatch.alerted["vdcrysvc"] || unitsWatch.down["vdcrysvc"] > 0 || vdcrysvcSeen
|
||
if ok {
|
||
vdcrysvcSeen = true
|
||
}
|
||
unitsWatch.Unlock()
|
||
if !ok && !seen && !unitEnabled("vdmkdev.service") {
|
||
return out
|
||
}
|
||
out = append(out, watchedUnit{Key: "vdcrysvc", Title: "служба ключей Валидаты", Unit: "vdmkdev", OK: ok,
|
||
State: "процесс vdcrysvc не запущен",
|
||
Hint: "Без неё ключ подписи не работает: шлюз НРД не подпишет и не отправит документы. Запустить: sudo /etc/init.d/vdmkdev start."})
|
||
}
|
||
return out
|
||
}
|
||
|
||
// vdcrysvcSeen — служба ключей Валидаты работала с запуска bj (под unitsWatch).
|
||
var vdcrysvcSeen bool
|
||
|
||
// unitEnabled — служба включена в автозапуск (systemctl is-enabled, без прав root).
|
||
func unitEnabled(unit string) bool {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||
defer cancel()
|
||
out, _ := exec.CommandContext(ctx, "systemctl", "is-enabled", unit).Output()
|
||
return strings.TrimSpace(string(out)) == "enabled"
|
||
}
|
||
|
||
// procRunning — есть ли процесс с таким именем (по /proc/*/comm, без прав root).
|
||
func procRunning(name string) bool {
|
||
dirs, _ := filepath.Glob("/proc/[0-9]*/comm")
|
||
for _, f := range dirs {
|
||
if b, err := os.ReadFile(f); err == nil && strings.TrimSpace(string(b)) == name {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func unitsWatchTick(rc *RuntimeConfig, units []watchedUnit) {
|
||
unitsWatch.Lock()
|
||
defer unitsWatch.Unlock()
|
||
for _, u := range units {
|
||
if u.OK {
|
||
if unitsWatch.alerted[u.Key] {
|
||
raiseSystemAlert(rc, "Служба «"+u.Title+"» снова работает", "Служба "+u.Unit+" ("+u.Title+") снова запущена.")
|
||
}
|
||
delete(unitsWatch.down, u.Key)
|
||
delete(unitsWatch.alerted, u.Key)
|
||
continue
|
||
}
|
||
unitsWatch.down[u.Key]++
|
||
if unitsWatch.down[u.Key] >= 2 && !unitsWatch.alerted[u.Key] {
|
||
unitsWatch.alerted[u.Key] = true
|
||
raiseSystemAlert(rc, "Служба «"+u.Title+"» не работает", "Служба "+u.Unit+" ("+u.Title+") не работает: "+u.State+". "+u.Hint)
|
||
}
|
||
}
|
||
}
|