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:
- take the Space's run lock (no two runs per Space)
- resolve the Space and its backup configuration
- resolve the target and TW-unwrap its credentials
- SRW-unwrap the Data Key
- snapshot the Space into its per-Space kopia repo on the target
- zeroize the Data Key
- 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
- Variables
- func NewSpaceSource(reader cs3.SpaceReader, space cs3.Space) snapshot.Source
- type Clock
- type Deps
- type PruneResult
- type Result
- type Runner
- func (r *Runner) RunBackup(ctx context.Context, spaceID string) (Result, error)
- func (r *Runner) RunPrune(ctx context.Context, spaceID string) (PruneResult, error)
- func (r *Runner) RunScheduled(ctx context.Context, spaceID string) (Result, error)
- func (r *Runner) StartBackup(ctx context.Context, spaceID string) (string, error)
Constants ¶
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 ¶
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") // 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 ¶
NewSpaceSource returns a snapshot.Source streaming directly from a Space.
Types ¶
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 (*Runner) RunBackup ¶
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 ¶
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 ¶
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 ¶
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.