package backup import ( "archive/zip" "encoding/json" "fmt" "io" "time" ) const ( // FormatVersion / FormatName identify the backup archive format. They are // byte-identical to the cms constants (backupFormatVersion / backupFormatName) // so archives interoperate with the existing cms restore path. FormatVersion = 3 FormatName = "blockninja-pgdump-backup" ) // Source identifies where a backup came from. Field-for-field identical to the // cms BackupSource JSON shape. type Source struct { SiteTitle string `json:"site_title"` Domain string `json:"domain,omitempty"` } // Manifest is the metadata stored in manifest.json. It is a field-for-field copy // of the cms BackupManifest so archives built here restore through the existing // cms path unchanged. type Manifest struct { Version int `json:"version"` Format string `json:"format"` ExportedAt time.Time `json:"exported_at"` Source Source `json:"source"` DatabaseSize int64 `json:"database_size"` MediaCount int `json:"media_count"` BrandCount int `json:"brand_count,omitempty"` IconsCount int `json:"icons_count,omitempty"` StylesCount int `json:"styles_count,omitempty"` PluginsCount int `json:"plugins_count,omitempty"` Encryption *EncryptionParams `json:"encryption,omitempty"` Checksum string `json:"checksum"` // SHA-256 of unencrypted database dump } // ReadManifest opens a backup zip archive and parses its manifest.json entry. func ReadManifest(r io.ReaderAt, size int64) (*Manifest, error) { zr, err := zip.NewReader(r, size) if err != nil { return nil, fmt.Errorf("failed to open backup archive: %w", err) } for _, f := range zr.File { if f.Name != "manifest.json" { continue } rc, err := f.Open() if err != nil { return nil, fmt.Errorf("failed to open manifest.json: %w", err) } defer func() { _ = rc.Close() }() data, err := io.ReadAll(rc) if err != nil { return nil, fmt.Errorf("failed to read manifest.json: %w", err) } var m Manifest if err := json.Unmarshal(data, &m); err != nil { return nil, fmt.Errorf("failed to parse manifest.json: %w", err) } return &m, nil } return nil, fmt.Errorf("manifest.json not found in backup archive") }