snapshot

package
v0.116.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package snapshot implements docs/multi-device-storage.md §3.6's "snapshot 起点復旧" path: a periodic, atomic point-in-time copy of the Hub's authoritative state that a backup peer can take over.

One snapshot is one directory under <configdir>/snapshots/<ts>/:

kojo.db                       — SQLite VACUUM INTO copy (consistent)
blobs/global/...              — copy of the global blob tree
manifest.json                 — schema version, ts, sha256 of kojo.db

The KEK (auth/kek.bin) is intentionally NOT included — operators must back it up separately so a snapshot leak cannot decrypt secret kv rows on its own. Per-peer secrets (machine credentials, local-scope blobs) are also excluded; restoring those is the new Hub's own responsibility.

This package owns the on-disk layout and the atomic creation guarantees. All exposed functions are safe to call concurrently with running writers: VACUUM INTO takes a SQLite read lock that snapshots a consistent view, and the blob tree copy walks the filesystem.

Index

Constants

View Source
const DBFileName = "kojo.db"

DBFileName inside a snapshot dir mirrors the live store filename so a restore is "stop service, swap kojo.db, start service".

View Source
const ManifestFileName = "manifest.json"

ManifestFileName lives in every snapshot dir and lists the things that should be there. Restore tooling reads this first to validate the snapshot before swapping it in.

Variables

This section is empty.

Functions

func Apply

func Apply(srcDir, targetConfigDir string, opts ApplyOptions) error

Apply restores a snapshot into the given target config directory. Manifest version + DB sha256 are verified before any data is laid down so a corrupted / mid-rsync snapshot fails the operation cleanly without touching the target.

Order of operations (every safety check runs BEFORE any write):

  1. Load + validate manifest.
  2. Verify snapshot DB sha256.
  3. Manifest BlobScopes ↔ source disk reconciliation.
  4. Existing-target overwrite gate (Force).
  5. Symlink / non-directory checks on every restore-touched subtree (configdir, blobs/, blobs/global/, auth/) — a planted symlink anywhere on the chain would let copyTree walk outside the configdir.
  6. Only after every check passes do we mkdir, chmod, copy.

Items intentionally NOT restored:

  • <target>/auth/kek.bin: the KEK is operator-managed via a side channel (snapshot intentionally excludes it; restoring it from a stale snapshot would break envelope decryption if the KEK has been rotated). The operator must supply a matching KEK to the target before kojo can boot.
  • <target>/auth/agent_tokens/*: per-peer machine credentials stay on each peer; peer-replicated kv rows in kojo.db carry the hashes.
  • Per-peer scoped blobs / credentials.db: these stay on their respective peers. The new Hub serves them through the same peer-fan-out path the original Hub did.

Returns a non-nil error if any precondition fails. On partial-write failures past the preflight gate (e.g. the filesystem fills mid-copy) the target may be left in an inconsistent state; the runbook in docs/snapshot-restore.md tells the operator to wipe the target dir and retry.

func Take

func Take(ctx context.Context, st *store.Store, blobRoot, root string, opts Options) (string, error)

Take creates a new snapshot under <root>/snapshots/<ts>/ and returns the absolute path to the directory. The directory layout is atomic in the sense that the manifest is written LAST — a crashed snapshot has no manifest and restore tooling treats manifest-less directories as in-progress / abandoned.

`st` provides the SQLite database connection. `blobRoot` is the directory containing the live blob scope subdirs; only `blobRoot/global` is copied. `root` is the Hub's config dir (the function creates `root/snapshots/<ts>/` under it).

On any failure mid-way the partially-written snapshot dir is left in place — the function does NOT clean up so an operator can inspect and recover. `kojo --clean snapshots` (Phase 6 #18) removes stale partials.

func VerifyDB

func VerifyDB(dir string) error

VerifyDB returns nil if dir/kojo.db hashes to the manifest's DBSHA256. Used after rsync / cp -R to confirm the copy is intact.

Types

type ApplyOptions

type ApplyOptions struct {
	// Force lets the restore proceed even when a live kojo.db already
	// exists at the target. The default refuses to overwrite a
	// non-empty target so an accidental `kojo --restore` on a Hub that
	// is still running can't silently nuke its DB.
	//
	// Even with Force, Apply NEVER overwrites a target whose
	// configdir lock is held — the configdir.Acquire path layers on
	// top in cmd/kojo and the operator is required to stop the
	// running kojo before restoring (docs/snapshot-restore.md §
	// "Restore on a backup peer" step 1).
	Force bool
}

ApplyOptions narrows Apply(). All fields are optional.

type Manifest

type Manifest struct {
	// Version of the manifest format itself. Bumped only when the
	// shape changes incompatibly.
	Version int `json:"version"`
	// Timestamp the snapshot started, RFC3339.
	StartedAt string `json:"started_at"`
	// SchemaVersion of kojo.db at the time of snapshot.
	SchemaVersion int `json:"schema_version"`
	// DBSHA256 over the snapshot's kojo.db file. Lets the restore
	// path verify the file wasn't truncated mid-rsync.
	DBSHA256 string `json:"db_sha256"`
	// DBSize bytes; redundant with sha256 but cheap and fast to check.
	DBSize int64 `json:"db_size"`
	// BlobScopes describes which blob scopes were captured; v1 always
	// only captures "global" (peer-shared content). "local"/"machine"
	// are per-peer and stay on the original Hub.
	BlobScopes []string `json:"blob_scopes"`
	// HostHint records the os.Hostname() at snapshot time. Purely
	// informational; restore on a different host is the whole point.
	HostHint string `json:"host_hint"`
}

Manifest is the on-disk metadata file. Stable shape — restore tooling and human operators rely on it.

func LoadManifest

func LoadManifest(dir string) (*Manifest, error)

LoadManifest reads and validates the manifest at dir/manifest.json. Used by restore tooling and `kojo snapshot list` (Phase 6).

type Options

type Options struct {
	// HostHint overrides the manifest's host_hint field. Empty falls
	// back to os.Hostname().
	HostHint string
	// Now overrides the wall clock for tests. Zero means time.Now().
	Now time.Time
}

Options narrows Take(). All fields are optional.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL