From d5b5597c673f672916cf3a1fff122fb8d564ff77 Mon Sep 17 00:00:00 2001 From: zuevav Date: Tue, 5 May 2026 14:42:18 +0300 Subject: [PATCH] =?UTF-8?q?chore:=20=D0=BA=D0=B0=D1=80=D0=BA=D0=B0=D1=81?= =?UTF-8?q?=20=D0=BC=D0=BE=D0=BD=D0=BE-=D1=80=D0=B5=D0=BF=D0=BE=20=D0=B8?= =?UTF-8?q?=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=87=D0=BD=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B4=D0=B3?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B2=D0=BA=D0=B8=20dev-=D0=92=D0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Содержимое первого коммита: - Структура моно-репо: cmd/{lk-gateway,m2m-core,nsd-adapter,lk-emulator,notify}/, internal/{m2m,nsdxml,fansystore,notify}/, services/crypto-service/, web/admin-ui/, deploy/docker-compose/, migrations/, docs/. - Заглушки main.go во всех cmd/ — make build проходит из коробки. - Makefile с целями build/test/lint/fmt/vet/tidy/ci/compose-up/compose-down. - .golangci.yml, .gitignore, README.md (на русском). - .claude/settings.json — общие ограничения Claude Code для команды (запрет sudo, rm -rf, доступа к /etc/cryptopro, /var/cryptopro). - README в каждом каталоге — назначение и стадия (M1..M5). - docs/architecture/overview.md — выжимка из плана проекта. - docs/fansy-contract/v1/, docs/lk-contract/v1/ — точки сборки контрактов с командами Fansy и ЛК клиента. - deploy/docker-compose/docker-compose.yml — dev-стек (PostgreSQL, MinIO). - scripts/setup-dev-vm.sh — первичная подготовка dev-ВМ под РЕД ОС 7.x и Ubuntu 22.04+ (для компаний без бюджета на лицензии); ставит Go 1.23, Liberica JDK 21, Node.js 20 LTS, Podman, podman-compose, Claude Code CLI; создаёт пользователя dev, /srv/dev, аудит-history. Идемпотентен. - scripts/README.md — описание скрипта и ограничений. Что НЕ коммитим: - Секреты, ключи, сертификаты — закрыто в .gitignore. - Локальные настройки Claude Code (settings.local.json) и сессионные каталоги (.claude/projects/, .claude/worktrees/, .claude/logs/). Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/settings.json | 50 +++ .gitignore | 60 ++++ .golangci.yml | 44 +++ Makefile | 52 ++++ README.md | 61 ++++ cmd/lk-emulator/main.go | 20 ++ cmd/lk-gateway/main.go | 17 ++ cmd/m2m-core/main.go | 17 ++ cmd/notify/main.go | 19 ++ cmd/nsd-adapter/main.go | 22 ++ deploy/docker-compose/docker-compose.yml | 37 +++ docs/architecture/overview.md | 52 ++++ docs/fansy-contract/v1/README.md | 33 ++ docs/lk-contract/v1/README.md | 18 ++ go.mod | 3 + internal/fansystore/README.md | 8 + internal/m2m/.gitkeep | 0 internal/m2m/README.md | 24 ++ internal/notify/README.md | 15 + internal/nsdxml/README.md | 14 + migrations/fansy-store/.gitkeep | 0 scripts/README.md | 56 ++++ scripts/setup-dev-vm.sh | 373 +++++++++++++++++++++++ services/crypto-service/README.md | 24 ++ web/admin-ui/README.md | 18 ++ 25 files changed, 1037 insertions(+) create mode 100644 .claude/settings.json create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/lk-emulator/main.go create mode 100644 cmd/lk-gateway/main.go create mode 100644 cmd/m2m-core/main.go create mode 100644 cmd/notify/main.go create mode 100644 cmd/nsd-adapter/main.go create mode 100644 deploy/docker-compose/docker-compose.yml create mode 100644 docs/architecture/overview.md create mode 100644 docs/fansy-contract/v1/README.md create mode 100644 docs/lk-contract/v1/README.md create mode 100644 go.mod create mode 100644 internal/fansystore/README.md create mode 100644 internal/m2m/.gitkeep create mode 100644 internal/m2m/README.md create mode 100644 internal/notify/README.md create mode 100644 internal/nsdxml/README.md create mode 100644 migrations/fansy-store/.gitkeep create mode 100644 scripts/README.md create mode 100755 scripts/setup-dev-vm.sh create mode 100644 services/crypto-service/README.md create mode 100644 web/admin-ui/README.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..072503c --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "allow": [ + "Bash(go *)", + "Bash(make *)", + "Bash(git status)", + "Bash(git diff *)", + "Bash(git log *)", + "Bash(git branch *)", + "Bash(git fetch *)", + "Bash(git pull *)", + "Bash(git remote -v)", + "Bash(podman *)", + "Bash(podman-compose *)", + "Bash(docker *)", + "Bash(docker-compose *)", + "Bash(xmlstarlet *)", + "Bash(jq *)", + "Bash(ls *)", + "Bash(cat *)", + "Bash(grep *)", + "Bash(find *)", + "Bash(npm run *)", + "Bash(npx *)", + "Bash(./scripts/*)" + ], + "deny": [ + "Bash(rm -rf /*)", + "Bash(rm -rf ~)", + "Bash(rm -rf $HOME)", + "Bash(sudo *)", + "Bash(dd *)", + "Bash(mkfs *)", + "Bash(curl * | sh)", + "Bash(curl * | bash)", + "Bash(wget * | sh)", + "Bash(wget * | bash)", + "Read(/etc/cryptopro/**)", + "Read(/var/cryptopro/**)", + "Read(/etc/ipsec.d/**)", + "Read(/root/**)", + "Read(/home/admin/**)", + "Write(/etc/**)", + "Write(/var/**)", + "Write(/root/**)", + "Write(/home/admin/**)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..565c2b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Сборки +/bin/ +/dist/ +*.exe +*.test +*.out + +# Go +vendor/ +go.sum.lock + +# Java / crypto-service +services/crypto-service/build/ +services/crypto-service/.gradle/ +services/crypto-service/target/ +*.jar +*.class + +# Node / admin-ui +web/admin-ui/node_modules/ +web/admin-ui/dist/ + +# Локальные конфиги и секреты +.env +.env.local +*.local.yaml +*.local.yml +secrets/ +certs/private/ + +# Контейнеры ключей и сертификаты — никогда не коммитим +*.p12 +*.pfx +*.pem +*.key +*.cer.priv + +# IDE и временные файлы +.idea/ +.vscode/ +*.swp +.DS_Store + +# Логи +*.log +logs/ + +# Тестовые артефакты +coverage.out +coverage.html +test-results/ + +# Claude Code сессионные логи и локальные настройки +.claude/logs/ +.claude/settings.local.json +.claude/projects/ +.claude/worktrees/ + +# macOS +.DS_Store diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d3a8721 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,44 @@ +run: + timeout: 3m + go: "1.23" + +linters: + disable-all: true + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - gofmt + - goimports + - misspell + - revive + - gocritic + - bodyclose + - errorlint + - nilerr + - prealloc + - unconvert + - unparam + - whitespace + +linters-settings: + goimports: + local-prefixes: git.zetit.ru/zuevav/Bridge-and-Join-s + misspell: + locale: US + revive: + rules: + - name: var-naming + disabled: false + - name: package-comments + disabled: true + +issues: + exclude-rules: + - path: _test\.go + linters: + - errcheck + - unparam diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..baf5db5 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +.PHONY: help build test lint fmt vet clean tidy ci compose-up compose-down + +GO ?= go +GOLANGCI_LINT ?= golangci-lint +COMPOSE ?= podman-compose + +help: + @echo "Цели:" + @echo " make build - сборка всех бинарников Go" + @echo " make test - юнит-тесты" + @echo " make lint - golangci-lint" + @echo " make fmt - gofmt + goimports" + @echo " make vet - go vet" + @echo " make tidy - go mod tidy" + @echo " make ci - все проверки CI локально" + @echo " make compose-up - поднять локальный стек (PostgreSQL, MinIO, заглушки)" + @echo " make compose-down - остановить локальный стек" + @echo " make clean - удалить артефакты" + +build: + @mkdir -p bin + $(GO) build -o bin/lk-gateway ./cmd/lk-gateway + $(GO) build -o bin/m2m-core ./cmd/m2m-core + $(GO) build -o bin/nsd-adapter ./cmd/nsd-adapter + $(GO) build -o bin/lk-emulator ./cmd/lk-emulator + $(GO) build -o bin/notify ./cmd/notify + +test: + $(GO) test ./... -race -count=1 + +lint: + $(GOLANGCI_LINT) run ./... + +fmt: + $(GO) fmt ./... + +vet: + $(GO) vet ./... + +tidy: + $(GO) mod tidy + +ci: tidy fmt vet lint test build + +compose-up: + $(COMPOSE) -f deploy/docker-compose/docker-compose.yml up -d + +compose-down: + $(COMPOSE) -f deploy/docker-compose/docker-compose.yml down + +clean: + rm -rf bin/ dist/ coverage.out coverage.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..144c34b --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Bridge and Join's — сервис M2M-перевода ценных бумаг через НРД + +Внутренний модуль брокера для обмена с НРД сообщениями о переводе ценных бумаг +(M2M, ЭДО НРД, сервис MOEX MOST). Реализация — российское ПО, готовится к +включению в Реестр (ПП РФ № 1236). + +## Назначение + +Связующий модуль между: + +- личным кабинетом клиента на платформе ESIA Finance (источник заявлений инвестора); +- учётной системой `Fansy` (источник реквизитов и остатков, выгрузка в нашу + принимающую БД делает отдельная команда); +- НРД через Интеграционный шлюз / Web-сервис ONYX (формат XML по схемам + `M2MTransferRequest/Decision/Response/Handbook/ParticipantForm`). + +Подробное описание архитектуры, потока, регуляторных требований, SLA и +дорожной карты — см. `docs/architecture/overview.md` и план в репозитории +команды. + +## Стек + +- Go 1.23+ — ядро (`cmd/`, `internal/`). +- Java 21 (Liberica JDK) + КриптоПро JCP — `services/crypto-service` (XMLDSig + по ГОСТ Р 34.10-2012, серверная подпись действий оператора). +- React + Vite — `web/admin-ui` (журнал, ручное согласование, сертификаты). +- PostgreSQL Pro Certified — основная БД (`m2m-core`, `fansy-store`, аудит). +- MinIO — эталоны подписанных XML. +- Развёртывание — одна ВМ внутри контура брокера, `podman-compose`. + +## Документация + +- `DOC/` — оригинальная документация НРД (схемы XSD, инструкции, эталоны). +- `docs/architecture/overview.md` — обзор архитектуры. +- `docs/fansy-contract/v1/` — DDL и контракт данных для команды Fansy. +- `docs/lk-contract/v1/` — OpenAPI контракта с ЛК клиента (ESIA Finance). +- `scripts/setup-dev-vm.sh` — скрипт первичной подготовки dev-ВМ. + +## Быстрый старт (на dev-ВМ) + +```bash +# под admin (один раз на ВМ) +sudo bash scripts/setup-dev-vm.sh + +# под dev +git clone https://git.zetit.ru/zuevav/Bridge-and-Join-s.git +cd Bridge-and-Join-s +make build +make test +``` + +## Ветви + +- `main` — стабильная. +- `feature/<имя>` — рабочие ветви, в основную через merge request. +- `claude/<...>` — служебные ветви автоматизированных правок. + +## Лицензия + +Внутренняя разработка. Лицензионная модель будет определена при подаче +в Реестр российского ПО. diff --git a/cmd/lk-emulator/main.go b/cmd/lk-emulator/main.go new file mode 100644 index 0000000..44309a8 --- /dev/null +++ b/cmd/lk-emulator/main.go @@ -0,0 +1,20 @@ +// Package main — сервис lk-emulator. Эмулятор ЛК клиента (ESIA Finance API V1) +// на время, пока реальный ЛК не готов. Позволяет «как будто загрузить» +// заявление через веб-форму и запустить полный путь обработки документа. +// +// Когда реальный ЛК подключится — эмулятор остаётся как тестовый инструмент +// в QA-окружении. +// +// На этапе M1 — заглушка. +package main + +import ( + "fmt" + "os" +) + +const serviceName = "lk-emulator" + +func main() { + fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) +} diff --git a/cmd/lk-gateway/main.go b/cmd/lk-gateway/main.go new file mode 100644 index 0000000..7dd2c16 --- /dev/null +++ b/cmd/lk-gateway/main.go @@ -0,0 +1,17 @@ +// Package main — сервис lk-gateway. Принимает заявления от ЛК клиента +// (платформа ESIA Finance, /api/v1/back_office/...), валидирует их подпись, +// передаёт в m2m-core, отдаёт callback-статусы обратно в ЛК. +// +// На этапе M1 — заглушка. Реализация контракта — M2. +package main + +import ( + "fmt" + "os" +) + +const serviceName = "lk-gateway" + +func main() { + fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) +} diff --git a/cmd/m2m-core/main.go b/cmd/m2m-core/main.go new file mode 100644 index 0000000..0319ec5 --- /dev/null +++ b/cmd/m2m-core/main.go @@ -0,0 +1,17 @@ +// Package main — сервис m2m-core. Бизнес-логика и FSM сделки M2M-перевода: +// идемпотентность по GUID, валидация по XSD, метрики SLA, ветка ручного +// согласования и таймаут-отказа MOST. +// +// На этапе M1 — заглушка. +package main + +import ( + "fmt" + "os" +) + +const serviceName = "m2m-core" + +func main() { + fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) +} diff --git a/cmd/notify/main.go b/cmd/notify/main.go new file mode 100644 index 0000000..c21f264 --- /dev/null +++ b/cmd/notify/main.go @@ -0,0 +1,19 @@ +// Package main — сервис notify. Отправка уведомлений по нескольким каналам: +// e-mail (SMTP), Yandex Messenger (Yandex 360), WebSocket-push в admin-ui, +// плюс расширяемая модель провайдеров-плагинов (smtp, yandex360, telegram, +// mattermost, webhook) под единый интерфейс Notifier — для тиражирования +// продукта другим компаниям. +// +// На этапе M1 — заглушка. +package main + +import ( + "fmt" + "os" +) + +const serviceName = "notify" + +func main() { + fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) +} diff --git a/cmd/nsd-adapter/main.go b/cmd/nsd-adapter/main.go new file mode 100644 index 0000000..cb39e44 --- /dev/null +++ b/cmd/nsd-adapter/main.go @@ -0,0 +1,22 @@ +// Package main — сервис nsd-adapter. Транспорт к НРД: +// - Интеграционный шлюз через REST API (основной канал, ИШ сам подписывает); +// - Web-сервис ONYX напрямую (резерв); +// - Файловый шлюз / обменные папки ИШ (fallback). +// +// Сериализация и парсинг XML по схемам M2MSchemas в windows-1251, +// маршрутизация по типам пакетов (#M2MTR / #M2MTD / #M2MER / SUBBR / SUBER / +// SUB16 / Справки / квитанции ЭДО). +// +// На этапе M1 — заглушка. +package main + +import ( + "fmt" + "os" +) + +const serviceName = "nsd-adapter" + +func main() { + fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) +} diff --git a/deploy/docker-compose/docker-compose.yml b/deploy/docker-compose/docker-compose.yml new file mode 100644 index 0000000..d7d8eb9 --- /dev/null +++ b/deploy/docker-compose/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3.9" + +# Локальный стек разработки и тестирования. +# В прод-среде сервисы поднимаются через systemd-юниты или podman-compose +# с production-overlay. Здесь — dev-конфиг с минимальными настройками +# и без секретов. + +services: + postgres: + image: postgres:16 + # В проде заменить на postgrespro/std-16 или registry.postgrespro.ru/pgpro/... + container_name: bj-postgres + environment: + POSTGRES_USER: bj + POSTGRES_PASSWORD: bj_dev + POSTGRES_DB: bj + ports: + - "127.0.0.1:5432:5432" + volumes: + - bj-postgres-data:/var/lib/postgresql/data + + minio: + image: minio/minio:latest + container_name: bj-minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: bj + MINIO_ROOT_PASSWORD: bj_dev_minio + ports: + - "127.0.0.1:9000:9000" + - "127.0.0.1:9001:9001" + volumes: + - bj-minio-data:/data + +volumes: + bj-postgres-data: + bj-minio-data: diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..3d62bfa --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,52 @@ +# Архитектура — обзор + +Краткая выжимка из плана проекта. Полный план хранится у разработки в +`/.claude/plans/precious-percolating-axolotl.md` (внешний документ). + +## Что строим + +Внутренний модуль брокера для M2M-перевода ценных бумаг через НРД +(сервис MOEX MOST). Сейчас разрабатывается «для себя», но архитектура +изначально закладывается под тиражирование другим компаниям. + +## Состав + +- `lk-gateway` — интеграция с ЛК клиента (ESIA Finance API V1). +- `lk-emulator` — эмулятор ЛК на время, пока реальный ЛК не готов. +- `m2m-core` — бизнес-логика и FSM сделки. +- `nsd-adapter` — три транспорта к НРД (ИШ REST — основной, WS ONYX — + резерв, ФШ — fallback) и маршрутизация по типам пакетов + (`#M2MTR/#M2MTD/#M2MER/SUBBR/SUBER/SUB16/Справки/квитанции ЭДО`). +- `crypto-service` — Java + КриптоПро JCP, КС1. +- `notify` — уведомления (e-mail, Yandex Messenger 360, WS-push, + webhook), архитектура плагинов. +- `fansy-store` — принимающая БД для выгрузки от команды Fansy. +- `admin-ui` — веб-интерфейс администратора и сотрудника депозитария. + +## Стек + +- Go 1.23, Java 21 (Liberica JDK), React + Vite + TypeScript. +- PostgreSQL Pro Certified (на проде), MinIO. +- Развёртывание — одна ВМ, podman-compose. +- Класс СКЗИ — КС1 (КриптоПро CSP/JCP). + +## Ключевые ограничения + +- SLA: 5 мин/этап на старте, 2 мин/этап в проде. Регуляторный таймаут + отсутствия ответа от Брокера 2 — 10 → 5 мин. +- Дедлайн прод-релиза — **01.09.2026** (вступление 124-ФЗ + Указания ЦБ). +- Реестр российского ПО (ПП-1236) — заявка на M5; стек и архитектура + соответствуют. +- Кодировка XML — windows-1251; формат `NSDDateTime` — только МСК-зона. + +## Документация интеграций + +- `docs/fansy-contract/v1/` — DDL и контракт данных для команды Fansy + (мы передаём, ETL делают они). +- `docs/lk-contract/v1/` — OpenAPI контракта с ЛК клиента. + +## Развёртывание + +- `deploy/docker-compose/docker-compose.yml` — dev-стек (PostgreSQL + + MinIO). +- `scripts/setup-dev-vm.sh` — подготовка dev-ВМ под РЕД ОС или Ubuntu. diff --git a/docs/fansy-contract/v1/README.md b/docs/fansy-contract/v1/README.md new file mode 100644 index 0000000..0d76884 --- /dev/null +++ b/docs/fansy-contract/v1/README.md @@ -0,0 +1,33 @@ +# docs/fansy-contract/v1 — контракт данных с командой Fansy + +ETL Fansy → принимающая БД (`fansy-store`) реализует **другая команда +разработки**. С нашей стороны: + +1. Спроектировать таблицы по требованиям документации НРД к данным M2M. +2. Передать команде Fansy DDL и контракт данных. +3. Согласовать тип load (UPSERT в staging), окна обновления, SLA на + свежесть данных. +4. Не давать ETL-роли DDL-прав в принимающей схеме. + +Состав каталога (создаём в M1, отправляем в начале M2): + +- `ddl/` — `*.sql` миграции PostgreSQL для всех таблиц. +- `data-dictionary.md` — семантика каждого поля (источник в Fansy, + nullable, единицы, примеры). +- `etl-requirements.md` — требования к процессу выгрузки: тип load, + расписание, способ записи, окна простоя, обработка ошибок, + конфиденциальность. +- `examples/` — пример заявки M2M «end-to-end», 5–10 тестовых клиентов + и заявок для совместного приёмочного теста. + +Минимальный набор таблиц (см. план): + +- Депоненты / клиенты. +- Документы инвестора (`IdentityDocumentCodeEnum`). +- ИИС-договоры (`IIAContractTypeEnum ∈ {T12, T03}`). +- Депо-счета и разделы (`AccountId`, `SectionId`, `DeponentCode`). +- Реквизиты расчётов (ИНН депозитария). +- Портфели и остатки (Whole / Fractional, `IsolationStatus = SGDN`). +- Справочник ЦБ (`SecurityCode`, `ISIN`, `Classification`, `Category`). +- Контрагенты-участники сервиса MOST (Справочник пользователей). +- Audit / staging-таблицы для каждой основной. diff --git a/docs/lk-contract/v1/README.md b/docs/lk-contract/v1/README.md new file mode 100644 index 0000000..dc9341b --- /dev/null +++ b/docs/lk-contract/v1/README.md @@ -0,0 +1,18 @@ +# docs/lk-contract/v1 — контракт с ЛК клиента (ESIA Finance) + +ЛК клиента работает на платформе **ESIA Finance**, контракт описан +в `DOC/API ЛК ЕСИА.pdf` (`/api/v1/back_office/...`, Basic HTTP, JSON, +UTF-8). + +На этапе M1 в `lk-emulator` мы воспроизводим этот контракт для запуска +сквозного потока. Реальный ЛК подключится по тому же контракту, без +правок на нашей стороне. + +В этом каталоге будут: + +- `openapi.yaml` — наш OpenAPI-контракт `lk-gateway`, согласованный + с командой ЛК. +- `examples/` — примеры заявлений и ответов. +- `changelog.md` — версионирование контракта. + +Реализация — задача M1. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8127697 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.zetit.ru/zuevav/Bridge-and-Join-s + +go 1.23 diff --git a/internal/fansystore/README.md b/internal/fansystore/README.md new file mode 100644 index 0000000..2afb16a --- /dev/null +++ b/internal/fansystore/README.md @@ -0,0 +1,8 @@ +# internal/fansystore — Go-репозиторий чтения нашей принимающей БД + +ETL Fansy → принимающая БД делает **отдельная команда**. Здесь только +типизированный Go-доступ на чтение/обогащение из `m2m-core`. + +DDL и контракт данных — `docs/fansy-contract/v1/`. + +Реализация — задача M2 (см. план). diff --git a/internal/m2m/.gitkeep b/internal/m2m/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/internal/m2m/README.md b/internal/m2m/README.md new file mode 100644 index 0000000..e6ef141 --- /dev/null +++ b/internal/m2m/README.md @@ -0,0 +1,24 @@ +# internal/m2m — доменные модели сообщений M2M + +Go-модели, генерируемые/выровненные по XSD из `DOC/M2MSchemas_260408/` +(namespace `http://nsd.ru/schemas/m2m/...`, version `2026-04-08`). + +Состав: + +- `M2MTransferRequest` — запрос на перевод. +- `M2MTransferDecision` — решение принимающей стороны. +- `M2MTransferResponse` — тех. ответ НРД (`StatusCode ∈ {INFO, ERROR}`). +- `M2MTransferHandbook(+Request)` — справочник участников. +- `M2MTransferParticipantForm` — карточка участника. + +Точные ограничения (валидаторы): + +- `ReferenceId` — длина 16, pattern `M2M[A-Z0-9]{13}`. +- `DeponentCode` — до 12 символов, `[A-Z0-9]*`. +- `ISIN` — длина 12, `[A-Z]{2}[A-Z0-9]{9}[0-9]`. +- `OrganizationINN` — ровно 10 цифр. +- `IIAContractType` — `T12 | T03`. +- `SecurityClassification` — `BOND | SHAR | MFUN`. +- `IsolationStatus` — единственное значение `SGDN`. + +Реализация — задача M1 (см. план). diff --git a/internal/notify/README.md b/internal/notify/README.md new file mode 100644 index 0000000..7637954 --- /dev/null +++ b/internal/notify/README.md @@ -0,0 +1,15 @@ +# internal/notify — провайдеры уведомлений + +Архитектура — плагины под единый интерфейс `Notifier`. Реализации в +конфиге включаются/выключаются без изменений в коде: + +- `smtp` — внутренняя корпоративная почта. +- `yandex360` — Yandex Messenger через Yandex 360 для бизнеса + (используем готового бота заказчика). +- `webhook` — универсальный POST по URL, для лёгкого подключения + других систем при тиражировании. +- `wspush` — push-уведомления в `admin-ui` через WebSocket. +- (опционально) `telegram`, `mattermost`, `rocketchat` — заглушки с + примером расширения. + +Реализация — задача M4 (см. план). diff --git a/internal/nsdxml/README.md b/internal/nsdxml/README.md new file mode 100644 index 0000000..c7e8829 --- /dev/null +++ b/internal/nsdxml/README.md @@ -0,0 +1,14 @@ +# internal/nsdxml — сериализация и парсинг XML по правилам НРД + +Особенности, которые этот пакет обязан учитывать: + +- **Кодировка `windows-1251`** на чтение и запись (XML-объявление и тело). + Для конвертации — `golang.org/x/text/encoding/charmap.Windows1251`. +- **Тип `NSDDateTime`** — формат `YYYY-MM-DDThh:mm:ss(МСК[+/-N])`, + pattern из XSD: + `[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\(МСК([+-][0-9]{1,2})?\)`. + Только зона МСК, опциональный сдвиг. +- **Каноникализация `xml-exc-c14n`** для проверки совпадения с эталоном. +- **Round-trip**: `Marshal(Unmarshal(x)) == x` после канонизации. + +Реализация — задача M1 (см. план). diff --git a/migrations/fansy-store/.gitkeep b/migrations/fansy-store/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..34e0f62 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,56 @@ +# scripts — служебные скрипты + +## setup-dev-vm.sh + +Первичная подготовка dev-ВМ. + +Поддерживаемые ОС: + +- **РЕД ОС 7.x** — целевая прод-ОС. +- **Ubuntu 22.04+** / **Debian 12+** — бюджетная альтернатива для + разработки и для компаний без лицензий на сертифицированные ОС. +- **Astra Linux** (deb-семейство) — поддерживается базовый сценарий. +- RHEL-совместимые (Rocky, Alma) — для разработки. + +Что ставит: + +- базовый dev-стек (git, make, jq, xmlstarlet и т. п.), +- Podman + podman-compose, +- Go 1.23, +- Liberica JDK 21 (BellSoft), +- Node.js 20 LTS, +- Claude Code CLI (`@anthropic-ai/claude-code`). + +Что готовит: + +- пользователя `dev` с домашней директорией, без sudo по умолчанию, +- рабочий каталог `/srv/dev`, +- bash-историю с timestamp для аудита, +- `/etc/profile.d/go.sh` для PATH к Go. + +Запуск: + +```bash +sudo bash scripts/setup-dev-vm.sh +``` + +Тонкая настройка через переменные окружения — см. шапку скрипта. + +Идемпотентен — повторный запуск ничего не ломает. + +### Что НЕ делает скрипт + +Эти шаги — вручную, по мере получения артефактов: + +1. Установка КриптоПро CSP и КриптоПро JCP под целевую ОС. +2. Установка дистрибутива Интеграционного шлюза НРД. +3. Импорт тестовых сертификатов GUEST/TEST3 (ГОСТ + RSA). +4. Настройка исходящего файрвола / прокси (whitelist для НРД, + git.zetit.ru, npmjs, golang, anthropic). + +### Прод-ограничения + +Скрипт удобен для разработки на любой из перечисленных ОС, но для +прод-стенда в финансовом секторе требуется **сертифицированная РЕД ОС +или Astra SE** + лицензированный **КриптоПро КС1**. Ubuntu/Debian/Rocky +для прода финсектора не подходят. diff --git a/scripts/setup-dev-vm.sh b/scripts/setup-dev-vm.sh new file mode 100755 index 0000000..4dd6e21 --- /dev/null +++ b/scripts/setup-dev-vm.sh @@ -0,0 +1,373 @@ +#!/usr/bin/env bash +# Скрипт первичной подготовки dev-ВМ для проекта Bridge and Join's +# (сервис M2M-перевода ЦБ через НРД). +# +# Поддерживаемые ОС: +# - РЕД ОС 7.x — целевая прод-ОС (вариант с бюджетом на лицензии) +# - Ubuntu 22.04+ — бюджетная альтернатива для разработки и для +# компаний, у которых ещё нет лицензий на РЕД ОС +# (для прод-сертификации в финсекторе всё равно +# потребуется РЕД ОС / Astra SE с сертификатом ФСТЭК) +# +# Что делает: +# - определяет ОС и пакетный менеджер; +# - устанавливает базовый dev-стек (Git, Make, jq, xmlstarlet, ...); +# - устанавливает Go 1.23, Liberica JDK 21, Node.js 20 LTS, Podman; +# - устанавливает Claude Code CLI; +# - создаёт пользователя dev и каталог /srv/dev; +# - готовит безопасный shell-history с timestamp. +# +# Запуск (под root или через sudo): +# sudo bash scripts/setup-dev-vm.sh +# +# Переменные окружения для тонкой настройки: +# GO_VERSION — версия Go (по умолчанию 1.23.4) +# JDK_VERSION — версия Liberica JDK (по умолчанию 21) +# NODE_VERSION — версия Node (по умолчанию 20) +# DEV_USER — имя пользователя для разработки (по умолчанию dev) +# DEV_HOME — домашняя директория dev (по умолчанию /home/dev) +# WORKSPACE_ROOT — корень рабочих репо (по умолчанию /srv/dev) +# SKIP_USER — 1, чтобы не создавать пользователя +# SKIP_CLAUDE — 1, чтобы не ставить Claude Code CLI +# +# Скрипт идемпотентен — повторный запуск не ломает то, что уже сделано. + +set -euo pipefail + +# --------------------------------------------------------------------- # +# Параметры +# --------------------------------------------------------------------- # +GO_VERSION="${GO_VERSION:-1.23.4}" +JDK_VERSION="${JDK_VERSION:-21}" +NODE_VERSION="${NODE_VERSION:-20}" +DEV_USER="${DEV_USER:-dev}" +DEV_HOME="${DEV_HOME:-/home/${DEV_USER}}" +WORKSPACE_ROOT="${WORKSPACE_ROOT:-/srv/dev}" +SKIP_USER="${SKIP_USER:-0}" +SKIP_CLAUDE="${SKIP_CLAUDE:-0}" + +REPO_URL="https://git.zetit.ru/zuevav/Bridge-and-Join-s.git" + +# --------------------------------------------------------------------- # +# Хелперы +# --------------------------------------------------------------------- # +log() { printf '[%s] %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*"; } +die() { printf '[ОШИБКА] %s\n' "$*" >&2; exit 1; } +need_root() { [[ "$EUID" -eq 0 ]] || die "Нужны права root. Запусти через sudo."; } + +# --------------------------------------------------------------------- # +# 0. Предусловия — определение ОС и менеджера пакетов +# --------------------------------------------------------------------- # +need_root + +if [[ ! -f /etc/os-release ]]; then + die "Файл /etc/os-release не найден. Проверь, что ОС поддерживает FHS." +fi + +# shellcheck disable=SC1091 +. /etc/os-release + +OS_FAMILY="" # rpm | deb +OS_HUMAN="" + +case "${ID:-}" in + redos) + OS_FAMILY="rpm" + OS_HUMAN="РЕД ОС ${VERSION_ID:-?}" + ;; + rhel|centos|rocky|almalinux|fedora) + OS_FAMILY="rpm" + OS_HUMAN="${PRETTY_NAME:-RHEL-совместимая}" + log "ВНИМАНИЕ: ОС ${OS_HUMAN} — для разработки подходит, для прод-сертификации в финсекторе нужна РЕД ОС / Astra SE." + ;; + ubuntu|debian) + OS_FAMILY="deb" + OS_HUMAN="${PRETTY_NAME:-${ID} ${VERSION_ID:-?}}" + log "ОС ${OS_HUMAN} — бюджетный вариант для разработки. Для прода в финсекторе нужна РЕД ОС / Astra SE с сертификатом ФСТЭК." + ;; + astra) + OS_FAMILY="deb" + OS_HUMAN="Astra Linux ${VERSION_ID:-?}" + ;; + *) + log "ВНИМАНИЕ: ОС определена как ${ID:-неизвестно} ${VERSION_ID:-?}." + if command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then + OS_FAMILY="rpm" + elif command -v apt-get >/dev/null 2>&1; then + OS_FAMILY="deb" + else + die "Не удалось определить пакетный менеджер. Поддерживаются dnf/yum (РЕД ОС, RHEL) и apt (Ubuntu, Debian, Astra)." + fi + log "Пробуем как ${OS_FAMILY}-совместимую." + ;; +esac + +log "ОС: ${OS_HUMAN}, семейство: ${OS_FAMILY}" + +PKG_INSTALL="" +PKG_UPDATE="" +case "${OS_FAMILY}" in + rpm) + if command -v dnf >/dev/null 2>&1; then PKG_INSTALL="dnf install -y"; PKG_UPDATE="dnf -y makecache" + elif command -v yum >/dev/null 2>&1; then PKG_INSTALL="yum install -y"; PKG_UPDATE="yum -y makecache" + else die "Не найден dnf/yum" + fi + ;; + deb) + export DEBIAN_FRONTEND=noninteractive + PKG_INSTALL="apt-get install -y --no-install-recommends" + PKG_UPDATE="apt-get update -y" + ;; +esac + +log "Обновление кэша пакетов" +${PKG_UPDATE} + +# --------------------------------------------------------------------- # +# 1. Базовый системный стек +# --------------------------------------------------------------------- # +log "Шаг 1/8: установка базовых пакетов" +case "${OS_FAMILY}" in + rpm) + ${PKG_INSTALL} \ + git make curl wget tar gzip unzip ca-certificates \ + jq xmlstarlet libxml2 \ + gcc gcc-c++ binutils \ + python3 python3-pip \ + postgresql-libs \ + openssl-libs + ;; + deb) + ${PKG_INSTALL} \ + git make curl wget tar gzip unzip ca-certificates \ + jq xmlstarlet libxml2-utils \ + build-essential \ + python3 python3-pip python3-venv \ + libpq5 \ + libssl3 \ + gnupg lsb-release software-properties-common + ;; +esac + +# --------------------------------------------------------------------- # +# 2. Podman + podman-compose +# --------------------------------------------------------------------- # +log "Шаг 2/8: установка Podman и podman-compose" +case "${OS_FAMILY}" in + rpm) + ${PKG_INSTALL} podman podman-docker || true + ;; + deb) + ${PKG_INSTALL} podman || true + # podman-docker может отсутствовать — не критично + apt-get install -y --no-install-recommends podman-docker 2>/dev/null || true + ;; +esac + +if ! command -v podman-compose >/dev/null 2>&1; then + pip3 install --quiet podman-compose || \ + log "ВНИМАНИЕ: не удалось установить podman-compose через pip3. Поставь вручную." +fi + +# Включаем сокет podman, чтобы клиенты, ожидающие docker.sock, работали без правок +systemctl enable --now podman.socket 2>/dev/null || true + +# --------------------------------------------------------------------- # +# 3. Go +# --------------------------------------------------------------------- # +log "Шаг 3/8: установка Go ${GO_VERSION}" +GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz" +GO_URL="https://go.dev/dl/${GO_TARBALL}" + +if [[ -d /usr/local/go && -x /usr/local/go/bin/go ]]; then + CURRENT_GO_VER="$(/usr/local/go/bin/go version | awk '{print $3}' | sed 's/go//')" + if [[ "${CURRENT_GO_VER}" == "${GO_VERSION}" ]]; then + log "Go ${GO_VERSION} уже установлен" + else + log "Найден Go ${CURRENT_GO_VER}, переустанавливаем на ${GO_VERSION}" + rm -rf /usr/local/go + fi +fi + +if [[ ! -d /usr/local/go ]]; then + TMP_TAR="/tmp/${GO_TARBALL}" + wget -q --show-progress -O "${TMP_TAR}" "${GO_URL}" + tar -C /usr/local -xzf "${TMP_TAR}" + rm -f "${TMP_TAR}" +fi + +cat > /etc/profile.d/go.sh <<'EOF' +export PATH=$PATH:/usr/local/go/bin +export GOPATH=$HOME/go +export PATH=$PATH:$GOPATH/bin +EOF +chmod 0644 /etc/profile.d/go.sh + +# --------------------------------------------------------------------- # +# 4. Liberica JDK +# --------------------------------------------------------------------- # +log "Шаг 4/8: установка Liberica JDK ${JDK_VERSION}" +case "${OS_FAMILY}" in + rpm) + if [[ ! -f /etc/yum.repos.d/bellsoft.repo ]]; then + cat > /etc/yum.repos.d/bellsoft.repo <<'EOF' +[bellsoft] +name=BellSoft Repository +baseurl=https://yum.bell-sw.com/ +enabled=1 +gpgcheck=1 +gpgkey=https://download.bell-sw.com/pki/GPG-KEY-bellsoft +EOF + fi + ${PKG_INSTALL} "bellsoft-java${JDK_VERSION}" || \ + log "ВНИМАНИЕ: не удалось установить bellsoft-java${JDK_VERSION}. Альтернатива — скачать tar.gz с https://bell-sw.com/pages/downloads/." + ;; + deb) + if [[ ! -f /etc/apt/sources.list.d/bellsoft.list ]]; then + curl -fsSL https://download.bell-sw.com/pki/GPG-KEY-bellsoft \ + | gpg --dearmor -o /usr/share/keyrings/bellsoft.gpg + echo "deb [signed-by=/usr/share/keyrings/bellsoft.gpg] https://apt.bell-sw.com/ stable main" \ + > /etc/apt/sources.list.d/bellsoft.list + apt-get update -y + fi + ${PKG_INSTALL} "bellsoft-java${JDK_VERSION}-full" || \ + ${PKG_INSTALL} "bellsoft-java${JDK_VERSION}" || \ + log "ВНИМАНИЕ: не удалось установить bellsoft-java${JDK_VERSION}. Альтернатива — скачать tar.gz с https://bell-sw.com/pages/downloads/." + ;; +esac + +# --------------------------------------------------------------------- # +# 5. Node.js LTS (для admin-ui и Claude Code CLI) +# --------------------------------------------------------------------- # +log "Шаг 5/8: установка Node.js ${NODE_VERSION} LTS" +need_install_node=1 +if command -v node >/dev/null 2>&1; then + CUR_NODE_MAJOR="$(node --version | sed 's/v//;s/\..*//')" + if [[ "${CUR_NODE_MAJOR}" == "${NODE_VERSION}" ]]; then + log "Node.js ${NODE_VERSION} уже установлен" + need_install_node=0 + fi +fi + +if [[ "${need_install_node}" == "1" ]]; then + case "${OS_FAMILY}" in + rpm) + curl -fsSL "https://rpm.nodesource.com/setup_${NODE_VERSION}.x" | bash - + ${PKG_INSTALL} nodejs + ;; + deb) + curl -fsSL "https://deb.nodesource.com/setup_${NODE_VERSION}.x" | bash - + ${PKG_INSTALL} nodejs + ;; + esac +fi + +# --------------------------------------------------------------------- # +# 6. Claude Code CLI +# --------------------------------------------------------------------- # +if [[ "${SKIP_CLAUDE}" != "1" ]]; then + log "Шаг 6/8: установка Claude Code CLI" + npm install -g @anthropic-ai/claude-code +else + log "Шаг 6/8: пропуск установки Claude Code CLI (SKIP_CLAUDE=1)" +fi + +# --------------------------------------------------------------------- # +# 7. Пользователь dev и рабочая директория +# --------------------------------------------------------------------- # +if [[ "${SKIP_USER}" != "1" ]]; then + log "Шаг 7/8: подготовка пользователя ${DEV_USER}" + if ! id -u "${DEV_USER}" >/dev/null 2>&1; then + useradd -m -s /bin/bash -d "${DEV_HOME}" "${DEV_USER}" + log "Создан пользователь ${DEV_USER}" + else + log "Пользователь ${DEV_USER} уже существует" + fi + + mkdir -p "${WORKSPACE_ROOT}" + chown "${DEV_USER}:${DEV_USER}" "${WORKSPACE_ROOT}" + + HIST_SNIPPET="${DEV_HOME}/.bashrc.d-history" + cat > "${HIST_SNIPPET}" <<'EOF' +# История с timestamp и общая для всех сессий — для аудита. +export HISTTIMEFORMAT="%F %T " +export HISTSIZE=10000 +export HISTFILESIZE=20000 +shopt -s histappend +PROMPT_COMMAND="history -a; ${PROMPT_COMMAND:-}" +EOF + chown "${DEV_USER}:${DEV_USER}" "${HIST_SNIPPET}" + + if ! grep -q "bashrc.d-history" "${DEV_HOME}/.bashrc" 2>/dev/null; then + echo "[ -f ${HIST_SNIPPET} ] && . ${HIST_SNIPPET}" >> "${DEV_HOME}/.bashrc" + fi + + if ! grep -q "/usr/local/go/bin" "${DEV_HOME}/.bashrc" 2>/dev/null; then + cat >> "${DEV_HOME}/.bashrc" <<'EOF' +[ -f /etc/profile.d/go.sh ] && . /etc/profile.d/go.sh +EOF + fi +else + log "Шаг 7/8: пропуск подготовки пользователя (SKIP_USER=1)" +fi + +# --------------------------------------------------------------------- # +# 8. Финальные проверки +# --------------------------------------------------------------------- # +log "Шаг 8/8: проверка установленных версий" +{ + printf 'git: '; git --version + printf 'make: '; make --version | head -n1 + printf 'jq: '; jq --version + printf 'xmlstarlet: '; xmlstarlet --version | head -n1 + printf 'podman: '; podman --version 2>/dev/null || echo "(не установлен)" + printf 'podman-compose:'; podman-compose --version 2>/dev/null || echo "(не установлен)" + printf 'go: '; /usr/local/go/bin/go version + printf 'java: '; java -version 2>&1 | head -n1 + printf 'node: '; node --version 2>/dev/null || echo "(не установлен)" + printf 'npm: '; npm --version 2>/dev/null || echo "(не установлен)" + if [[ "${SKIP_CLAUDE}" != "1" ]]; then + printf 'claude: '; claude --version 2>/dev/null || echo "(не установлен или требует первого запуска)" + fi +} || true + +cat <