diff --git a/deploy/linux/sbin/bj-igate-admin b/deploy/linux/sbin/bj-igate-admin index 3388c6d..94b3f24 100755 --- a/deploy/linux/sbin/bj-igate-admin +++ b/deploy/linux/sbin/bj-igate-admin @@ -89,15 +89,21 @@ case "$cmd" in echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"channels\":${chans:-[]}}" ;; install) - pgrep -f "bj-igate-admin install-now" >/dev/null && err "установка ИШ уже идёт" + if pgrep -f "bj-igate-admin install-now" >/dev/null; then + # идёт — не мешать; но если ход не менялся 15 минут, установка зависла: снять её и начать заново + age=$(( $(date +%s) - $(stat -c %Y "$STATUS" 2>/dev/null || echo 0) )) + [ "$age" -gt 900 ] || err "установка ИШ уже идёт" + systemctl stop 'bj-igate-install-*' >/dev/null 2>&1; pkill -f "bj-igate-admin install-now" 2>/dev/null; sleep 2 + fi file=$(j file); ver=$(j version) [ "$(j bundled)" = 1 ] && [ -z "$file$ver" ] && { file=$(ls /var/lib/bj/igate-dist/igate-*-amd64.zip 2>/dev/null | sort -V | tail -1); [ -n "$file" ] || err "в образе нет дистрибутива ИШ"; BJ_EXTRA="{\"file\":\"$file\"}"; } [ -n "$file$ver" ] || err "не указан дистрибутив ИШ (файл или версия)" [ -z "$file" ] || [ -f "$file" ] || err "файл дистрибутива не найден: $file" [[ -z "$ver" || "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || err "версия ИШ — вида 107.0.930" (umask 077; printf '%s' "${BJ_EXTRA:-}" > "$PARAMS") - echo '{"stage":"start","msg":"установка запущена"}' > "$STATUS"; chown bj:bj "$STATUS" 2>/dev/null - systemd-run --unit="bj-igate-install-$(date +%s)" --collect /usr/local/sbin/bj-igate-admin install-now >/dev/null 2>&1 || err "не удалось запустить установку" + echo '{"stage":"queued","msg":"установка запущена"}' > "$STATUS"; chown bj:bj "$STATUS" 2>/dev/null + # не дольше 40 минут: что бы ни зависло, служба установки будет остановлена (ход покажет «прервалась») + systemd-run --unit="bj-igate-install-$(date +%s)" --collect -p RuntimeMaxSec=2400 /usr/local/sbin/bj-igate-admin install-now >/dev/null 2>&1 || err "не удалось запустить установку" ok "установка ИШ запущена" ;; install-now) @@ -121,7 +127,7 @@ PY { DEBIAN_FRONTEND=noninteractive apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $need; } || die "не удалось установить пакеты:$need" fi - st package "дистрибутив ИШ" + st dist "дистрибутив ИШ" file=$(j file); ver=$(j version) if [ -z "$file" ]; then v=$(printf '%s' "$ver" | tr . -) @@ -160,7 +166,7 @@ PY runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null fi - st package "установка пакета ИШ" + st install "установка пакета ИШ" 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 @@ -216,7 +222,8 @@ EOF st schema "база ИШ: создание схемы" if ! psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t; then - run_as_igate_keyring "timeout 90 $IGATE_DIR/igate-cli/igate-cli --data $HOME_DIR /dev/null 2>&1; true" + # -k: .NET-программа может не завершиться по SIGTERM — через 15 с принудительно (иначе ждали бы бесконечно) + timeout -k 15 150 bash -c "$(declare -f run_as_igate_keyring); SVC_USER=$SVC_USER HOME_DIR=$HOME_DIR run_as_igate_keyring 'timeout -k 15 90 $IGATE_DIR/igate-cli/igate-cli --data $HOME_DIR /dev/null 2>&1; true'" || true psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t || die "ИШ не создал схему базы (igate-cli). Журнал: $LOG" fi # Общие параметры ИШ. Обязательные для работы без графики и для bj (движок при старте, Web API на diff --git a/deploy/release/igate-approved b/deploy/release/igate-approved new file mode 100644 index 0000000..0f09558 --- /dev/null +++ b/deploy/release/igate-approved @@ -0,0 +1 @@ +107.0.930 996bcf9f8f0001dc0e73b9582a5ebc0fa05bda264bc8e8b1785d069ed07f13c3 diff --git a/internal/lkgateway/igate.go b/internal/lkgateway/igate.go index 0379c7e..4e5c6e6 100644 --- a/internal/lkgateway/igate.go +++ b/internal/lkgateway/igate.go @@ -5,6 +5,8 @@ package lkgateway import ( "context" "encoding/json" + "errors" + "fmt" "io" "log" "net/http" @@ -153,13 +155,14 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) { return } params["file"] = dst - } else if r.FormValue("bundled") == "1" { - if igateBundledVersion() == "" { - jsonOut(w, map[string]any{"ok": false, "error": "в образе нет дистрибутива ИШ — скачайте с сайта НРД или загрузите файл"}) + } else if r.FormValue("bundled") == "1" { // проверенный дистрибутив на сервере (образ или обновление bj) + file, _ := igateDistFile() + if file == "" { + jsonOut(w, map[string]any{"ok": false, "error": "дистрибутива ИШ на сервере нет — он приходит с обновлением bj"}) return } - params["bundled"] = "1" - } else if v := strings.TrimSpace(r.FormValue("version")); igateVersionRe.MatchString(v) { + params["file"] = file + } else if v := strings.TrimSpace(r.FormValue("version")); igateVersionRe.MatchString(v) && devMode(rc) { // с сайта НРД — только разработчикам params["version"] = v } else { jsonOut(w, map[string]any{"ok": false, "error": "загрузите архив ИШ или укажите версию вида 107.0.930"}) @@ -170,15 +173,22 @@ 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/install-status", func(w http.ResponseWriter, r *http.Request) { - raw, err := os.ReadFile("/var/lib/bj/igate-install-status.json") - if err != nil { + raw, err := os.ReadFile(igateInstallStatusPath) + out := map[string]any{} + if err != nil || json.Unmarshal(raw, &out) != nil { jsonOut(w, map[string]any{}) return } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Header().Set("Cache-Control", "no-store") - _, _ = w.Write(raw) + 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) }) // канал контура в ИШ (адрес, отпечаток УЦ, папка обмена); ключ — мастером установки ключа mux.HandleFunc("/admin/setup/igate/contour", func(w http.ResponseWriter, r *http.Request) { @@ -361,13 +371,71 @@ func igateInstallStatus() igateInstallState { } func igateBundledVersion() string { + _, v := igateDistFile() + return v +} + +// igateDistFile — самый новый дистрибутив ИШ на сервере (файл и версия): с образа (igate-<версия>-amd64.zip) +// или из обновления bj (компонент igate-dist, версия — в списке установленных компонентов). Это версии, +// проверенные ZETIT; другой ИШ клиенту не ставится. +func igateDistFile() (string, string) { + best, file := "", "" m, _ := filepath.Glob("/var/lib/bj/igate-dist/igate-*-amd64.zip") - best := "" for _, f := range m { v := strings.ReplaceAll(strings.TrimSuffix(strings.TrimPrefix(filepath.Base(f), "igate-"), "-amd64.zip"), "-", ".") if igateVersionRe.MatchString(v) && (best == "" || release.IsNewer(v, best)) { - best = v + best, file = v, f + } + } + if c, ok := release.ComponentByName("igate-dist"); ok { + if inst, ok := release.LoadInstalled(release.InstalledFile)[c.Name]; ok && igateVersionRe.MatchString(inst.Version) { + if _, err := os.Stat(c.Path); err == nil && (best == "" || release.IsNewer(inst.Version, best)) { + best, file = inst.Version, c.Path + } + } + } + return file, best +} + +// igateInstallDist — поставить (или обновить) ИШ из самого нового дистрибутива на сервере. +func igateInstallDist(ctx context.Context) error { + file, _ := igateDistFile() + if file == "" { + return errors.New("дистрибутива ИШ на сервере нет — он приходит с обновлением bj") + } + extra, _ := json.Marshal(map[string]string{"file": file}) + res, err := runKeymediaOp(ctx, "igate-install", map[string]string{"extra": string(extra)}) + if err == nil { + if ok, _ := res["ok"].(bool); !ok { + err = fmt.Errorf("%v", res["error"]) + } + } + return err +} + +// igateAutoUpgrade — ИШ на сервере старее дистрибутива, пришедшего с обновлением bj: обновить (настройки, +// каналы и база ИШ сохраняются; прежняя версия копируется в /var/backups). Вызывается после обновления bj +// и при запуске bj-server (обновление ядра перезапускает его до того, как успел бы сработать первый вызов). +func igateAutoUpgrade(ctx context.Context, rc *RuntimeConfig) { + cur := ishInstalledVersion() + _, dist := igateDistFile() + if !igateInstalled() || cur == "" || dist == "" || !release.IsNewer(dist, cur) || igateInstallStatus().Busy { + return + } + log.Printf("lk-gateway: ИШ %s старее проверенного дистрибутива %s — обновляю", cur, dist) + if err := igateInstallDist(ctx); err != nil { + raiseSystemAlert(rc, "ИШ не обновлён до "+dist, "Пришёл дистрибутив ИШ "+dist+", но обновление не запустилось: "+err.Error()+". Повторить — мастер, шаг «Шлюз НРД».") + return + } + for ctx.Err() == nil { + time.Sleep(5 * time.Second) + if st := igateInstallStatus(); !st.Busy { + if st.Stage == "done" { + raiseSystemAlert(rc, "ИШ обновлён до "+dist, "Интеграционный шлюз НРД обновлён с "+cur+" до "+dist+" (версия, проверенная ZETIT). Настройки и каналы сохранены.") + } else { + raiseSystemAlert(rc, "ИШ не обновлён до "+dist, "Обновление ИШ с "+cur+" до "+dist+" не прошло: "+st.Msg+". Прежняя версия сохранена в /var/backups; повторить — мастер, шаг «Шлюз НРД».") + } + return } } - return best } diff --git a/internal/lkgateway/prepare.go b/internal/lkgateway/prepare.go index 70707a0..ad5933d 100644 --- a/internal/lkgateway/prepare.go +++ b/internal/lkgateway/prepare.go @@ -7,7 +7,6 @@ package lkgateway import ( "context" - "fmt" "net/http" "os" "os/exec" @@ -198,13 +197,7 @@ func prepareIgate(ctx context.Context) { return } if st := igateInstallStatus(); !st.Busy { - res, err := runKeymediaOp(ctx, "igate-install", map[string]string{"extra": `{"bundled":"1"}`}) - if err == nil { - if ok, _ := res["ok"].(bool); !ok { - err = fmt.Errorf("%v", res["error"]) - } - } - if err != nil { + if err := igateInstallDist(ctx); err != nil { prepareRun.Lock() prepareRun.IgateErr = err.Error() prepareRun.Unlock() @@ -228,7 +221,7 @@ func prepareIgateItem() PrepareItem { prepareRun.Unlock() switch { case st.Busy: - it.State, it.Detail = "run", "устанавливается: "+st.Msg+" (2–5 минут)" + it.State, it.Detail = "run", "устанавливается: "+st.Msg+" (вся установка — 2–5 минут)" case igateInstalled(): it.State, it.Detail = "ok", "установлен"+pickStr(prefixed(", версия ", ishInstalledVersion()), "") case st.Stage == "error": diff --git a/internal/lkgateway/server.go b/internal/lkgateway/server.go index c366cb4..f39cb21 100644 --- a/internal/lkgateway/server.go +++ b/internal/lkgateway/server.go @@ -366,6 +366,13 @@ func (s *Server) Run(ctx context.Context) error { // Лицензия: ежесуточное продление ключа на сервере лицензий lic.zetit.ru. go superviseLoop(ctx, "license", s.rc, s.licenseLoop) go publicIPLoop(ctx) // внешний адрес сервера — для письма в ARQA (у сервера лицензий, раз в час) + go func() { // ИШ старее дистрибутива из обновления bj (ядро перезапускалось при обновлении) — обновить + select { + case <-ctx.Done(): + case <-time.After(2 * time.Minute): + igateAutoUpgrade(ctx, s.rc) + } + }() // Авто-обновление сертификатов УЦ раз в сутки (если оператор включил). stopCACerts := StartCACertsAutoUpdater(s.rc) diff --git a/internal/lkgateway/updater.go b/internal/lkgateway/updater.go index 8b02808..7aa6ccc 100644 --- a/internal/lkgateway/updater.go +++ b/internal/lkgateway/updater.go @@ -237,6 +237,9 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro progressStage(1, "Проверка выпуска на сервере лицензий") msg, err := u.applyUpdate(ctx, names) progressEnd(msg, err) + if err == nil { // с обновлением мог прийти новый проверенный дистрибутив ИШ — обновить шлюз + go igateAutoUpgrade(context.Background(), u.rc) + } return msg, err } @@ -443,7 +446,7 @@ func (u *Updater) ensureSplitModules(ctx context.Context) (string, error) { if len(names) == 0 { return "", nil } - cctx, cancel := context.WithTimeout(ctx, 10*time.Minute) + cctx, cancel := context.WithTimeout(ctx, 30*time.Minute) // с дистрибутивом ИШ (160 МБ) скачивание дольше defer cancel() msg, err := u.ApplyUpdate(cctx, names) if err != nil { diff --git a/internal/lkgateway/web/templates/part_igate.html b/internal/lkgateway/web/templates/part_igate.html index 6afa780..e0abaa9 100644 --- a/internal/lkgateway/web/templates/part_igate.html +++ b/internal/lkgateway/web/templates/part_igate.html @@ -8,6 +8,19 @@ #ig-ct td{vertical-align:top;padding:6px 8px 6px 0} #ig-box .btn.sm{padding:4px 10px;font-size:12.5px} #ig-box .btn.ghost{background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)} +/* ход установки ИШ: этапы по порядку, у текущего — сколько идёт и сколько обычно */ +.ig-steps{list-style:none;margin:10px 0 0;padding:10px 14px;border:1px solid var(--border);border-radius:10px;display:grid;gap:2px} +.ig-steps li{display:flex;gap:10px;align-items:flex-start;padding:4px 0;font-size:13.5px;color:var(--muted)} +.ig-steps li.done{color:var(--text-2)} .ig-steps li.cur{color:var(--text);font-weight:500} .ig-steps li.err{color:var(--err)} +.ig-ic{flex:none;position:relative;width:16px;height:16px;border-radius:50%;margin-top:2px;box-sizing:border-box;border:1.5px solid var(--border-strong)} +.ig-steps li.done .ig-ic{background:var(--ok);border-color:var(--ok)} +.ig-steps li.done .ig-ic::after{content:"";position:absolute;left:50%;top:45%;width:3.5px;height:7px;border:solid #fff;border-width:0 1.6px 1.6px 0;transform:translate(-50%,-50%) rotate(45deg)} +.ig-steps li.cur .ig-ic{border:2px solid var(--accent-weak);border-top-color:var(--accent);animation:ig-spin .8s linear infinite} +.ig-steps li.err .ig-ic{background:var(--err);border-color:var(--err)} +.ig-t small{display:block;font-weight:400;color:var(--muted);font-size:12px} +.ig-t small.slow{color:var(--warn)} +@keyframes ig-spin{to{transform:rotate(360deg)}} +@media (prefers-reduced-motion:reduce){.ig-steps li.cur .ig-ic{animation:none;border-color:var(--accent)}}
@@ -15,8 +28,10 @@
Проверяю…

