Оповещения: группа «Администратор системы», блок «Кому писать» открыт; копии: адрес хранилища как в проводнике
Администратору (адрес из «Почты») — сбои (ИШ, связь с НРД, службы bj, DEPO) и обновления (новый выпуск bj, ИШ, лицензия, сертификаты); отделам — переводы и поручения. Существующие установки: администратор получает всё, что получал. Новые оповещения: «Доступно обновление bj» (раз на выпуск), «Служба … не работает / снова работает» (модули и служба ключей Валидаты). Резервные копии: \\сервер\папка или сервер + общая папка по полям → //сервер/папка; понятная причина отказа доступа. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user