From 414227796bb898c123c1236692c541c2d2fba38b Mon Sep 17 00:00:00 2001 From: zuevav Date: Fri, 18 Sep 2026 19:28:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9?= =?UTF-8?q?=D0=BA=D0=B0:=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=81=D0=BB=D1=83=D0=B6=D0=B1=20=D1=8F=D0=B4?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B8=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9=20=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=BE=D0=BC=20=D0=B8=D0=B7=20=D0=B0=D0=B4?= =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Модули работают отдельными службами (bj-npr, bj-quikexport, bj-lk), и когда служба лежит, раздел модуля отвечает ошибкой связи, а администратору предлагалось идти в консоль за systemctl status. Теперь в разделе «Обновления» карточка «Службы»: состояние каждой службы, с какого времени, причина отказа и кнопка «Перезапустить». Состояние читаем обычным systemctl show — прав не требует. Перезапуск делает root-демон bj-keymedia (тот же канал, что у установки обновлений) по закрытому списку служб: чужую службу и сам демон отсюда не тронуть. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL --- deploy/linux/sbin/bj-keymedia-worker | 14 + internal/lkgateway/setup.go | 3 + internal/lkgateway/units.go | 126 +++ .../lkgateway/web/templates/admin_setup.html | 16 + s.html | 1005 +++++++++++++++++ 5 files changed, 1164 insertions(+) create mode 100644 internal/lkgateway/units.go create mode 100644 s.html diff --git a/deploy/linux/sbin/bj-keymedia-worker b/deploy/linux/sbin/bj-keymedia-worker index 90a7744..54bfc71 100755 --- a/deploy/linux/sbin/bj-keymedia-worker +++ b/deploy/linux/sbin/bj-keymedia-worker @@ -11,6 +11,20 @@ process_one(){ case "$action" in quikexport-*) out=$(BJ_EXTRA="$extra" /usr/local/sbin/bj-quikexport-admin "${action#quikexport-}" "$staging" 2>&1) ;; component-install) out=$(/usr/local/sbin/bj-component-install "$staging" 2>&1) ;; + # Перезапуск службы модуля из админки. Список закрытый: чужую службу + # (и сам этот демон) отсюда тронуть нельзя. + unit-restart) + case "$extra" in + bj-server|bj-npr|bj-quikexport|bj-lk|bj-crypto) + systemctl reset-failed "$extra" >/dev/null 2>&1 + if err=$(systemctl restart "$extra" 2>&1); then + out="{\"ok\":true,\"output\":\"перезапущено: $extra\"}" + else + err=$(printf '%s' "$err" | tr -d '"\\' | tr '\n' ' ') + out="{\"ok\":false,\"error\":\"$extra: $err\"}" + fi ;; + *) out="{\"ok\":false,\"error\":\"недопустимая служба\"}" ;; + esac ;; *) out=$(BJ_TARGET_DEV="$tdev" BJ_TARGET_MNT="$tmnt" BJ_EXTRA="$extra" /usr/local/sbin/bj-keymedia-install "$action" "$staging" "$profile" 2>&1) ;; esac printf '%s' "$out" > "$res.tmp"; chown bj:bj "$res.tmp" 2>/dev/null diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 21b2207..87122fb 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -93,6 +93,7 @@ func registerSetup(mux *http.ServeMux, a *admin, rc *RuntimeConfig, svc *Service mux.HandleFunc("/admin/setup/lk/relay-php.zip", h.lkPHPRelayDownload) mux.HandleFunc("/admin/setup/lk/preview", h.lkPreviewSave) mux.HandleFunc("/admin/setup/lk/data", h.lkDataSave) + mux.HandleFunc("/admin/setup/units/restart", h.unitRestart) mux.HandleFunc("/admin/setup/lk-integration", h.saveLKIntegration) mux.HandleFunc("/admin/setup/lk-check", h.checkLKConn) mux.HandleFunc("/admin/setup/lk-simulate", h.lkSimulate) @@ -324,6 +325,7 @@ type SetupData struct { LKUsersError string LKPreview LKPreviewStatus // кабинет на этом сервере для просмотра LKData LKDataView // курсы валют и ежедневный срез стоимости + Units []UnitView // службы bj и её модулей Versions SystemVersions // версии компонентов bj и внешних систем Modules map[string]bool // какие лицензируемые модули разрешены (ключ = признак) Features []LicenseFeatureView // признаки лицензии для карточки @@ -391,6 +393,7 @@ func (h *setupHandlers) renderSetup(w http.ResponseWriter, req *http.Request, fl LKAgent: lkAgentStatus(s), LKPreview: lkPreviewStatus(h.rc, req), LKData: lkDataView(h.rc), + Units: unitViews(h.rc), LKKeyMask: lkKeyForPage(s.LK.APIKey), Versions: systemVersions(h.rc), Modules: modulesAllowed(h.rc), diff --git a/internal/lkgateway/units.go b/internal/lkgateway/units.go new file mode 100644 index 0000000..0d4813d --- /dev/null +++ b/internal/lkgateway/units.go @@ -0,0 +1,126 @@ +package lkgateway + +// units.go — состояние служб bj и её модулей для страницы настроек. +// +// Модули вынесены в отдельные службы (bj-npr, bj-quikexport, bj-lk), и когда одна +// из них лежит, раздел модуля в bj отвечает ошибкой связи. Раньше администратору +// оставалось идти в консоль за systemctl status; здесь он видит состояние всех +// служб и может перезапустить нужную. Читаем состояние обычным systemctl show +// (прав не требует), перезапуск делает root-демон bj-keymedia по закрытому списку. + +import ( + "context" + "net/http" + "os/exec" + "strings" + "time" + + "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release" +) + +// UnitView — служба для карточки настроек. +type UnitView struct { + Unit string // bj-npr + Title string // Риск-монитор НПР / УДС + Core bool // ядро (перезапуск рвёт текущую страницу) + State string // active | inactive | failed | unknown + Sub string // running, dead, … + Since string // с какого времени в этом состоянии + Result string // причина отказа (systemd Result) + Present bool // компонент установлен на этом сервере +} + +// OK — служба работает. +func (u UnitView) OK() bool { return u.State == "active" && u.Sub == "running" } + +// unitState — состояние одной службы (без прав root). +func unitState(unit string) UnitView { + v := UnitView{Unit: unit, State: "unknown"} + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + out, err := exec.CommandContext(ctx, "systemctl", "show", unit, + "--property=ActiveState,SubState,ActiveEnterTimestamp,Result").Output() + if err != nil { + return v + } + for _, line := range strings.Split(string(out), "\n") { + k, val, ok := strings.Cut(strings.TrimSpace(line), "=") + if !ok || val == "" { + continue + } + switch k { + case "ActiveState": + v.State = val + case "SubState": + v.Sub = val + case "Result": + if val != "success" { + v.Result = val + } + case "ActiveEnterTimestamp": + // «Wed 2026-09-16 01:13:56 MSK» — оставляем дату и время + if f := strings.Fields(val); len(f) >= 3 { + v.Since = f[1] + " " + f[2] + } + } + } + return v +} + +// unitViews — службы bj на этом сервере: ядро и установленные модули. +func unitViews(rc *RuntimeConfig) []UnitView { + installed := release.LoadInstalled(release.InstalledFile) + var out []UnitView + for _, c := range release.Components { + if c.Unit == "" { + continue + } + _, present := installed[c.Name] + if !present && !c.Core { + continue // модуля на сервере нет — и службы быть не должно + } + v := unitState(c.Unit) + v.Title, v.Core, v.Present = c.Title, c.Core, true + out = append(out, v) + } + return out +} + +// restartUnit — перезапуск службы через root-демон (см. bj-keymedia-worker). +func restartUnit(ctx context.Context, unit string) (string, error) { + cctx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + res, err := runKeymediaOp(cctx, "unit-restart", map[string]string{"extra": unit}) + if err != nil { + return "", err + } + if s, ok := res["output"].(string); ok && s != "" { + return s, nil + } + return "перезапущено: " + unit, nil +} + +// unitRestart — POST /admin/setup/units/restart: перезапуск службы из настроек. +func (h *setupHandlers) unitRestart(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method", http.StatusMethodNotAllowed) + return + } + unit := strings.TrimSpace(r.FormValue("unit")) + known := false + for _, c := range release.Components { + if c.Unit == unit { + known = true + } + } + if !known { + setupFlash(w, r, "Неизвестная служба.") + return + } + msg, err := restartUnit(r.Context(), unit) + if err != nil { + setupFlash(w, r, "Перезапуск "+unit+": "+err.Error()) + return + } + setupFlash(w, r, msg) +} diff --git a/internal/lkgateway/web/templates/admin_setup.html b/internal/lkgateway/web/templates/admin_setup.html index 91a58e3..f533ca1 100644 --- a/internal/lkgateway/web/templates/admin_setup.html +++ b/internal/lkgateway/web/templates/admin_setup.html @@ -1073,6 +1073,22 @@ function hbBind(inp,box,list,onPick){

