9737c787f9
Инфраструктура 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>
170 lines
5.4 KiB
Go
170 lines
5.4 KiB
Go
// Command bj-release — инструмент издателя: генерация ключей подписи,
|
|
// сборка манифеста релиза из каталога артефактов и его подпись Ed25519.
|
|
//
|
|
// Использование:
|
|
//
|
|
// bj-release keygen -out ./keys/signing
|
|
// → создаёт signing.priv (base64 seed) и signing.pub (base64 pubkey)
|
|
//
|
|
// bj-release build -dir ./dist -version 1.2.0 -channel stable \
|
|
// -key ./keys/signing.priv -keyid main -out ./dist/manifest.json
|
|
// → хеширует все файлы в ./dist, собирает Manifest, подписывает,
|
|
// пишет SignedManifest в manifest.json
|
|
//
|
|
// Манифест подписывается целиком; клиент (bj-server auto-update) проверяет
|
|
// подпись зашитым публичным ключом ДО доверия версиям/хешам.
|
|
package main
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/release"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
}
|
|
switch os.Args[1] {
|
|
case "keygen":
|
|
keygen(os.Args[2:])
|
|
case "build":
|
|
build(os.Args[2:])
|
|
default:
|
|
usage()
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "bj-release keygen -out <prefix>")
|
|
fmt.Fprintln(os.Stderr, "bj-release build -dir <artifacts> -version <v> -channel <c> -key <priv> -keyid <id> -out <manifest.json> [-notes <txt>]")
|
|
os.Exit(2)
|
|
}
|
|
|
|
func keygen(args []string) {
|
|
fs := flag.NewFlagSet("keygen", flag.ExitOnError)
|
|
out := fs.String("out", "signing", "префикс файлов ключей (создаст <out>.priv и <out>.pub)")
|
|
_ = fs.Parse(args)
|
|
|
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
fatal("keygen: %v", err)
|
|
}
|
|
seed := priv.Seed()
|
|
if err := os.WriteFile(*out+".priv", []byte(base64.StdEncoding.EncodeToString(seed)+"\n"), 0o600); err != nil {
|
|
fatal("write priv: %v", err)
|
|
}
|
|
if err := os.WriteFile(*out+".pub", []byte(base64.StdEncoding.EncodeToString(pub)+"\n"), 0o644); err != nil {
|
|
fatal("write pub: %v", err)
|
|
}
|
|
fmt.Printf("Приватный ключ: %s.priv (НЕ КОММИТИТЬ, держать в секрете)\n", *out)
|
|
fmt.Printf("Публичный ключ: %s.pub\n", *out)
|
|
fmt.Printf("Публичный ключ (зашить в bj-server):\n %s\n", base64.StdEncoding.EncodeToString(pub))
|
|
}
|
|
|
|
func build(args []string) {
|
|
fs := flag.NewFlagSet("build", flag.ExitOnError)
|
|
dir := fs.String("dir", "./dist", "каталог с артефактами")
|
|
version := fs.String("version", "", "версия релиза, напр. 1.2.0")
|
|
channel := fs.String("channel", "stable", "канал: stable|beta")
|
|
keyPath := fs.String("key", "", "путь к приватному ключу (base64 seed)")
|
|
keyID := fs.String("keyid", "main", "идентификатор ключа")
|
|
out := fs.String("out", "", "путь для записи manifest.json (по умолчанию <dir>/manifest.json)")
|
|
notes := fs.String("notes", "", "заметки к релизу")
|
|
_ = fs.Parse(args)
|
|
|
|
if *version == "" || *keyPath == "" {
|
|
fatal("build: требуются -version и -key")
|
|
}
|
|
if *out == "" {
|
|
*out = filepath.Join(*dir, "manifest.json")
|
|
}
|
|
|
|
priv, err := release.LoadPrivateKey(*keyPath)
|
|
if err != nil {
|
|
fatal("load key: %v", err)
|
|
}
|
|
|
|
// Имена артефактов, которые издаём (логическое имя → ставить +x).
|
|
known := map[string]bool{
|
|
"bj-server": true, // Go-бинарь
|
|
"crypto-service.jar": false, // Java сайдкар
|
|
"install-validata.sh": true,
|
|
"install.sh": true,
|
|
"configure-ish.sql": false,
|
|
}
|
|
|
|
entries, err := os.ReadDir(*dir)
|
|
if err != nil {
|
|
fatal("read dir: %v", err)
|
|
}
|
|
var arts []release.Artifact
|
|
for _, e := range entries {
|
|
if e.IsDir() || e.Name() == "manifest.json" {
|
|
continue
|
|
}
|
|
full := filepath.Join(*dir, e.Name())
|
|
sha, size, err := release.HashFile(full)
|
|
if err != nil {
|
|
fatal("hash %s: %v", e.Name(), err)
|
|
}
|
|
exec, ok := known[e.Name()]
|
|
if !ok {
|
|
// неизвестный файл — включаем, +x по расширению
|
|
exec = strings.HasSuffix(e.Name(), ".sh")
|
|
}
|
|
arts = append(arts, release.Artifact{
|
|
Name: e.Name(),
|
|
File: e.Name(),
|
|
Version: *version,
|
|
SHA256: sha,
|
|
Size: size,
|
|
Exec: exec,
|
|
})
|
|
}
|
|
sort.Slice(arts, func(i, j int) bool { return arts[i].Name < arts[j].Name })
|
|
if len(arts) == 0 {
|
|
fatal("build: в каталоге %s нет артефактов", *dir)
|
|
}
|
|
|
|
m := &release.Manifest{
|
|
Schema: release.CurrentSchema,
|
|
Version: *version,
|
|
Channel: *channel,
|
|
ReleasedAt: time.Now().UTC(),
|
|
Notes: *notes,
|
|
Artifacts: arts,
|
|
}
|
|
sm, err := release.Sign(m, priv, *keyID)
|
|
if err != nil {
|
|
fatal("sign: %v", err)
|
|
}
|
|
b, err := json.MarshalIndent(sm, "", " ")
|
|
if err != nil {
|
|
fatal("marshal: %v", err)
|
|
}
|
|
if err := os.WriteFile(*out, b, 0o644); err != nil {
|
|
fatal("write manifest: %v", err)
|
|
}
|
|
fmt.Printf("Манифест %s: версия %s, канал %s, артефактов %d, подписан ключом %s\n",
|
|
*out, *version, *channel, len(arts), *keyID)
|
|
for _, a := range arts {
|
|
fmt.Printf(" %-22s %10d B %s\n", a.Name, a.Size, a.SHA256[:16])
|
|
}
|
|
}
|
|
|
|
func fatal(format string, a ...any) {
|
|
fmt.Fprintf(os.Stderr, "bj-release: "+format+"\n", a...)
|
|
os.Exit(1)
|
|
}
|