Files
Bridge-and-Join-s/internal/lkgateway/unitswatch.go
T
fontvielle f50b338294 «Диагностика» объединена со «Статусом системы»
Службы (с перезапуском), здоровье фоновых компонентов, запрос справочника
участников и тестовые прогоны переехали из «Настройка → Диагностика» в
«Статус системы» — одно место про состояние bj. Раздел «Диагностика» и
пункт меню убраны, старые ссылки (#tests, #units) ведут в «Статус».
«Настройка» больше не опрашивает systemctl при каждом открытии.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-22 11:05:24 +03:00

127 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}