0dd38bbae4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
191 lines
7.8 KiB
Go
191 lines
7.8 KiB
Go
package lkgateway
|
|
|
|
// «Подготовка модулей» (05.10.2026) — экран между вводом ключа продукта и мастером настройки. Образ ставит
|
|
// базовый набор (ядро); после ввода ключа bj узнаёт состав лицензии, ставит модули, которые в неё входят
|
|
// (файлы — из комплекта образа, если там тот же выпуск, иначе с сервера обновлений), запускает их службы
|
|
// и только потом открывает мастер: мастер настраивает уже установленное.
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release"
|
|
)
|
|
|
|
// PrepareItem — строка экрана: что входит в лицензию и в каком оно состоянии.
|
|
type PrepareItem struct {
|
|
Title string `json:"title"`
|
|
State string `json:"state"` // ok | run | wait | warn | err | info
|
|
Detail string `json:"detail,omitempty"`
|
|
}
|
|
|
|
var prepareRun struct {
|
|
sync.Mutex
|
|
Running, Finished bool
|
|
Result, Err string
|
|
}
|
|
|
|
// prepareCoreFeatures — признаки лицензии, которые работают внутри ядра (отдельных файлов нет).
|
|
var prepareCoreFeatures = []string{"quik-mnp", "esia-finance"}
|
|
|
|
// prepareItems — состав лицензии этой установки и состояние каждого модуля.
|
|
func prepareItems(rc *RuntimeConfig, running, finished bool, runErr string) []PrepareItem {
|
|
out := []PrepareItem{{Title: "Ядро bj: обмен с НРД, переводы M2M", State: "ok", Detail: "установлено с образа, версия " + BuildVersion}}
|
|
for _, f := range prepareCoreFeatures {
|
|
if moduleAllowed(rc, f) {
|
|
out = append(out, PrepareItem{Title: moduleTitles[f], State: "ok", Detail: "входит в ядро — устанавливать нечего"})
|
|
}
|
|
}
|
|
for _, c := range release.Components {
|
|
if !c.FromCore || !moduleAllowed(rc, c.Feature) {
|
|
continue
|
|
}
|
|
it := PrepareItem{Title: prepareTitle(c)}
|
|
_, statErr := os.Stat(c.Path)
|
|
present := statErr == nil
|
|
us := ""
|
|
if present && c.Unit != "" {
|
|
us = unitState(c.Unit).State
|
|
}
|
|
switch {
|
|
case us == "active":
|
|
it.State, it.Detail = "ok", "установлен, служба работает"
|
|
case us == "activating" || us == "reloading":
|
|
it.State, it.Detail = "run", "установлен, служба запускается…"
|
|
case present && c.Unit == "bj-crypto":
|
|
it.State, it.Detail = "warn", "установлен, служба не запущена ("+us+") — обмену не мешает: пакеты для НРД подписывает шлюз"
|
|
case present:
|
|
it.State, it.Detail = "ok", "установлен; служба заработает, когда модуль будет настроен (глава «Запуск»)"
|
|
case running:
|
|
it.State, it.Detail = "run", "устанавливается…"
|
|
case runErr != "":
|
|
it.State, it.Detail = "err", "не установлен — "+runErr
|
|
case finished:
|
|
it.State, it.Detail = "warn", "не установлен: в текущем выпуске на сервере обновлений его нет — поставится обновлением"
|
|
default:
|
|
it.State, it.Detail = "wait", "будет установлен"
|
|
}
|
|
out = append(out, it)
|
|
}
|
|
if moduleAllowed(rc, "web-cabinet") {
|
|
out = append(out, PrepareItem{Title: moduleTitles["web-cabinet"], State: "info", Detail: "ставится на веб-сервер организации: дистрибутив и ключ кабинета — в мастере, глава «Запуск»"})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// prepareTitle — название модуля для экрана.
|
|
func prepareTitle(c release.Component) string {
|
|
switch c.Name {
|
|
case "crypto-service.jar":
|
|
return "Служба пробной подписи (Валидата)"
|
|
}
|
|
if t := moduleTitles[c.Feature]; t != "" {
|
|
return t
|
|
}
|
|
return c.Title
|
|
}
|
|
|
|
// prepareStart — поставить модули лицензии (один запуск за раз; повторный — пока идёт — ничего не делает).
|
|
func prepareStart(rc *RuntimeConfig) {
|
|
prepareRun.Lock()
|
|
if prepareRun.Running {
|
|
prepareRun.Unlock()
|
|
return
|
|
}
|
|
prepareRun.Running, prepareRun.Finished, prepareRun.Result, prepareRun.Err = true, false, "", ""
|
|
prepareRun.Unlock()
|
|
go func() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Minute)
|
|
defer cancel()
|
|
var msg, errText string
|
|
if _, err := os.Stat(keymediaReqDir); err != nil {
|
|
errText = "на сервере нет службы установки (bj-keymedia)"
|
|
} else if m, err := NewUpdater(rc).ensureSplitModules(ctx); err != nil {
|
|
errText = strings.TrimPrefix(err.Error(), "Установка модулей ")
|
|
} else {
|
|
msg = m
|
|
}
|
|
prepareRun.Lock()
|
|
prepareRun.Running, prepareRun.Finished, prepareRun.Result, prepareRun.Err = false, true, msg, errText
|
|
prepareRun.Unlock()
|
|
}()
|
|
}
|
|
|
|
// safeNext — куда идти после подготовки: только свои страницы (как return_to).
|
|
func safeNext(v string) string {
|
|
if strings.HasPrefix(v, "/admin/") && !strings.HasPrefix(v, "//") && !strings.ContainsAny(v, "\r\n") {
|
|
return v
|
|
}
|
|
return "/admin/wizard"
|
|
}
|
|
|
|
// PrepareData — данные страницы «Подготовка модулей».
|
|
type PrepareData struct {
|
|
page
|
|
Tenant, Plan, Until string
|
|
Next string
|
|
Flash string
|
|
}
|
|
|
|
func registerPrepare(mux *http.ServeMux, h *setupHandlers) {
|
|
mux.HandleFunc("/admin/setup/prepare", func(w http.ResponseWriter, r *http.Request) {
|
|
st := licenseStatus(h.rc)
|
|
if !st.Valid {
|
|
http.Redirect(w, r, "/admin/wizard?step=1", http.StatusSeeOther)
|
|
return
|
|
}
|
|
prepareRun.Lock()
|
|
if !prepareRun.Running { // новый заход (ключ сменили, модуль добавили) — прошлый итог не в счёт
|
|
prepareRun.Finished, prepareRun.Result, prepareRun.Err = false, "", ""
|
|
}
|
|
prepareRun.Unlock()
|
|
d := PrepareData{page: nowPage("Подготовка модулей", "wizard"), Tenant: st.Tenant, Plan: st.Plan, Until: st.Message,
|
|
Next: safeNext(r.URL.Query().Get("next")), Flash: r.URL.Query().Get("flash")}
|
|
render(w, h.tpl.a.prepare, d)
|
|
})
|
|
mux.HandleFunc("/admin/setup/prepare/start", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if !licenseStatus(h.rc).Valid {
|
|
jsonOut(w, map[string]any{"ok": false, "error": "лицензия не активирована"})
|
|
return
|
|
}
|
|
prepareStart(h.rc)
|
|
jsonOut(w, map[string]any{"ok": true})
|
|
})
|
|
mux.HandleFunc("/admin/setup/prepare/status", func(w http.ResponseWriter, r *http.Request) {
|
|
prepareRun.Lock()
|
|
running, finished, res, errText := prepareRun.Running, prepareRun.Finished, prepareRun.Result, prepareRun.Err
|
|
prepareRun.Unlock()
|
|
items := prepareItems(h.rc, running, finished, errText)
|
|
pending := false // модули лицензии, которых на сервере ещё нет
|
|
for _, c := range release.Components {
|
|
if c.FromCore && moduleAllowed(h.rc, c.Feature) {
|
|
if _, err := os.Stat(c.Path); err != nil {
|
|
pending = true
|
|
}
|
|
}
|
|
}
|
|
updProg.Lock()
|
|
pg := updProg.s
|
|
updProg.Unlock()
|
|
busy := false // служба модуля ещё запускается
|
|
for _, it := range items {
|
|
if it.State == "run" {
|
|
busy = true
|
|
}
|
|
}
|
|
out := map[string]any{"running": running, "finished": finished, "items": items, "pending": pending, "busy": busy, "result": res, "error": errText}
|
|
if running && pg.Active {
|
|
out["stage"], out["done"], out["total"] = pg.Stage, pg.Done, pg.Total
|
|
}
|
|
jsonOut(w, out)
|
|
})
|
|
}
|