Ключ: два пути — «Подключить флешку с ключом» или «Сформировать ключ на новой флешке»; обновление одним заходом
- Шаг «Ключ подписи» начинается с вопроса «Где ваш ключ?»: подключить подготовленную флешку (bj находит на ней ключ и подключает как есть) или сформировать ключ на новой флешке (архив НРД и пароль → распаковка → bj ждёт чистую флешку; без флешки записи нет — «вставьте и обновите страницу»). Этапы показываются, когда путь выбран, — свои у каждого пути. - Обновление больше не делится на два захода, когда вместе с bj обновляется и установщик обновлений: откладывание bj-installer нужно только установщикам старше 25.09.2026, которые его не знают. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -74,7 +74,7 @@ func defaultKeySteps() []keyWizardStep {
|
||||
}
|
||||
return []keyWizardStep{
|
||||
{Title: "Загрузка архива и распаковка", Status: "pending"},
|
||||
{Title: "Запись ключа на выбранную флешку (с бэкапом)", Status: "pending"},
|
||||
{Title: "Запись ключа на новую флешку (копия прежнего содержимого — на сервере)", Status: "pending"},
|
||||
{Title: "Формирование справочника сертификатов (CRL)", Status: "pending"},
|
||||
{Title: "Перезапуск и проверка ИШ", Status: "pending"},
|
||||
{Title: last, Status: "pending"},
|
||||
@@ -377,8 +377,8 @@ func (h *setupHandlers) keyWizardFromMedia(w http.ResponseWriter, r *http.Reques
|
||||
h.keyWiz.StagingID, h.keyWiz.VDK = id, vdk
|
||||
h.keyWiz.MediaDevice, h.keyWiz.MediaProfile = drive.Device, prof
|
||||
// флешка уже подготовлена: используется как есть — этапы называются по-другому, записи на флешку нет
|
||||
h.keyWiz.Steps[0].Title = "Ключ найден на подготовленной флешке"
|
||||
h.keyWiz.Steps[1].Title = "Флешка подключается как есть — на неё ничего не записывается"
|
||||
h.keyWiz.Steps[0].Title = "Поиск ключа на флешке"
|
||||
h.keyWiz.Steps[1].Title = "Подключение флешки к шлюзу как есть — на неё ничего не записывается"
|
||||
h.keyWiz.Steps[2].Title = "Справочник сертификатов для ИШ — на диске сервера"
|
||||
h.keyWiz.set(0, "ok", fmt.Sprintf("Ключ найден на флешке %s: %s, справочник %s%s", fallback(drive.Model, drive.Device), vdk, spr, prefixed(", профиль ", prof)))
|
||||
h.keyWiz.set(1, "active", "")
|
||||
|
||||
@@ -466,11 +466,18 @@ func TestDumpOnboardingPages(t *testing.T) {
|
||||
// шаг «Ключ подписи»: ключ найден на подготовленной флешке — используется как есть
|
||||
h.keyWiz = newKeyWizardState()
|
||||
h.keyWiz.StagingID, h.keyWiz.VDK, h.keyWiz.MediaDevice, h.keyWiz.MediaProfile = "media-test", "CKJCI7AYNIOSNLXK.vdk", "/dev/sdb1", "PrUser1046"
|
||||
h.keyWiz.Steps[0].Title, h.keyWiz.Steps[1].Title = "Ключ найден на подготовленной флешке", "Флешка подключается как есть — на неё ничего не записывается"
|
||||
h.keyWiz.Steps[0].Title, h.keyWiz.Steps[1].Title = "Поиск ключа на флешке", "Подключение флешки к шлюзу как есть — на неё ничего не записывается"
|
||||
h.keyWiz.set(0, "ok", "Ключ найден на флешке USB2FlashStorage: CKJCI7AYNIOSNLXK.vdk, справочник spr1046, профиль PrUser1046")
|
||||
wk := httptest.NewRecorder()
|
||||
h.renderWizard(wk, httptest.NewRequest(http.MethodGet, "/admin/wizard?step=8", nil))
|
||||
_ = os.WriteFile(filepath.Join(dir, "wizard-08-media.html"), wk.Body.Bytes(), 0o644)
|
||||
// путь «новая флешка»: архив распакован, ждём флешку
|
||||
h.keyWiz = newKeyWizardState()
|
||||
h.keyWiz.StagingID, h.keyWiz.VDK = "test-archive", "CKJCI7AYNIOSNLXK.vdk"
|
||||
h.keyWiz.set(0, "ok", "Ключ CKJCI7AYNIOSNLXK.vdk, справочник spr1046")
|
||||
wn := httptest.NewRecorder()
|
||||
h.renderWizard(wn, httptest.NewRequest(http.MethodGet, "/admin/wizard?step=8", nil))
|
||||
_ = os.WriteFile(filepath.Join(dir, "wizard-08-new.html"), wn.Body.Bytes(), 0o644)
|
||||
h.keyWiz = nil
|
||||
// шаг QUIK после проверки подключения — выбор «Как bj работает с QUIK»
|
||||
q := rc.Snapshot().Quik
|
||||
|
||||
@@ -42,6 +42,9 @@ const updateStagingDir = "/var/lib/bj/update-staging"
|
||||
// DefaultUpdateURL — сервер обновлений по умолчанию (встроен в сервер лицензий).
|
||||
const DefaultUpdateURL = "https://lic.zetit.ru/updates"
|
||||
|
||||
// installerKnowsAll — с этой версии root-установщик обновлений знает bj-installer и ставит выделенные модули.
|
||||
const installerKnowsAll = "2026.09.25"
|
||||
|
||||
type Updater struct{ rc *RuntimeConfig }
|
||||
|
||||
func NewUpdater(rc *RuntimeConfig) *Updater { return &Updater{rc: rc} }
|
||||
@@ -287,11 +290,13 @@ func (u *Updater) applyUpdate(ctx context.Context, names []string) (string, erro
|
||||
}
|
||||
return "", fmt.Errorf("нечего обновлять: все установленные компоненты актуальны (релиз %s)", m.Version)
|
||||
}
|
||||
// Прежний root-установщик не умеет впервые ставить выделенные из ядра модули: пока он сам не
|
||||
// обновлён, такие модули откладываются — их поставит ensureSplitModules после перезапуска.
|
||||
// Очень старый root-установщик (до 25.09.2026) не знает bj-installer и не ставит впервые выделенные
|
||||
// из ядра модули: пока он сам не обновлён, такие компоненты откладываются (второй заход). Установщик
|
||||
// новее этого умеет всё — обновление одним заходом, даже если вместе с ним обновляется и он сам
|
||||
// (раньше любое его обновление делило установку на два раза: «скачал 24 МБ — снова „Обновить“ — ещё 6»).
|
||||
installerOutdated := false
|
||||
for _, c := range plan {
|
||||
if c.Name == "bj-component-install" && c.Update {
|
||||
if c.Name == "bj-component-install" && c.Update && (c.Installed == "" || release.IsNewer(installerKnowsAll, c.Installed)) {
|
||||
installerOutdated = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,19 @@
|
||||
{{define "keyWizBody"}}
|
||||
{{$s := .State}}
|
||||
|
||||
{{/* ===== Лента шагов ===== */}}
|
||||
<style>
|
||||
.kw-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:6px}
|
||||
.kw-tile{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border:1px solid var(--border);border-radius:12px;cursor:pointer;font-weight:400;margin:0}
|
||||
.kw-tile:has(input:checked){border-color:var(--accent);background:var(--accent-weak)}
|
||||
.kw-tile input{margin-top:3px;flex:none;width:auto}
|
||||
.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-drive{display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--border,#ddd);border-radius:8px;cursor:pointer;margin:0}
|
||||
@media (max-width:600px){.kw-tiles{grid-template-columns:minmax(0,1fr)}}
|
||||
</style>
|
||||
{{/* ===== Лента шагов — когда путь выбран (флешка с ключом или новая флешка из архива) ===== */}}
|
||||
{{if or $s.StagingID $s.Done}}
|
||||
<div class="card">
|
||||
<ol style="list-style:none;padding:0;margin:0;display:grid;gap:12px">
|
||||
{{range $i, $step := $s.Steps}}
|
||||
@@ -27,6 +39,7 @@
|
||||
</div>
|
||||
|
||||
{{/* ===== Действие в зависимости от состояния ===== */}}
|
||||
{{end}}
|
||||
{{if $s.Done}}
|
||||
<div class="card" style="background:var(--ok-weak)">
|
||||
<h2>✓ Готово</h2>
|
||||
@@ -68,10 +81,12 @@
|
||||
{{else if $s.StagingID}}
|
||||
{{/* Архив загружен — выбор флешки + запись */}}
|
||||
<div class="card">
|
||||
<h2>Шаг 2 — выбор флешки и запись</h2>
|
||||
<h2>Шаг 2 — вставьте новую флешку и запишите ключ</h2>
|
||||
<p class="muted">Архив распакован. Ключ: <code>{{fallbackTpl $s.VDK "—"}}</code>.
|
||||
Выберите носитель — запись сделает бэкап, запишет ключ и справочник
|
||||
сертификатов, дотянет CRL и перезапустит ИШ.</p>
|
||||
Вставьте в сервер чистую флешку — на неё запишется ключ и справочник сертификатов, затем bj
|
||||
подключит её к шлюзу и перезапустит ИШ. Копия прежнего содержимого флешки сохраняется на сервере.
|
||||
Не используйте флешку с рабочим ключом другого сервера.</p>
|
||||
{{if not .Drives}}<p style="margin:10px 0 0">Флешка не найдена — вставьте её в сервер и <a href="javascript:location.reload()">обновите страницу</a>.</p>{{else}}
|
||||
<form method="post" action="/admin/setup/keywizard/install" style="margin-top:12px;display:grid;gap:12px;max-width:640px"
|
||||
onsubmit="this.querySelector('button[type=submit]').disabled=true;this.querySelector('button[type=submit]').textContent='Устанавливаю… (до нескольких минут)';">
|
||||
{{template "keyWizRet" .}}
|
||||
@@ -90,8 +105,6 @@
|
||||
</span>
|
||||
</label>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p class="muted">Съёмные носители не обнаружены — будет использован текущий ключевой носитель ИШ по умолчанию.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -111,40 +124,54 @@
|
||||
<span class="muted" style="font-size:12px">Пусто = имя берётся из архива автоматически.</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-ok">Записать на флешку, сформировать справочник и проверить ИШ</button>
|
||||
</form>
|
||||
<button type="submit" class="btn btn-ok">Записать ключ на флешку</button>
|
||||
</form>{{end}}
|
||||
<form method="post" action="/admin/setup/keywizard/reset" style="margin-top:8px">{{template "keyWizRet" .}}
|
||||
<button type="submit" class="btn btn-secondary">Отмена / загрузить другой архив</button>
|
||||
<button type="submit" class="btn btn-secondary">Отмена</button>
|
||||
</form>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/* Начало — форма загрузки */}}
|
||||
{{/* Начало: где ключ — на подготовленной флешке или в архиве НРД (тогда — на новую флешку) */}}
|
||||
<div class="card">
|
||||
<h2>Шаг 1 — откуда взять ключ</h2>
|
||||
<p class="muted"><b>Архив от НРД</b>: выберите .7z-архив с ключом и введите пароль архива.</p>
|
||||
<form method="post" action="/admin/setup/keywizard/upload" enctype="multipart/form-data"
|
||||
style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||||
{{template "keyWizRet" .}}
|
||||
<input type="file" name="archive" accept=".7z,.zip" required>
|
||||
<input type="password" name="password" placeholder="Пароль архива (например 11)" autocomplete="off">
|
||||
<button type="submit" class="btn btn-ok">Загрузить и распаковать</button>
|
||||
</form>
|
||||
<div style="margin-top:18px;padding-top:14px;border-top:1px solid var(--border)">
|
||||
<p class="muted" style="margin:0 0 8px"><b>У меня уже есть подготовленная флешка</b> — использовать ключ с неё (например, флешка с другого сервера bj). Флешка используется как есть: bj её только читает, ничего на неё не записывает. Архив не нужен.</p>
|
||||
<h2>Где ваш ключ?</h2>
|
||||
<div class="kw-tiles">
|
||||
<label class="kw-tile"><input type="radio" name="kwmode" value="media" {{if .Drives}}checked{{end}}><span><b>Подключить флешку с ключом</b><small>Флешка уже подготовлена (например, с другого сервера bj): bj найдёт на ней ключ и подключит к шлюзу как есть — ничего на неё не записывая.</small></span></label>
|
||||
<label class="kw-tile"><input type="radio" name="kwmode" value="new" {{if not .Drives}}checked{{end}}><span><b>Сформировать ключ на новой флешке</b><small>Есть архив ключа от НРД и пароль: bj распакует его и запишет ключ на чистую флешку.</small></span></label>
|
||||
</div>
|
||||
|
||||
<div class="kw-panel" id="kw-media">
|
||||
{{if .Drives}}
|
||||
<form method="post" action="/admin/setup/keywizard/from-media" style="display:grid;gap:8px;max-width:640px">
|
||||
{{template "keyWizRet" .}}
|
||||
<p class="muted" style="margin:0">Флешка с ключом:</p>
|
||||
{{range $i, $d := .Drives}}
|
||||
<label style="display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--border,#ddd);border-radius:8px;cursor:pointer">
|
||||
<input type="radio" name="source_device" value="{{$d.Device}}" {{if eq $i 0}}checked{{end}} style="margin-top:3px">
|
||||
<label class="kw-drive">
|
||||
<input type="radio" name="source_device" value="{{$d.Device}}" {{if eq $i 0}}checked{{end}} style="margin-top:3px;width:auto">
|
||||
<span><b>{{fallbackTpl $d.Model "USB-носитель"}}</b> · {{$d.Size}} · {{$d.FSType}}{{if $d.Label}} · метка «{{$d.Label}}»{{end}}<br>
|
||||
<span class="muted" style="font-size:12px">{{$d.Device}}{{if $d.IsKeymedia}} · <b style="color:var(--accent)">сейчас ключевой носитель ИШ</b>{{end}}</span></span>
|
||||
</label>
|
||||
{{end}}
|
||||
<button type="submit" class="btn btn-secondary" onclick="this.textContent='Читаю флешку…'">Использовать ключ с флешки</button>
|
||||
<button type="submit" class="btn btn-ok" onclick="this.textContent='Ищу ключ на флешке…'">Подключить флешку</button>
|
||||
</form>
|
||||
{{else}}<p style="margin:0">Флешка не найдена — вставьте флешку с ключом в сервер и <a href="javascript:location.reload()">обновите страницу</a>.</p>{{end}}
|
||||
</div>
|
||||
|
||||
<div class="kw-panel" id="kw-new" hidden>
|
||||
<p class="muted" style="margin:0 0 8px">Шаг 1 — архив ключа от НРД (.7z) и пароль архива. Флешку bj попросит после распаковки.</p>
|
||||
<form method="post" action="/admin/setup/keywizard/upload" enctype="multipart/form-data"
|
||||
style="display:grid;gap:10px;max-width:560px">
|
||||
{{template "keyWizRet" .}}
|
||||
<input type="file" name="archive" accept=".7z,.zip" required>
|
||||
<input type="password" name="password" placeholder="Пароль архива (например 11)" autocomplete="off">
|
||||
<button type="submit" class="btn btn-ok">Загрузить и распаковать</button>
|
||||
</form>
|
||||
{{else}}<p class="muted" style="font-size:13px;margin:0">Флешка не найдена — вставьте её в сервер и обновите страницу.</p>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<script>(function(){
|
||||
var r=document.querySelectorAll('input[name=kwmode]');
|
||||
function sync(){ var m=document.querySelector('input[name=kwmode]:checked'); m=m?m.value:'media';
|
||||
document.getElementById('kw-media').hidden=m!=='media'; document.getElementById('kw-new').hidden=m!=='new'; }
|
||||
r.forEach(function(x){ x.addEventListener('change', sync); }); sync();
|
||||
})();</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user