backup

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package backup orchestrates one backup run:

[CS3 read] -> [kopia snapshot/encrypt/dedup] -> [S3 target]

It and internal/restore are the only server-side places that hold a Space's plaintext Data Key and a target's plaintext S3 credentials at the same time, and both hold them only in memory for the duration of the run (decisions.md #1, #14). Neither is ever logged, returned, or persisted.

The run sequence follows the phase-4 plan:

  1. take the Space's run lock (no two runs per Space)
  2. resolve the Space and its backup configuration
  3. resolve the target and TW-unwrap its credentials
  4. SRW-unwrap the Data Key
  5. snapshot the Space into its per-Space kopia repo on the target
  6. zeroize the Data Key
  7. record the outcome in the job store, then release the lock — in that order, so a lost outcome write leaves the lease to recover from (decisions.md, R6 amendment)

Retention is never applied inside a backup run. It is applied by a run of its own — see prune.go — which the scheduler starts on its own slow cadence and which takes the same per-Space lock (decisions.md #9 Tier 1).

Index

Constants

View Source
const DefaultRunTimeout = 6 * time.Hour

DefaultRunTimeout bounds a background backup run. It is generous: a first run of a large Space uploads everything, and being killed halfway wastes the work.

Variables

View Source
var (
	// ErrSpaceNotFound means the worker credential cannot see the Space.
	ErrSpaceNotFound = errors.New("backup: space not found")
	// ErrNotConfigured means the Space has no backup configuration or no keys.
	ErrNotConfigured = errors.New("backup: backup is not configured for this space")
	// ErrTargetUnavailable means the configured target is missing or its stored
	// credentials could not be opened.
	ErrTargetUnavailable = errors.New("backup: backup target is unavailable")
	// ErrRunInProgress means another run holds the Space's lock.
	ErrRunInProgress = errors.New("backup: a run is already in progress for this space")
	// ErrRunFailed means the snapshot itself failed.
	ErrRunFailed = errors.New("backup: snapshot run failed")
	// ErrPruneFailed means retention or maintenance failed.
	ErrPruneFailed = errors.New("backup: prune run failed")
	// ErrRunTimedOut means the run exhausted its time limit and was stopped.
	ErrRunTimedOut = errors.New("backup: the run exceeded its time limit")
)

Sentinel errors callers (the API) map onto status codes. Their messages are user-safe: they never name internal services, endpoints, or key material.

Functions

func NewSpaceSource

func NewSpaceSource(reader cs3.SpaceReader, space cs3.Space) snapshot.Source

NewSpaceSource returns a snapshot.Source streaming directly from a Space.

Types

type Clock

type Clock interface {
	Now() time.Time
}

Clock supplies the current time.

type Deps

type Deps struct {
	Spaces  cs3.SpaceReader
	Configs spacecfg.Store
	Targets targets.Store
	Sealer  targets.CredSealer
	Keys    keys.Store
	Unwrap  keys.Wrapper
	Engine  snapshot.Engine
	Jobs    jobs.Store
	Locks   jobs.Locker
	// Envelopes publishes the Space's RK-wrapped Data Key envelope to the
	// target, so an admin Take-Out is self-contained and Path A works with
	// OpenCloud down. Optional: when nil, publication is skipped and Path A
	// depends on the envelope being exported some other way.
	Envelopes remote.Publisher
	// Immutability observes what a target can actually enforce, so the answer is
	// something this deployment saw rather than something a planning document
	// assumed (decisions.md #8). It is read on prune runs only. Optional: when
	// nil, nothing is observed and nothing is claimed.
	Immutability objstore.Prober
	// Logger receives operational detail. It must never be handed key material;
	// the Runner only logs identifiers, counts and sanitized error text.
	Logger *slog.Logger
	// Clock is injected for deterministic tests.
	Clock Clock
	// RunTimeout bounds a background run started by StartBackup and every
	// scheduled run. Zero uses DefaultRunTimeout.
	RunTimeout time.Duration
	// Outcome tunes how a run's terminal outcome is written. The zero value is
	// the production setting; tests shorten the retry backoff.
	Outcome jobs.RecordOptions
	// Background owns the runs StartBackup puts in the background, so that
	// shutdown cancels and waits for them. Optional: when nil the Runner keeps
	// its own, which nothing stops — fine for tests, wrong for the service.
	Background *jobs.Background
}

Deps are the Runner's injected collaborators. Every one is an interface so a run can be exercised end to end without OpenCloud, S3, or real keys.

type PruneResult

type PruneResult struct {
	JobID   string
	SpaceID string
	// Deleted is how many snapshots the run expired, Kept how many restorable
	// snapshots the Space still has.
	Deleted    int
	Kept       int
	StartedAt  time.Time
	FinishedAt time.Time
}

PruneResult summarises a completed prune run. Like Result it carries no key material — only counts and identifiers the status board may show.

type Result

type Result struct {
	JobID      string
	SpaceID    string
	SnapshotID snapshot.SnapshotID
	StartedAt  time.Time
	FinishedAt time.Time
	FileCount  int64
	TotalBytes int64
}

Result summarises a completed run. It carries no key material.

type Runner

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

Runner executes backup runs.

func NewRunner

func NewRunner(d Deps) (*Runner, error)

NewRunner validates dependencies and constructs a Runner.

func (*Runner) RunBackup

func (r *Runner) RunBackup(ctx context.Context, spaceID string) (Result, error)

RunBackup performs one user-requested backup run and waits for it to finish. The HTTP trigger uses StartBackup; this is the synchronous variant.

func (*Runner) RunPrune

func (r *Runner) RunPrune(ctx context.Context, spaceID string) (PruneResult, error)

RunPrune applies a Space's retention window and reclaims what expires.

It is unattended work, so it is bounded in time exactly like a scheduled backup: maintenance against a target that accepts a connection and then stops responding must not hold a Space's run lock until the process is restarted.

The run is refused while a backup or a restore of the same Space is under way (ErrRunInProgress). That is the whole reason it takes the run lock rather than trusting the scheduler not to overlap it: deleting manifests and rewriting indexes underneath a running backup is how a repository gets corrupted.

func (*Runner) RunScheduled

func (r *Runner) RunScheduled(ctx context.Context, spaceID string) (Result, error)

RunScheduled performs one unattended run for the scheduler. It differs from RunBackup only in what the job record says started it — which is what lets the status board tell "your backup ran last night" from "you pressed the button". The credentials and the code path are identical: the SRW-wrapped Data Key (decisions.md #1), with no user session involved. It is also the only run nobody is watching, so it is bounded in time exactly like a backgrounded manual run: a target that accepts a connection and then stops responding must not hold one of the scheduler's few run slots until the process is restarted.

func (*Runner) StartBackup

func (r *Runner) StartBackup(ctx context.Context, spaceID string) (string, error)

StartBackup accepts a run and executes it in the background, returning the job id to poll. Everything that can fail fast — the Space's existence and the run lock — is resolved before returning, so the caller gets a real answer rather than a job that immediately fails.

Jump to

Keyboard shortcuts

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