Help us shape the future of Go! Take the 2026 Go Developer Survey

backup

package
v0.2.0-alpha Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const ArchiveFilename = "snapshot.tar.gz"

ArchiveFilename is the name of the compressed archive inside a backup directory.

View Source
const DefaultRetentionCount = 5

DefaultRetentionCount is the default number of unpinned backups to keep. Pinned backups do not count toward this limit and are never pruned.

View Source
const ManifestFilename = "manifest.json"

Variables

View Source
var BackupRootFn = backupRoot

BackupRootFn is the function used to resolve the backup root directory. Package-level var for testability — swapped in tests to use a temp directory. Exported so tests in other packages (e.g. internal/update/upgrade) can override it.

View Source
var UserHomeDirFn = os.UserHomeDir

UserHomeDirFn is the function used to resolve the user's home directory. Package-level var for testability — swapped in tests to use a temp directory.

Functions

func ComputeChecksum

func ComputeChecksum(paths []string) (string, error)

ComputeChecksum computes a composite SHA-256 checksum over the given file paths.

Algorithm:

  1. Filter to paths that exist and are regular files; skip others silently.
  2. Sort filtered paths lexicographically (deterministic ordering).
  3. For each path, compute SHA-256 of its contents.
  4. Concatenate all "path:hexhash\n" pairs into a single string.
  5. SHA-256 the concatenated result and return the hex-encoded digest.

If paths is empty or all paths are missing/non-regular, ComputeChecksum returns ("", nil) — callers treat an empty checksum as "no dedup possible".

Note: checksums include absolute file paths, so the same files at different paths (e.g., different home directories) will produce different checksums. This is intentional: dedup is per-machine, not cross-machine.

func CreateArchive

func CreateArchive(archivePath string, entries []ArchiveEntry) error

CreateArchive writes a tar.gz archive at archivePath containing all entries. Each entry's content is read from SourcePath and stored under RelPath inside the archive. Intermediate directories in archivePath are created automatically.

The archive uses filepath.ToSlash for cross-platform tar path compatibility.

func DeleteBackup

func DeleteBackup(manifest Manifest) error

DeleteBackup removes the entire backup directory.

func Prune

func Prune(backupDir string, retentionCount int) ([]string, error)

Prune deletes the oldest unpinned backups in backupDir, keeping at most retentionCount unpinned backups. Pinned backups are never deleted.

If retentionCount <= 0 the function is a no-op (unlimited retention).

Directories inside backupDir that do not contain a readable manifest.json are silently skipped and do not count toward the limit.

Deletion errors for individual backup directories are logged but do not abort the loop — remaining backups are still evaluated.

Returns the list of deleted backup IDs.

func RenameBackup

func RenameBackup(manifest Manifest, newDescription string) error

RenameBackup updates the backup's Description field in the manifest file. This does not rename the directory — it updates the human-readable description.

func TogglePin

func TogglePin(manifest Manifest) error

TogglePin flips the Pinned field of the manifest and rewrites the manifest.json file inside the backup's RootDir. Pinned backups are excluded from retention pruning. Returns an error if RootDir is empty or the write fails.

func WriteManifest

func WriteManifest(path string, manifest Manifest) error

Types

type ArchiveEntry

type ArchiveEntry struct {
	// RelPath is the relative path inside the archive (e.g. "files/.config/foo.json").
	RelPath string
	// SourcePath is the absolute path on disk (source when creating; destination when extracting).
	SourcePath string
	// Mode is the file permission bits.
	Mode os.FileMode
}

ArchiveEntry describes a single file to include in a tar.gz archive (for creation) or a file that was extracted from one (for extraction).

func ExtractArchive

func ExtractArchive(archivePath string, destDir string) ([]ArchiveEntry, error)

ExtractArchive extracts a tar.gz archive at archivePath into destDir. It returns the list of extracted entries (RelPath = path inside archive, SourcePath = absolute path of the extracted file inside destDir).

Path traversal protection: any entry whose RelPath contains ".." is rejected with an error and extraction is aborted.

type BackupSource

type BackupSource string

BackupSource identifies what operation created a backup. New values may be added in future — consumers must handle unknown values gracefully.

const (
	// BackupSourceInstall indicates the backup was created before an install run.
	BackupSourceInstall BackupSource = "install"
	// BackupSourceSync indicates the backup was created before a sync run.
	BackupSourceSync BackupSource = "sync"
	// BackupSourceUpgrade indicates the backup was created before an upgrade run.
	BackupSourceUpgrade BackupSource = "upgrade"
	// BackupSourceUninstall indicates the backup was created before an uninstall run.
	BackupSourceUninstall BackupSource = "uninstall"
)

func (BackupSource) Label

func (s BackupSource) Label() string

Label returns a human-readable string for the BackupSource. Unknown or empty sources return "unknown source" so old manifests display gracefully.

type Manifest

