ИШ ждёт флешку с ключом — bj это видит и перезапускает шлюз сам; итог установки ключа; список копий; скрытые блоки
- ИШ без флешки ждёт её в окне Валидаты («Вставьте ключевой носитель»): статус «ждёт флешку», без кнопки переустановки; сторож не тратит перезапуски, а когда флешка появилась — перезапускает ИШ сам; письмо - шаг ключа: итог — карточка «Ключ подключён», «Заменить ключ» вместо «Установить ещё один», этапы свёрнуты - копии: «Копии на хранилище» ищет там же, куда ложится копия (подпапка по умолчанию bj-backup, не из старых настроек); поля показываются разобранными - [hidden] прячет всегда (пустая полоска под версией ИШ); «Шлюз НРД на другом сервере?» — что это за адрес Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -583,13 +583,14 @@
|
||||
<p>Через интеграционный шлюз НРД (ИШ) на этом сервере bj отправляет переводы и получает ответы. bj ставит и настраивает шлюз сам; доступ к тестовому и боевому контуру НРД открывает по заявке на своём сайте — как её подать, ниже.</p>
|
||||
{{template "igateInstall" .}}
|
||||
<div class="wiz-info" style="margin:12px 0 0"><span><b>Доступ к контурам НРД</b> — заявкой на сайте НРД, письмами НРД не подключает. Страница сервиса <a href="https://www.nsd.ru/services/novye-servisy/moex-most-dlya-m2m/" target="_blank" rel="noopener">MOEX МОСТ для M2M</a>, раздел «Как подключиться»: заявка на тестирование — система МОСТ, тип «M2M Автотестирование», затем «M2M Подключение»; после тестирования — договор с НРД и боевой контур.</span></div>
|
||||
<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Дополнительно: адрес шлюза (обычно не нужно)</summary>
|
||||
<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Шлюз НРД на другом сервере? (обычно не нужно)</summary>
|
||||
<p class="muted" style="font-size:13px;margin:8px 0 0">bj работает со шлюзом, который сам поставил на этом сервере: адрес — <code>http://localhost:8090</code>, менять его не нужно. Меняйте, только если у организации уже есть свой интеграционный шлюз НРД на другом сервере и bj должен работать через него: укажите адрес его веб-API (обычно порт 8090). Ключ подписи и каналы тогда настраиваются на том шлюзе.</p>
|
||||
<form method="post" action="/admin/setup/nsd" style="margin-top:10px;display:grid;gap:10px;max-width:680px">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=7">
|
||||
{{if .Settings.NSD.IGWv904}}<input type="hidden" name="igw_v904" value="on">{{end}}
|
||||
<input type="hidden" name="keep_requisites" value="1">
|
||||
<input type="hidden" name="profile" value="{{if .Settings.NSD.Profile}}{{.Settings.NSD.Profile}}{{else}}test3-gost{{end}}">
|
||||
<label style="margin:0">Адрес шлюза на этом сервере
|
||||
<label style="margin:0">Адрес веб-API шлюза НРД
|
||||
<input type="text" name="igw_base_url" value="{{if .Settings.NSD.IGWBaseURL}}{{.Settings.NSD.IGWBaseURL}}{{else}}http://localhost:8090{{end}}" placeholder="http://localhost:8090" style="width:100%;margin-top:4px"></label>
|
||||
<p id="igw-onyx" style="display:none;color:var(--err);font-size:13px;margin:0">Это адрес веб-сервиса НРД (ONYX), а не шлюза: bj работает через ИШ на этом сервере. Укажите адрес шлюза — обычно <a href="#" id="igw-def">http://localhost:8090</a>.</p>
|
||||
<script>(function(){var u=document.querySelector('[name=igw_base_url]'),w=document.getElementById('igw-onyx');
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{.Title}} · Bridge&Join</title>
|
||||
<style>
|
||||
/* hidden прячет всегда: иначе display:grid/flex у класса рисовал пустые рамки (полоска под версией ИШ) */
|
||||
[hidden]{display:none!important}
|
||||
|
||||
/* ===================== Дизайн-система ===================== */
|
||||
/* «Диспетчерская депозитария»: приборная панель оператора НРД/M2M.
|
||||
Restrained — прохладные нейтрали + один индиго-акцент; MOEX-красный
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<form method="post" action="/admin/setup/backup" id="bk-form" style="display:grid;gap:10px;max-width:640px">
|
||||
<label class="chk" style="display:flex;align-items:center;gap:8px;font-weight:400"><input type="checkbox" name="enabled" style="width:auto"{{if $b.Enabled}} checked{{end}}> <b>Делать копии по расписанию</b></label>
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:10px">
|
||||
<label>Сетевая папка — как в проводнике<input name="share" data-need="сетевая папка" value="{{smbWin $b.Share}}" placeholder="\\nas\backup"></label>
|
||||
<label>Подпапка для копий<input name="dir" value="{{$b.Dir}}" placeholder="bj-backup"></label>
|
||||
<label>Сетевая папка — как в проводнике<input name="share" data-need="сетевая папка" value="{{if $b.Share}}{{(smbView $b.Share $b.Dir).Share}}{{end}}" placeholder="\\nas\backup"></label>
|
||||
<label>Подпапка для копий<input name="dir" value="{{if $b.Share}}{{(smbView $b.Share $b.Dir).Dir}}{{end}}" placeholder="bj-backup"></label>
|
||||
</div>
|
||||
<p class="muted" style="font-size:12px;margin:-4px 0 0">Например <code>\\10.10.10.11\backup</code> — та же папка, что открывается в проводнике Windows с этим логином. Копии ложатся в подпапку внутри неё.</p>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
|
||||
@@ -138,8 +138,9 @@
|
||||
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>';
|
||||
if(!running && d.keymedia==='missing'){ head.innerHTML='<span style="color:var(--warn)">⚠ ИШ '+esc(d.version)+' ждёт флешку с ключом подписи — её нет в сервере. Вставьте флешку: bj перезапустит шлюз сам. Переустанавливать ИШ не нужно.</span>'; }
|
||||
// не стоит или не работает — одна кнопка: поставить заново проверенную версию с сервера (систему переставлять не нужно)
|
||||
ri.style.display=running?'none':'block';
|
||||
ri.style.display=(running||d.keymedia==='missing')?'none':'block'; // без флешки переустановка не поможет
|
||||
if(!running){
|
||||
if(d.bundled){ bb.style.display=''; bb.textContent=d.installed?'Установить ИШ заново':'Установить ИШ'; bv.textContent='версия '+d.bundled+' — проверена ZETIT, дистрибутив на сервере'; }
|
||||
else { bb.style.display='none'; bv.textContent='Дистрибутива ИШ на сервере нет — он приходит с обновлением bj («Настройка → Обновления»).'; }
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
.kw-tile b{display:block;font-size:14.5px}
|
||||
.kw-tile small{display:block;color:var(--muted);font-size:13px;line-height:1.4;margin-top:3px}
|
||||
.kw-panel{margin-top:16px}
|
||||
.kw-done{display:flex;gap:14px;align-items:flex-start;padding:16px 18px;margin-top:4px;border-radius:14px;background:color-mix(in srgb,var(--ok) 9%,var(--card));border:1px solid color-mix(in srgb,var(--ok) 32%,transparent)}
|
||||
.kw-done-ic{flex:none;width:30px;height:30px;border-radius:50%;background:var(--ok);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px}
|
||||
.kw-done-b b{display:block;font-size:15.5px;margin-top:4px}
|
||||
.kw-done-b p{margin:6px 0 0;font-size:14px;line-height:1.5}
|
||||
.kw-done-act{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px}
|
||||
.kw-link{background:none;border:0;padding:0;color:var(--accent);font:inherit;font-size:13.5px;cursor:pointer;text-decoration:underline;text-underline-offset:3px}
|
||||
.kw-howto{margin:0 0 12px}.kw-howto summary{cursor:pointer;color:var(--accent);font-size:13.5px;margin-bottom:8px}
|
||||
.kw-drive{display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--border,#ddd);border-radius:8px;cursor:pointer;margin:0}
|
||||
.kw-pad{height:200px;margin-top:12px;border:2px dashed var(--border);border-radius:12px;display:flex;align-items:center;justify-content:center;text-align:center;padding:0 16px;color:var(--muted);user-select:none;-webkit-user-select:none;touch-action:none;cursor:default}
|
||||
.kw-pad.on{border-color:var(--accent);background:var(--accent-weak);color:var(--accent);cursor:crosshair}
|
||||
@@ -83,6 +90,7 @@
|
||||
<div id="kw-main">
|
||||
{{/* ===== Лента шагов — когда путь выбран (флешка с ключом или новая флешка из архива) ===== */}}
|
||||
{{if or $s.StagingID $s.Done}}
|
||||
{{if $s.Done}}<details class="kw-howto"><summary>Как прошла установка — {{len $s.Steps}} этапов</summary>{{end}}
|
||||
<div class="card">
|
||||
<ol style="list-style:none;padding:0;margin:0;display:grid;gap:12px">
|
||||
{{range $i, $step := $s.Steps}}
|
||||
@@ -102,22 +110,21 @@
|
||||
{{end}}
|
||||
</ol>
|
||||
</div>
|
||||
{{if $s.Done}}</details>{{end}}
|
||||
|
||||
{{/* ===== Действие в зависимости от состояния ===== */}}
|
||||
{{end}}
|
||||
{{if $s.Done}}
|
||||
<div class="card" style="background:var(--ok-weak)">
|
||||
<h2>✓ Готово</h2>
|
||||
<p>{{if $s.MediaDevice}}Ключ с флешки подключён к шлюзу НРД как есть и подписывает — флешка не менялась.{{else}}Ключ записан на флешку, справочник сертификатов сформирован, ключ подписывает.{{end}}</p>
|
||||
{{if $s.Backup}}<p class="muted">Бэкап прежнего носителя: <code>{{$s.Backup}}</code></p>{{end}}
|
||||
<div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap">
|
||||
{{if devOn}}<form method="post" action="/admin/setup/test-nsd" style="margin:0">
|
||||
<input type="hidden" name="scenario" value="2001">
|
||||
<button type="submit" class="btn btn-ok">→ Отправить тестовый документ роботу</button>
|
||||
</form>{{else if not .InWizard}}<a href="/admin/wizard?step=8" class="btn btn-ok">→ Вернуться в мастер настройки</a> <a href="/admin/status" class="btn btn-secondary">Проверить подключения</a>{{end}}
|
||||
<form method="post" action="/admin/setup/keywizard/reset" style="margin:0">{{template "keyWizRet" .}}
|
||||
<button type="submit" class="btn btn-secondary">Установить ещё один ключ</button>
|
||||
</form>
|
||||
<div class="kw-done">
|
||||
<span class="kw-done-ic" aria-hidden="true">✓</span>
|
||||
<div class="kw-done-b">
|
||||
<b>Ключ подключён и подписывает</b>
|
||||
<p>{{if $s.MediaDevice}}Флешка с ключом подключена к шлюзу НРД как есть — на неё ничего не записывалось.{{else}}Ключ записан на флешку, справочник сертификатов сформирован, шлюз НРД подписывает им документы.{{end}} Флешка должна оставаться в сервере: без неё шлюз не запустится.</p>
|
||||
{{if $s.Backup}}<p class="muted" style="font-size:12.5px">Копия прежнего содержимого флешки: <code>{{$s.Backup}}</code></p>{{end}}
|
||||
<div class="kw-done-act">
|
||||
{{if devOn}}<form method="post" action="/admin/setup/test-nsd" style="margin:0"><input type="hidden" name="scenario" value="2001"><button type="submit" class="btn btn-ok">→ Отправить тестовый документ роботу</button></form>{{else if not .InWizard}}<a href="/admin/wizard?step=8" class="btn btn-ok">→ Вернуться в мастер настройки</a> <a href="/admin/status" class="btn btn-secondary">Проверить подключения</a>{{end}}
|
||||
<form method="post" action="/admin/setup/keywizard/reset" style="margin:0">{{template "keyWizRet" .}}<button type="submit" class="kw-link" title="Когда истечёт сертификат или понадобится ключ другого контура">Заменить ключ</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else if and $s.StagingID $s.MediaDevice}}
|
||||
|
||||
Reference in New Issue
Block a user