ИШ 110.0.997 в обновлении + полный бэкап и откат ИШ

- Одобрен дистрибутив ИШ 110.0.997 (deploy/release/igate-approved): клиенты
  получают его компонентом igate-dist, bj-server обновляет ИШ сам (igateAutoUpgrade)
  после применения обновления. Проверено на 28: установка 107.0.930 → 110.0.997
  штатным bj-igate-admin, каналы сохранены, круг #M2MHR → #M2MTH с НРД TEST3 и
  приём ответа поллером bj — работают.
- Бэкап перед установкой/обновлением ИШ стал полным: кроме /opt/nsd/igate и дампа
  базы — данные /var/lib/igate (без флешки с ключом и кэша), юнит, обёртка запуска,
  версия. Раньше откатиться было нечем: igate-dist.zip перезаписывается новой версией.
- bj-igate-admin rollback / rollback-now: возврат программы, данных и базы ИШ из
  последнего бэкапа (pg_restore от postgres — с мандатными метками Astra). После
  отката пришедшая версия не ставится автоматически (/var/lib/bj/igate-autoupgrade-skip),
  следующая — снова да. Проверено на 28: 110.0.997 → 107.0.930 за 40 с, обмен с НРД работает.
- «Шлюз НРД»: кнопка «Откатить ИШ к <версия>», когда есть полная резервная копия.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-21 19:59:19 +03:00
parent efe56bdde1
commit 41d33fefad
6 changed files with 77 additions and 6 deletions
+17
View File
@@ -215,6 +215,16 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
defer cancel()
jsonOut(w, opResult(runKeymediaOp(ctx, "igate-install", map[string]string{"extra": string(extra)})))
})
// откат ИШ к версии из последней резервной копии (её делает каждая установка/обновление)
mux.HandleFunc("/admin/setup/igate/rollback", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
defer cancel()
jsonOut(w, opResult(runKeymediaOp(ctx, "igate-rollback", nil)))
})
// ход установки: этап, сообщение, журнал и сколько секунд идёт текущий этап (по времени записи файла)
mux.HandleFunc("/admin/setup/igate/install-status", func(w http.ResponseWriter, r *http.Request) {
raw, err := os.ReadFile(igateInstallStatusPath)
@@ -461,12 +471,19 @@ func igateInstallDist(ctx context.Context) error {
// igateAutoUpgrade — ИШ на сервере старее дистрибутива, пришедшего с обновлением bj: обновить (настройки,
// каналы и база ИШ сохраняются; прежняя версия копируется в /var/backups). Вызывается после обновления bj
// и при запуске bj-server (обновление ядра перезапускает его до того, как успел бы сработать первый вызов).
// igateAutoUpgradeSkipPath — версия дистрибутива, с которой ИШ откатили (её автоматически не ставим).
var igateAutoUpgradeSkipPath = "/var/lib/bj/igate-autoupgrade-skip"
func igateAutoUpgrade(ctx context.Context, rc *RuntimeConfig) {
cur := ishInstalledVersion()
_, dist := igateDistFile()
if !igateInstalled() || cur == "" || dist == "" || !release.IsNewer(dist, cur) || igateInstallStatus().Busy {
return
}
// ИШ откатили с этой версии вручную — сам не ставим, пока не придёт следующая
if b, err := os.ReadFile(igateAutoUpgradeSkipPath); err == nil && strings.TrimSpace(string(b)) == dist {
return
}
log.Printf("lk-gateway: ИШ %s старее проверенного дистрибутива %s — обновляю", cur, dist)
if err := igateInstallDist(ctx); err != nil {
raiseSystemAlert(rc, "ИШ не обновлён до "+dist, "Пришёл дистрибутив ИШ "+dist+", но обновление не запустилось: "+err.Error()+". Повторить — мастер, шаг «Шлюз НРД».")
@@ -52,6 +52,7 @@
<div id="ig-head" class="muted">Проверяю…</div>
<p class="muted" style="font-size:12px;margin:4px 0 0">Версию ИШ проверяет ZETIT; новые приходят с обновлением bj.</p>
<div id="ig-reinst" style="display:none;margin-top:10px"><button type="button" class="btn" id="ig-bnd">Установить ИШ</button> <span class="muted" id="ig-bnd-v" style="font-size:12.5px"></span></div>
<div id="ig-rb" style="display:none;margin-top:8px"><button type="button" class="btn" id="ig-rb-btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Откатить ИШ к <span id="ig-rb-v"></span></button> <span class="muted" style="font-size:12.5px">вернуть версию, которая стояла до последнего обновления (программа, данные, база ИШ)</span></div>
<ol class="ig-steps" id="ig-steps" hidden></ol>
<div id="ig-out" style="margin-top:8px;font-size:13px"></div>
<details id="ig-logbox" hidden style="margin-top:6px;font-size:12.5px"><summary style="cursor:pointer;color:var(--accent)">Журнал установки</summary>
@@ -250,6 +251,7 @@
if(!d.ok){ head.innerHTML=bad(d.error); return; }
var ri=document.getElementById('ig-reinst'), bb=document.getElementById('ig-bnd'), bv=document.getElementById('ig-bnd-v');
var running=d.installed&&d.engine==='Running';
var rb=document.getElementById('ig-rb'); if(rb){ var can=d.rollback&&d.rollback!==d.version; rb.style.display=can?'block':'none'; if(can) document.getElementById('ig-rb-v').textContent=d.rollback; }
if(!d.installed) head.innerHTML=bad('ИШ на этом сервере не установлен');
else head.innerHTML=(running?ok('ИШ '+d.version+' установлен и работает'):bad('ИШ '+d.version+' установлен, но не работает: движок '+(d.engine||'не отвечает')+', служба '+d.service));
if(d.busy) head.innerHTML='<span style="color:var(--accent)">⟳ Шлюз перезапускается — '+esc(d.busy)+'; состояние — последнее известное</span>';
@@ -347,6 +349,9 @@
if(Date.now()-t0>2700000){ clearInterval(iv); clearInterval(tick); } },3000); }
document.getElementById('ig-bnd').onclick=function(){ var p=new URLSearchParams(); p.set('bundled','1');
out.textContent='Запускаю…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
document.getElementById('ig-rb-btn').onclick=function(){ var v=document.getElementById('ig-rb-v').textContent;
if(!confirm('Откатить ИШ к версии '+v+'? Шлюз остановится на 1–3 минуты; программа, данные и база ИШ вернутся к состоянию до обновления. Пришедшую версию bj сам ставить больше не будет, пока не выйдет следующая.')) return;
out.textContent='Запускаю откат…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/rollback',new URLSearchParams()).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
if(document.getElementById('ig-dl')) document.getElementById('ig-dl').onclick=function(){ var p=new URLSearchParams(); p.set('version',document.getElementById('ig-ver').value.trim());
out.textContent='Запускаю…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
if(document.getElementById('ig-up')) document.getElementById('ig-up').onclick=function(){ var f=document.getElementById('ig-file').files[0]; if(!f){ out.textContent='Выберите файл igate-…-amd64.zip'; return; }