c9ab60130f
- окно обновления bj после перезапуска ведёт обновление ИШ по этапам (загрузка, остановка, резервная копия, установка, запуск, проверка) и закрывается только после проверки; пока шлюз обновляется, то же окно на любой странице - bj-igate-admin: этапы stop/backup/verify; после запуска сверяются версия, служба, движок, каналы и ключ с прежними (иначе автооткат), обмен с НРД - версия ИШ в строке НРД читается всегда заново (кэш 6 ч — только сайт НРД); после установки версии пересобираются сразу Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
219 lines
8.1 KiB
Go
219 lines
8.1 KiB
Go
package lkgateway
|
||
|
||
// ish_version.go — определение установленной версии Интеграционного шлюза НРД
|
||
// (ИШ) и сравнение с версией, доступной на сайте НРД.
|
||
//
|
||
// Установленная версия берётся из igate.deps.json (world-readable), доступная —
|
||
// со страницы дистрибутивов НРД. ВАЖНО: на странице соседствуют сборки под разные
|
||
// ОС с РАЗНЫМИ номерами (на момент внедрения Windows x86/x64 = 107.0.0.933, а
|
||
// Astra-Linux/amd64 = 107.0.0.930). Мы работаем на Linux (.deb, amd64), поэтому
|
||
// сравнивать нужно именно с Linux-сборкой, иначе система ложно покажет «доступно
|
||
// обновление» на версию, которой под нашу ОС ещё нет.
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"os"
|
||
"regexp"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// ishDepsPath — файл манифеста .NET-сборки ИШ; содержит "igate/<версия>".
|
||
const ishDepsPath = "/opt/nsd/igate/igate/igate.deps.json"
|
||
|
||
// ishSiteURL — страница дистрибутивов ПО НРД (там же ИШ).
|
||
const ishSiteURL = "https://www.nsd.ru/workflow/system/programs/web-service/"
|
||
|
||
var (
|
||
ishVerRe = regexp.MustCompile(`"igate/(\d+(?:\.\d+){2,3})"`)
|
||
ishAstraRe = regexp.MustCompile(`Astra-Linux[^"]*?(\d{2,3}(?:\.\d+){3})`)
|
||
ishAnyRe = regexp.MustCompile(`\d{2,3}(?:\.\d+){3}`)
|
||
)
|
||
|
||
// IshVersionInfo — сводка по версиям ИШ для отображения в интерфейсе.
|
||
type IshVersionInfo struct {
|
||
Installed string `json:"installed"` // напр. 107.0.930 (из deps.json)
|
||
SiteLinux string `json:"site_linux,omitempty"` // Astra-Linux сборка на сайте НРД
|
||
SiteLatest string `json:"site_latest,omitempty"` // самая новая любая ОС (инфо)
|
||
UpToDate bool `json:"up_to_date"` // installed соответствует Linux-сборке
|
||
Note string `json:"note,omitempty"` // человекочитаемый комментарий
|
||
CheckedAt time.Time `json:"checked_at"`
|
||
}
|
||
|
||
// ishInstalledVersion читает версию ИШ из deps.json. Пусто, если ИШ не найден.
|
||
func ishInstalledVersion() string {
|
||
b, err := os.ReadFile(ishDepsPath)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
m := ishVerRe.FindSubmatch(b)
|
||
if m == nil {
|
||
return ""
|
||
}
|
||
return string(m[1])
|
||
}
|
||
|
||
// canonVer приводит версию к форме без внутренних нулей: 107.0.0.930 и 107.0.930
|
||
// дают одинаковый результат (сборочная версия .deb опускает нулевой компонент).
|
||
func canonVer(v string) string {
|
||
p := strings.Split(strings.TrimSpace(v), ".")
|
||
if len(p) < 2 {
|
||
return v
|
||
}
|
||
core := []string{p[0]}
|
||
for _, x := range p[1 : len(p)-1] {
|
||
if x != "0" {
|
||
core = append(core, x)
|
||
}
|
||
}
|
||
core = append(core, p[len(p)-1])
|
||
return strings.Join(core, ".")
|
||
}
|
||
|
||
var (
|
||
ishVerMu sync.Mutex
|
||
ishVerCache *IshVersionInfo
|
||
ishVerTTL = 6 * time.Hour
|
||
)
|
||
|
||
// ishVersionInfo возвращает сводку по версиям ИШ (с кэшем на 6ч, чтобы не дёргать
|
||
// сайт НРД на каждый показ статуса). Сетевая часть best-effort: при недоступности
|
||
// сайта возвращается только установленная версия.
|
||
func ishVersionInfo(ctx context.Context) IshVersionInfo {
|
||
// установленную версию читаем всегда заново (ИШ мог обновиться минуту назад), сайт — из кэша
|
||
ishVerMu.Lock()
|
||
site := ishVerCache
|
||
ishVerMu.Unlock()
|
||
if site == nil || time.Since(site.CheckedAt) >= ishVerTTL {
|
||
linux, latest := ishSiteVersions(ctx)
|
||
site = &IshVersionInfo{SiteLinux: linux, SiteLatest: latest, CheckedAt: time.Now().UTC()}
|
||
ishVerMu.Lock()
|
||
ishVerCache = site
|
||
ishVerMu.Unlock()
|
||
}
|
||
info := IshVersionInfo{
|
||
Installed: ishInstalledVersion(),
|
||
SiteLinux: site.SiteLinux,
|
||
SiteLatest: site.SiteLatest,
|
||
CheckedAt: site.CheckedAt,
|
||
}
|
||
linux, latest := info.SiteLinux, info.SiteLatest
|
||
|
||
switch {
|
||
case info.Installed == "":
|
||
info.Note = "ИШ не обнаружен (deps.json недоступен)"
|
||
case linux == "":
|
||
info.UpToDate = true // не с чем сравнивать — не тревожим
|
||
info.Note = "версия на сайте НРД недоступна (проверьте вручную)"
|
||
case canonVer(info.Installed) == canonVer(linux):
|
||
info.UpToDate = true
|
||
info.Note = "актуальная версия для Linux (Astra/amd64)"
|
||
if latest != "" && canonVer(latest) != canonVer(linux) {
|
||
info.Note += "; для Windows доступна " + latest
|
||
}
|
||
default:
|
||
info.UpToDate = false
|
||
info.Note = "доступно обновление до " + linux + " (Astra-Linux)"
|
||
}
|
||
return info
|
||
}
|
||
|
||
// ishSiteVersions скачивает страницу НРД и возвращает (Linux-сборка, самая новая
|
||
// по всем ОС). Обе строки пусты при ошибке сети/парсинга.
|
||
func ishSiteVersions(ctx context.Context) (linux, latest string) {
|
||
c := &http.Client{Timeout: 15 * time.Second}
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ishSiteURL, nil)
|
||
if err != nil {
|
||
return "", ""
|
||
}
|
||
// WAF сайта НРД отвечает 403 на «Mozilla»-UA без заголовка Accept (эвристика
|
||
// анти-бот). Отправляем полноценный браузерный набор заголовков.
|
||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0")
|
||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||
req.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8")
|
||
resp, err := c.Do(req)
|
||
if err != nil {
|
||
return "", ""
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode != http.StatusOK {
|
||
return "", ""
|
||
}
|
||
body := make([]byte, 0, 256*1024)
|
||
buf := make([]byte, 32*1024)
|
||
for len(body) < 4*1024*1024 { // не читаем больше 4 МБ
|
||
n, rerr := resp.Body.Read(buf)
|
||
body = append(body, buf[:n]...)
|
||
if rerr != nil {
|
||
break
|
||
}
|
||
}
|
||
if m := ishAstraRe.FindSubmatch(body); m != nil {
|
||
linux = string(m[1])
|
||
}
|
||
// Самая новая версия по всем сборкам (для информационной строки).
|
||
var best string
|
||
for _, v := range ishAnyRe.FindAllString(string(body), -1) {
|
||
if best == "" || verLess(best, v) {
|
||
best = v
|
||
}
|
||
}
|
||
latest = best
|
||
return linux, latest
|
||
}
|
||
|
||
// verLess сравнивает версии покомпонентно как числа: verLess(a,b)=true если a<b.
|
||
func verLess(a, b string) bool {
|
||
pa, pb := strings.Split(a, "."), strings.Split(b, ".")
|
||
for i := 0; i < len(pa) || i < len(pb); i++ {
|
||
var x, y int
|
||
if i < len(pa) {
|
||
x = atoiSafe(pa[i])
|
||
}
|
||
if i < len(pb) {
|
||
y = atoiSafe(pb[i])
|
||
}
|
||
if x != y {
|
||
return x < y
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func atoiSafe(s string) int {
|
||
n := 0
|
||
for _, r := range s {
|
||
if r < '0' || r > '9' {
|
||
return n
|
||
}
|
||
n = n*10 + int(r-'0')
|
||
}
|
||
return n
|
||
}
|
||
|
||
// ishVersionStatus — версия ИШ как отдельная проверка для панели статуса.
|
||
func ishVersionStatus(ctx context.Context) Status {
|
||
info := ishVersionInfo(ctx)
|
||
s := Status{Name: "Версия Интеграционного шлюза", Optional: true}
|
||
if info.Installed == "" {
|
||
s.OK = true
|
||
s.Message = "ИШ не обнаружен на этом сервере"
|
||
return s
|
||
}
|
||
s.OK = info.UpToDate
|
||
if info.UpToDate {
|
||
s.Message = "установлена " + info.Installed + " — " + info.Note
|
||
} else {
|
||
s.Message = "установлена " + info.Installed + "; " + info.Note
|
||
}
|
||
if info.SiteLinux != "" {
|
||
s.Detail = "на сайте НРД (Linux): " + info.SiteLinux
|
||
if info.SiteLatest != "" && info.SiteLatest != info.SiteLinux {
|
||
s.Detail += ", последняя (Windows): " + info.SiteLatest
|
||
}
|
||
}
|
||
return s
|
||
}
|