From c62225dd7d6247ed4ebebb072a1f089059d72a5b Mon Sep 17 00:00:00 2001 From: zuevav Date: Wed, 7 Oct 2026 00:11:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=A8=20=D0=B6=D0=B4=D1=91=D1=82=20?= =?UTF-8?q?=D1=84=D0=BB=D0=B5=D1=88=D0=BA=D1=83=20=D1=81=20=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=BE=D0=BC=20=E2=80=94=20bj=20=D1=8D=D1=82=D0=BE?= =?UTF-8?q?=20=D0=B2=D0=B8=D0=B4=D0=B8=D1=82=20=D0=B8=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D1=88=D0=BB=D1=8E=D0=B7=20=D1=81=D0=B0=D0=BC;=20=D0=B8=D1=82?= =?UTF-8?q?=D0=BE=D0=B3=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B0;=20=D1=81=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BE=D0=BA=20=D0=BA=D0=BE=D0=BF=D0=B8=D0=B9;=20=D1=81?= =?UTF-8?q?=D0=BA=D1=80=D1=8B=D1=82=D1=8B=D0=B5=20=D0=B1=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ИШ без флешки ждёт её в окне Валидаты («Вставьте ключевой носитель»): статус «ждёт флешку», без кнопки переустановки; сторож не тратит перезапуски, а когда флешка появилась — перезапускает ИШ сам; письмо - шаг ключа: итог — карточка «Ключ подключён», «Заменить ключ» вместо «Установить ещё один», этапы свёрнуты - копии: «Копии на хранилище» ищет там же, куда ложится копия (подпапка по умолчанию bj-backup, не из старых настроек); поля показываются разобранными - [hidden] прячет всегда (пустая полоска под версией ИШ); «Шлюз НРД на другом сервере?» — что это за адрес Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- deploy/linux/sbin/bj-backup-admin | 3 +- internal/lkgateway/admin.go | 1 + internal/lkgateway/backup.go | 16 ++++++-- internal/lkgateway/igate.go | 1 + internal/lkgateway/igwwatch.go | 40 +++++++++++++++++++ .../lkgateway/web/templates/admin_wizard.html | 5 ++- internal/lkgateway/web/templates/layout.html | 3 ++ .../lkgateway/web/templates/part_backup.html | 4 +- .../lkgateway/web/templates/part_igate.html | 3 +- .../lkgateway/web/templates/part_keywiz.html | 31 ++++++++------ 10 files changed, 86 insertions(+), 21 deletions(-) diff --git a/deploy/linux/sbin/bj-backup-admin b/deploy/linux/sbin/bj-backup-admin index 9d8fdf8..7779524 100755 --- a/deploy/linux/sbin/bj-backup-admin +++ b/deploy/linux/sbin/bj-backup-admin @@ -19,7 +19,8 @@ err() { echo "{\"ok\":false,\"error\":$(q "$1")}"; exit 0; } [ -r "$CONF" ] && . "$CONF" # хранилище из запроса (восстановление на новом сервере, проверка до сохранения) или из настроек SHARE_IN=$(j share); DIR_IN=$(j dir); USER_IN=$(j user); PASS_IN=$(j password) -SHARE=${SHARE_IN:-${SHARE:-}}; DIR=${DIR_IN:-${DIR:-}} +# хранилище из запроса — и подпапка из запроса (не из прежних настроек: иначе список копий искал не там) +if [ -n "$SHARE_IN" ]; then SHARE=$SHARE_IN; DIR=$DIR_IN; else SHARE=${SHARE:-}; DIR=${DIR:-}; fi # smbloc — адрес хранилища, как его вводят (\\nas\backup\bj как в проводнике, //nas/backup, smb://…, сервер # в «Хранилище» + общая папка в «Папке») → две строки: общая папка для smbclient (//сервер/папка) и путь внутри неё smbloc(){ python3 - "$1" "$2" <<'PY' diff --git a/internal/lkgateway/admin.go b/internal/lkgateway/admin.go index 23bf602..b707e75 100644 --- a/internal/lkgateway/admin.go +++ b/internal/lkgateway/admin.go @@ -60,6 +60,7 @@ var templateFuncs = template.FuncMap{ "backupStatus": backupStatus, "backupWhen": backupWhen, "smbWin": smbWin, + "smbView": smbView, "pgLocal": pgDSNLocal, "whForm": func(w WorkHoursSettings, ret string) map[string]any { return map[string]any{"W": w, "Ret": ret} }, "weekdayNames": func() []string { return weekdayShort }, diff --git a/internal/lkgateway/backup.go b/internal/lkgateway/backup.go index 077b175..7be7027 100644 --- a/internal/lkgateway/backup.go +++ b/internal/lkgateway/backup.go @@ -86,6 +86,16 @@ func smbWin(share string) string { return strings.ReplaceAll(share, "/", `\`) } +// smbView — адрес хранилища для формы: сетевая папка как в проводнике и подпапка (сохранённые прежними +// версиями «\\сервер\» + «папка» показываются уже разобранными). +func smbView(share, dir string) map[string]string { + sh, d := smbLocation(share, dir) + if sh == "" { + return map[string]string{"Share": share, "Dir": dir} + } + return map[string]string{"Share": smbWin(sh), "Dir": pickStr(d, "bj-backup")} +} + // smbShareOK — в адресе есть и сервер, и общая папка. func smbShareOK(share string) bool { return strings.Count(strings.TrimPrefix(share, "//"), "/") == 1 } @@ -129,10 +139,10 @@ func backupParams(r *http.Request) map[string]string { } } if p["share"] != "" { // адрес как в проводнике → общая папка и путь внутри неё + // подпапка не указана — та же, что у копий по умолчанию (bj-backup): иначе root подставлял папку из + // прежних настроек и «Копии на хранилище» искала не там, куда легла копия p["share"], p["dir"] = smbLocation(p["share"], p["dir"]) - if p["dir"] == "" { - delete(p, "dir") - } + p["dir"] = pickStr(p["dir"], "bj-backup") } return p } diff --git a/internal/lkgateway/igate.go b/internal/lkgateway/igate.go index 6267c93..e3da48a 100644 --- a/internal/lkgateway/igate.go +++ b/internal/lkgateway/igate.go @@ -166,6 +166,7 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) { res["depo_db"] = s.Fancy.DepoDB() res["depo_db_test"], res["depo_db_prod"] = s.NSD.DepoDBTest, s.NSD.DepoDBProd res["bundled"] = igateBundledVersion() + res["keymedia"] = keymediaState() // missing — ИШ ждёт флешку с ключом: переустановка не поможет jsonOut(w, res) }) // установка или обновление ИШ: загруженный архив НРД или версия для скачивания с nsd.ru diff --git a/internal/lkgateway/igwwatch.go b/internal/lkgateway/igwwatch.go index 49f1afd..5860f02 100644 --- a/internal/lkgateway/igwwatch.go +++ b/internal/lkgateway/igwwatch.go @@ -18,6 +18,8 @@ import ( "log" "net/http" "net/url" + "os" + "path/filepath" "strings" "sync" "time" @@ -45,6 +47,9 @@ type igwWatchState struct { LastRestart time.Time LastRemind time.Time RestartMsg string + // KeyWait — ИШ ждёт флешку с ключом подписи (окно Валидаты «Вставьте ключевой носитель»): перезапуск не + // поможет, пока её нет, а когда она появится — bj перезапустит ИШ сам. + KeyWait bool } var igwWatch igwWatchState @@ -155,6 +160,23 @@ func igwWatchTick(ctx context.Context, rc *RuntimeConfig) { needAlert := !w.Alerted && down >= igwAlertAfter canRestart := igwLocal(base) && down >= igwAlertAfter && w.Restarts < igwRestartMax && (w.LastRestart.IsZero() || now.Sub(w.LastRestart) >= igwRestartEvery) + keyAlert := false + if igwLocal(base) { + switch keymediaState() { + case "missing": // без флешки ИШ ждёт её в окне Валидаты — перезапуски бесполезны + canRestart = false + if !w.KeyWait { + w.KeyWait, keyAlert = true, true + needAlert = false + w.Alerted, w.LastRemind = true, now + } + case "ok": + if w.KeyWait { // флешку вернули — перезапустить ИШ сразу, попытки — заново + w.KeyWait, w.Restarts, w.LastRestart = false, 0, time.Time{} + canRestart = true + } + } + } remind := w.Alerted && !canRestart && w.Restarts >= igwRestartMax && now.Sub(w.LastRemind) >= igwRemindEvery if needAlert { w.Alerted, w.LastRemind = true, now @@ -169,6 +191,9 @@ func igwWatchTick(ctx context.Context, rc *RuntimeConfig) { attempt, since, lastErr := w.Restarts, w.DownSince, w.LastErr w.mu.Unlock() + if keyAlert { + raiseSystemAlert(rc, "ИШ НРД ждёт флешку с ключом подписи", fmt.Sprintf("Интеграционный шлюз НРД не запускается: флешки с ключом подписи в сервере нет (%s пуста) — Валидата ждёт ключевой носитель. Вставьте флешку с ключом в этот сервер: bj перезапустит шлюз сам. Переустанавливать ИШ не нужно.", keymediaDir)) + } if needAlert { plan := "ИШ на другом сервере — перезапустите службу ИШ там." if igwLocal(base) { @@ -186,6 +211,21 @@ func igwWatchTick(ctx context.Context, rc *RuntimeConfig) { } } +// keymediaDir — куда подключается флешка с ключом для ИШ (bj-keymedia-install, строка fstab по UUID). +const keymediaDir = "/var/lib/igate/keymedia" + +// keymediaState — флешка с ключом для ИШ: "" — не настроена (ключ не с флешки), "ok" — на месте, "missing" — нет. +func keymediaState() string { + b, err := os.ReadFile("/etc/fstab") + if err != nil || !strings.Contains(string(b), " "+keymediaDir+" ") { + return "" + } + if m, _ := filepath.Glob(keymediaDir + "/vdkeys/*.vdk"); len(m) > 0 { + return "ok" + } + return "missing" +} + // igwTryRestart — перезапуск службы ИШ и проверка, поднялся ли он. func igwTryRestart(rc *RuntimeConfig, base string, attempt int) { log.Printf("igw-watch: перезапуск службы %s (попытка %d из %d)", igwServiceUnit, attempt, igwRestartMax) diff --git a/internal/lkgateway/web/templates/admin_wizard.html b/internal/lkgateway/web/templates/admin_wizard.html index f8b2183..0570679 100644 --- a/internal/lkgateway/web/templates/admin_wizard.html +++ b/internal/lkgateway/web/templates/admin_wizard.html @@ -583,13 +583,14 @@

Через интеграционный шлюз НРД (ИШ) на этом сервере bj отправляет переводы и получает ответы. bj ставит и настраивает шлюз сам; доступ к тестовому и боевому контуру НРД открывает по заявке на своём сайте — как её подать, ниже.

{{template "igateInstall" .}}
Доступ к контурам НРД — заявкой на сайте НРД, письмами НРД не подключает. Страница сервиса MOEX МОСТ для M2M, раздел «Как подключиться»: заявка на тестирование — система МОСТ, тип «M2M Автотестирование», затем «M2M Подключение»; после тестирования — договор с НРД и боевой контур.
-
Дополнительно: адрес шлюза (обычно не нужно) +
Шлюз НРД на другом сервере? (обычно не нужно) +

bj работает со шлюзом, который сам поставил на этом сервере: адрес — http://localhost:8090, менять его не нужно. Меняйте, только если у организации уже есть свой интеграционный шлюз НРД на другом сервере и bj должен работать через него: укажите адрес его веб-API (обычно порт 8090). Ключ подписи и каналы тогда настраиваются на том шлюзе.

{{if .Settings.NSD.IGWv904}}{{end}} -