Обновления

{{template "verline" (.Versions.Get "bj-server")}} +
+

Службы

+

Ядро и модули работают отдельными службами: если служба модуля не запущена, его раздел в bj отвечает ошибкой связи. Здесь видно состояние и можно перезапустить службу, не заходя на сервер.

+
+ + {{range .Units}} + + + + + + {{end}}
СлужбаЧто делаетСостояниеС
{{.Unit}}{{.Title}}{{if .OK}}работает{{else}}{{.State}}{{with .Sub}} / {{.}}{{end}}{{with .Result}} ({{.}}){{end}}{{end}}{{if .Since}}{{.Since}}{{else}}—{{end}}
+ +
+
+

Компоненты

Каждый компонент обновляется отдельно: своя версия в релизе, перезапускается только его служба. Файлы ставит root-установщик после повторной проверки подписи и контрольных сумм. Модули, которых нет на этом сервере, обновлением не ставятся — кроме выделенных из ядра (НПР / УДС, экспорт QUIK): если модуль входит в лицензию, он ставится вместе с обновлением, иначе его раздел перестал бы работать.

diff --git a/s.html b/s.html new file mode 100644 index 0000000..f4f4aca --- /dev/null +++ b/s.html @@ -0,0 +1,1005 @@ + + + + + +Настройка · Bridge&Join + + + +
×
+ +
+ + + Bridge&Join + + +
+ + НРД на связи + 14.08.2026 13:17:32 + +
+
+ + + +
+ + + +
+ + + +
+ + +
+

