diff --git a/deploy/linux/sbin/bj-igate-admin b/deploy/linux/sbin/bj-igate-admin index 14fadb6..153a500 100755 --- a/deploy/linux/sbin/bj-igate-admin +++ b/deploy/linux/sbin/bj-igate-admin @@ -73,19 +73,22 @@ run_as_igate_keyring() { # команда в сессии igate с разбло dbus-run-session -- bash -c 'eval "$(printf "%s\n" "$(cat "$HOME/.keyring-pass")" | gnome-keyring-daemon --replace --unlock --components=secrets,pkcs11 2>/dev/null)"; export GNOME_KEYRING_CONTROL; '"$1" } +channels_json() { # каналы ИШ: код, включён, адрес НРД, ключ, крипто, депонент, папка + if runuser -u postgres -- psql -lqt 2>/dev/null | cut -d'|' -f1 | grep -qw "$DB"; then + psqlq -c "select coalesce(json_agg(json_build_object('id',c.id,'code',c.code,'enabled',c.enable, + 'endpoint',(select value from parameters p where p.chanel_id=c.id and p.name='wslEndpoint'), + 'profile',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.profile'), + 'crypto',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.type'), + 'deponent',(select value from parameters p where p.chanel_id=c.id and p.name='repositoryCode'), + 'dir',(select value from parameters p where p.chanel_id=c.id and p.name='dir')) order by c.id),'[]') from channels c" 2>/dev/null || echo "[]" + else echo "[]"; fi +} + case "$cmd" in status) inst=false; [ -x "$IGATE_DIR/igate-svc/igate-svc" ] && inst=true svc=$(systemctl is-active igate 2>/dev/null); st=$(engine_state) - chans="[]" - if runuser -u postgres -- psql -lqt 2>/dev/null | cut -d'|' -f1 | grep -qw "$DB"; then - chans=$(psqlq -c "select coalesce(json_agg(json_build_object('id',c.id,'code',c.code,'enabled',c.enable, - 'endpoint',(select value from parameters p where p.chanel_id=c.id and p.name='wslEndpoint'), - 'profile',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.profile'), - 'crypto',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.type'), - 'deponent',(select value from parameters p where p.chanel_id=c.id and p.name='repositoryCode'), - 'dir',(select value from parameters p where p.chanel_id=c.id and p.name='dir')) order by c.id),'[]') from channels c" 2>/dev/null || echo "[]") - fi + chans=$(channels_json) rb=""; BK=$(cat /var/backups/ish-last-backup.txt 2>/dev/null) [ -n "$BK" ] && [ -s "$BK/version" ] && [ -s "$BK/opt-nsd-igate.tar.gz" ] && [ -s "$BK/igate-db.dump" ] && [ -s "$BK/var-lib-igate.tar.gz" ] && rb=$(cat "$BK/version") echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"rollback\":$(q "$rb"),\"channels\":${chans:-[]}}" ;; @@ -168,11 +171,14 @@ PY runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null fi - st install "установка пакета ИШ" + oldv=""; [ -d "$IGATE_DIR" ] && oldv=$(igate_version) + st stop "остановка ${oldv:+старого }ИШ${oldv:+ $oldv}" systemctl stop igate >/dev/null 2>&1 || true pkill -9 -u "$SVC_USER" -f igate-svc 2>/dev/null; pkill -9 -u "$SVC_USER" -f Xvfb 2>/dev/null; rm -f /tmp/CoreFxPipe_NSD.IGate.* if [ -d "$IGATE_DIR" ]; then + st backup "резервная копия ИШ $oldv (программа, данные, база)" BK=/var/backups/ish-$(date +%Y%m%d-%H%M%S); mkdir -p "$BK" + channels_json > "$BK/channels.json" # для проверки после обновления: каналы и ключи те же tar czf "$BK/opt-nsd-igate.tar.gz" -C "$(dirname "$IGATE_DIR")" "$(basename "$IGATE_DIR")" 2>/dev/null runuser -u postgres -- pg_dump -Fc "$DB" > "$BK/igate-db.dump" 2>/dev/null # для отката: данные ИШ (без флешки с ключом и кэша), юнит, обёртка запуска, версия @@ -188,6 +194,7 @@ PY newv=$(basename "$deb" | sed -E "s/^igate_([0-9.]+)-([0-9]+)_.*/\\1.\\2/") # igate_110.0-997_amd64.deb → 110.0.997 die() { st rollback "$1 — откатываю к прежней версии"; echo "== ошибка обновления: $1 — откат"; BJ_RB_REASON="$1" BJ_RB_TO="$newv" /usr/local/sbin/bj-igate-admin rollback-now; exit 1; } fi + st install "установка ${FULLBK:+нового }ИШ${newv:+ $newv}" echo "igate igate/service boolean false" | debconf-set-selections echo "igate igate/shortcut boolean false" | debconf-set-selections DEBIAN_FRONTEND=noninteractive dpkg -i --force-confnew "$deb" || { DEBIAN_FRONTEND=noninteractive apt-get -f install -y -qq || die "пакет ИШ не установился (dpkg)"; } @@ -264,7 +271,44 @@ SQL systemctl restart igate || die "служба igate не запустилась" wait_engine 120 || die "ИШ запущен, но движок не перешёл в Running за 2 минуты. Журнал ИШ: $DATA_DIR/logs-svc/log.txt" case "$(j file)" in /var/lib/bj/igate-upload/*) rm -f "$(j file)" ;; esac # загруженный архив больше не нужен (с образа — остаётся) - st done "ИШ $(igate_version) установлен и работает; дальше — канал контура (TEST3 или боевой)" ;; + if [ -n "${FULLBK:-}" ]; then + # обновление: «готово» — только когда новый ИШ проверен; не прошло — die (автооткат к прежней версии) + st verify "проверка нового ИШ: версия, служба, каналы, ключ, обмен с НРД" + v=$(igate_version) + [ "$v" = "$newv" ] || die "после установки версия ИШ $v, ожидалась $newv" + echo "✓ версия ИШ $v" + sleep 10 + [ "$(systemctl is-active igate)" = active ] && [ "$(engine_state)" = Running ] || die "ИШ $v остановился после запуска" + echo "✓ служба igate работает, движок Running" + now=$(channels_json) + chk=$(python3 - "$FULLBK/channels.json" "$now" <<'PY' +import json,sys +keys=('code','enabled','endpoint','profile','crypto','deponent','dir') +try: old=json.load(open(sys.argv[1])) +except Exception: old=None +new=json.loads(sys.argv[2] or '[]') +pick=lambda l:[{k:c.get(k) for k in keys} for c in l] +if old is not None and pick(old)!=pick(new): + print('ERR каналы ИШ после обновления не совпадают с прежними: было %s, стало %s'%(pick(old),pick(new))); sys.exit() +on=[c for c in new if c.get('enabled')] +print('OK '+'; '.join('канал %s включён, ключ %s'%(c['code'],c.get('profile') or '—') for c in on) if on else 'OK каналов не настроено') +PY +) + case "$chk" in OK*) echo "✓ ${chk#OK } — как до обновления" ;; *) die "${chk#ERR }" ;; esac + xok="" + if printf '%s' "$now" | grep -q '"enabled" : true'; then + pid=$(pgrep -u "$SVC_USER" -f "$IGATE_DIR/igate-svc/igate-svc" | tail -1) + for _ in $(seq 1 30); do + grep -a "PID_$pid " "$DATA_DIR/logs-svc/log.txt" 2>/dev/null | grep -q -- '-> OK (HTTP 200)' && { xok=1; break; } + sleep 3 + done + # НРД не ответил — не повод откатывать (старая версия связаться тоже не сможет): итог с предупреждением + [ -n "$xok" ] && echo "✓ обмен с НРД: запрос к НРД прошёл (HTTP 200)" || echo "⚠ за 90 с ИШ не получил ответа НРД — проверьте связь" + fi + st done "ИШ обновлён с $oldv до $v и проверен: служба и движок работают, каналы и ключ как до обновления${xok:+, обмен с НРД идёт}${xok:-; ответа НРД пока нет — проверьте связь}" + else + st done "ИШ $(igate_version) установлен и работает; дальше — канал контура (TEST3 или боевой)" + fi ;; rollback) pgrep -f "bj-igate-admin (install|rollback)-now" >/dev/null && err "установка или откат ИШ уже идёт" diff --git a/internal/lkgateway/igate.go b/internal/lkgateway/igate.go index a5c7275..e5e0767 100644 --- a/internal/lkgateway/igate.go +++ b/internal/lkgateway/igate.go @@ -234,20 +234,7 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) { }) // ход установки: этап, сообщение, журнал и сколько секунд идёт текущий этап (по времени записи файла) mux.HandleFunc("/admin/setup/igate/install-status", func(w http.ResponseWriter, r *http.Request) { - raw, err := os.ReadFile(igateInstallStatusPath) - out := map[string]any{} - if err != nil || json.Unmarshal(raw, &out) != nil { - jsonOut(w, map[string]any{}) - return - } - if fi, err := os.Stat(igateInstallStatusPath); err == nil { - out["elapsed"] = int(time.Since(fi.ModTime()).Seconds()) - } - st := igateInstallStatus() - if st.Stage == "error" && out["stage"] != "error" { // молчит 20 минут — прервалась - out["stage"], out["msg"] = "error", st.Msg - } - jsonOut(w, out) + jsonOut(w, igateInstallStatusJSON(rc)) }) // канал контура в ИШ (адрес, отпечаток УЦ, папка обмена); ключ — мастером установки ключа mux.HandleFunc("/admin/setup/igate/contour", func(w http.ResponseWriter, r *http.Request) { @@ -410,6 +397,30 @@ type igateInstallState struct { // igateInstallStatusPath — файл хода установки; var — для тестов. var igateInstallStatusPath = "/var/lib/bj/igate-install-status.json" +// igateInstallStatusJSON — ход установки/обновления ИШ для страниц: этап, сообщение, журнал, сколько +// секунд идёт этап (elapsed), идёт ли сейчас (busy). Как только установка закончилась, версии +// пересобираются тут же — строка «Интеграционный шлюз НРД (ИШ): …» не показывает прежнюю. +func igateInstallStatusJSON(rc *RuntimeConfig) map[string]any { + out := map[string]any{} + raw, err := os.ReadFile(igateInstallStatusPath) + if err != nil || json.Unmarshal(raw, &out) != nil { + return map[string]any{} + } + fi, err := os.Stat(igateInstallStatusPath) + if err == nil { + out["elapsed"] = int(time.Since(fi.ModTime()).Seconds()) + } + st := igateInstallStatus() + if st.Stage == "error" && out["stage"] != "error" { // молчит 20 минут — прервалась + out["stage"], out["msg"] = "error", st.Msg + } + out["busy"] = st.Busy + if !st.Busy && err == nil && fi.ModTime().After(systemVersions(rc).At) { + refreshVersions(rc) + } + return out +} + func igateInstallStatus() igateInstallState { p := igateInstallStatusPath var st igateInstallState diff --git a/internal/lkgateway/ish_version.go b/internal/lkgateway/ish_version.go index 2fc246e..23eada3 100644 --- a/internal/lkgateway/ish_version.go +++ b/internal/lkgateway/ish_version.go @@ -82,20 +82,24 @@ var ( // сайт НРД на каждый показ статуса). Сетевая часть best-effort: при недоступности // сайта возвращается только установленная версия. func ishVersionInfo(ctx context.Context) IshVersionInfo { + // установленную версию читаем всегда заново (ИШ мог обновиться минуту назад), сайт — из кэша ishVerMu.Lock() - if ishVerCache != nil && time.Since(ishVerCache.CheckedAt) < ishVerTTL { - c := *ishVerCache - ishVerMu.Unlock() - return c - } + site := ishVerCache ishVerMu.Unlock() - - info := IshVersionInfo{ - Installed: ishInstalledVersion(), - CheckedAt: time.Now().UTC(), + 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() } - linux, latest := ishSiteVersions(ctx) - info.SiteLinux, info.SiteLatest = linux, latest + info := IshVersionInfo{ + Installed: ishInstalledVersion(), + SiteLinux: site.SiteLinux, + SiteLatest: site.SiteLatest, + CheckedAt: site.CheckedAt, + } + linux, latest := info.SiteLinux, info.SiteLatest switch { case info.Installed == "": @@ -113,10 +117,6 @@ func ishVersionInfo(ctx context.Context) IshVersionInfo { info.UpToDate = false info.Note = "доступно обновление до " + linux + " (Astra-Linux)" } - - ishVerMu.Lock() - ishVerCache = &info - ishVerMu.Unlock() return info } diff --git a/internal/lkgateway/updprogress.go b/internal/lkgateway/updprogress.go index 3c093e3..4622ea2 100644 --- a/internal/lkgateway/updprogress.go +++ b/internal/lkgateway/updprogress.go @@ -15,22 +15,27 @@ import ( ) type updateProgressState struct { - Seq int64 `json:"seq"` - Active bool `json:"active"` - Finished bool `json:"finished"` - Step int `json:"step"` - Steps int `json:"steps"` - Stage string `json:"stage"` - Done int64 `json:"done"` - Total int64 `json:"total"` - Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется) - Result string `json:"result,omitempty"` - Note string `json:"note,omitempty"` // пояснение, которое нужно показать и после перезапуска ядра - Err string `json:"error,omitempty"` - Started time.Time `json:"started"` - Version string `json:"version"` // версия работающего bj-server + Seq int64 `json:"seq"` + Active bool `json:"active"` + Finished bool `json:"finished"` + Step int `json:"step"` + Steps int `json:"steps"` + Stage string `json:"stage"` + Done int64 `json:"done"` + Total int64 `json:"total"` + Target string `json:"target,omitempty"` // версия ядра после обновления (если ядро обновляется) + Result string `json:"result,omitempty"` + Note string `json:"note,omitempty"` // пояснение, которое нужно показать и после перезапуска ядра + Err string `json:"error,omitempty"` + Started time.Time `json:"started"` + Version string `json:"version"` // версия работающего bj-server + IgateTo string `json:"igate_to,omitempty"` // bj сам обновит ИШ до этой версии (ещё не обновил) + Igate map[string]any `json:"igate,omitempty"` // ход установки ИШ (как /admin/setup/igate/install-status) + Up int `json:"up"` // секунд с запуска bj-server } +var procStarted = time.Now() + var updProg = struct { sync.Mutex s updateProgressState @@ -110,6 +115,7 @@ func (h *setupHandlers) updateProgress(w http.ResponseWriter, r *http.Request) { s := updProg.s updProg.Unlock() s.Version = BuildVersion + s.IgateTo, s.Igate, s.Up = igatePendingUpgrade(), igateInstallStatusJSON(h.rc), int(time.Since(procStarted).Seconds()) w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") _ = json.NewEncoder(w).Encode(s) diff --git a/internal/lkgateway/web/templates/layout.html b/internal/lkgateway/web/templates/layout.html index 0bc979f..13bde5e 100644 --- a/internal/lkgateway/web/templates/layout.html +++ b/internal/lkgateway/web/templates/layout.html @@ -1030,6 +1030,9 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
+ + @@ -1055,6 +1058,62 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd tick(); } } + /* Обновление шлюза НРД (ИШ): с обновлением bj пришёл дистрибутив ИШ — bj ставит его сам после перезапуска. + Окно не закрывается, пока новый ИШ не установлен и не проверен; на любой странице, пока идёт, — + то же окно (страницы не показывают полуобновлённое состояние). Ошибка — текст, журнал и автооткат. */ + var IGS=[['prep','Загрузка и проверка дистрибутива ИШ'],['stop','Остановка старого ИШ'],['backup','Резервная копия старого ИШ'], + ['install','Установка нового ИШ'],['start','Запуск нового ИШ'],['verify','Проверка: версия, каналы, ключ, обмен с НРД']]; + function igKey(s){ var g=s.stage||''; if(/^(queued|deps|dist|user|db|package)$/.test(g)) return 'prep'; + if(g==='config'||g==='schema') return 'install'; return g; } + var ig={on:false,t0:0,seen:false,rb:false,idx:-1}; + function igDraw(s, waiting){ + var k=igKey(s||{}), i=-1; IGS.forEach(function(x,j){ if(x[0]===k) i=j; }); + if(!ig.rb && i>ig.idx) ig.idx=i; if(waiting) ig.idx=-1; + var list=document.getElementById('upd-ig'); list.style.display='block'; + list.innerHTML=IGS.map(function(x,j){ + var st=j