feat: живой цикл M2M с НРД + мастер установки ключа на флешку

Инфраструктура M2M (живой обмен с НРД через ИШ):
- обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение
  ответа; INFO → ждём Decision; идемпотентность поллера
- fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO
- сырой XML ответа НРД в карточке заявки (для пересылки в ТП)
- тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes,
  4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта
- редирект из теста сразу в карточку заявки

Мастер установки ключа Валидаты на флешку (admin/setup/keywizard):
- пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник
  сертификатов (CRL) → перезапуск+проверка ИШ → готово
- привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен,
  bj-server остаётся в песочнице
- сохранение структуры профиля архива (spr<N>), перечисление съёмных USB

Прочее:
- пакет-доказательство для ТП НРД + форма регистрации участника M2M
- эталонные образцы робота (DOC/m2m_robot_samples)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-06-19 00:03:21 +03:00
parent 6e503433d4
commit 9737c787f9
110 changed files with 10771 additions and 1690 deletions
+111
View File
@@ -0,0 +1,111 @@
// Package main — bj-installer.
//
// Web-инсталлятор для bj-server: на машине клиента после установки
// Debian/Astra поднимает локальный HTTP на 127.0.0.1:8181, проводит
// через 5-страничный wizard (welcome → precheck → config → install → done)
// и за кадром выполняет 20+ шагов установки Валидаты + bj-server + ИШ.
//
// Прогресс шагов прилетает в UI через Server-Sent Events. Каждый шаг
// идемпотентен — можно повторно запускать инсталлятор на уже настроенной
// машине, он пропустит то, что сделано.
//
// Запуск: sudo ./bj-installer [--addr 127.0.0.1:8181] [--no-browser]
// Артефакты ожидаются рядом с бинарём в каталоге ./artifacts/:
//
// artifacts/ClientL_Other/zpki-*.deb
// artifacts/ClientL_Other/zsdk-*.deb
// artifacts/bj-server (Go-бинарь)
// artifacts/crypto-service.jar (Java-сайдкар)
// artifacts/ish/igate_*.deb (ИШ НРД)
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"runtime"
"syscall"
"time"
)
const banner = `
======================================================================
bj-installer — мастер установки Bridge-and-Join-s
======================================================================
`
func main() {
addr := flag.String("addr", "127.0.0.1:8181", "адрес web-инсталлятора")
noBrowser := flag.Bool("no-browser", false, "не пытаться открыть браузер автоматически")
artifactsDir := flag.String("artifacts", "./artifacts", "каталог с дистрибутивами (Validata deb, bj-server, ish)")
flag.Parse()
if os.Geteuid() != 0 {
fmt.Fprintln(os.Stderr, "Установщик должен быть запущен от root (sudo).")
os.Exit(1)
}
fmt.Print(banner)
fmt.Printf(" адрес: http://%s\n", *addr)
fmt.Printf(" артефакты: %s\n", *artifactsDir)
fmt.Println("======================================================================")
st := newState(*artifactsDir)
srv := newServer(st)
httpSrv := &http.Server{
Addr: *addr,
Handler: srv,
ReadHeaderTimeout: 10 * time.Second,
}
// SIGINT/SIGTERM → корректный shutdown
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP-сервер упал: %v", err)
}
}()
url := "http://" + *addr
log.Printf("Откройте в браузере: %s", url)
if !*noBrowser {
tryOpenBrowser(url)
}
<-ctx.Done()
log.Println("Завершаем работу...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = httpSrv.Shutdown(shutdownCtx)
}
// tryOpenBrowser — без фанатизма. Если xdg-open/sensible-browser есть и
// $DISPLAY поднят (xrdp, Fly DE) — откроем. Иначе пользователь увидит URL
// в выводе и перейдёт сам с другого компа (типичный сценарий headless).
func tryOpenBrowser(url string) {
if os.Getenv("DISPLAY") == "" && os.Getenv("WAYLAND_DISPLAY") == "" {
return
}
var bin string
switch runtime.GOOS {
case "linux":
for _, cand := range []string{"xdg-open", "sensible-browser", "x-www-browser"} {
if p, err := exec.LookPath(cand); err == nil {
bin = p
break
}
}
}
if bin == "" {
return
}
_ = exec.Command(bin, url).Start()
}