Мастер: пояснение к уже выполненным шагам, Валидата определяется сама без пути PKCS#11

Шаг «База bj» показывает, что базу создал установщик; шаг DEPO — почему пропущен.
Поле libvdpkcs11.so убрано (в Валидате 6.0 такого модуля нет, работа через bj-crypto).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-09-24 17:26:41 +03:00
parent 9ee2641431
commit 27bf8f6414
3 changed files with 91 additions and 36 deletions
+65 -7
View File
@@ -163,15 +163,19 @@ func (h *setupHandlers) renderNews(w http.ResponseWriter, r *http.Request) {
// WizardData — данные для шаблона /admin/wizard. // WizardData — данные для шаблона /admin/wizard.
type WizardData struct { type WizardData struct {
page page
Step int Step int
Settings Settings Settings Settings
Suggest *DepoRequisites // предложение реквизитов из DEPO (после «Определить из DEPO») Suggest *DepoRequisites // предложение реквизитов из DEPO (после «Определить из DEPO»)
Lic LicenseStatus Lic LicenseStatus
DepoLicensed bool DepoLicensed bool
DepoDB string DepoDB string
Certs []cryptocli.Certificate ValidataVer string // версия установленной «Валидаты Клиент L» (пусто — не установлена)
Flash string CryptoSvc string // состояние службы bj-crypto (systemctl is-active)
Done struct { PGSummary string // куда подключена база bj, без пароля
AutoDone []string // шаги до текущего, уже выполненные без участия пользователя, — с пояснением
Certs []cryptocli.Certificate
Flash string
Done struct {
License bool License bool
Depo bool Depo bool
Postgres bool Postgres bool
@@ -204,7 +208,17 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
d.DepoDB = s.Fancy.DepoDB() d.DepoDB = s.Fancy.DepoDB()
d.Done.Depo = strings.TrimSpace(s.Fancy.Server) != "" || !d.DepoLicensed d.Done.Depo = strings.TrimSpace(s.Fancy.Server) != "" || !d.DepoLicensed
d.Done.Postgres = s.Postgres.DSN != "" d.Done.Postgres = s.Postgres.DSN != ""
d.Done.Crypto = s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.ModulePath != "" {
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
d.ValidataVer, _, _ = validataVersion(ctx)
out, _ := exec.CommandContext(ctx, "systemctl", "is-active", "bj-crypto.service").Output()
cancel()
d.CryptoSvc = strings.TrimSpace(string(out))
}
d.PGSummary = dsnSummary(s.Postgres.DSN)
// Валидата ставится с образа/установщиком: если она на месте, шаг выполнен без ввода параметров
// (bj работает с ней через свой криптосервис, путь к модулю не нужен).
d.Done.Crypto = d.ValidataVer != "" || (s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.ModulePath != "")
d.Done.Certs = len(d.Certs) > 0 d.Done.Certs = len(d.Certs) > 0
d.Done.NSD = s.NSD.IGWBaseURL != "" && s.NSD.Profile != "" d.Done.NSD = s.NSD.IGWBaseURL != "" && s.NSD.Profile != ""
d.Done.TestRun = s.LastTest != nil || (!d.DevMode && d.Done.NSD) // вне режима разработки тестовой заявки нет d.Done.TestRun = s.LastTest != nil || (!d.DevMode && d.Done.NSD) // вне режима разработки тестовой заявки нет
@@ -235,9 +249,53 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
} }
} }
d.Step = step d.Step = step
d.AutoDone = wizardAutoDone(&d)
render(w, h.tpl.a.wizard, d) render(w, h.tpl.a.wizard, d)
} }
// wizardAutoDone — пояснения к шагам до текущего, которые уже выполнены: мастер переходит к первому
// незавершённому шагу сам, и без пояснения непонятно, почему предыдущие отмечены галочкой.
func wizardAutoDone(d *WizardData) []string {
var out []string
if d.Step > 1 && d.Done.License {
out = append(out, "1. Лицензия — активирована: "+d.Lic.Tenant+".")
}
if d.Step > 2 && d.Done.Postgres {
t := "2. База bj — готова"
if d.PGSummary != "" {
t += " (" + d.PGSummary + ")"
}
out = append(out, t+": её создал установщик, настраивать ничего не нужно.")
}
if d.Step > 3 && d.Done.Depo {
if !d.DepoLicensed {
out = append(out, "3. Учёт DEPO — не нужен: модуль обмена с DEPO и QUIK не входит в лицензию. Когда модуль добавят, шаг станет доступен.")
} else {
out = append(out, "3. Учёт DEPO — подключение уже сохранено ("+d.Settings.Fancy.Server+").")
}
}
if d.Step > 4 && d.Done.Crypto && d.ValidataVer != "" {
out = append(out, "4. Валидата — установлена вместе с bj (версия "+d.ValidataVer+").")
}
return out
}
// dsnSummary — «host:port, база name» из строки подключения PostgreSQL, без логина и пароля.
func dsnSummary(dsn string) string {
u, err := url.Parse(strings.TrimSpace(dsn))
if err != nil || u.Host == "" {
return ""
}
host := u.Host
if h := u.Hostname(); h == "127.0.0.1" || h == "localhost" || h == "::1" {
host = "PostgreSQL на этом сервере"
}
if db := strings.TrimPrefix(u.Path, "/"); db != "" {
return host + ", база " + db
}
return host
}
// runCmd выполняет команду и возвращает stdout+stderr строкой. // runCmd выполняет команду и возвращает stdout+stderr строкой.
func runCmd(ctx context.Context, name string, args ...string) (string, error) { func runCmd(ctx context.Context, name string, args ...string) (string, error) {
cmd := exec.CommandContext(ctx, name, args...) cmd := exec.CommandContext(ctx, name, args...)
@@ -427,8 +427,8 @@
{{if devOn}} <option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>{{end}} {{if devOn}} <option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>{{end}}
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option> <option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option>
</select> </select>
<label>Путь к модулю PKCS#11</label> {{/* путь к PKCS#11 не используется: bj работает с Валидатой через bj-crypto; в Валидате 6.0 модуля libvdpkcs11.so нет */}}
<input type="text" name="module_path" value="{{.Settings.Crypto.ModulePath}}" placeholder="/opt/Validata/VDCSP/lib/amd64/libvdpkcs11.so"> <input type="hidden" name="module_path" value="{{.Settings.Crypto.ModulePath}}">
<label>UDS-сокет</label> <label>UDS-сокет</label>
<input type="text" name="socket_path" value="{{.Settings.Crypto.SocketPath}}" placeholder="/run/bj/crypto.sock"> <input type="text" name="socket_path" value="{{.Settings.Crypto.SocketPath}}" placeholder="/run/bj/crypto.sock">
<div><button type="submit" class="btn">Сохранить</button></div> <div><button type="submit" class="btn">Сохранить</button></div>
@@ -41,6 +41,12 @@
<div class="wiz-card"> <div class="wiz-card">
{{if .AutoDone}}<div class="card" style="border-left:3px solid var(--ok)">
<p style="margin:0 0 6px;font-weight:600">Уже готово — эти шаги пропущены, делать там ничего не нужно:</p>
{{range .AutoDone}}<p style="margin:2px 0;font-size:14px">✓ {{.}}</p>{{end}}
<p class="muted" style="margin:6px 0 0;font-size:12px">Открыть любой шаг можно кнопками «← К шагу» внизу.</p>
</div>{{end}}
{{/* ============= ШАГ 1: Лицензия ============= */}} {{/* ============= ШАГ 1: Лицензия ============= */}}
{{if eq .Step 1}} {{if eq .Step 1}}
<div class="card"> <div class="card">
@@ -92,11 +98,12 @@
{{if eq .Step 2}} {{if eq .Step 2}}
<div class="card"> <div class="card">
<h2><span class="dot {{if .Done.Postgres}}ok{{else}}err{{end}}"></span>Шаг 2. PostgreSQL</h2> <h2><span class="dot {{if .Done.Postgres}}ok{{else}}err{{end}}"></span>Шаг 2. PostgreSQL</h2>
<p>Сюда система пишет журнал сделок и принимает данные от команды Fansy.</p> <p>База самого bj: журнал заявок и поручений, история обмена с НРД и DEPO, пользователи.</p>
{{if .Done.Postgres}}<p>✓ База готова{{if .PGSummary}}: {{.PGSummary}}{{end}}. Её создал установщик — ничего делать не нужно, переходите к следующему шагу.</p>{{end}}
<div class="help-block"> {{if not .Done.Postgres}}<div class="help-block">
<strong>Что выбрать?</strong> Если у вас уже есть рабочий PostgreSQL — нажмите «У меня уже есть PostgreSQL» и введите DSN. Если впервые настраиваете — выберите «Поднять автоматически», система сама развернёт контейнер с PostgreSQL и накатит миграции. <strong>Что выбрать?</strong> Если у вас уже есть рабочий PostgreSQL — нажмите «У меня уже есть PostgreSQL» и введите DSN. Если впервые настраиваете — выберите «Поднять автоматически», система сама развернёт контейнер с PostgreSQL и накатит миграции.
</div> </div>{{end}}
{{if and devOn (not .Settings.Postgres.DSN)}} {{if and devOn (not .Settings.Postgres.DSN)}}
<div style="background:var(--bg);border:1px solid var(--accent);border-radius:6px;padding:14px;margin-top:12px"> <div style="background:var(--bg);border:1px solid var(--accent);border-radius:6px;padding:14px;margin-top:12px">
@@ -109,8 +116,8 @@
</div> </div>
{{end}} {{end}}
<details style="margin-top:14px" {{if .Settings.Postgres.DSN}}open{{end}}> <details style="margin-top:14px" {{if not .Settings.Postgres.DSN}}open{{end}}>
<summary style="cursor:pointer;color:var(--accent)">Вариант Б — у меня уже есть PostgreSQL, введу DSN сам</summary> <summary style="cursor:pointer;color:var(--accent)">{{if .Settings.Postgres.DSN}}Подключить другую базу (обычно не нужно){{else}}Вариант Б — у меня уже есть PostgreSQL, введу DSN сам{{end}}</summary>
<form method="post" action="/admin/setup/postgres" style="margin-top:12px"> <form method="post" action="/admin/setup/postgres" style="margin-top:12px">
<label>DSN (строка подключения) <span class="tooltip" title="Формат: postgres://пользователь:пароль@хост:порт/база?sslmode=disable. Например: postgres://bj:secret@db.example.com:5432/bj?sslmode=require">?</span></label> <label>DSN (строка подключения) <span class="tooltip" title="Формат: postgres://пользователь:пароль@хост:порт/база?sslmode=disable. Например: postgres://bj:secret@db.example.com:5432/bj?sslmode=require">?</span></label>
<input type="text" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://bj:secret@127.0.0.1:5432/bj?sslmode=disable" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:6px"> <input type="text" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://bj:secret@127.0.0.1:5432/bj?sslmode=disable" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:6px">
@@ -131,30 +138,20 @@
{{if eq .Step 4}} {{if eq .Step 4}}
<div class="card"> <div class="card">
<h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>Шаг 4. СКЗИ «Валидата Клиент L»</h2> <h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>Шаг 4. СКЗИ «Валидата Клиент L»</h2>
<p>СКЗИ нужен для подписи XMLDSig и проверки квитанций НРД.</p> <p>СКЗИ «Валидата Клиент L» проверяет подписи НРД на квитанциях и сообщениях и нужно для работы с ключами и сертификатами (шаг 5).</p>
{{if .ValidataVer}}
<p>✓ Валидата установлена вместе с bj: версия {{.ValidataVer}}, каталог <code>/opt/Validata/VDCSP</code>. Ничего настраивать на этом шаге не нужно.</p>
<p class="muted" style="font-size:13px">bj работает с Валидатой через свою службу криптографии <code>bj-crypto</code>{{if eq .CryptoSvc "active"}} — она запущена{{else}} — она запустится, когда на шаге 5 будут загружены ключи и сертификаты{{end}}.</p>
{{else}}
<p style="color:var(--err)">Валидата на этом сервере не найдена.</p>
<div class="help-block"> <div class="help-block">
<strong>Что это?</strong> АПК «Валидата Клиент L» — российское СКЗИ с поддержкой ГОСТ Р 34.10-2012, поставляемое НРД для подключения к ЭДО. На Linux работает напрямую через PKCS#11 — отдельной лицензии и регистрационных данных <em>не требует</em>.<br> <strong>Как поставить.</strong> С образа Bridge&amp;Join она ставится сама. Если bj ставили на готовую систему: пакеты <code>zpki</code> и <code>zsdk</code> из дистрибутива НРД (<a href="https://fs.moex.com/cdp/po/ClientL_Other.zip" target="_blank">ClientL_Other.zip</a> для Ubuntu 22.04, <a href="https://fs.moex.com/cdp/po/ClientL_ALSE.zip" target="_blank">ClientL_ALSE.zip</a> для Astra Linux) — <code>sudo apt install ./zpki_*.deb ./zsdk_*.deb</code>, подробнее — <a href="/admin/help/crypto">справка</a>. После установки обновите эту страницу.
<strong>Где взять?</strong> Дистрибутив для Astra Linux SE — <a href="https://fs.moex.com/cdp/po/ClientL_ALSE.zip" target="_blank">fs.moex.com/cdp/po/ClientL_ALSE.zip</a>. Установка — <code>sudo dpkg -i zpki-*.deb zsdk-*.deb</code> (см. <a href="/admin/help/crypto">/admin/help/crypto</a>).
</div> </div>
{{if devOn}}<form method="post" action="/admin/setup/crypto" style="margin-top:12px">
<details style="margin-top:14px" {{if not .Done.Crypto}}open{{end}}> <input type="hidden" name="provider" value="stub">
<summary style="cursor:pointer;color:var(--accent)">Параметры провайдера</summary> <button type="submit" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Режим разработки: работать без криптографии (stub)</button>
<form method="post" action="/admin/setup/crypto" style="margin-top:12px;display:grid;gap:10px"> </form>{{end}}
<div> {{end}}
<label>Провайдер <span class="tooltip" title="validata — АПК «Валидата Клиент L»{{if devOn}}; stub — без криптографии (демо-режим без подписи){{end}}">?</span></label>
<select name="provider" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
{{if devOn}} <option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>{{end}}
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option>
</select>
</div>
<div>
<label>Путь к модулю PKCS#11 <span class="tooltip" title="После установки пакета zpki модуль находится в /opt/Validata/VDCSP/lib/amd64/libvdpkcs11.so">?</span></label>
<input type="text" name="module_path" value="{{if .Settings.Crypto.ModulePath}}{{.Settings.Crypto.ModulePath}}{{else}}/opt/Validata/VDCSP/lib/amd64/libvdpkcs11.so{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
</div>
<button type="submit" class="btn">Сохранить</button>
</form>
</details>
<div style="margin-top:20px;display:flex;justify-content:space-between"> <div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=3" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 3</a> <a href="/admin/wizard?step=3" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 3</a>