Первичная настройка, часть 3: установка ИШ из bj (скачать с nsd.ru или из файла; без графики: keyring, xvfb, dbus; база, служба) — проверено чистой установкой на Ubuntu 22.04; два контура НРД: каналы TEST3 и боевой в ИШ (адреса и отпечатки УЦ из документа НРД об ИШ 107), свои ключи (мастер ключа — выбор контура, профиль только к каналу контура), переключение bj между контурами с базой DEPO контура и подтверждением для боевого; папка входящих и слежение за связью — по активному контуру; пакеты ИШ в установщике и образе
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -4,10 +4,14 @@ package lkgateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -47,8 +51,233 @@ func igateState(ctx context.Context, base string) (bool, string) {
|
||||
}
|
||||
|
||||
func registerIgate(mux *http.ServeMux, rc *RuntimeConfig) {
|
||||
registerIgateAdmin(mux, rc)
|
||||
mux.HandleFunc("/admin/setup/igate/state", func(w http.ResponseWriter, r *http.Request) {
|
||||
ok, msg := igateState(r.Context(), rc.Snapshot().NSD.IGWBaseURL)
|
||||
jsonOut(w, map[string]any{"ok": ok, "msg": msg, "installed": igateInstalled()})
|
||||
})
|
||||
}
|
||||
|
||||
// ---- контуры НРД: TEST3 и боевой ----
|
||||
|
||||
func currentNSDProfile() string {
|
||||
if globalRC == nil {
|
||||
return ""
|
||||
}
|
||||
return globalRC.Snapshot().NSD.Profile
|
||||
}
|
||||
|
||||
// contourOf — контур по профилю bj: prod | guest | test3.
|
||||
func contourOf(profile string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(profile, "prod"):
|
||||
return "prod"
|
||||
case strings.HasPrefix(profile, "guest"):
|
||||
return "guest"
|
||||
}
|
||||
return "test3"
|
||||
}
|
||||
|
||||
// contourChannel — начало кода канала ИШ контура (канал = <код><депозитарный код>).
|
||||
func contourChannel(contour string) string {
|
||||
switch contour {
|
||||
case "prod":
|
||||
return "PROD"
|
||||
case "guest":
|
||||
return "GUEST"
|
||||
}
|
||||
return "TEST3"
|
||||
}
|
||||
|
||||
// igateExchangeDir — папка обмена канала ИШ контура (у боевого — своя, чтобы входящие контуров не смешивались).
|
||||
func igateExchangeDir(profile string) string {
|
||||
if contourOf(profile) == "prod" {
|
||||
return "/var/lib/igate/exchange-prod"
|
||||
}
|
||||
return "/var/lib/igate/exchange"
|
||||
}
|
||||
|
||||
const igateUploadDir = "/var/lib/bj/igate-upload"
|
||||
|
||||
var igateVersionRe = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
|
||||
|
||||
func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
|
||||
// состояние ИШ и каналов + что выбрано в bj
|
||||
mux.HandleFunc("/admin/setup/igate/status", func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
res, err := runKeymediaOp(ctx, "igate-status", nil)
|
||||
if err != nil {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
s := rc.Snapshot()
|
||||
res["active_contour"] = contourOf(s.NSD.Profile)
|
||||
res["active_profile"] = s.NSD.Profile
|
||||
res["deponent"] = pickStr(s.NSD.DeponentCode, orgCode(s))
|
||||
res["depo_db"] = s.Fancy.DepoDB()
|
||||
res["depo_db_test"], res["depo_db_prod"] = s.NSD.DepoDBTest, s.NSD.DepoDBProd
|
||||
jsonOut(w, res)
|
||||
})
|
||||
// установка или обновление ИШ: загруженный архив НРД или версия для скачивания с nsd.ru
|
||||
mux.HandleFunc("/admin/setup/igate/install", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
params := map[string]string{}
|
||||
if f, hdr, err := r.FormFile("file"); err == nil {
|
||||
defer f.Close()
|
||||
name := strings.ToLower(hdr.Filename)
|
||||
if !strings.HasSuffix(name, ".zip") && !strings.HasSuffix(name, ".deb") {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "нужен дистрибутив ИШ от НРД: igate-…-amd64.zip (или .deb из него)"})
|
||||
return
|
||||
}
|
||||
_ = os.MkdirAll(igateUploadDir, 0o750)
|
||||
dst := filepath.Join(igateUploadDir, "igate"+filepath.Ext(name))
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(out, io.LimitReader(f, 600<<20))
|
||||
out.Close()
|
||||
if err != nil {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "загрузка: " + err.Error()})
|
||||
return
|
||||
}
|
||||
params["file"] = dst
|
||||
} else if v := strings.TrimSpace(r.FormValue("version")); igateVersionRe.MatchString(v) {
|
||||
params["version"] = v
|
||||
} else {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "загрузите архив ИШ или укажите версию вида 107.0.930"})
|
||||
return
|
||||
}
|
||||
extra, _ := json.Marshal(params)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
||||
defer cancel()
|
||||
jsonOut(w, opResult(runKeymediaOp(ctx, "igate-install", map[string]string{"extra": string(extra)})))
|
||||
})
|
||||
mux.HandleFunc("/admin/setup/igate/install-status", func(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := os.ReadFile("/var/lib/bj/igate-install-status.json")
|
||||
if err != nil {
|
||||
jsonOut(w, map[string]any{})
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_, _ = w.Write(raw)
|
||||
})
|
||||
// канал контура в ИШ (адрес, отпечаток УЦ, папка обмена); ключ — мастером установки ключа
|
||||
mux.HandleFunc("/admin/setup/igate/contour", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
s := rc.Snapshot()
|
||||
contour, crypto := r.FormValue("contour"), r.FormValue("crypto")
|
||||
if contour != "prod" {
|
||||
contour = "test3"
|
||||
}
|
||||
if crypto != "rsa" {
|
||||
crypto = "gost"
|
||||
}
|
||||
dep := pickStr(s.NSD.DeponentCode, orgCode(s))
|
||||
if dep == "" {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "сначала укажите депозитарный код организации (шаг 4)"})
|
||||
return
|
||||
}
|
||||
extra, _ := json.Marshal(map[string]string{"contour": contour, "crypto": crypto, "deponent": dep})
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Minute)
|
||||
defer cancel()
|
||||
jsonOut(w, opResult(runKeymediaOp(ctx, "igate-contour", map[string]string{"extra": string(extra)})))
|
||||
})
|
||||
// переключить bj на контур: профиль отправки, база DEPO контура; bj перезапускается
|
||||
mux.HandleFunc("/admin/setup/igate/switch", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
contour, crypto := r.FormValue("contour"), r.FormValue("crypto")
|
||||
if contour != "prod" {
|
||||
contour = "test3"
|
||||
}
|
||||
if crypto != "rsa" {
|
||||
crypto = "gost"
|
||||
}
|
||||
s := rc.Snapshot()
|
||||
dep := pickStr(s.NSD.DeponentCode, orgCode(s))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
// канал контура должен быть в ИШ и включён (ключ контура установлен)
|
||||
st, err := runKeymediaOp(ctx, "igate-status", nil)
|
||||
if err != nil {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "состояние ИШ: " + err.Error()})
|
||||
return
|
||||
}
|
||||
want := contourChannel(contour) + dep
|
||||
ready := false
|
||||
if chans, ok := st["channels"].([]any); ok {
|
||||
for _, c := range chans {
|
||||
if m, ok := c.(map[string]any); ok && m["code"] == want && m["enabled"] == true {
|
||||
ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ready {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "в ИШ нет включённого канала " + want + ": настройте канал контура и установите ключ этого контура"})
|
||||
return
|
||||
}
|
||||
if contour == "prod" && r.FormValue("confirm") != "БОЕВОЙ" {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "для перехода на боевой контур введите слово БОЕВОЙ"})
|
||||
return
|
||||
}
|
||||
depoDB := strings.TrimSpace(r.FormValue("depo_db"))
|
||||
if depoDB != "" && !validDBName(depoDB) {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": "имя базы DEPO — латинские буквы, цифры и _"})
|
||||
return
|
||||
}
|
||||
n, f := s.NSD, s.Fancy
|
||||
from := contourOf(n.Profile)
|
||||
// запоминаем базу DEPO контура, с которого уходим, и берём базу контура, на который переходим
|
||||
if from == "prod" {
|
||||
n.DepoDBProd = f.DepoDB()
|
||||
} else {
|
||||
n.DepoDBTest = f.DepoDB()
|
||||
}
|
||||
target := depoDB
|
||||
if target == "" {
|
||||
if contour == "prod" {
|
||||
target = n.DepoDBProd
|
||||
} else {
|
||||
target = n.DepoDBTest
|
||||
}
|
||||
}
|
||||
n.Profile = contour + "-" + crypto
|
||||
n.IGWBaseURL = pickStr(n.IGWBaseURL, igateDefaultURL)
|
||||
n.SwitchedAt = time.Now()
|
||||
if target != "" && target != f.DepoDB() {
|
||||
f.DepoDatabase = target
|
||||
f.DepoTablesCheck, f.DepoTablesCheckOK = "", false // таблицы и обработку в другой базе — проверить заново
|
||||
f.DepoJobCheck, f.DepoJobCheckOK = "", false
|
||||
f.DepoProcCheck, f.DepoProcCheckOK = "", false
|
||||
if err := rc.UpdateFancy(f); err != nil {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := rc.UpdateNSD(n); err != nil {
|
||||
jsonOut(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
log.Printf("nsd: контур переключён %s → %s (профиль %s, база DEPO %s)", from, contour, n.Profile, rc.Snapshot().Fancy.DepoDB())
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
if _, err := restartUnit(context.Background(), "bj-server"); err != nil {
|
||||
log.Printf("nsd: перезапуск bj-server после смены контура: %v", err)
|
||||
}
|
||||
}()
|
||||
name := map[string]string{"prod": "боевой", "test3": "тестовый TEST3"}[contour]
|
||||
jsonOut(w, map[string]any{"ok": true, "output": "bj переключён на " + name + " контур (база DEPO " + rc.Snapshot().Fancy.DepoDB() + ") и перезапускается — обновите страницу через 15 секунд"})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user