Обзор

+
+

Готовность системы: 3 из 4

+
+ +
+
postgres
+
настроено
+
+ +
+
crypto-service
+
настроено
+
+ +
+
nsd-adapter
+
настроено
+
+ +
+
lk-callback
+
не настроено
+
+ +
+
+
+ + +
+

База данных

+
+

PostgreSQL

+

Журнал сделок m2m-core и принимающая БД. Сейчас: подключено.

+ +
+ Изменить подключение +
+ + +

При сохранении выполняется Ping.

+
+
+
+
+
+ + +
+

Обмен Fancy (MSSQL / PostgreSQL)

+
+

Подключение к серверу БД клиента

+

Канал обмена с Fancy: запрос из ЛК → таблица в БД → «кран» Fancy проверяет по своей БД и пишет ответ → bj-server забирает. Эта же БД — журнал обмена с НРД. Поддерживаются Microsoft SQL Server и PostgreSQL.
+ Справка для разработчика Fancy — как подготовить сервер БД (создать БД, пользователя, права), структура таблиц, контракт «крана», копируемый SQL.

+
+ + + + + + + +
+
+
+ +
+

Подготовка базы данных

+

Порядок: 1) проверить соединение → 2) создать БД (или создайте её на сервере руками) → 3) налить схему (таблицы). После этого БД готова к обмену.

+
+
+
+
+
+

Статус схемы: + ✓ налита — БД готова

