ИШ обновляется через сервер лицензий вместе с bj; наглядный ход установки ИШ
- Дистрибутив ИШ — компонент выпуска igate-dist (версия = версия ИШ). В выпуск попадает только одобренный архив: deploy/release/igate-approved (версия и sha256), сборка сверяет контрольную сумму. Клиент получает его обновлением; bj-server после обновления (и при запуске) сам обновляет ИШ, если на сервере он старее; итог — событие в «Новостях». Настройки, каналы и база ИШ сохраняются, прежняя версия — в /var/backups. - Установка «с сервера» берёт самый новый проверенный дистрибутив: с образа или из обновления. С сайта НРД — только в режиме разработки. - Большие файлы: одна прежняя копия вместо пяти; предел скачивания — по размеру. - Ход установки ИШ — этапами: пройденные, текущий («идёт 0:41 · обычно до 1:30», дольше обычного — предупреждение), журнал под раскрытием; кнопка на время установки выключена. - Установщик: уникальные этапы; igate-cli и сессия ключницы с принудительным завершением (timeout -k); зависшая установка (ход не менялся 15 минут) снимается повторным запуском; служба установки — не дольше 40 минут. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -89,15 +89,21 @@ case "$cmd" in
|
|||||||
echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"channels\":${chans:-[]}}" ;;
|
echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"channels\":${chans:-[]}}" ;;
|
||||||
|
|
||||||
install)
|
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)
|
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\"}"; }
|
[ "$(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 "не указан дистрибутив ИШ (файл или версия)"
|
[ -n "$file$ver" ] || err "не указан дистрибутив ИШ (файл или версия)"
|
||||||
[ -z "$file" ] || [ -f "$file" ] || err "файл дистрибутива не найден: $file"
|
[ -z "$file" ] || [ -f "$file" ] || err "файл дистрибутива не найден: $file"
|
||||||
[[ -z "$ver" || "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || err "версия ИШ — вида 107.0.930"
|
[[ -z "$ver" || "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || err "версия ИШ — вида 107.0.930"
|
||||||
(umask 077; printf '%s' "${BJ_EXTRA:-}" > "$PARAMS")
|
(umask 077; printf '%s' "${BJ_EXTRA:-}" > "$PARAMS")
|
||||||
echo '{"stage":"start","msg":"установка запущена"}' > "$STATUS"; chown bj:bj "$STATUS" 2>/dev/null
|
echo '{"stage":"queued","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 "не удалось запустить установку"
|
# не дольше 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 "установка ИШ запущена" ;;
|
ok "установка ИШ запущена" ;;
|
||||||
|
|
||||||
install-now)
|
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"
|
{ DEBIAN_FRONTEND=noninteractive apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $need; } || die "не удалось установить пакеты:$need"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
st package "дистрибутив ИШ"
|
st dist "дистрибутив ИШ"
|
||||||
file=$(j file); ver=$(j version)
|
file=$(j file); ver=$(j version)
|
||||||
if [ -z "$file" ]; then
|
if [ -z "$file" ]; then
|
||||||
v=$(printf '%s' "$ver" | tr . -)
|
v=$(printf '%s' "$ver" | tr . -)
|
||||||
@@ -160,7 +166,7 @@ PY
|
|||||||
runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null
|
runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
st package "установка пакета ИШ"
|
st install "установка пакета ИШ"
|
||||||
systemctl stop igate >/dev/null 2>&1 || true
|
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.*
|
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
|
if [ -d "$IGATE_DIR" ]; then
|
||||||
@@ -216,7 +222,8 @@ EOF
|
|||||||
|
|
||||||
st schema "база ИШ: создание схемы"
|
st schema "база ИШ: создание схемы"
|
||||||
if ! psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t; then
|
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 >/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 >/dev/null 2>&1; true'" || true
|
||||||
psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t || die "ИШ не создал схему базы (igate-cli). Журнал: $LOG"
|
psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t || die "ИШ не создал схему базы (igate-cli). Журнал: $LOG"
|
||||||
fi
|
fi
|
||||||
# Общие параметры ИШ. Обязательные для работы без графики и для bj (движок при старте, Web API на
|
# Общие параметры ИШ. Обязательные для работы без графики и для bj (движок при старте, Web API на
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
107.0.930 996bcf9f8f0001dc0e73b9582a5ebc0fa05bda264bc8e8b1785d069ed07f13c3
|
||||||
+81
-13
@@ -5,6 +5,8 @@ package lkgateway
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -153,13 +155,14 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
params["file"] = dst
|
params["file"] = dst
|
||||||
} else if r.FormValue("bundled") == "1" {
|
} else if r.FormValue("bundled") == "1" { // проверенный дистрибутив на сервере (образ или обновление bj)
|
||||||
if igateBundledVersion() == "" {
|
file, _ := igateDistFile()
|
||||||
jsonOut(w, map[string]any{"ok": false, "error": "в образе нет дистрибутива ИШ — скачайте с сайта НРД или загрузите файл"})
|
if file == "" {
|
||||||
|
jsonOut(w, map[string]any{"ok": false, "error": "дистрибутива ИШ на сервере нет — он приходит с обновлением bj"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
params["bundled"] = "1"
|
params["file"] = file
|
||||||
} else if v := strings.TrimSpace(r.FormValue("version")); igateVersionRe.MatchString(v) {
|
} else if v := strings.TrimSpace(r.FormValue("version")); igateVersionRe.MatchString(v) && devMode(rc) { // с сайта НРД — только разработчикам
|
||||||
params["version"] = v
|
params["version"] = v
|
||||||
} else {
|
} else {
|
||||||
jsonOut(w, map[string]any{"ok": false, "error": "загрузите архив ИШ или укажите версию вида 107.0.930"})
|
jsonOut(w, map[string]any{"ok": false, "error": "загрузите архив ИШ или укажите версию вида 107.0.930"})
|
||||||
@@ -170,15 +173,22 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
jsonOut(w, opResult(runKeymediaOp(ctx, "igate-install", map[string]string{"extra": string(extra)})))
|
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) {
|
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")
|
raw, err := os.ReadFile(igateInstallStatusPath)
|
||||||
if err != nil {
|
out := map[string]any{}
|
||||||
|
if err != nil || json.Unmarshal(raw, &out) != nil {
|
||||||
jsonOut(w, map[string]any{})
|
jsonOut(w, map[string]any{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
if fi, err := os.Stat(igateInstallStatusPath); err == nil {
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
out["elapsed"] = int(time.Since(fi.ModTime()).Seconds())
|
||||||
_, _ = w.Write(raw)
|
}
|
||||||
|
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) {
|
mux.HandleFunc("/admin/setup/igate/contour", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -361,13 +371,71 @@ func igateInstallStatus() igateInstallState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func igateBundledVersion() string {
|
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")
|
m, _ := filepath.Glob("/var/lib/bj/igate-dist/igate-*-amd64.zip")
|
||||||
best := ""
|
|
||||||
for _, f := range m {
|
for _, f := range m {
|
||||||
v := strings.ReplaceAll(strings.TrimSuffix(strings.TrimPrefix(filepath.Base(f), "igate-"), "-amd64.zip"), "-", ".")
|
v := strings.ReplaceAll(strings.TrimSuffix(strings.TrimPrefix(filepath.Base(f), "igate-"), "-amd64.zip"), "-", ".")
|
||||||
if igateVersionRe.MatchString(v) && (best == "" || release.IsNewer(v, best)) {
|
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ package lkgateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -198,13 +197,7 @@ func prepareIgate(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if st := igateInstallStatus(); !st.Busy {
|
if st := igateInstallStatus(); !st.Busy {
|
||||||
res, err := runKeymediaOp(ctx, "igate-install", map[string]string{"extra": `{"bundled":"1"}`})
|
if err := igateInstallDist(ctx); err != nil {
|
||||||
if err == nil {
|
|
||||||
if ok, _ := res["ok"].(bool); !ok {
|
|
||||||
err = fmt.Errorf("%v", res["error"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
prepareRun.Lock()
|
prepareRun.Lock()
|
||||||
prepareRun.IgateErr = err.Error()
|
prepareRun.IgateErr = err.Error()
|
||||||
prepareRun.Unlock()
|
prepareRun.Unlock()
|
||||||
@@ -228,7 +221,7 @@ func prepareIgateItem() PrepareItem {
|
|||||||
prepareRun.Unlock()
|
prepareRun.Unlock()
|
||||||
switch {
|
switch {
|
||||||
case st.Busy:
|
case st.Busy:
|
||||||
it.State, it.Detail = "run", "устанавливается: "+st.Msg+" (2–5 минут)"
|
it.State, it.Detail = "run", "устанавливается: "+st.Msg+" (вся установка — 2–5 минут)"
|
||||||
case igateInstalled():
|
case igateInstalled():
|
||||||
it.State, it.Detail = "ok", "установлен"+pickStr(prefixed(", версия ", ishInstalledVersion()), "")
|
it.State, it.Detail = "ok", "установлен"+pickStr(prefixed(", версия ", ishInstalledVersion()), "")
|
||||||
case st.Stage == "error":
|
case st.Stage == "error":
|
||||||
|
|||||||
@@ -366,6 +366,13 @@ func (s *Server) Run(ctx context.Context) error {
|
|||||||
// Лицензия: ежесуточное продление ключа на сервере лицензий lic.zetit.ru.
|
// Лицензия: ежесуточное продление ключа на сервере лицензий lic.zetit.ru.
|
||||||
go superviseLoop(ctx, "license", s.rc, s.licenseLoop)
|
go superviseLoop(ctx, "license", s.rc, s.licenseLoop)
|
||||||
go publicIPLoop(ctx) // внешний адрес сервера — для письма в ARQA (у сервера лицензий, раз в час)
|
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)
|
stopCACerts := StartCACertsAutoUpdater(s.rc)
|
||||||
|
|||||||
@@ -237,6 +237,9 @@ func (u *Updater) ApplyUpdate(ctx context.Context, names []string) (string, erro
|
|||||||
progressStage(1, "Проверка выпуска на сервере лицензий")
|
progressStage(1, "Проверка выпуска на сервере лицензий")
|
||||||
msg, err := u.applyUpdate(ctx, names)
|
msg, err := u.applyUpdate(ctx, names)
|
||||||
progressEnd(msg, err)
|
progressEnd(msg, err)
|
||||||
|
if err == nil { // с обновлением мог прийти новый проверенный дистрибутив ИШ — обновить шлюз
|
||||||
|
go igateAutoUpgrade(context.Background(), u.rc)
|
||||||
|
}
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,7 +446,7 @@ func (u *Updater) ensureSplitModules(ctx context.Context) (string, error) {
|
|||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
cctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
cctx, cancel := context.WithTimeout(ctx, 30*time.Minute) // с дистрибутивом ИШ (160 МБ) скачивание дольше
|
||||||
defer cancel()
|
defer cancel()
|
||||||
msg, err := u.ApplyUpdate(cctx, names)
|
msg, err := u.ApplyUpdate(cctx, names)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,6 +8,19 @@
|
|||||||
#ig-ct td{vertical-align:top;padding:6px 8px 6px 0}
|
#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.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-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)}}
|
||||||
</style>
|
</style>
|
||||||
<div id="ig-box">
|
<div id="ig-box">
|
||||||
<div class="card" style="margin:10px 0">
|
<div class="card" style="margin:10px 0">
|
||||||
@@ -15,8 +28,10 @@
|
|||||||
<div id="ig-head" class="muted">Проверяю…</div>
|
<div id="ig-head" class="muted">Проверяю…</div>
|
||||||
<p class="muted" style="font-size:13px;margin:6px 0 0">Версия ИШ — та, что проверена ZETIT вместе с bj; новые версии приходят с обновлением bj. bj ставит шлюз сам при подготовке.</p>
|
<p class="muted" style="font-size:13px;margin:6px 0 0">Версия ИШ — та, что проверена ZETIT вместе с bj; новые версии приходят с обновлением bj. 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-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>
|
||||||
|
<ol class="ig-steps" id="ig-steps" hidden></ol>
|
||||||
<div id="ig-out" style="margin-top:8px;font-size:13px"></div>
|
<div id="ig-out" style="margin-top:8px;font-size:13px"></div>
|
||||||
<pre id="ig-log" style="display:none;max-height:180px;overflow:auto;font-size:11px;margin-top:6px"></pre>
|
<details id="ig-logbox" hidden style="margin-top:6px;font-size:12.5px"><summary style="cursor:pointer;color:var(--accent)">Журнал установки</summary>
|
||||||
|
<pre id="ig-log" style="max-height:220px;overflow:auto;font-size:11px;margin-top:6px"></pre></details>
|
||||||
{{if devOn}}<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Для разработчиков: другая версия ИШ</summary>
|
{{if devOn}}<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Для разработчиков: другая версия ИШ</summary>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:end;margin-top:8px">
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:end;margin-top:8px">
|
||||||
<label class="f">Версия на сайте НРД<input id="ig-ver" value="107.0.930" style="width:140px"></label>
|
<label class="f">Версия на сайте НРД<input id="ig-ver" value="107.0.930" style="width:140px"></label>
|
||||||
@@ -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); });
|
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;
|
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=i<idx?'done':i===idx?(s.stage==='error'?'err':'cur'):'', note='';
|
||||||
|
if(i===idx && s.stage!=='error'){ var slow=el>x[2]*2;
|
||||||
|
note='<small'+(slow?' class="slow"':'')+'>идёт '+mmss(el)+' · обычно до '+mmss(x[2])+(slow?' — дольше обычного; установка работает, пока счётчик идёт; зависшая снимается сама через 40 минут':'')+'</small>'; }
|
||||||
|
return '<li class="'+cls+'"><span class="ig-ic"></span><span class="ig-t">'+esc(x[1])+note+'</span></li>'; }).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){
|
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) return;
|
||||||
if(s.stage==='done'){ clearInterval(iv); out.innerHTML=ok(s.msg); load(); }
|
if(stageKey(s)!==cur.key){ cur.key=stageKey(s); cur.since=Date.now()-(s.elapsed||0)*1000; }
|
||||||
if(s.stage==='error'){ clearInterval(iv); out.innerHTML=bad(s.msg); }
|
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(){});
|
}).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');
|
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());
|
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; }
|
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+'…';
|
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(); }); };
|
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();
|
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(){});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -88,7 +88,14 @@ func (c *Client) DownloadArtifact(ctx context.Context, a Artifact, destDir strin
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
c.authorize(req)
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("release: скачивание %s: %w", a.Name, err)
|
return "", fmt.Errorf("release: скачивание %s: %w", a.Name, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ type Component struct {
|
|||||||
FromCore bool
|
FromCore bool
|
||||||
// Feature — признак лицензии, без которого сервер обновлений не отдаёт файл компонента.
|
// Feature — признак лицензии, без которого сервер обновлений не отдаёт файл компонента.
|
||||||
Feature string
|
Feature string
|
||||||
|
// Big — большой файл (дистрибутив): при замене хранится одна прежняя копия, а не пять.
|
||||||
|
Big bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Components — все компоненты bj. Порядок — порядок показа и установки (ядро перезапускается последним).
|
// 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-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-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"},
|
{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 — компонент по имени артефакта.
|
// ComponentByName — компонент по имени артефакта.
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) {
|
|||||||
// с образа кладёт unit-файлы всех служб сразу (выключенными), а файл модуля приезжает обновлением.
|
// с образа кладёт unit-файлы всех служб сразу (выключенными), а файл модуля приезжает обновлением.
|
||||||
_, statErr := os.Stat(j.c.PathAt(opt.Root))
|
_, statErr := os.Stat(j.c.PathAt(opt.Root))
|
||||||
fresh := statErr != nil
|
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)
|
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()}
|
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 — копия прежнего файла, запись нового рядом и атомарная замена.
|
// 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 {
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(dst); err == nil {
|
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 {
|
if err := copyFileMode(dst, dst+".bak-"+ts, 0o755); err != nil {
|
||||||
return fmt.Errorf("копия прежнего файла: %w", err)
|
return fmt.Errorf("копия прежнего файла: %w", err)
|
||||||
}
|
}
|
||||||
pruneBackups(dst, 5)
|
pruneBackups(dst, keep)
|
||||||
}
|
}
|
||||||
mode := os.FileMode(0o644)
|
mode := os.FileMode(0o644)
|
||||||
if execBit {
|
if execBit {
|
||||||
|
|||||||
@@ -96,6 +96,20 @@ if [ -f "$JAR" ]; then
|
|||||||
cver crypto-service.jar CRYPTO_VERSION "$(sha256sum "$JAR" | cut -d' ' -f1)" > "$DIST/crypto-service.jar.version"
|
cver crypto-service.jar CRYPTO_VERSION "$(sha256sum "$JAR" | cut -d' ' -f1)" > "$DIST/crypto-service.jar.version"
|
||||||
fi
|
fi
|
||||||
echo " криптосервис: $(cat "$DIST/crypto-service.jar.version" 2>/dev/null || echo нет)"
|
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"
|
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"
|
mkdir -p "$(dirname "$CV_STATE")" && mv "$CV_NEW" "$CV_STATE"
|
||||||
|
|||||||
Reference in New Issue
Block a user