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 '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 }