+
+ Что будет создано (черновик схемы, согласуем с Fancy) +
    +
  • bj_transfer_requests — шапка запроса из ЛК: колонки 1:1 с XML НРД (guid, sender/receiver_code, cost_code, инвестор, документ, ИИС, transferring/receiving INN)
  • +
  • bj_transfer_securities — бумаги запроса, по строке на бумагу: reference_id, security_code, isin, quantity, settlement_inn, deponent_code, account_id, section_id, isolation_status
  • +
  • bj_transfer_responses — ответ «крана» после проверки по БД Fancy (verdict, reason)
  • +
  • bj_nsd_log — журнал обмена с НРД (direction, doc_type, коды, raw XML)
  • +
+
+
+
+ + +
+

QUIK — Модуль неторговых поручений

+
+

Подключение к БД МНП QUIK (MSSQL)

+

При выводе бумаг клиентом bj блокирует их количество в QUIK через API Модуля неторговых поручений (хранимые процедуры). БД боевая — тест только на чтение (версия + наличие M2M-объектов), без записи. Подключение под логином с ролью InstrEu.
+ Про архитектуру M2M · документация QUIK МНП 21.4.0.

+
+ + + + + + + +
+ +
+
+
+
+
+

Статус: ✓ подключение проверено

+
+ + +
+

Полный ответ сервера (последний тест)

+

Можно выделить и скопировать целиком — для отправки коллегам/аутсорсеру QUIK. + Тест от 20.07.2026 09:50:50 UTC.

+
+ +
+
=== Тест подключения к БД МНП QUIK ===
+Время:          2026-07-20 12:50:50 MSK
+Сервер:         91.209.122.88:8849
+БД:             88_INSTR
+Логин:          BROKER_88
+Шифрование:     encrypt=true, TrustServerCertificate=true
+Клиент (bj):    исходящий IP 195.239.71.22, драйвер go-mssqldb
+----------------------------------------
+РЕЗУЛЬТАТ: ПОДКЛЮЧЕНИЕ OK
+
+Версия сервера: Microsoft SQL Server 2016 (SP3-GDR) (KB5029186) - 13.0.6435.1 (X64)
+Текущая БД:     88_INSTR
+Логин на серв.: BROKER_88
+M2M-готовность: true — БД похожа на МНП с поддержкой M2M — ключевые объекты найдены.
+
+Объекты МНП/M2M (видимость под логином):
+  proc:sp_CancelInstr          нет
+  proc:sp_NewInstrSec          ДА
+  proc:sp_SignInstr            нет
+  table:Contracts              ДА
+  table:Export                 ДА
+  table:Export2                ДА
+  table:Instructions           ДА
+  table:M2MDepoRecipients      ДА
+  table:QuikSecurities         ДА
+
+
+ +
+ + +
+

Криптография

+ +
+

Установка ключа на флешку

+

Пошаговый мастер: загрузить архив НРД с паролем → запись на флешку → справочник сертификатов → проверка Валидаты → готово.

+ Открыть мастер установки ключа → +
+ +
+

СКЗИ «Валидата Клиент L»

+

Активный профиль: moex · провайдер validata. Подробно — справка.

+
+
+
+
+
+
+
+ Параметры провайдера (для совместимости) +
+ + + + + + +
+
+
+
+ + +
+

Сертификаты УЦ (авто-загрузка)

+

URL .cer-файлов УЦ НРД (nsd.ru). Скачиваются, парсятся и сохраняются в /var/lib/bj/ca-certs/.

+
+ + + + +
+
+
+ +
+
+ + +
+

НРД

+
+

Интеграционный шлюз (ИШ)

+

Профиль test3-gost, ИШ http://localhost:8090. Стенды и установка — справка.

+
+ + + + + + +
+

Депозитарные реквизиты (откуда списываются бумаги) — из договора/письма НРД. Нужны для формирования заявки на перевод.

+ + + + + + +
+
+ +
+
+ + +
+

Личный кабинет · ЕСИА Финанс

+

