From b58c4fb89852c382c73223c07a0cf30b5180455d Mon Sep 17 00:00:00 2001 From: zuevav Date: Tue, 6 Oct 2026 18:01:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BB=D1=8E=D1=87=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B8:=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B2=D0=B7=D1=8F=D1=82=D1=8C=20=D0=BA=D0=BB=D1=8E=D1=87,=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B9=20=D1=83=D0=B6=D0=B5?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D1=84=D0=BB=D0=B5=D1=88=D0=BA=D0=B5,=20?= =?UTF-8?q?=E2=80=94=20=D0=B1=D0=B5=D0=B7=20=D0=B0=D1=80=D1=85=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=20=D0=9D=D0=A0=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit На шаге 1 мастера ключа — второй путь «Ключ уже на флешке»: выбор флешки и «Взять ключ с флешки». Флешка только читается (bj-keymedia-install stage-media): ключ (*.vdk) и справочник (local.pse + local.gdbm; раскладка bj — профиль с key.reg) копируются во временный каталог в раскладке архива, дальше — обычная установка на ту же флешку под тем же именем профиля (резервная копия флешки, пересборка справочника, профиль ИШ, перезапуск). Ключ без справочника — понятный отказ: справочник есть только в архиве НРД. Устройство — только из обнаруженных съёмных. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- deploy/linux/sbin/bj-keymedia-install | 32 +++++++++++ internal/lkgateway/keywizard.go | 56 ++++++++++++++++++- .../lkgateway/web/templates/part_keywiz.html | 24 ++++++-- 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/deploy/linux/sbin/bj-keymedia-install b/deploy/linux/sbin/bj-keymedia-install index 0ef254b..8bdf2c3 100755 --- a/deploy/linux/sbin/bj-keymedia-install +++ b/deploy/linux/sbin/bj-keymedia-install @@ -88,6 +88,38 @@ if [ "$ACTION" = "certs-apply" ]; then echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"backup\":\"$BK\",\"igate\":\"$ACT\",\"rebuild\":$OUT,\"certs\":$(certs_list_json)}"; exit 0 fi +# ---- ключ уже на флешке: взять с неё ключ и справочник (только чтение) во временный каталог — дальше обычная +# установка (тот же носитель, то же имя профиля). Раскладка bj: <профиль>/key.reg + <профиль>/spr/local.{pse,gdbm} +# + vdkeys/*.vdk; другая — ключ (*.vdk) и справочник (local.gdbm + local.pse) где угодно на носителе. +if [ "$ACTION" = "stage-media" ]; then + MNT="$TARGET_MNT"; RO_MNT="" + if [ -z "$MNT" ]; then + [ -n "$TARGET_DEV" ] || err "не выбран носитель" + RO_MNT="/run/bj-keymedia-ro-$$"; mkdir -p "$RO_MNT" + mount -o ro "$TARGET_DEV" "$RO_MNT" || { rmdir "$RO_MNT"; err "не смонтировать $TARGET_DEV"; } + MNT="$RO_MNT" + fi + done_ro(){ [ -n "$RO_MNT" ] && { umount "$RO_MNT" 2>/dev/null; rmdir "$RO_MNT" 2>/dev/null; }; } + VDKF=$(find "$MNT" -maxdepth 4 -iname '*.vdk' 2>/dev/null | sort | head -1) + GD=$(find "$MNT" -maxdepth 5 -iname 'local.gdbm' 2>/dev/null | sort | head -1) + [ -n "$VDKF" ] || { done_ro; err "на носителе нет ключа (файла *.vdk)"; } + [ -n "$GD" ] && [ -f "$(dirname "$GD")/local.pse" ] || { done_ro; err "на носителе есть ключ, но нет справочника сертификатов (local.pse и local.gdbm) — загрузите архив ключа от НРД: справочник есть в нём"; } + SPR=$(dirname "$GD"); PROFD=$(dirname "$SPR") + ID="media-$(date -u +%Y%m%d%H%M%S)"; ST="/var/lib/bj/media/iso/$ID"; mkdir -p "$ST/vdkeys" + if [ "$PROFD" != "$MNT" ] && [ -f "$PROFD/key.reg" ]; then + cp -a "$PROFD"/. "$ST"/ 2>/dev/null; PNAME=$(basename "$PROFD") # профиль bj: key.reg + spr/ + else + cp -a "$SPR" "$ST"/ 2>/dev/null; PNAME="" # справочник без профиля bj — имя по номеру + fi + cp -f "$VDKF" "$ST/vdkeys/" + rm -rf "$ST/vdkeys/vdkeys" 2>/dev/null + chown -R bj:bj "$ST" 2>/dev/null + done_ro + case "$PNAME" in *[!A-Za-z0-9_-]*) PNAME="" ;; esac + printf '{"ok":true,"staging":"%s","vdk":"%s","profile":"%s","spr":"%s"}\n' "$ID" "$(basename "$VDKF")" "$PNAME" "$(basename "$SPR")" + exit 0 +fi + [ -n "$STAGING" ] && [ -d "$STAGING" ] || err "staging не найден" case "$STAGING" in /var/lib/bj/media/iso/*) : ;; *) err "staging вне зоны" ;; esac VDK=$(find "$STAGING" -iname '*.vdk' | head -1) diff --git a/internal/lkgateway/keywizard.go b/internal/lkgateway/keywizard.go index 972bed2..d319566 100644 --- a/internal/lkgateway/keywizard.go +++ b/internal/lkgateway/keywizard.go @@ -47,6 +47,8 @@ type keyWizardState struct { Steps []keyWizardStep // 1..5 Done bool // все шаги пройдены Flash string + // ключ взят с флешки (а не из архива): на ту же флешку и под тем же именем профиля + MediaDevice, MediaProfile string } func newKeyWizardState() *keyWizardState { @@ -62,6 +64,7 @@ func (s *keyWizardState) reset() { s.Steps = defaultKeySteps() s.Done = false s.Flash = "" + s.MediaDevice, s.MediaProfile = "", "" } func defaultKeySteps() []keyWizardStep { @@ -121,8 +124,10 @@ func (h *setupHandlers) keyWizView(want string, inWizard bool) *keyWizardData { if want == "test3" || want == "prod" { d.WantContour = want } - if st.StagingID != "" && !st.Done { // список флешек — на шаге выбора носителя (архив уже загружен) + if !st.Done { // флешки: в начале — откуда взять ключ, после загрузки — куда записать d.Drives = listFlashDrives() + } + if st.StagingID != "" && !st.Done { d.SuggestContour, d.SuggestIssuer = keyArchiveContour(filepath.Join("/var/lib/bj/media/iso", st.StagingID)) } return d @@ -253,6 +258,7 @@ func (h *setupHandlers) registerKeyWizard(mux *http.ServeMux) { mux.HandleFunc("/admin/setup/keywizard/upload", h.keyWizardUpload) mux.HandleFunc("/admin/setup/keywizard/install", h.keyWizardInstall) mux.HandleFunc("/admin/setup/keywizard/reset", h.keyWizardReset) + mux.HandleFunc("/admin/setup/keywizard/from-media", h.keyWizardFromMedia) } func (h *setupHandlers) renderKeyWizard(w http.ResponseWriter, r *http.Request) { @@ -268,6 +274,54 @@ func (h *setupHandlers) renderKeyWizard(w http.ResponseWriter, r *http.Request) render(w, h.tpl.a.keyWizard, *d) } +// keyWizardFromMedia — POST /admin/setup/keywizard/from-media: ключ уже на флешке (подготовленной bj раньше +// или с ключом и справочником от НРД). Флешка только читается: ключ и справочник копируются во временный +// каталог — дальше обычная установка на эту же флешку под тем же именем профиля (с резервной копией флешки). +func (h *setupHandlers) keyWizardFromMedia(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method", http.StatusMethodNotAllowed) + return + } + dev := strings.TrimSpace(r.FormValue("source_device")) + var drive *flashDrive + for _, d := range listFlashDrives() { + if d.Device == dev { + d := d + drive = &d + } + } + if drive == nil { // только обнаруженные съёмные: иначе root-воркер смонтировал бы что угодно из формы + h.keyWizFlash(w, r, "Флешка не найдена среди съёмных носителей — вставьте её и обновите страницу.") + return + } + ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) + defer cancel() + res, err := runKeymediaOp(ctx, "stage-media", map[string]string{"target_dev": drive.Device, "target_mnt": drive.Mountpoint}) + if err == nil && res["ok"] != true { + err = fmt.Errorf("%v", res["error"]) + } + if err != nil { + h.keyWizFlash(w, r, "Ключ с флешки не взят: "+err.Error()) + return + } + id, _ := res["staging"].(string) + if !strings.HasPrefix(id, "media-") { + h.keyWizFlash(w, r, "Ключ с флешки не взят: установщик не вернул каталог") + return + } + vdk, _ := res["vdk"].(string) + prof, _ := res["profile"].(string) + spr, _ := res["spr"].(string) + h.keyWiz.mu.Lock() + h.keyWiz.reset() + h.keyWiz.StagingID, h.keyWiz.VDK = id, vdk + h.keyWiz.MediaDevice, h.keyWiz.MediaProfile = drive.Device, prof + 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", "") + h.keyWiz.mu.Unlock() + http.Redirect(w, r, strings.TrimRight(keyWizBack(r), "?&"), http.StatusSeeOther) +} + func (h *setupHandlers) keyWizardReset(w http.ResponseWriter, r *http.Request) { h.keyWiz.mu.Lock() h.keyWiz.reset() diff --git a/internal/lkgateway/web/templates/part_keywiz.html b/internal/lkgateway/web/templates/part_keywiz.html index 4d8eec7..2b952e3 100644 --- a/internal/lkgateway/web/templates/part_keywiz.html +++ b/internal/lkgateway/web/templates/part_keywiz.html @@ -58,7 +58,7 @@ {{if .Drives}} {{range $i, $d := .Drives}}