feat: bind schema verification to release manifest

This commit is contained in:
2026-09-20 21:23:30 +08:00
parent cebb32138c
commit 30a8081bd9
7 changed files with 95 additions and 6 deletions

View File

@@ -6,8 +6,10 @@ import (
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"time"
"git.yuntuo.hk/k2safe/orbit/internal/buildinfo"
@@ -69,9 +71,17 @@ func runRelease(args []string) error {
if len(args) != 1 || args[0] != "manifest" {
return errors.New("usage: orbit-admin release manifest")
}
manifest, err := currentReleaseManifest()
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(manifest)
}
func currentReleaseManifest() (releaseManifest, error) {
migrations, err := mysql.Migrations()
if err != nil || len(migrations) == 0 {
return errors.New("release migration manifest unavailable")
return releaseManifest{}, errors.New("release migration manifest unavailable")
}
items := make([]releaseMigrationManifest, 0, len(migrations))
for _, migration := range migrations {
@@ -81,7 +91,7 @@ func runRelease(args []string) error {
Checksum: migration.Checksum,
})
}
manifest := releaseManifest{
return releaseManifest{
ManifestVersion: 1,
Build: buildinfo.Current(),
Schema: releaseSchemaManifest{
@@ -89,18 +99,52 @@ func runRelease(args []string) error {
MigrationCount: len(items),
Migrations: items,
},
}, nil
}
// validateReleaseManifest 确认外部保存的发布清单与当前管理二进制完全一致。
// 它只比较静态身份,不连接数据库,也不改变任何状态。
func validateReleaseManifest(path string) error {
file, err := os.Open(path)
if err != nil {
return errors.New("release manifest could not be opened")
}
return json.NewEncoder(os.Stdout).Encode(manifest)
defer file.Close()
decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields()
var got releaseManifest
if err := decoder.Decode(&got); err != nil {
return errors.New("release manifest is invalid")
}
var trailing any
if err := decoder.Decode(&trailing); err != io.EOF {
return errors.New("release manifest contains trailing data")
}
want, err := currentReleaseManifest()
if err != nil {
return err
}
if !reflect.DeepEqual(got, want) {
return errors.New("release manifest does not match this binary")
}
return nil
}
func runVerify(args []string) error {
flags := flag.NewFlagSet("verify", flag.ContinueOnError)
var manifestPath string
flags.StringVar(&manifestPath, "manifest", "", "saved release manifest to compare with this binary")
if err := flags.Parse(args); err != nil {
return err
}
if flags.NArg() != 0 {
return errors.New("verify does not accept positional arguments")
}
if manifestPath != "" {
if err := validateReleaseManifest(manifestPath); err != nil {
return err
}
}
cfg, err := config.Load()
if err != nil {
return err