Files
Bridge-and-Join-s/cmd/bj-installer/server.go
T
zuevav 9737c787f9 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>
2026-06-19 00:03:21 +03:00

130 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"embed"
"encoding/json"
"io/fs"
"log"
"net/http"
"strings"
)
//go:embed web
var webFS embed.FS
type server struct {
state *State
mux *http.ServeMux
}
func newServer(st *State) *server {
s := &server{state: st, mux: http.NewServeMux()}
// Статика (HTML/CSS/JS из embed)
sub, _ := fs.Sub(webFS, "web")
s.mux.Handle("/", http.FileServer(http.FS(sub)))
// API
s.mux.HandleFunc("/api/state", s.handleState)
s.mux.HandleFunc("/api/precheck", s.handlePrecheck)
s.mux.HandleFunc("/api/config", s.handleConfig)
s.mux.HandleFunc("/api/install", s.handleInstall)
s.mux.HandleFunc("/api/events", s.handleSSE)
s.mux.HandleFunc("/api/reset", s.handleReset)
return s
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Защита: только localhost (даже если addr 0.0.0.0 поставят)
host := r.RemoteAddr
if i := strings.LastIndex(host, ":"); i != -1 {
host = host[:i]
}
switch host {
case "127.0.0.1", "::1", "[::1]", "localhost":
// ok
default:
http.Error(w, "installer is local-only", http.StatusForbidden)
return
}
s.mux.ServeHTTP(w, r)
}
// GET /api/state — полный snapshot для холодного открытия страницы.
func (s *server) handleState(w http.ResponseWriter, r *http.Request) {
snap := s.state.Snapshot()
writeJSON(w, snap)
}
// POST /api/precheck — запускает все pre-check проверки и возвращает результат.
// Wizard переходит на стадию precheck.
func (s *server) handlePrecheck(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
return
}
s.state.setStage(StagePrecheck)
results := runPrechecks(s.state.artifactsDir)
s.state.setPrecheck(results)
writeJSON(w, results)
}
// POST /api/config — сохраняет org INN, email, license. Переход на стадию config.
func (s *server) handleConfig(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
return
}
var c Config
if err := json.NewDecoder(r.Body).Decode(&c); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
s.state.setConfig(c)
s.state.setStage(StageConfig)
writeJSON(w, map[string]bool{"ok": true})
}
// POST /api/install — стартует установку (в горутине), переход на стадию installing.
// UI слушает /api/events для прогресса.
func (s *server) handleInstall(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
return
}
s.state.setStage(StageInstalling)
go func() {
if err := runInstallation(s.state); err != nil {
log.Printf("install error: %v", err)
s.state.setError(err.Error())
return
}
s.state.setStage(StageDone)
}()
writeJSON(w, map[string]bool{"ok": true})
}
// POST /api/reset — сброс wizard'а на welcome (после ошибки).
func (s *server) handleReset(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
return
}
s.state.mu.Lock()
s.state.Stage = StageWelcome
s.state.ErrorMsg = ""
s.state.Precheck = nil
s.state.Steps = buildStepList()
s.state.mu.Unlock()
s.state.bus.publish(event{Type: "reset", Data: "{}"})
writeJSON(w, map[string]bool{"ok": true})
}
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
_ = enc.Encode(v)
}