Версия ИШ — та, что проверена ZETIT вместе с bj; новые версии приходят с обновлением bj. bj ставит шлюз сам при подготовке.

+
- + {{if devOn}}
Для разработчиков: другая версия ИШ
@@ -104,23 +119,51 @@ post('/admin/setup/igate/switch',p).then(function(d){ cout.innerHTML=d.ok?ok(d.output):bad(d.error); if(d.ok) setTimeout(function(){location.reload()},15000); }); }; document.getElementById('ig-crypto').onchange=load; - function poll(){ var t0=Date.now(), iv=setInterval(function(){ + // этапы установщика (bj-igate-admin) по порядку и сколько каждый обычно длится, секунд + var STAGES=[['queued','Запуск установки',15],['deps','Системные пакеты для работы без графики',120],['dist','Дистрибутив ИШ',30], + ['user','Пользователь igate и хранилище паролей',15],['db','База ИШ в PostgreSQL',20],['install','Установка пакета ИШ',90], + ['config','Настройка для работы без графики',15],['schema','Создание схемы базы ИШ',90],['start','Запуск ИШ и движка',120]]; + var stepsEl=document.getElementById('ig-steps'), logbox=document.getElementById('ig-logbox'), cur={key:'',since:0,s:null}, tick=0; + function stageKey(s){ // прежний установщик называл два этапа «package», а первый и последний — «start» + if(s.stage==='package') return /дистрибутив/.test(s.msg||'')?'dist':'install'; + if(s.stage==='start' && /запущена/.test(s.msg||'')) return 'queued'; + return s.stage; } + function mmss(n){ n=Math.max(0,Math.round(n)); return Math.floor(n/60)+':'+('0'+n%60).slice(-2); } + function draw(){ + var s=cur.s; if(!s) return; + var k=stageKey(s), idx=-1; STAGES.forEach(function(x,i){ if(x[0]===k) idx=i; }); + if(s.stage==='done') idx=STAGES.length; + var el=(Date.now()-cur.since)/1000; + stepsEl.hidden=false; + stepsEl.innerHTML=STAGES.map(function(x,i){ + var cls=ix[2]*2; + note='идёт '+mmss(el)+' · обычно до '+mmss(x[2])+(slow?' — дольше обычного; установка работает, пока счётчик идёт; зависшая снимается сама через 40 минут':'')+''; } + return '
  • '+esc(x[1])+note+'
  • '; }).join(''); + } + function poll(){ var t0=Date.now(), btn=document.getElementById('ig-bnd'); + btn.disabled=true; // пока идёт установка — второй раз не нажать + clearInterval(tick); tick=setInterval(draw,1000); + var iv=setInterval(function(){ fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){ - if(!s.stage) return; out.textContent='Установка: '+s.msg; if(s.log){ lg.style.display='block'; lg.textContent=s.log; lg.scrollTop=lg.scrollHeight; } - if(s.stage==='done'){ clearInterval(iv); out.innerHTML=ok(s.msg); load(); } - if(s.stage==='error'){ clearInterval(iv); out.innerHTML=bad(s.msg); } + if(!s.stage) return; + if(stageKey(s)!==cur.key){ cur.key=stageKey(s); cur.since=Date.now()-(s.elapsed||0)*1000; } + cur.s=s; draw(); out.textContent=''; + if(s.log){ logbox.hidden=false; lg.textContent=s.log; lg.scrollTop=lg.scrollHeight; } + if(s.stage==='done'){ clearInterval(iv); clearInterval(tick); btn.disabled=false; out.innerHTML=ok(s.msg); load(); } + if(s.stage==='error'){ clearInterval(iv); clearInterval(tick); btn.disabled=false; out.innerHTML=bad(s.msg)+' — можно нажать «Установить ИШ заново»'; logbox.open=true; load(); } }).catch(function(){}); - if(Date.now()-t0>1200000) clearInterval(iv); },3000); } + 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='Запускаю…'; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); }; + 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-dl')) document.getElementById('ig-dl').onclick=function(){ var p=new URLSearchParams(); p.set('version',document.getElementById('ig-ver').value.trim()); - out.textContent='Запускаю…'; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); }; + 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; } var fd=new FormData(); fd.append('file',f); out.textContent='Загружаю '+f.name+'…'; fetch('/admin/setup/igate/install',{method:'POST',body:fd}).then(function(r){return r.json()}).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); }; load(); // установка уже идёт (например, её начала «Подготовка модулей») — показать ход - fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){ if(s.stage&&s.stage!=='done'&&s.stage!=='error'){ out.textContent='Установка: '+s.msg; poll(); } }).catch(function(){}); + fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){ if(s.stage&&s.stage!=='done'&&s.stage!=='error') poll(); }).catch(function(){}); })(); {{end}} diff --git a/internal/release/client.go b/internal/release/client.go index ebcaf4f..5f029e8 100644 --- a/internal/release/client.go +++ b/internal/release/client.go @@ -88,7 +88,14 @@ func (c *Client) DownloadArtifact(ctx context.Context, a Artifact, destDir strin return "", err } c.authorize(req) - resp, err := c.HTTP.Do(req) + hc := c.HTTP + if hc.Timeout > 0 && a.Size > 16<<20 { + // большой файл (дистрибутив ИШ — 160 МБ): общий предел запроса — по размеру, из расчёта 256 КБ/с + cp := *hc + cp.Timeout = hc.Timeout + time.Duration(a.Size/(256<<10))*time.Second + hc = &cp + } + resp, err := hc.Do(req) if err != nil { return "", fmt.Errorf("release: скачивание %s: %w", a.Name, err) } diff --git a/internal/release/components.go b/internal/release/components.go index 4d4d1d2..70811d0 100644 --- a/internal/release/components.go +++ b/internal/release/components.go @@ -26,6 +26,8 @@ type Component struct { FromCore bool // Feature — признак лицензии, без которого сервер обновлений не отдаёт файл компонента. Feature string + // Big — большой файл (дистрибутив): при замене хранится одна прежняя копия, а не пять. + Big bool } // Components — все компоненты bj. Порядок — порядок показа и установки (ядро перезапускается последним). @@ -42,6 +44,10 @@ var Components = []Component{ {Name: "bj-lk", Title: "Личный кабинет клиента", Path: "/opt/bj/bj-lk", Unit: "bj-lk", Exec: true, Feature: "web-cabinet"}, {Name: "bj-npr", Title: "Риск-монитор НПР / УДС", Path: "/opt/bj/bj-npr", Unit: "bj-npr", Exec: true, FromCore: true, Feature: "npr"}, {Name: "bj-quikexport", Title: "Экспорт QUIK", Path: "/opt/bj/bj-quikexport", Unit: "bj-quikexport", Exec: true, FromCore: true, Feature: "quik-export"}, + // Дистрибутив интеграционного шлюза НРД — версия, проверенная ZETIT вместе с bj (версия компонента = + // версия ИШ). Обновление кладёт файл; bj-server сам переставляет ИШ, если на сервере он старее + // (настройки и каналы сохраняются). С сайта НРД ИШ у клиента не качается. + {Name: "igate-dist", Title: "Интеграционный шлюз НРД (дистрибутив)", Path: "/var/lib/bj/igate-dist/igate-dist.zip", Core: true, Big: true, Feature: "updates"}, } // ComponentByName — компонент по имени артефакта. diff --git a/internal/release/install.go b/internal/release/install.go index 2870249..7ba99e3 100644 --- a/internal/release/install.go +++ b/internal/release/install.go @@ -134,7 +134,7 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) { // с образа кладёт unit-файлы всех служб сразу (выключенными), а файл модуля приезжает обновлением. _, statErr := os.Stat(j.c.PathAt(opt.Root)) fresh := statErr != nil - if err := installFile(j.file, j.c.PathAt(opt.Root), j.c.Exec, ts); err != nil { + if err := installFile(j.file, j.c.PathAt(opt.Root), j.c.Exec, ts, j.c.Big); err != nil { return fail("установка %s: %v", j.c.Name, err) } installed[j.c.Name] = Installed{Version: j.a.Version, SHA256: j.a.SHA256, InstalledAt: time.Now().UTC()} @@ -248,15 +248,20 @@ func readStagedJSON(dir, name string, v any) error { } // installFile — копия прежнего файла, запись нового рядом и атомарная замена. -func installFile(src, dst string, execBit bool, ts string) error { +func installFile(src, dst string, execBit bool, ts string, big bool) error { if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } if _, err := os.Stat(dst); err == nil { + keep := 5 + if big { // дистрибутив в сотни мегабайт — одна прежняя копия + keep = 1 + } + pruneBackups(dst, keep-1) if err := copyFileMode(dst, dst+".bak-"+ts, 0o755); err != nil { return fmt.Errorf("копия прежнего файла: %w", err) } - pruneBackups(dst, 5) + pruneBackups(dst, keep) } mode := os.FileMode(0o644) if execBit { diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 07dd2ca..7af2d86 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -96,6 +96,20 @@ if [ -f "$JAR" ]; then cver crypto-service.jar CRYPTO_VERSION "$(sha256sum "$JAR" | cut -d' ' -f1)" > "$DIST/crypto-service.jar.version" fi echo " криптосервис: $(cat "$DIST/crypto-service.jar.version" 2>/dev/null || echo нет)" +# Дистрибутив ИШ — только версия, проверенная ZETIT вместе с bj: deploy/release/igate-approved («версия sha256»). +# Архив НРД — из IGATE_ZIP (по умолчанию ~/ish-upgrade/igate-<версия через дефис>-amd64.zip); контрольная +# сумма должна совпасть с одобренной. Версия компонента = версия ИШ: клиенты получают его с обновлением, +# bj-server сам обновляет ИШ, если на сервере он старее. +IGA="$ROOT/deploy/release/igate-approved" +if [ -f "$IGA" ]; then + read -r IG_VER IG_SHA < "$IGA" + IG_ZIP=${IGATE_ZIP:-$HOME/ish-upgrade/igate-$(echo "$IG_VER" | tr . -)-amd64.zip} + [ -f "$IG_ZIP" ] || { echo "нет дистрибутива ИШ $IG_VER: $IG_ZIP (IGATE_ZIP=…)"; exit 1; } + [ "$(sha256sum "$IG_ZIP" | cut -d' ' -f1)" = "$IG_SHA" ] || { echo "дистрибутив ИШ $IG_ZIP не совпадает с одобренным (deploy/release/igate-approved)"; exit 1; } + cp "$IG_ZIP" "$DIST/igate-dist" + echo "$IG_VER" > "$DIST/igate-dist.version" + echo " ИШ: $IG_VER (одобренный дистрибутив)" +fi go run ./cmd/bj-release build -dir "$DIST" -version "$VERSION" -channel "$CHANNEL" -key "$KEY" -keyid release -out "$DIST/manifest.json" # версии и отпечатки этого релиза — в репозиторий (закоммитить вместе с релизом) mkdir -p "$(dirname "$CV_STATE")" && mv "$CV_NEW" "$CV_STATE"