9737c787f9
Инфраструктура M2M (живой обмен с НРД через ИШ): - обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение ответа; INFO → ждём Decision; идемпотентность поллера - fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO - сырой XML ответа НРД в карточке заявки (для пересылки в ТП) - тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes, 4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта - редирект из теста сразу в карточку заявки Мастер установки ключа Валидаты на флешку (admin/setup/keywizard): - пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник сертификатов (CRL) → перезапуск+проверка ИШ → готово - привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен, bj-server остаётся в песочнице - сохранение структуры профиля архива (spr<N>), перечисление съёмных USB Прочее: - пакет-доказательство для ТП НРД + форма регистрации участника M2M - эталонные образцы робота (DOC/m2m_robot_samples) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
205 lines
7.1 KiB
Go
205 lines
7.1 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release"
|
|
)
|
|
|
|
// BuildVersion — версия bj-server. Переопределяется при сборке:
|
|
//
|
|
// go build -ldflags "-X .../lkgateway.BuildVersion=1.2.0"
|
|
var BuildVersion = "0.1.0"
|
|
|
|
// DefaultUpdatePublicKey — публичный ключ артефактории, зашитый в релиз.
|
|
// Пустой в исходниках; подставляется при официальной сборке. Если задан в
|
|
// настройках (UpdateSettings.PublicKey) — приоритет у настроек.
|
|
var DefaultUpdatePublicKey = ""
|
|
|
|
// installPaths — куда устанавливать артефакты по логическому имени.
|
|
// Файлы не из этого списка при авто-обновлении пропускаются (скрипты/SQL
|
|
// обновляются отдельно, не на лету).
|
|
var installPaths = map[string]string{
|
|
"bj-server": "/opt/bj/bj-server",
|
|
"crypto-service.jar": "/opt/bj/crypto-service.jar",
|
|
}
|
|
|
|
// Updater — авто-обновление bj-server из артефактории (работает поверх rc).
|
|
type Updater struct{ rc *RuntimeConfig }
|
|
|
|
// NewUpdater создаёт Updater на текущем runtime-конфиге.
|
|
func NewUpdater(rc *RuntimeConfig) *Updater { return &Updater{rc: rc} }
|
|
|
|
// UpdateStatus — сводка для UI/handler.
|
|
type UpdateStatus struct {
|
|
Configured bool
|
|
CurrentVersion string
|
|
Available string
|
|
HasUpdate bool
|
|
Channel string
|
|
Notes string
|
|
Message string
|
|
}
|
|
|
|
func (u *Updater) updateClient() (*release.Client, error) {
|
|
cfg := u.rc.Snapshot().Update
|
|
pub := cfg.PublicKey
|
|
if pub == "" {
|
|
pub = DefaultUpdatePublicKey
|
|
}
|
|
if cfg.BaseURL == "" || pub == "" {
|
|
return nil, fmt.Errorf("обновления не настроены (нужны URL артефактории и публичный ключ)")
|
|
}
|
|
channel := cfg.Channel
|
|
if channel == "" {
|
|
channel = "stable"
|
|
}
|
|
return release.NewClient(cfg.BaseURL, channel, pub)
|
|
}
|
|
|
|
// CheckForUpdate скачивает манифест, проверяет подпись, сравнивает версии и
|
|
// сохраняет результат в настройки. Возвращает сводку.
|
|
func (u *Updater) CheckForUpdate(ctx context.Context) (UpdateStatus, error) {
|
|
st := UpdateStatus{CurrentVersion: BuildVersion, Channel: u.rc.Snapshot().Update.Channel}
|
|
cl, err := u.updateClient()
|
|
if err != nil {
|
|
st.Message = err.Error()
|
|
return st, nil // не настроено — не ошибка
|
|
}
|
|
st.Configured = true
|
|
|
|
m, err := cl.FetchManifest(ctx)
|
|
if err != nil {
|
|
st.Message = "проверка не удалась: " + err.Error()
|
|
u.saveCheckResult(st)
|
|
return st, err
|
|
}
|
|
st.Available = m.Version
|
|
st.Notes = m.Notes
|
|
st.HasUpdate = release.IsNewer(m.Version, BuildVersion)
|
|
if st.HasUpdate {
|
|
st.Message = fmt.Sprintf("доступна версия %s (текущая %s)", m.Version, BuildVersion)
|
|
} else {
|
|
st.Message = "установлена актуальная версия " + BuildVersion
|
|
}
|
|
u.saveCheckResult(st)
|
|
return st, nil
|
|
}
|
|
|
|
func (u *Updater) saveCheckResult(st UpdateStatus) {
|
|
cfg := u.rc.Snapshot().Update
|
|
cfg.LastCheck = time.Now().UTC()
|
|
cfg.LastResult = st.Message
|
|
cfg.Available = st.Available
|
|
cfg.Notes = st.Notes
|
|
if err := u.rc.SaveUpdateSettings(cfg); err != nil {
|
|
log.Printf("lk-gateway: сохранение результата проверки обновления: %v", err)
|
|
}
|
|
}
|
|
|
|
// ApplyUpdate скачивает обновлённые артефакты (с проверкой подписи манифеста
|
|
// и sha256 каждого файла), атомарно заменяет бинари и завершает процесс с
|
|
// ненулевым кодом — systemd (Restart=on-failure) поднимает новую версию.
|
|
func (u *Updater) ApplyUpdate(ctx context.Context) error {
|
|
// Гейт лицензией: если лицензирование включено — требуется валидная
|
|
// лицензия с фичей updates. Без лицензирования (открытый режим) — пропускаем.
|
|
if licensingEnabled(u.rc) {
|
|
ls := licenseStatus(u.rc)
|
|
if !ls.Valid {
|
|
return fmt.Errorf("обновления заблокированы — лицензия: %s", ls.Message)
|
|
}
|
|
if !ls.AllowsUpdates {
|
|
return fmt.Errorf("обновления не входят в план %q", ls.Plan)
|
|
}
|
|
}
|
|
cl, err := u.updateClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m, err := cl.FetchManifest(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("манифест: %w", err)
|
|
}
|
|
if !release.IsNewer(m.Version, BuildVersion) {
|
|
return fmt.Errorf("обновление не требуется (текущая %s, доступна %s)", BuildVersion, m.Version)
|
|
}
|
|
|
|
updated := 0
|
|
for _, a := range m.Artifacts {
|
|
dst, ok := installPaths[a.Name]
|
|
if !ok {
|
|
continue // скрипты/SQL не обновляем на лету
|
|
}
|
|
dir := dirOf(dst)
|
|
path, err := cl.DownloadArtifact(ctx, a, dir)
|
|
if err != nil {
|
|
return fmt.Errorf("скачивание %s: %w", a.Name, err)
|
|
}
|
|
// DownloadArtifact кладёт файл под именем a.File; если целевое имя
|
|
// иное — переименуем атомарно.
|
|
if path != dst {
|
|
if err := os.Rename(path, dst); err != nil {
|
|
return fmt.Errorf("установка %s: %w", a.Name, err)
|
|
}
|
|
}
|
|
log.Printf("lk-gateway: обновлён %s → %s (%s)", a.Name, dst, m.Version)
|
|
updated++
|
|
}
|
|
if updated == 0 {
|
|
return fmt.Errorf("в манифесте %s нет обновляемых бинарей", m.Version)
|
|
}
|
|
|
|
log.Printf("lk-gateway: обновление до %s применено (%d файлов), перезапуск через systemd…", m.Version, updated)
|
|
// Завершаемся с ненулевым кодом — systemd Restart=on-failure поднимет
|
|
// новый бинарь. Даём пару секунд на флаш логов/ответа.
|
|
go func() {
|
|
time.Sleep(800 * time.Millisecond)
|
|
os.Exit(42)
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// updateLoop — фоновая авто-проверка обновлений (если включена).
|
|
func (u *Updater) updateLoop(ctx context.Context) {
|
|
ticker := time.NewTicker(6 * time.Hour)
|
|
defer ticker.Stop()
|
|
check := func() {
|
|
if !u.rc.Snapshot().Update.AutoCheck {
|
|
return
|
|
}
|
|
cctx, cancel := context.WithTimeout(ctx, 90*time.Second)
|
|
defer cancel()
|
|
if st, err := u.CheckForUpdate(cctx); err == nil && st.HasUpdate {
|
|
log.Printf("lk-gateway: доступно обновление %s (текущая %s)", st.Available, st.CurrentVersion)
|
|
}
|
|
}
|
|
// первая проверка через минуту после старта
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(time.Minute):
|
|
check()
|
|
}
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
check()
|
|
}
|
|
}
|
|
}
|
|
|
|
func dirOf(path string) string {
|
|
for i := len(path) - 1; i >= 0; i-- {
|
|
if path[i] == '/' {
|
|
return path[:i]
|
|
}
|
|
}
|
|
return "."
|
|
}
|