Интеграция с платформой ЛК ЕСИА Финанс: адрес для клиента, приём заявок M2M, возврат статуса и подключение к API (тест/бой). НРД настраивается отдельно — в разделе «НРД».

+ +
+

Личный кабинет — интеграция

+

Адрес ЛК (стенд/бой), куда клиент подаёт заявку M2M. Заявка приходит к нам по контракту POST /api/v1/back_office/claims/, а мы возвращаем статус через Callback URL.

+
+
+ + + Показывается ссылкой на дашборде для первичного теста. +
+
+ + + Не настроен — уведомления в ЛК отключены. Для обмена с НРД не требуется. +
+
+
+
+ +
+

Подключение к API ЛК — тест и бой

+

Реквизиты, по которым bj обращается к API платформы ЛК ЕСИА Финанс (HTTP Basic). Заполните нужные окружения и нажмите «Проверить связь» — сразу видно, есть подключение или нет.

+ +
+ +
+
+ Тестовый стенд + связь есть + +
+
+ +
+
+
+
+ + +
+
+

Последняя проверка: связь есть (HTTP 200) · 14.08 12:28

+
+ + +
+
+ Боевой контур + связь есть + +
+
+ +
+
+
+
+ + +
+
+

Последняя проверка: связь есть (HTTP 200) · 14.08 12:21

+
+
+ +
+ + + + bj работает с ЛК через выбранное окружение. +
+
+ +
+

Симулятор заявки M2M

+

Пока подача через веб-форму у ЕСИА Финанс не готова — bj сам собирает заявку depo_m2m и отправляет её в API ЛК (POST /claims/) через активное окружение. Ответ показывается ниже; затем заявку ловим опросом в «Переводы». Поля предзаполнены рабочим тестовым договором.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Результат (сырой ответ API) появится вверху страницы. Если «Раздел счёта не доступен для быстрых переводов» — Финансы должны включить признак M2M на разделе счёта.

+
+
+ + +
+

Тесты

+ +
+

Тестовый пакет роботу НРД

+

Робот MC0012500000 эмулирует вторую сторону перевода. Выберите сценарий — bj-server отправит эталонный запрос через ИШ, ответ придёт во входящие. Требуется настроенный ИШ + профиль Валидаты.

+
+ + +
+

Ответ робота — асинхронно (~30-60 сек) во входящие ИШ.

+
+ +
+

Сквозной тестовый прогон (mock/реальный)

+

Заявка с предзаполненными данными через всю цепочку до финального статуса.

+
+ +
+ Последний прогон + + + + + + + +
Статус✓ успешно
FSM-статусawaiting_decision
ClaimIDff99dc31-d5a2-44e7-9595-1170cf2a821a → карточка
СообщениеСценарий 3333 «Робот — принимающая сторона»: пакет отправлен роботу MC0012500000 (GUID ff99dc31-d5a2-44e7-9595-1170cf2a821a). Ждём ответ робота во входящих ИШ.
+
+ +
+
+ + +
+

Обновления

+ +
+

Версия bj-server

+
+ 0.1.0 + +
+
+ +

Последняя проверка: 17.06.2026 09:45 — проверка не удалась: release: запрос манифеста: Get "http://127.0.0.1:8099/v1/stable/manifest.json": dial tcp 127.0.0.1:8099: connect: connection refused

+ +
+ +
+

Источник обновлений

+

Артефактория раздаёт подписанные релизы. Обновления проверяются по подписи Ed25519 и sha256 — без валидной подписи установка не выполняется.

+
+ + + + + + + +
+
+
+
+ + +
+

Лицензия

+
+

+ Не активирована +

+ +

Лицензионный ключ не введён. Без лицензии сервис работает, но автообновления заблокированы.

+ +
+ +
+

Активация

+

Вставьте лицензионный ключ, полученный от поставщика. Проверка офлайн по подписи — связь с сервером лицензий не требуется.

+
+ + +
+ Публичный ключ издателя (если не зашит) + +
+
+
+
+
+ +
+
+ + + +
+ + + +