backuprestore

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package backuprestore verifies a DevX private backup by restoring it into a fixture-owned directory only. It never writes outside that directory and never executes anything restored.

Backup layout (produced by create-private-backup.py, schema 1):

manifest.json          files[], worktrees[], archive_sha256, bundle_sha256
private-files.tar.gz   members "files/<absolute source without leading />"
devx.git.bundle        `git bundle create --all` of the main repo
worktree-NN.patch      `git diff --binary HEAD` per worktree

Safety rules, checked BEFORE any write:

  • the fixture root must be an existing, empty-or-owned directory with a marker file this package wrote, and must not be (or contain, or be inside) any protected root (real HOME config, project roots, /);
  • archive/bundle/patch hashes must match the manifest;
  • every archive member must be a regular file whose name is relative, clean, under "files/", without "..", and listed in the manifest with a matching sha256 and size; links/devices/dirs-as-files are rejected;
  • every destination is computed by joining the fixture root with the cleaned member name and re-checked to stay inside the root.

Files are written 0600 into 0700 directories regardless of archived mode (they are data, not executables).

Index

Constants

This section is empty.

Variables

View Source
var ErrRejected = errors.New("backuprestore: rejected")

ErrRejected marks a refusal made before any write.

Functions

func CheckFixtureRoot

func CheckFixtureRoot(root string, protected []string) error

CheckFixtureRoot validates root against protected roots and the marker.

func DestFor

func DestFor(root, member string) (string, error)

DestFor maps an archive member to its path under root, rejecting anything that is not a clean relative "files/..." path inside root.

func NewFixtureRoot

func NewFixtureRoot(base string) (string, error)

NewFixtureRoot creates a fresh 0700 directory under base and marks it as owned by this package.

func VerifyArtifacts

func VerifyArtifacts(dir string, m *Manifest) error

VerifyArtifacts checks archive, bundle and patch hashes against the manifest. It writes nothing.

Types

type File

type File struct {
	Source      string `json:"source"`
	ArchivePath string `json:"archive_path"`
	SHA256      string `json:"sha256"`
	Size        int64  `json:"size"`
	Mode        int    `json:"mode"`
}

type Git

type Git struct {
	Root string // fixture root
}

Git runs git with an environment that cannot reach the user's config, hooks, credentials or other repositories: HOME and XDG point inside the fixture, system/global config are disabled, GIT_* routing variables are removed, and hooks are pointed at an empty fixture directory.

func (Git) Run

func (g Git) Run(dir string, args ...string) (string, error)

Run executes git in dir, which must be inside the fixture root.

type Manifest

type Manifest struct {
	Schema        int        `json:"schema"`
	Files         []File     `json:"files"`
	Worktrees     []Worktree `json:"worktrees"`
	ArchiveSHA256 string     `json:"archive_sha256"`
	BundleSHA256  string     `json:"bundle_sha256"`
}

Manifest mirrors the backup manifest (only fields we use).

func LoadManifest

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

LoadManifest reads and structurally validates the manifest.

type Options

type Options struct {
	BackupDir string
	// FixtureRoot receives everything. Must be created by NewFixtureRoot.
	FixtureRoot string
	// Protected roots: restore refuses a fixture root equal to, inside, or
	// containing any of these. Callers add the real HOME, config dirs and
	// project roots.
	Protected []string
}

Options controls a restore.

type Result

type Result struct {
	FilesRestored  int    `json:"files_restored"`
	BytesRestored  int64  `json:"bytes_restored"`
	HashMatches    int    `json:"hash_matches"`
	ManifestFiles  int    `json:"manifest_files"`
	RestoredDigest string `json:"restored_set_digest"`
}

Result reports counts and hashes only.

func RestoreFiles

func RestoreFiles(opts Options) (*Result, error)

RestoreFiles restores archive members as data under <root>/restored/files/... after all pre-write checks pass. The archive is streamed twice: a validation pass with no writes, then the write pass.

type RewriteResult

type RewriteResult struct {
	SessionsRewritten  int  `json:"sessions_rewritten"`
	ProjectsRewritten  int  `json:"projects_rewritten"`
	RealPathRefsAfter  int  `json:"real_path_references_after_rewrite"`
	ConfigSanitized    bool `json:"config_sanitized"`
	AllowlistSynthetic bool `json:"allowlist_synthetic"`
}

RewriteResult reports the fixture-only config copies (counts only).

func RewriteForFixture

func RewriteForFixture(root, realHome string, allowProject string) (*RewriteResult, error)

RewriteForFixture builds a fixture-only DevX config dir at <root>/home/.config/devx from the restored data copies:

  • sessions.json and projects.json are parsed and every absolute path that pointed under realHome is remapped under <root>/mapped/...;
  • config.yaml is NOT copied (it contains real tokens, domains and service settings). A fresh minimal config is written instead, with caddy/updates/web autostart disabled and a synthetic pi_mcp allowlist;
  • nothing is executed.

The original restored data under <root>/restored is left untouched for hash comparison.

type Worktree

type Worktree struct {
	Worktree       string `json:"worktree"`
	HEAD           string `json:"HEAD"`
	Branch         string `json:"branch"`
	PatchFile      string `json:"patch_file"`
	PatchSHA256    string `json:"patch_sha256"`
	Dirty          bool   `json:"dirty"`
	UntrackedCount int    `json:"untracked_count"`
}

type WorktreeResult

type WorktreeResult struct {
	Index             int    `json:"index"`
	Name              string `json:"name"` // basename of the original worktree
	HEADMatches       bool   `json:"head_matches"`
	Dirty             bool   `json:"dirty"`
	PatchApplied      bool   `json:"patch_applied"`
	DiffMatchesPatch  bool   `json:"diff_matches_patch"`
	UntrackedExpected int    `json:"untracked_expected"`
	UntrackedRestored int    `json:"untracked_restored"`
	UntrackedHashOK   int    `json:"untracked_hash_ok"`
	TrackedTreeDigest string `json:"restored_tree_digest"`
	Error             string `json:"error,omitempty"`
}

WorktreeResult is per-worktree fidelity, hashes and counts only.

func RestoreWorktrees

func RestoreWorktrees(opts Options, m *Manifest) ([]WorktreeResult, error)

RestoreWorktrees clones the bundle under <root>/git/repo, then for each manifest worktree checks out its HEAD into <root>/git/wt-NN, applies the patch, copies untracked files from the already-restored archive data, and verifies `git diff --binary HEAD` reproduces the patch byte-for-byte.

Jump to

Keyboard shortcuts

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