Files
Bridge-and-Join-s/internal/lkgateway/unitswatch.go
T
zuevav 6b490986e4 «Обновления» — одна карточка состояния и одна кнопка; проверка при открытии раздела; «Службы» — в «Диагностике»
- Сверху: «Установлены последние версии» (когда проверено) или «Доступно обновление X — Установить обновление» (всё сразу), или «Проверить не удалось»; кнопка «Проверить сейчас».
- При открытии раздела bj проверяет обновления сам, если последней проверке больше 10 минут (и не чаще раза в 10 минут из браузера — без зацикливания, если источник не настроен); check отвечает JSON для экрана.
- «Компоненты и версии» (обновить один компонент) и «Расширенные настройки» (обновление из файла, источник и канал, ключ, автопроверка) — под раскрытием.
- «Службы» с перезапуском перенесены в «Диагностику»; подсказка в письме о службе — туда же.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
2026-10-07 08:19:34 +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)
}
}
}