Documentation
¶
Overview ¶
Package backup is a snapshot engine for applications whose state consists of metadata plus a tree of content-addressed files. Metadata may be captured either as incremental SQLite page maps or as an application-defined portable stream that the application rebuilds into its current runtime database on restore.
Create pins the database in a frozen read transaction, captures changed SQLite pages and any new content files into content-addressed packs (kit/pack), and writes a manifest chained to the previous snapshot. Restore and Verify reconstruct and validate snapshots from the repository. Plain content capture, verification, and loose restore use bounded-memory streams; format-v1 encrypted entries retain their whole-entry authentication contract and are not exposed as verified-prefix streams.
Existing Create, Verify, and Restore callers receive the plain-content streaming implementation without changing repository format or application adapters. Repo.OpenBlob is available to sequential consumers; bytes from it become authoritative only after terminal EOF or a successful Verify, and an early Close reports incomplete verification. Capture preparation trades object-sized heap for private repository scratch, so callers must provision scratch capacity for the configured capture concurrency.
The engine is application-neutral: everything specific to a given application (its database filename, content directory, referenced-file enumeration, portable metadata format, and the opaque stats payload recorded per snapshot) is supplied through application interfaces. The engine records stats bytes at create and byte-compares them at restore without interpreting them.
On-disk formats are versioned by the FormatVersion and MinReaderVersion constants and the per-snapshot manifest fields; readers refuse snapshots that require a newer reader.
Index ¶
- Constants
- Variables
- func BuildBlobContent(r io.ReaderAt, pageSize uint32, plan BlobPlan) ([]byte, error)
- func CaptureExtras(ctx context.Context, opts ExtrasOptions, appender *PackAppender) (pack.BlobID, bool, error)
- func ComputeSnapshotID(createdAt time.Time, m *Manifest) (string, error)
- func EncodeAttachmentList(refs []ContentRef) ([]byte, error)
- func EncodeHashDelta(d *PageHashDelta) []byte
- func EncodeHashKeyframe(m *PageHashMap) []byte
- func EncodeIndex(entries []IndexEntry) ([]byte, error)
- func EncodePageMap(m *PageMap, delta bool) []byte
- func PageHash(page []byte) [pageHashSize]byte
- func SaveHashMapCache(cacheDir, repoID, snapshotID string, m *PageHashMap) error
- type App
- type AttachmentCapture
- type AuxiliaryArtifact
- type AuxiliaryRestore
- type AuxiliarySource
- type AuxiliaryTarget
- type BlobPlan
- type BlobStream
- type CaptureOptions
- type ContentInfo
- type ContentRef
- type ContentSource
- type CreateOptions
- type DefaultSQLiteOpener
- type ExtrasDirSpec
- type ExtrasEntry
- type ExtrasFileSpec
- type ExtrasGlobSpec
- type ExtrasOptions
- type ExtrasSpec
- type ExtrasTree
- type FreezeCoordinator
- type FrozenSession
- type FrozenView
- type IndexEntry
- type LockInfo
- type Manifest
- type ManifestAttachments
- type ManifestAuxiliary
- type ManifestDB
- type ManifestExtras
- type ManifestMetadata
- type ManifestOptions
- type MetadataRestorer
- type MetadataSnapshot
- type MetadataSource
- type NoopFreezeCoordinator
- type PackAppender
- func (a *PackAppender) Abort()
- func (a *PackAppender) Add(raw []byte) (pack.BlobID, bool, error)
- func (a *PackAppender) AddEncoded(id pack.BlobID, frame []byte, rawLen uint64, compressed bool) (bool, error)
- func (a *PackAppender) AddPrepared(ctx context.Context, prepared *pack.PreparedBlob) (bool, error)
- func (a *PackAppender) Finish() ([]string, []IndexEntry, error)
- type PackedContentTarget
- type PageHashDelta
- type PageHashMap
- func ApplyHashDelta(base *PageHashMap, d *PageHashDelta) (*PageHashMap, error)
- func DecodeHashKeyframe(data []byte) (*PageHashMap, error)
- func LoadHashMapCache(cacheDir, repoID string) (string, *PageHashMap, error)
- func MaterializeHashMap(fetch func(pack.BlobID) ([]byte, error), chain []pack.BlobID) (*PageHashMap, error)
- type PageMap
- type PageRange
- type PageRun
- type Problem
- type ProgressEvent
- type ProgressStage
- type Repo
- func (r *Repo) AcquireExclusiveLock(operation string, force bool) (*RepoLock, error)
- func (r *Repo) AcquireSharedLock(operation string, force bool) (*RepoLock, error)
- func (r *Repo) CleanStaging() error
- func (r *Repo) Config() RepoConfig
- func (r *Repo) HashMapChain(head *Manifest) ([]pack.BlobID, error)
- func (r *Repo) LatestSnapshot() (*Manifest, error)
- func (r *Repo) ListSnapshots() ([]*Manifest, error)
- func (r *Repo) LoadBlobIndex() (map[pack.BlobID]IndexEntry, error)
- func (r *Repo) LoadManifest(id string) (*Manifest, error)
- func (r *Repo) OpenBlob(ctx context.Context, known map[pack.BlobID]IndexEntry, id pack.BlobID, ...) (*BlobStream, error)
- func (r *Repo) PageMapChain(head *Manifest) ([]pack.BlobID, error)
- func (r *Repo) Path(parts ...string) string
- func (r *Repo) ReadBlob(known map[pack.BlobID]IndexEntry, id pack.BlobID, crypter *pack.Crypter, ...) ([]byte, error)
- func (r *Repo) Root() string
- func (r *Repo) SetPageSize(pageSize int) error
- func (r *Repo) WriteIndex(entries []IndexEntry) (string, error)
- func (r *Repo) WriteManifest(m *Manifest) (string, error)
- type RepoConfig
- type RepoLock
- type RestoreOptions
- type RestorePublicationTarget
- type RestoreResult
- type RestoreTargetCoordinator
- type RestoreTargetLease
- type RestoredAuxiliary
- type SQLiteAccess
- type SQLiteOpenFunc
- type SQLiteOpenOptions
- type SQLiteOpener
- type ScanResult
- type VerifyOptions
- type VerifyResult
Constants ¶
const ( // FormatVersion is the backup repository format version this code writes. FormatVersion = 1 // MinReaderVersion is the oldest reader able to read repos this code // writes. MinReaderVersion = 1 // SupportedReaderVersion is the newest format this code can read. It is // deliberately distinct from FormatVersion ("what we write"): a future // release may read formats newer than the one it writes, or vice versa. // Repo.Open and LoadManifest refuse anything whose min_reader_version // exceeds this. SupportedReaderVersion = 4 )
Variables ¶
var ErrRepoLocked = errors.New("backup: repository is locked")
ErrRepoLocked reports that another operation holds a conflicting repo lock.
Functions ¶
func BuildBlobContent ¶
BuildBlobContent reads the plan's pages in order into one blob.
func CaptureExtras ¶
func CaptureExtras(ctx context.Context, opts ExtrasOptions, appender *PackAppender) (pack.BlobID, bool, error)
CaptureExtras stores extras file blobs and the tree object. ctx is checked before each file read, so a canceled backup stops within one file instead of walking and reading every remaining extras source.
func ComputeSnapshotID ¶
ComputeSnapshotID derives the time-ordered, content-derived snapshot ID (FORMAT.md): UTC timestamp plus the first 32 hex chars (128 bits) of the SHA-256 of the manifest JSON with snapshot_id blanked. The digest must be long enough that crafting a different manifest with the same ID is infeasible: LoadManifest's recompute check is what stops a forged manifest from being served under a known snapshot ID, and a short, brute-forceable suffix would defeat it.
func EncodeAttachmentList ¶
func EncodeAttachmentList(refs []ContentRef) ([]byte, error)
EncodeAttachmentList serializes refs in their given (first-seen) order.
func EncodeHashDelta ¶
func EncodeHashDelta(d *PageHashDelta) []byte
EncodeHashDelta serializes a hash-map delta.
func EncodeHashKeyframe ¶
func EncodeHashKeyframe(m *PageHashMap) []byte
EncodeHashKeyframe serializes a full hash map.
func EncodeIndex ¶
func EncodeIndex(entries []IndexEntry) ([]byte, error)
EncodeIndex serializes entries sorted ascending by blob ID with a SHA-256 integrity trailer.
func EncodePageMap ¶
EncodePageMap serializes a page map as a keyframe or delta object.
func SaveHashMapCache ¶
func SaveHashMapCache(cacheDir, repoID, snapshotID string, m *PageHashMap) error
SaveHashMapCache atomically replaces the local hash-map cache. repoID must be a canonical generated repository ID: it becomes a filename under cacheDir, so any other value could write outside the cache. An empty cacheDir means the cache is disabled — the convention loading already uses — so saving is an explicit successful no-op rather than an accident of empty-path handling (MkdirAll("") happens to fail).
Types ¶
type App ¶
type App interface {
FrozenView(s *FrozenSession) FrozenView
DBFileName() string // e.g. "app.db"
ContentDirName() string // e.g. "content"
// PackFileExtension returns the file extension for pack files, including
// the leading dot (e.g. ".kpack"). Like the other layout names it must
// remain fixed for the life of a repository: packs are located by
// <packID><ext>, so changing it strands previously written packs.
PackFileExtension() string
// RestoredContentPaths re-derives hash → relative paths from a restored
// DB so restore can materialize and verify every referenced file. Returned
// paths must be relative and local to the content directory (no absolute
// paths, no ".." escapes); the engine also rejects any non-local path at
// restore time.
RestoredContentPaths(ctx context.Context, db *sql.DB) (map[string][]string, error)
// RestoredStats recomputes stats from a restored DB for the fidelity proof.
RestoredStats(ctx context.Context, db *sql.DB) (json.RawMessage, error)
// CheckManifest returns app-level manifest consistency problems (verify).
CheckManifest(m *Manifest) []string
ExcludedPaths() []string
Version() string // recorded as the manifest's app version
}
App supplies every application-specific behavior the engine needs. The engine treats stats payloads as opaque bytes: it records them at create and byte-compares them at restore.
type AttachmentCapture ¶
type AttachmentCapture struct {
NewList []ContentRef
NewListBlob pack.BlobID
HasNewList bool
Blobs int64
BlobBytes int64
}
AttachmentCapture reports one snapshot's attachment capture results.
func CaptureAttachments ¶
func CaptureAttachments( ctx context.Context, attachmentsDir string, refs []ContentRef, parentSeen map[string]bool, appender *PackAppender, opts CaptureOptions, ) (*AttachmentCapture, error)
CaptureAttachments stores every referenced attachment content blob, re-hashing each file as it goes (FORMAT.md, Attachment Lists: backup verifies the live store). Refs not present in parentSeen become the snapshot's new list segment.
Reading, hashing, and trial compression fan out to opts.Jobs workers; results are recorded in ref order by a single collector feeding the appender, so pack contents, list order, accounting, and progress reporting match a serial capture exactly. Blobs already stored in the repository are detected before compression and skip it entirely, keeping the no-change incremental case cheap.
attachmentsDir is ignored entirely when opts.Source is non-nil; content is read through the source instead.
type AuxiliaryArtifact ¶ added in v0.17.0
type AuxiliaryArtifact struct {
Name string
Format string
Open func(context.Context) (io.ReadCloser, int64, error)
}
AuxiliaryArtifact is one application-defined, immutable snapshot artifact. Open must return the same bytes and exact size for the lifetime of its originating snapshot.
type AuxiliaryRestore ¶ added in v0.17.0
AuxiliaryRestore is one staged auxiliary-state replacement. Commit runs only after Kit has published and durably synced the restored target. Rollback must discard all staged work and clean up any partial effects from a failed Commit. Kit calls Commit at most once and, if Commit does not succeed, Rollback exactly once with an independently bounded context.
type AuxiliarySource ¶ added in v0.17.0
type AuxiliarySource interface {
AuxiliaryArtifacts(context.Context) ([]AuxiliaryArtifact, error)
}
AuxiliarySource optionally extends a pinned MetadataSnapshot or FrozenView with application-neutral auxiliary artifacts captured under the same preservation boundary.
type AuxiliaryTarget ¶ added in v0.17.0
type AuxiliaryTarget interface {
StageAuxiliary(context.Context, []RestoredAuxiliary) (AuxiliaryRestore, error)
}
AuxiliaryTarget stages verified application-defined artifacts without making them externally visible. A StageAuxiliary error must leave no work for Kit to clean up. Kit never interprets artifact payloads.
type BlobPlan ¶
type BlobPlan struct {
Ranges []PageRange
}
BlobPlan is one storage blob: either a single large run or a group of scattered small ranges concatenated in page order.
type BlobStream ¶ added in v0.8.0
type BlobStream struct {
// contains filtered or unexported fields
}
BlobStream reads one repository blob without buffering the complete raw content. Successful terminal EOF or Verify proves the blob's stored CRC, decoded length, and content identity. Close before verification returns pack.ErrVerificationIncomplete.
func (*BlobStream) Close ¶ added in v0.8.0
func (s *BlobStream) Close() error
Close releases the blob stream and its pack descriptor.
func (*BlobStream) Read ¶ added in v0.8.0
func (s *BlobStream) Read(p []byte) (int, error)
Read implements io.Reader.
func (*BlobStream) Size ¶ added in v0.8.0
func (s *BlobStream) Size() int64
Size returns the authoritative decoded length from the pack footer.
func (*BlobStream) Verified ¶ added in v0.8.0
func (s *BlobStream) Verified() bool
Verified reports whether terminal verification succeeded.
func (*BlobStream) Verify ¶ added in v0.8.0
func (s *BlobStream) Verify() error
Verify consumes the stream and verifies its terminal integrity.
type CaptureOptions ¶
type CaptureOptions struct {
// Jobs is the number of concurrent read+hash+compress workers. Zero or
// negative selects one per CPU. Use 1 for strictly serial file reads —
// the right choice when the live archive sits on a spinning disk or NAS
// share that degrades under concurrent reads.
Jobs int
// Progress, if non-nil, is called after each file is captured with the
// number of files done so far, the total file count, and the cumulative
// bytes read; it does not otherwise affect capture behavior.
Progress func(done, total int, bytesRead int64)
// Source, when non-nil, supplies attachment bytes instead of the engine
// reading them from the attachments directory; the directory is then
// ignored entirely. Reads are still hash-verified and size-capped.
Source ContentSource
}
CaptureOptions tunes CaptureAttachments.
type ContentInfo ¶
type ContentInfo struct {
Refs []ContentRef // one per unique hash, first-seen order
Rows int64 // DB rows referencing content (manifest attachments.rows)
// NonCanonicalPaths reports any ref recorded at a path other than the
// canonical "<hash[:2]>/<hash>" layout; such snapshots require a
// path-aware restore and a higher manifest reader version.
NonCanonicalPaths bool
}
ContentInfo is what the engine needs to know about the application's content-addressed files, computed inside the frozen snapshot.
type ContentRef ¶
ContentRef identifies one attachment (or thumbnail) content blob by its SHA-256 and size. Size -1 means unknown until read from disk.
StoragePath is the blob's location relative to the attachments directory as recorded in the archive database; importers may namespace it (for example synctech-sms writes "synctech-sms/<aa>/<hash>"). Empty means the canonical loose layout "<aa>/<hash>". It is capture-time routing only and is not serialized into attachment list segments, which carry hash and size.
func DecodeAttachmentList ¶
func DecodeAttachmentList(data []byte) ([]ContentRef, error)
DecodeAttachmentList parses and integrity-checks a list segment.
func LoadListRefs ¶
func LoadListRefs(r *Repo, known map[pack.BlobID]IndexEntry, listBlobIDs []string, crypter *pack.Crypter, ext string) ([]ContentRef, map[string]bool, error)
LoadListRefs fetches and decodes a manifest's attachment list blobs. ext is the pack file extension (App.PackFileExtension).
type ContentSource ¶ added in v0.4.0
type ContentSource interface {
Open(ctx context.Context, ref ContentRef) (io.ReadCloser, error)
}
ContentSource supplies attachment content bytes during capture, replacing the engine's own reads of the attachments directory. Implementations resolve a ref however the application stores content (loose files, pack files, object stores); the engine still verifies every blob's SHA-256 against ref.Hash and enforces the per-blob size cap, so a source cannot weaken capture integrity. Open is called from concurrent capture workers and must be safe for concurrent use; it should honor ctx and return promptly once ctx is done, or a cancelled capture blocks until every in-flight Open returns. Capture uses ref.Size to pace concurrent work and scratch use; a declared size that understates the payload weakens that admission policy (never integrity), so sources should report actual sizes.
type CreateOptions ¶
type CreateOptions struct {
DBPath string
ContentDir string
// SQLiteOpener selects the SQLite implementation used for page-map
// capture. Nil preserves Kit's mattn/go-sqlite3 default.
SQLiteOpener SQLiteOpener
// MetadataSource selects portable application metadata instead of SQLite
// page capture. DBPath is ignored in this mode.
MetadataSource MetadataSource
// ContentSource, when non-nil, supplies attachment content bytes during
// capture instead of the engine reading ContentDir; ContentDir is then
// ignored for content reads (it may be empty). Extras and the page scan
// are unaffected. See ContentSource.
ContentSource ContentSource
// DataDir anchors the Extras spec's directory walks and glob matches.
DataDir string
// Extras selects the operational files that ride along with the
// snapshot. The engine imposes no default set; the application supplies
// an explicit spec (ExtrasSpec).
Extras ExtrasSpec
AllowPlaintextSecrets bool
// IncludeConfig/IncludeTokens are recorded verbatim into the manifest's
// options (wire-frozen fields, FORMAT.md); they select nothing by
// themselves — the Extras spec does.
IncludeConfig bool
IncludeTokens bool
Tag string
ZstdLevel int
CacheDir string
Freezer FreezeCoordinator
ForceUnlock bool
// Jobs is the number of concurrent attachment read+compress workers.
// Zero or negative selects one per CPU. Use 1 for strictly serial file
// reads when the live archive sits on a spinning disk or NAS share. The
// page scan is unaffected: its disk reads are sequential at any setting.
Jobs int
// Progress, if non-nil, receives structured progress events as Create
// runs. nil means fully silent. Create emits events freely and cheaply;
// throttling for display is a rendering concern of the callback, not
// Create's.
Progress func(ProgressEvent)
}
CreateOptions parameterizes one snapshot capture.
type DefaultSQLiteOpener ¶ added in v0.10.0
type DefaultSQLiteOpener struct{}
DefaultSQLiteOpener uses mattn/go-sqlite3 and preserves Kit's historical connection behavior.
func (DefaultSQLiteOpener) OpenSQLite ¶ added in v0.10.0
func (DefaultSQLiteOpener) OpenSQLite(path string, opts SQLiteOpenOptions) (*sql.DB, error)
OpenSQLite implements SQLiteOpener.
type ExtrasDirSpec ¶
type ExtrasDirSpec struct {
Name string
// Sensitive marks the directory as carrying secrets (see ExtrasSpec).
Sensitive bool
}
ExtrasDirSpec walks one DataDir-relative directory recursively; every regular file found is recorded under its DataDir-relative path. A missing directory is skipped, not an error: operational directories often appear only once the application has something to put in them.
type ExtrasEntry ¶
type ExtrasEntry struct {
Path string `json:"path"`
Mode uint32 `json:"mode"`
Size int64 `json:"size"`
Blob string `json:"blob"`
}
ExtrasEntry is one captured file in the extras tree.
type ExtrasFileSpec ¶
ExtrasFileSpec captures one file, which may live outside DataDir, recorded in the tree under RecordAs (a slash-separated relative path). Unlike dir walks, a missing file is an error: naming a specific file is an explicit request that must not silently produce a snapshot without it.
type ExtrasGlobSpec ¶
ExtrasGlobSpec matches file basenames at DataDir's top level. The pattern must be a pure basename (no separators); matching happens against directory entries, so a DataDir path containing glob metacharacters cannot corrupt it.
type ExtrasOptions ¶
type ExtrasOptions struct {
// DataDir anchors the spec's Dirs walks and Globs matches. Empty means
// no directory- or glob-based extras are captured.
DataDir string
Spec ExtrasSpec
AllowPlaintextSecrets bool
Encrypted bool
// ContentDirName/DBFileName name the restore-layout paths an extras
// record path may never claim — the database, its SQLite sidecars, and
// the attachments tree — so capture refuses them with the same rules
// restore enforces (validateExtrasEntryPath) instead of publishing a
// snapshot restore and verify then reject. Create fills them from the
// App; a caller leaving them empty skips only the reserved-name rule
// (locality and Windows-safe component rules always apply).
ContentDirName string
DBFileName string
}
ExtrasOptions parameterizes one extras capture (docs/usage/backup.md, Extras).
type ExtrasSpec ¶
type ExtrasSpec struct {
Dirs []ExtrasDirSpec
Globs []ExtrasGlobSpec
Files []ExtrasFileSpec
}
ExtrasSpec declares which operational files ride along with a snapshot. The engine imposes no default set: the application decides what its snapshots carry. Sources marked Sensitive are refused on an unencrypted repository unless the caller sets AllowPlaintextSecrets, so secrets never land in plaintext packs by accident.
type ExtrasTree ¶
type ExtrasTree struct {
Entries []ExtrasEntry `json:"entries"`
}
ExtrasTree is the small JSON tree object referenced by the manifest.
type FreezeCoordinator ¶
type FreezeCoordinator interface {
Begin(ctx context.Context) error
End(ctx context.Context) error
}
FreezeCoordinator brackets the freeze window: Begin drains and holds the daemon's operation gate, End releases it (FORMAT.md, Freeze Protocol). The pinned read transaction — which keeps the main DB file frozen afterwards — is owned by FrozenSession, not the coordinator.
type FrozenSession ¶
type FrozenSession struct {
PageSize uint32
PageCount uint64
// contains filtered or unexported fields
}
FrozenSession holds the pinned read transaction that freezes the main DB file in content and size while writers proceed into the WAL.
func OpenFrozenSession ¶
func OpenFrozenSession(ctx context.Context, dbPath string, fc FreezeCoordinator) (*FrozenSession, error)
OpenFrozenSession executes the freeze protocol: gate -> checkpoint TRUNCATE -> pinned read transaction -> capture geometry -> gate release.
The gate is released here, before attachment capture runs, so a gated operation that deletes attachment files (remove-account) can race a long-running capture and delete a file the pinned transaction still references. That backup fails loudly (read or hash error) and is retryable; holding the gate through the whole capture window would block every daemon write for minutes instead. Accepted limitation — see FORMAT.md, Current Limitations.
func OpenFrozenSessionWithOpener ¶ added in v0.10.0
func OpenFrozenSessionWithOpener( ctx context.Context, dbPath string, fc FreezeCoordinator, opener SQLiteOpener, ) (*FrozenSession, error)
OpenFrozenSessionWithOpener executes the freeze protocol with the supplied SQLite implementation. Nil opener preserves the default.
func (*FrozenSession) Close ¶
func (s *FrozenSession) Close() error
Close releases the pinned transaction and connection. Idempotent.
func (*FrozenSession) Tx ¶
func (s *FrozenSession) Tx() *sql.Tx
Tx exposes the pinned read transaction so an App's FrozenView can run its schema queries inside the frozen snapshot.
type FrozenView ¶
type FrozenView interface {
ContentInfo(ctx context.Context) (*ContentInfo, error)
Stats(ctx context.Context) (json.RawMessage, error)
}
FrozenView answers the application-schema questions Create asks against the pinned read transaction of a FrozenSession. It may additionally implement AuxiliarySource to contribute artifacts from that same view.
type IndexEntry ¶
type IndexEntry struct {
Blob pack.BlobID
PackID string
Offset uint64
StoredLen uint64
Flags pack.BlobFlags
}
IndexEntry maps one blob to its location inside a sealed pack.
func DecodeIndex ¶
func DecodeIndex(data []byte) ([]IndexEntry, error)
DecodeIndex parses and integrity-checks an index object.
type LockInfo ¶
type LockInfo struct {
Hostname string `json:"hostname"`
PID int `json:"pid"`
Operation string `json:"operation"`
AcquiredAt string `json:"acquired_at"`
}
LockInfo is the JSON body of a repo lock file. Freshness is carried by the file's mtime (heartbeat), not by fields, so observers need no clock sync.
type Manifest ¶
type Manifest struct {
FormatVersion int `json:"format_version"`
MinReaderVersion int `json:"min_reader_version"`
// AppVersion records the application version that wrote the snapshot. The
// wire key is frozen at format v1: renaming it would break every existing
// repo's snapshot-ID recompute.
AppVersion string `json:"msgvault_version"`
SnapshotID string `json:"snapshot_id"`
ParentID string `json:"parent_id"`
CreatedAt string `json:"created_at"`
Options ManifestOptions `json:"options"`
DB ManifestDB `json:"db"`
Metadata *ManifestMetadata `json:"metadata,omitempty"`
Auxiliary []ManifestAuxiliary `json:"auxiliary,omitempty"`
Attachments ManifestAttachments `json:"attachments"`
Extras ManifestExtras `json:"extras"`
Excluded []string `json:"excluded"`
// Stats is the application-defined stats payload. The engine treats it as
// opaque bytes: recorded at create, byte-compared at restore.
Stats json.RawMessage `json:"stats"`
NewPacks []string `json:"new_packs"`
NewIndex string `json:"new_index"`
DurationSeconds float64 `json:"duration_seconds"`
BytesAdded int64 `json:"bytes_added"`
}
type ManifestAttachments ¶
type ManifestAuxiliary ¶ added in v0.17.0
type ManifestAuxiliary struct {
Name string `json:"name"`
Format string `json:"format"`
Blob string `json:"blob"`
Bytes int64 `json:"bytes"`
SHA256 string `json:"sha256"`
}
ManifestAuxiliary identifies one content-addressed auxiliary artifact.
type ManifestDB ¶
type ManifestExtras ¶
type ManifestExtras struct {
Tree string `json:"tree"`
}
type ManifestMetadata ¶ added in v0.9.0
type ManifestMetadata struct {
Format string `json:"format"`
Blob string `json:"blob"`
Bytes int64 `json:"bytes"`
}
ManifestMetadata identifies one application-owned portable metadata blob. nil denotes the historical SQLite page-map representation in DB.
type ManifestOptions ¶
type MetadataRestorer ¶ added in v0.9.0
type MetadataRestorer interface {
RestoreMetadata(ctx context.Context, format string, metadata io.Reader, targetPath string) error
}
MetadataRestorer builds the application's current runtime database at targetPath from one verified portable metadata stream. targetPath is inside Kit-owned private scratch, not the eventual restore target. The restorer must consume the stream through EOF, close and checkpoint the database, and leave no sidecars. Kit copies the closed result into its confined target root.
type MetadataSnapshot ¶ added in v0.9.0
type MetadataSnapshot interface {
OpenMetadata(context.Context) (io.ReadCloser, int64, error)
ContentInfo(context.Context) (*ContentInfo, error)
Stats(context.Context) (json.RawMessage, error)
Close() error
}
MetadataSnapshot supplies portable metadata bytes, content membership, and fidelity stats from one stable application view. It may additionally implement AuxiliarySource to contribute artifacts from that same view.
type MetadataSource ¶ added in v0.9.0
type MetadataSource interface {
Format() string
OpenSnapshot(context.Context) (MetadataSnapshot, error)
}
MetadataSource opens one application-owned logical metadata snapshot while Create holds its FreezeCoordinator. OpenSnapshot must establish a stable view before returning; the coordinator is released immediately afterward.
type NoopFreezeCoordinator ¶
type NoopFreezeCoordinator struct{}
NoopFreezeCoordinator is for tests and capture paths with no daemon.
type PackAppender ¶
type PackAppender struct {
// contains filtered or unexported fields
}
PackAppender routes blobs into sealed packs, deduplicating against the repository's known blob set plus everything added during this run.
After any returned error, the appender is poisoned and must not be used again; callers must call Abort() and discard it. All subsequent Add and Finish calls will return the original error without writing.
func NewPackAppender ¶
func NewPackAppender(r *Repo, known map[pack.BlobID]IndexEntry, zstdLevel int, crypter *pack.Crypter, ext string) *PackAppender
NewPackAppender creates an appender. known is mutated: added blobs join it so later Adds in the same run dedup against them. ext is the pack file extension (App.PackFileExtension) new packs are sealed with.
func (*PackAppender) Abort ¶
func (a *PackAppender) Abort()
Abort discards the open pack; already-sealed packs remain (they are unreferenced without a manifest and get ignored, see FORMAT.md, Crash Consistency).
func (*PackAppender) Add ¶
Add stores raw as a blob unless it is already known. It returns the blob ID and whether a new pack entry was written.
func (*PackAppender) AddEncoded ¶
func (a *PackAppender) AddEncoded(id pack.BlobID, frame []byte, rawLen uint64, compressed bool) (bool, error)
AddEncoded stores one blob whose frame the caller already encoded with pack.EncodeFrame, unless it is already known. The caller inherits pack.Writer.AppendEncoded's contract: id and (frame, compressed) must derive from the same raw bytes. It reports whether a new pack entry was written.
func (*PackAppender) AddPrepared ¶ added in v0.8.0
func (a *PackAppender) AddPrepared(ctx context.Context, prepared *pack.PreparedBlob) (bool, error)
AddPrepared consumes prepared and appends its bounded-scratch frame unless the blob is already known. Prepared is consumed or closed on every return.
func (*PackAppender) Finish ¶
func (a *PackAppender) Finish() ([]string, []IndexEntry, error)
Finish seals the open pack (aborting it if empty) and returns the packs sealed this run and their index entries.
type PackedContentTarget ¶ added in v0.7.0
type PackedContentTarget interface {
// Limits returns the target store's configured compatibility and
// allocation ceilings.
Limits() packstore.Limits
// AcquireRestoreLease acquires a mutation lease from the same Coordinator
// used by every maintainer of the target content store. The target transfers
// sole ownership of a successful lease to Restore. Applications must acquire
// their own operation gates before this method is called.
AcquireRestoreLease(context.Context) (*packstore.Lease, error)
// OpenRestoreCatalog returns the packed-authority adapter for db. db is
// Restore's unpublished staged database, not the currently visible target.
// It and the returned catalog's ReplaceRestoredPacks method run while the
// restore lease is held and must not reenter its Coordinator.
OpenRestoreCatalog(context.Context, *sql.DB) (packstore.RestoreCatalog, error)
}
PackedContentTarget supplies the application-owned packed-storage policy for an optional mixed packed-and-loose restore. It opens catalog authority only against Restore's unpublished staged SQLite database; Kit never opens an application's live catalog through this interface. Implementations must keep catalog replacement structurally valid and neutral to RestoredStats so Restore can prove the final staged database before publishing it.
type PageHashDelta ¶
PageHashDelta patches a parent PageHashMap: PageCount is the new total, Pages lists changed page numbers ascending, Hashes their new hashes.
func BuildHashDelta ¶
func BuildHashDelta(res *ScanResult) *PageHashDelta
BuildHashDelta converts a scan's dirty set into a hash-map delta.
func DecodeHashDelta ¶
func DecodeHashDelta(data []byte) (*PageHashDelta, error)
DecodeHashDelta parses and integrity-checks a delta object.
type PageHashMap ¶
PageHashMap holds the truncated SHA-256 of every DB page. It is the input to incremental change detection (FORMAT.md, Page-Map Objects).
func ApplyHashDelta ¶
func ApplyHashDelta(base *PageHashMap, d *PageHashDelta) (*PageHashMap, error)
ApplyHashDelta produces the child hash map from a parent and a delta.
func DecodeHashKeyframe ¶
func DecodeHashKeyframe(data []byte) (*PageHashMap, error)
DecodeHashKeyframe parses and integrity-checks a keyframe object.
func LoadHashMapCache ¶
func LoadHashMapCache(cacheDir, repoID string) (string, *PageHashMap, error)
LoadHashMapCache reads the disposable local hash-map cache. Any read or parse failure returns empty results, never an error: the cache is rebuilt from the repository when unusable. A repoID that is not a canonical generated ID is an error, not a miss — it is joined into cacheDir as a filename, so anything else could address a file outside the cache.
func MaterializeHashMap ¶
func MaterializeHashMap( fetch func(pack.BlobID) ([]byte, error), chain []pack.BlobID, ) (*PageHashMap, error)
MaterializeHashMap walks a newest-to-oldest blob chain until it finds a keyframe, then replays the deltas oldest-first.
type PageMap ¶
PageMap is the run-length-encoded page -> (blob, offset) mapping (FORMAT.md, Page-Map Objects). A keyframe covers every page exactly once; a delta covers only the ranges it replaces.
func ApplyPageMapDelta ¶
ApplyPageMapDelta merges a delta into a base map: delta ranges win, base runs are split around them, and the result is resized to the delta's page count.
func DecodePageMap ¶
DecodePageMap parses either page-map object form and validates structure.
func MaterializePageMap ¶
func MaterializePageMap(fetch func(pack.BlobID) ([]byte, error), chain []pack.BlobID) (*PageMap, error)
MaterializePageMap walks a newest-to-oldest blob chain to a keyframe and replays the deltas oldest-first.
func (*PageMap) CheckCoverage ¶
CheckCoverage verifies a complete map covers every page exactly once.
type ProgressEvent ¶
type ProgressEvent struct {
Stage ProgressStage
Done int64
Total int64
BytesDone int64
BytesTotal int64
Final bool
}
ProgressEvent reports one step of progress within a Stage. Done and Total are item counts (pages, files, blobs, snapshots) — Total is 0 when the item count isn't known in advance. BytesDone and BytesTotal are the corresponding byte counts where meaningful; BytesTotal is 0 when the byte total isn't known ahead of time (a renderer can still show BytesDone and a derived rate). Final marks the last event Create or Verify will emit for this Stage, i.e., the stage has completed.
type ProgressStage ¶
type ProgressStage string
ProgressStage names one phase of a Create or Verify run that reports progress.
const ( // ProgressStageFreeze covers opening the frozen read session. ProgressStageFreeze ProgressStage = "freeze" // ProgressStageScan covers the full page-hash scan. ProgressStageScan ProgressStage = "scan" // ProgressStageMetadata covers portable metadata capture or restore. ProgressStageMetadata ProgressStage = "metadata" // ProgressStagePack covers compressing and writing changed-page blobs // into pack files. On a first backup this is most of the wall clock // (every page is new); on a no-change backup it is skipped entirely. ProgressStagePack ProgressStage = "pack" // ProgressStageAttachments covers attachment content capture. ProgressStageAttachments ProgressStage = "attachments" // ProgressStageSeal covers sealing packs and writing the index. ProgressStageSeal ProgressStage = "seal" // ProgressStageVerify covers Verify's per-snapshot and per-blob checks. ProgressStageVerify ProgressStage = "verify" // ProgressStageRestoreDB covers materializing the database file from // page-map runs during Restore. ProgressStageRestoreDB ProgressStage = "db" // ProgressStageExtras covers laying out captured extras files during // Restore. ProgressStageExtras ProgressStage = "extras" // ProgressStageIntegrityCheck covers Restore's optional SQLite // integrity_check. The check reports only start and completion because // SQLite does not expose intermediate progress. ProgressStageIntegrityCheck ProgressStage = "integrity_check" // ProgressStageRestoreStats covers recomputing and comparing the restored // database's application-defined manifest statistics. ProgressStageRestoreStats ProgressStage = "restore_stats" // ProgressStageProof is retained for source compatibility. Restore emits // the more specific IntegrityCheck and RestoreStats stages instead. // Deprecated: use ProgressStageIntegrityCheck and ProgressStageRestoreStats. ProgressStageProof ProgressStage = "proof" )
type Repo ¶
type Repo struct {
// contains filtered or unexported fields
}
Repo is an opened backup repository rooted at a directory.
func Init ¶
Init creates a new empty repository at root. It refuses to reuse a directory that already contains a repository config.
func (*Repo) AcquireExclusiveLock ¶
AcquireExclusiveLock takes locks/exclusive.json for a mutating operation. It removes stale locks, refuses fresh ones unless force is set, and after planting the exclusive file waits out fresh shared locks (releasing and failing if they persist past sharedWaitTimeout).
func (*Repo) AcquireSharedLock ¶
AcquireSharedLock takes locks/shared-<ulid>.json for a read-walking operation (verify, restore). It refuses under a fresh exclusive lock.
The pre-plant check alone is racy: AcquireExclusiveLock could plant exclusive.json and finish its (single) freshSharedLocks scan in the window between our check and our own plant, and both sides would then believe they hold a compatible lock. Closing that requires the standard create-then-verify handshake: after planting our shared file we re-check for a fresh exclusive lock and back off if one is now present. This is safe for the mirrored ordering too — if our shared file lands first, AcquireExclusiveLock's freshSharedLocks scan (which always runs after its own plant) will see it and wait.
func (*Repo) CleanStaging ¶
CleanStaging removes in-flight write debris. Callers must hold the exclusive repo lock (concurrent writers stage under the same directory).
func (*Repo) HashMapChain ¶
HashMapChain collects the newest-to-oldest page-hash-map blob chain from head down to (and including) its keyframe manifest.
func (*Repo) LatestSnapshot ¶
LatestSnapshot returns the newest manifest, or nil for an empty repo.
func (*Repo) ListSnapshots ¶
ListSnapshots returns every manifest sorted ascending by snapshot ID (IDs are time-prefixed, so this is chronological). Create enforces strictly increasing CreatedAt timestamps per repo (see nextCreatedAt in create.go), so even snapshots created within the same wall-clock second still sort chronologically by ID. Lock-free by design.
func (*Repo) LoadBlobIndex ¶
func (r *Repo) LoadBlobIndex() (map[pack.BlobID]IndexEntry, error)
LoadBlobIndex reads every index object and returns the union blob map, trusting every *.mvidx file in the indexes directory without checking whether a manifest actually references it.
An index can be orphaned if Create fails after WriteIndex succeeds but before the manifest is written (e.g. SetPageSize or WriteManifest fails afterward). That orphan is still safe to dedupe against: WriteIndex is only ever called after appender.Finish() has sealed its packs durably (create.go), so every entry in a published index — orphaned or not — points at a real, durable, sealed blob. A later run that re-derives the same content will see it already indexed here and skip re-storing it, reusing the orphaned blob instead of duplicating it. There is no unsafe window: the failure that can orphan an index happens strictly after the data it describes is already durable.
func (*Repo) LoadManifest ¶
LoadManifest reads one manifest by snapshot ID.
func (*Repo) OpenBlob ¶ added in v0.8.0
func (r *Repo) OpenBlob( ctx context.Context, known map[pack.BlobID]IndexEntry, id pack.BlobID, crypter *pack.Crypter, ext string, ) (*BlobStream, error)
OpenBlob opens a verified-on-EOF stream by resolving id through known and matching that index record to the pack's authoritative footer entry. Plain format-v1 entries are streamable; encrypted format-v1 entries return pack.ErrStreamUnsupported and remain available through ReadBlob.
func (*Repo) PageMapChain ¶
PageMapChain collects the newest-to-oldest page-map blob chain.
func (*Repo) ReadBlob ¶
func (r *Repo) ReadBlob(known map[pack.BlobID]IndexEntry, id pack.BlobID, crypter *pack.Crypter, ext string) ([]byte, error)
ReadBlob fetches one blob by resolving its pack through the index map. The pack footer is authoritative for the entry's RawLen and CRC. ext is the pack file extension (App.PackFileExtension).
func (*Repo) SetPageSize ¶
SetPageSize records the DB page size after the first backup.
func (*Repo) WriteIndex ¶
func (r *Repo) WriteIndex(entries []IndexEntry) (string, error)
WriteIndex publishes a new immutable index object and returns its ULID.
type RepoConfig ¶
type RepoConfig struct {
RepoID string `toml:"repo_id"`
FormatVersion int `toml:"format_version"`
MinReaderVersion int `toml:"min_reader_version"`
Encryption string `toml:"encryption"`
CreatedAt string `toml:"created_at"`
PageSize int `toml:"page_size"`
}
RepoConfig is the plaintext repository descriptor (FORMAT.md). It stays unencrypted even in encrypted repos because it bootstraps everything else.
type RepoLock ¶
type RepoLock struct {
// contains filtered or unexported fields
}
RepoLock is a held repository lock with a heartbeat goroutine. info holds the exact LockInfo this process wrote, so Release can verify it still owns the file at path before removing it (the file may have been reaped as stale and replanted by another holder in the meantime).
func (*RepoLock) Release ¶
Release stops the heartbeat and removes the lock file, but only if the file still holds the LockInfo this RepoLock planted. If this holder was slow enough to be reaped as stale, another holder may have replanted the same path with its own live lock; removing it would delete that lock out from under its owner. Removal is made atomic against that race by claiming the file with a rename first: only one caller can win the rename, so the file this Release inspects and deletes is one it solely owns. When the claimed file is not ours it is restored to its path (without clobbering any newer lock), and — because our lock is already gone — Release reports no error.
A cheap pre-read confines the claim protocol to the only case that needs it. If the file at l.path already, provably, holds a different holder's LockInfo, it belongs to a successor and Release returns without claiming: the old behavior of never touching a file whose body is not ours. This avoids the claim's momentary rename-away vacancy — into which a third acquirer could plant a lock that the return step then drops — in the common replanted case. Only an ours-looking or unreadable lock proceeds to the claim handshake.
type RestoreOptions ¶
type RestoreOptions struct {
SnapshotID string // empty: latest
// TargetDir receives the restored archive: the database file, content dir,
// and any captured extras. It must not exist, or be an empty directory,
// unless Overwrite is set. Overwrite merges into the existing tree:
// restored files replace same-named ones, files the snapshot does not
// carry are left in place, and the existing database and its SQLite
// sidecars survive until the replacement database is fully materialized,
// every attachment and extras blob has been read and verified, and the
// replacement has passed the restore proof — only then are the sidecars
// set aside (a stale -wal, -shm, or -journal would otherwise be replayed
// over the restored file on its first normal open), the database renamed
// into place, and the set-aside sidecars removed. A failed rename puts
// the sidecars back.
TargetDir string
Overwrite bool
// Jobs is the number of concurrent pack-read workers. Zero or negative
// selects one per CPU. Use 1 to read packs strictly one at a time when
// the repository lives on a spinning disk or NAS share.
Jobs int
ForceUnlock bool
// SkipIntegrityCheck omits SQLite's full PRAGMA integrity_check after
// materialization. Database pages and content blobs remain SHA-256 verified,
// and restored application statistics are still compared with the manifest.
SkipIntegrityCheck bool
// Progress, if non-nil, receives structured progress events as Restore
// runs. nil means fully silent.
Progress func(ProgressEvent)
// MetadataRestorer is required for snapshots whose application state is a
// portable metadata artifact instead of SQLite page maps.
MetadataRestorer MetadataRestorer
// PackedContent optionally restores compatible repository packs into the
// target content store and replaces packed authority in the unpublished
// staged DB before its integrity/stats proof and final publication. Pack and
// entry compatibility use the target's configured limits; declined hashes
// are restored and verified loose. nil preserves a fully-loose restore.
// When non-nil, "packs" is reserved as the first component below the
// application's content directory.
PackedContent PackedContentTarget
// TargetCoordinator optionally acquires application-owned coordination
// against the exact pre-opened target root Restore will mutate. This closes
// the pathname gap that would exist if a caller locked TargetDir before Kit
// opened it. The lease is held through publication and durability sync.
TargetCoordinator RestoreTargetCoordinator
// SQLiteOpener selects the SQLite implementation used to validate and
// update the staged database. Nil preserves Kit's mattn/go-sqlite3 default.
SQLiteOpener SQLiteOpener
// AuxiliaryTarget stages verified application-defined snapshot artifacts
// after all restore proofs and commits them only after the target is
// published and durably synced.
AuxiliaryTarget AuxiliaryTarget
// BeforePublication runs after every content object has been restored and
// verified but before the staged database's integrity/stats proof and
// canonical publication. Applications may update a private scratch copy to
// bind restored physical state; DBPath is outside TargetDir and does not
// resolve through it. The callback must close/checkpoint its database and
// leave no SQLite sidecars; returning an error leaves it unpublished.
BeforePublication func(context.Context, RestorePublicationTarget) error
}
RestoreOptions parameterizes one restore run (FORMAT.md, Restore).
type RestorePublicationTarget ¶ added in v0.17.0
RestorePublicationTarget identifies the private scratch state passed to BeforePublication. DBPath is outside TargetDir and valid only for the duration of the callback.
type RestoreResult ¶
type RestoreResult struct {
SnapshotID string
DBPath string
DBBytes int64
AttachmentBlobs int64
AttachmentBytes int64
// PackedAttachmentBlobs and LooseAttachmentBlobs partition
// AttachmentBlobs by restored representation.
PackedAttachmentBlobs int64
LooseAttachmentBlobs int64
// AttachmentPacks is the number of repository packs imported and granted
// authority in the restored application catalog.
AttachmentPacks int
// PackFallbacks records why packs or individual selected hashes were
// restored loose. An empty Hash means the reason applies to the whole pack.
PackFallbacks []packstore.ImportFallback
ExtrasFiles int
// AuxiliaryArtifacts is the number of verified artifacts committed through
// AuxiliaryTarget.
AuxiliaryArtifacts int
// DatabaseIntegrityChecked reports whether Restore ran SQLite's full
// PRAGMA integrity_check against the staged database.
DatabaseIntegrityChecked bool
Duration time.Duration
}
RestoreResult reports what Restore materialized and proved.
func Restore ¶
func Restore(ctx context.Context, r *Repo, app App, opts RestoreOptions) (res *RestoreResult, err error)
Restore materializes one snapshot into TargetDir and then proves the result (FORMAT.md, Restore): every database page is hash-verified against the snapshot's page-hash map as it is written, every blob read re-derives its SHA-256 identity, and the restored database reproduces the manifest's recorded stats exactly before Restore reports success, and passes PRAGMA integrity_check unless SkipIntegrityCheck is set. When PackedContent is set, compatible packs are durably published before one staged catalog replacement; every fallback is durably restored loose before that authority change.
It takes a SHARED repository lock: concurrent restores and verifies are safe, a running create is not.
type RestoreTargetCoordinator ¶ added in v0.9.2
type RestoreTargetCoordinator interface {
AcquireRestoreTarget(context.Context, *os.Root) (RestoreTargetLease, error)
}
RestoreTargetCoordinator binds application-owned coordination to the exact directory descriptor Restore uses for every target mutation. Restore calls AcquireRestoreTarget after securely opening the target root and before stale staging cleanup or content publication. The root is borrowed: implementations must not close it or retain it after the lease is released.
type RestoreTargetLease ¶ added in v0.9.2
type RestoreTargetLease interface {
Release() error
}
RestoreTargetLease holds application-owned target coordination for one complete restore. Release runs before Kit closes the borrowed target root.
type RestoredAuxiliary ¶ added in v0.17.0
RestoredAuxiliary carries independently verified artifact bytes to the application before target publication.
type SQLiteAccess ¶ added in v0.10.0
type SQLiteAccess uint8
SQLiteAccess describes how a backup operation may use a database file.
const ( // SQLiteReadWriteExisting opens an existing database without creating it. SQLiteReadWriteExisting SQLiteAccess = iota + 1 // SQLiteReadOnlyImmutable opens a completed, writer-free database without // creating SQLite sidecars. SQLiteReadOnlyImmutable )
type SQLiteOpenFunc ¶ added in v0.10.0
type SQLiteOpenFunc func(path string, opts SQLiteOpenOptions) (*sql.DB, error)
SQLiteOpenFunc adapts a function to SQLiteOpener.
func (SQLiteOpenFunc) OpenSQLite ¶ added in v0.10.0
func (f SQLiteOpenFunc) OpenSQLite(path string, opts SQLiteOpenOptions) (*sql.DB, error)
OpenSQLite implements SQLiteOpener.
type SQLiteOpenOptions ¶ added in v0.10.0
type SQLiteOpenOptions struct {
Access SQLiteAccess
BusyTimeout time.Duration
}
SQLiteOpenOptions describes the filesystem and locking semantics required by one backup database open.
type SQLiteOpener ¶ added in v0.10.0
type SQLiteOpener interface {
OpenSQLite(path string, opts SQLiteOpenOptions) (*sql.DB, error)
}
SQLiteOpener lets an application keep backup and restore on the same SQLite implementation as its live metadata store.
type ScanResult ¶
ScanResult carries the full page-hash pass and the dirty set vs the parent.
func ScanPages ¶
func ScanPages( ctx context.Context, r io.ReaderAt, pageSize uint32, pageCount uint64, parent *PageHashMap, progress func(done, total uint64), ) (*ScanResult, error)
ScanPages hashes every page and diffs against the parent hash map. The full scan is the honest cost of on-demand backup (FORMAT.md, Page-Map Objects); it doubles as live-DB bitrot detection. progress, if non-nil, is called once per chunk with the page count scanned so far and the total page count; it does not otherwise affect scan behavior.
Internally the scan pipelines: one goroutine reads chunks strictly sequentially (the disk access pattern is identical to a serial scan, so this is safe on spinning disks), a worker per CPU hashes pages, and a collector reassembles per-chunk results in order. The output is byte-identical to a serial scan.
type VerifyOptions ¶
type VerifyOptions struct {
SnapshotID string // empty: latest
All bool
Quick bool
ForceUnlock bool
// Jobs is the number of concurrent content-blob read workers used in
// full mode. Zero or negative selects one worker per CPU. Use 1 to read
// packs strictly one at a time — the right choice when the repository
// lives on a spinning disk or NAS share that degrades under concurrent
// reads. Quick mode reads no content and ignores Jobs.
Jobs int
// Progress, if non-nil, receives structured progress events as Verify
// runs. nil means fully silent. Verify emits events freely and cheaply;
// throttling for display is a rendering concern of the callback, not
// Verify's.
Progress func(ProgressEvent)
}
VerifyOptions parameterizes one integrity check run (FORMAT.md, Verification Model).
type VerifyResult ¶
type VerifyResult struct {
Snapshots []string
BlobsChecked int64
BytesRead int64
Problems []Problem
}
VerifyResult reports what Verify checked and found.
func Verify ¶
func Verify(ctx context.Context, r *Repo, app App, opts VerifyOptions) (*VerifyResult, error)
Verify checks a backup repository's integrity (FORMAT.md, Verification Model). It takes a SHARED repo lock (released on return): concurrent verifies and restores are safe, but a running create/prune (exclusive) is not.
Snapshot selection: All verifies every manifest, SnapshotID verifies one (an error if it does not exist), and the default verifies only the latest.
In Quick mode, every referenced blob is resolved through the index and its pack footer, but content blobs are not read. The default full mode also reads and hash-verifies every content blob, checks the materialized page map's coverage, and cross-checks attachment-list totals against the manifest. Per-object failures are collected as Problems naming the snapshot, blob, and pack; Verify keeps going so every affected snapshot is named, rather than stopping at the first Problem.