package lkgateway // Запись канала контура НРД в ИШ (06.10.2026). Root-обработчик пишет канал в базу ИШ и перезапускает шлюз — до // двух минут. Раньше это делал сам запрос страницы: перезагрузка страницы прерывала ожидание, а запрос // состояния ИШ вставал в очередь за записью канала — страница висела на «Проверяю…» без объяснений, и человек // мог уйти «Далее» посреди перезапуска. Теперь запись идёт в фоне bj, страница показывает её ход (и после // перезагрузки), а в конце bj проверяет, что шлюз действительно загрузил канал. import ( "context" "strings" "sync" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter" ) // igateContourView — ход записи канала для страницы. type igateContourView struct { Running bool `json:"running"` Contour string `json:"contour,omitempty"` Stage string `json:"stage,omitempty"` // write | engine | channel | done Started int64 `json:"started,omitempty"` Done bool `json:"done,omitempty"` OK bool `json:"ok"` Msg string `json:"msg,omitempty"` At int64 `json:"at,omitempty"` } type igateContourState struct { sync.Mutex v igateContourView } var igateContourJob igateContourState func (j *igateContourState) start(contour string) bool { j.Lock() defer j.Unlock() if j.v.Running { return false } j.v = igateContourView{Running: true, Contour: contour, Stage: "write", Started: time.Now().Unix(), OK: true} return true } func (j *igateContourState) stage(st string) { j.Lock() j.v.Stage = st j.Unlock() } func (j *igateContourState) finish(ok bool, msg string) { j.Lock() j.v.Running, j.v.Done, j.v.OK, j.v.Msg, j.v.Stage, j.v.At = false, true, ok, msg, "done", time.Now().Unix() j.Unlock() } func (j *igateContourState) view() igateContourView { j.Lock() defer j.Unlock() v := j.v if v.Done && time.Since(time.Unix(v.At, 0)) > 15*time.Minute { return igateContourView{OK: true} // итог давний — не показываем } return v } // igateLastStatus — последнее состояние ИШ от root-обработчика (отдаётся, пока он занят записью канала). var igateLastStatus lastStatus type lastStatus struct { sync.Mutex m map[string]any } func (l *lastStatus) set(m map[string]any) { l.Lock() l.m = m l.Unlock() } func (l *lastStatus) get() map[string]any { l.Lock() defer l.Unlock() if l.m == nil { return nil } out := map[string]any{} for k, v := range l.m { out[k] = v } return out } // igateContourRun — запись канала контура в ИШ и проверка, что шлюз его загрузил (в фоне, без запроса страницы). func igateContourRun(rc *RuntimeConfig, contour, crypto, dep string) { extra := `{"contour":"` + contour + `","crypto":"` + crypto + `","deponent":"` + dep + `"}` ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) defer cancel() go func() { // этап «перезапуск ИШ» — root-обработчик пишет канал за секунды и дальше ждёт движок time.Sleep(4 * time.Second) if v := igateContourJob.view(); v.Running && v.Stage == "write" { igateContourJob.stage("engine") } }() res, err := runKeymediaOp(ctx, "igate-contour", map[string]string{"extra": extra}) if err != nil || res["ok"] != true { msg := errStr(err) if e, ok := res["error"].(string); ok && e != "" { msg = e } igateContourJob.finish(false, msg) return } out := asString(res["output"]) code := strings.ToUpper(contour) + dep if prof, err := nsdadapter.LookupProfile(contour + "-" + crypto); err == nil { code = prof.Channel + dep } enabled := strings.Contains(out, "включён") if enabled { // канал с ключом: шлюз должен его загрузить — иначе любой обмен даёт HTTP 500 igateContourJob.stage("channel") base := pickStr(rc.Snapshot().NSD.IGWBaseURL, igateDefaultURL) loaded := false for i := 0; i < 20 && !loaded; i++ { if found, en, err := igateChannelState(ctx, base, code); err == nil && found && en { loaded = true break } time.Sleep(3 * time.Second) } if !loaded { igateContourJob.finish(false, "канал "+code+" записан, но шлюз его не загрузил — журнал ИШ: /var/lib/igate/.local/share/NSD Integration Gateway/logs-svc/log.txt") return } out += "; шлюз загрузил канал " + code } if rc.Snapshot().NSD.Profile == "" { // контур в bj ещё не выбран (новая установка) — первый настроенный канал и есть рабочий n := rc.Snapshot().NSD n.Profile, n.IGWBaseURL = contour+"-"+crypto, pickStr(n.IGWBaseURL, igateDefaultURL) if rc.UpdateNSD(n) == nil { out += ". bj работает с этим контуром и перезапускается — страница обновится сама" go func() { time.Sleep(3 * time.Second) _, _ = restartUnit(context.Background(), "bj-server") }() } } refreshCertStore(rc) igateContourJob.finish(true, out) }