type Manifest struct {
	ID        string          `json:"id"`
	CreatedAt time.Time       `json:"created_at"`
	RootDir   string          `json:"root_dir"`
	Entries   []ManifestEntry `json:"entries"`

	// Source identifies what operation created this backup.
	// Optional: omitted for backward-compatibility with old manifests.
	Source BackupSource `json:"source,omitempty"`

	// Description is a short human-readable note about the backup context.
	// Optional: omitted for backward-compatibility with old manifests.
	Description string `json:"description,omitempty"`

	// FileCount is the number of files that existed and were actually snapshotted.
	// Entries where Existed==false (files that did not exist at snapshot time) are
	// not counted. Optional: omitted when zero for backward-compatibility.
	FileCount int `json:"file_count,omitempty"`

	// CreatedByVersion is the teicos version that created this backup.
	// Optional: omitted when empty for backward-compatibility with old manifests.
	CreatedByVersion string `json:"created_by_version,omitempty"`

	// Pinned marks the backup as protected from retention pruning.
	// Optional: omitted when false for backward-compatibility with old manifests.
	Pinned bool `json:"pinned,omitempty"`

	// Compressed indicates the backup files are stored as a tar.gz archive.
	// Optional: omitted when false for backward-compatibility with old manifests.
	Compressed bool `json:"compressed,omitempty"`

	// Checksum is the SHA-256 composite hash of the snapshotted files, used for deduplication.
	// Optional: omitted when empty for backward-compatibility with old manifests.
	Checksum string `json:"checksum,omitempty"`
}

func DuplicateManifest

func DuplicateManifest(backupDir string, newChecksum string) (Manifest, bool, error)

DuplicateManifest returns the most recent manifest when its checksum matches newChecksum. It reports no match when the checksum is empty, no prior backup exists, or the latest manifest predates checksum-based deduplication. Directories without a manifest.json are silently skipped.

func ReadManifest

func ReadManifest(path string) (Manifest, error)

func (Manifest) DisplayLabel

func (m Manifest) DisplayLabel() string

DisplayLabel returns a human-readable label for the backup suitable for display in the CLI restore list and TUI backup screen. It combines the source label and the formatted creation timestamp, and appends the file count when known.

Old manifests without Source will show "unknown source" as a graceful fallback. Old manifests without FileCount will not show any file count.

type ManifestEntry

type ManifestEntry struct {
	OriginalPath string   `json:"original_path"`
	SnapshotPath string   `json:"snapshot_path"`
	Existed      bool     `json:"existed"`
	Mode         uint32   `json:"mode,omitempty"`
	Kind         PathKind `json:"kind,omitempty"`

	// LinkTarget is the recorded symlink target. Only set when Kind
	// is PathKindSymlinkDirectory. Restore validates that it is a safe
	// relative path (no leading '/', no '..' segments that escape).
	LinkTarget string `json:"link_target,omitempty"`
}

type PathKind

type PathKind string

PathKind classifies a backed-up path for restore. The empty value is the legacy "unknown" state — persisted manifests written before this field existed carry it implicitly, and the restore path applies a safe compatibility policy (see RestoreService for details).

const (
	// PathKindUnknown is the legacy default. Restore treats unknown
	// Existed==false entries as preserve-only (do not delete) so we
	// never destroy a path whose original type we cannot prove.
	PathKindUnknown PathKind = ""

	// PathKindRegularFile marks a regular file. Snapshotted into the
	// archive and restored by reading the archive entry.
	PathKindRegularFile PathKind = "regular"

	// PathKindDirectory marks an empty directory. Not archived.
	// Restore ensures the directory still exists; never deletes a
	// pre-existing directory even if the install/sync that produced
	// this snapshot created it.
	PathKindDirectory PathKind = "directory"

	// PathKindSymlinkDirectory marks a relative symlink to a
	// directory. Not archived. Restore validates LinkTarget is a safe
	// relative path and recreates the symlink if it is missing.
	PathKindSymlinkDirectory PathKind = "symlink_directory"
)

type RestoreService

type RestoreService struct {
	Roots []string
}

RestoreService restores a backup manifest, writing back or removing files at their OriginalPath.

Roots restricts which directories a restore is allowed to write to or remove from. Every non-pre-existing manifest entry's OriginalPath must resolve under at least one Roots entry, or the restore refuses it.

When Roots is empty, Restore falls back to the single directory returned by UserHomeDirFn — the historical, backward-compatible behavior. This is the correct default for standalone restores (`gentle-ai restore <id>` and the TUI "restore from list" screen): the backup being restored may be arbitrarily old, and the workspace root that was in effect when it was created is not something the current process can safely rediscover on its own, so it falls back to the one directory it explicitly owns.

Callers that are still inside the same install/sync run that produced the manifest (e.g. pipeline rollback) know their own scope roots out of band and should set Roots explicitly — see rollbackRoots in internal/cli.

func (RestoreService) Restore

func (s RestoreService) Restore(manifest Manifest) error

type Snapshotter

type Snapshotter struct {
	// contains filtered or unexported fields
}

func NewSnapshotter

func NewSnapshotter() Snapshotter

func (Snapshotter) Create

func (s Snapshotter) Create(snapshotDir string, paths []string) (Manifest, error)

Jump to

Keyboard shortcuts

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