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

backup

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ManifestName  = "manifest.json"
	PostgresName  = "postgres.dump"
	SecretsKey    = "secrets.key"
	TokenKey      = "token.key"
	AdaptersName  = "adapters.json"
	PolicyName    = "policy.json"
	VolumesPrefix = "volumes/"

	// ServicesPrefix holds provisioned services whose data is not in an app
	// volume (R-212).
	//
	// Empty for every install running only the in-bundle provisioner, whose
	// databases live in app volumes and are already under volumes/. It exists
	// for the adapter that provisions somewhere Pando can only reach over the
	// wire, where Snapshot is the only way the data gets into the bundle.
	ServicesPrefix = "services/"

	// EdgesPrefix holds the storage of what Pando runs in front of itself —
	// Traefik's certificate store (R-174, design 03 §4.4). Restoring it means
	// a rebuilt host does not ask Let's Encrypt for every certificate again
	// at once and meet its rate limits. Named by the runtime's own handle.
	EdgesPrefix = "edges/"

	// UploadsPrefix holds uploaded source archives, one per app, by file name
	// (R-262). An uploaded app's source exists nowhere else: the archive is
	// what its next deploy builds from, and after a restore onto a registry
	// that has lost its image it is the only thing it can be rebuilt from
	// (O-37). The registry's images are not in a bundle.
	UploadsPrefix = "uploads/"
)

Named parts of a bundle. Fixed names because restore looks for them.

View Source
const AppSpecName = "spec.json"

AppBundleName is the fixed name of the spec inside a per-app bundle.

View Source
const BundleVersion = 1

BundleVersion is the on-disk format version.

Restore refuses a version it does not know rather than guessing, because a bundle written by a newer Pando may describe objects this one cannot place. Guessing here fails halfway through, which is the one outcome R-215 forbids.

View Source
const MinPassphraseLength = 16

MinPassphraseLength is the only rule, and it is longer than a password's.

A password protects an account somebody can regain by other means; this protects every secret in the installation and there is no other means. No composition requirements, for the same reason as elsewhere: they produce shorter, more guessable secrets and a note on a monitor.

Variables

View Source
var ErrPassphrase = errors.New("the passphrase does not open this bundle")

ErrPassphrase is returned when a bundle will not decrypt.

One error for a wrong passphrase and for a corrupt header, deliberately. An attacker learning *which* of those it was learns whether they have a real bundle, and R-214's cost is already high enough without leaking that.

Functions

func Decrypt

func Decrypt(dst io.Writer, src io.Reader, passphrase secret.Value) error

Decrypt streams an encrypted bundle from src to dst.

func Encrypt

func Encrypt(dst io.Writer, src io.Reader, passphrase secret.Value) error

Encrypt streams src to dst, encrypted under passphrase.

Types

type AppCreateRequest

type AppCreateRequest struct {
	AppID string

	// Kind is rolling or on_delete. An on_delete backup is kept until somebody
	// discards it (R-204), so it carries no retention at all.
	Kind string

	// Spec is the app's pinned spec, stored beside the data. Without it a
	// restore has bytes and no idea what ran them.
	Spec json.RawMessage

	// Volumes are the app's volumes, resolved to their runtime and handle.
	Volumes []VolumeRef

	DestinationRef string
	RetainFor      time.Duration
}

AppCreateRequest asks for a backup of one app.

type AppRestoreRequest

type AppRestoreRequest struct {
	AppID      string
	AdapterRef string
	ObjectName string

	// Volumes are the app's volumes as they exist now, to restore into.
	Volumes []VolumeRef

	// Confirm is required. Restoring replaces the app's data with what was in
	// the backup, and anything written since is gone.
	Confirm bool
}

AppRestoreRequest asks for an app's data to be put back.

type AppRestoreResult

type AppRestoreResult struct {
	Manifest       Manifest
	VolumesApplied int
}

AppRestoreResult reports what was put back.

type CreateRequest

type CreateRequest struct {
	// Passphrase is supplied at backup time and never stored (R-213). Losing it
	// makes the bundle unusable (R-214), which is an accepted cost and one the
	// console states at creation rather than in documentation.
	Passphrase secret.Value

	// DestinationRef names the backup adapter to write to. Empty uses the
	// configured default.
	DestinationRef string

	// RetainFor is how long to keep it. Zero means keep until discarded.
	RetainFor time.Duration
}

CreateRequest asks for a bundle.

type Created

type Created struct {
	ObjectName  string
	AdapterRef  string
	SizeBytes   int64
	Manifest    Manifest
	RetainUntil *time.Time
}

Created reports what a backup produced.

type Entry

type Entry struct {
	Name   string `json:"name"`
	Size   int64  `json:"size"`
	SHA256 string `json:"sha256"`
}

Entry is one file inside the bundle.

type Manifest

type Manifest struct {
	Version   int       `json:"version"`
	Kind      string    `json:"kind"`
	CreatedAt time.Time `json:"created_at"`

	// PandoVersion and SchemaVersion are what restore checks compatibility
	// against. The schema version is the migration number: a bundle from a
	// newer schema cannot be restored into an older binary, because the
	// migrations that would make sense of it do not exist here yet.
	PandoVersion  string `json:"pando_version"`
	SchemaVersion uint   `json:"schema_version"`

	// Entries is every file in the bundle with its size and checksum. Restore
	// compares against this before applying, and the comparison is what makes
	// "incomplete" a detectable state rather than a surprise.
	Entries []Entry `json:"entries"`

	// Counts are object counts from the state store — apps, users, grants,
	// specs, volumes. Checksums prove the bytes arrived; counts prove the
	// bytes describe the install someone thinks they are restoring. An
	// operator staring at a restore prompt needs the second one.
	Counts map[string]int `json:"counts"`
}

Manifest is what restore verifies against before touching anything (R-215).

Written as the FIRST entry in the tar so it can be read without buffering the whole bundle — a DR bundle holds a pg_dump and every app volume, and a verify that has to hold all of it in memory is a verify nobody runs.

type RestoreRequest

type RestoreRequest struct {
	AdapterRef string
	ObjectName string
	Passphrase secret.Value

	// Confirm is Sequence D step 3. Restoring replaces the install — every app,
	// every user, every secret — and a caller that has not said so explicitly
	// is a caller who meant to verify.
	Confirm bool
}

RestoreRequest asks for a bundle to be applied.

type RestoreResult

type RestoreResult struct {
	Manifest       Manifest
	VolumesApplied int

	// EdgeVolumesApplied counts the edge's storage put back — Traefik's
	// certificates (EdgesPrefix).
	EdgeVolumesApplied int

	// UploadsApplied counts uploaded source archives put back (O-37).
	UploadsApplied int
}

RestoreResult reports what was applied.

type Service

type Service struct {
	Registry *api.Registry

	// DatabaseURL is dumped and restored. Held as a secret.Value because it
	// carries the password and would otherwise reach a log line the first time
	// someone logged the config (R-194).
	DatabaseURL secret.Value

	// SecretsKeyPath is the local secrets adapter's key file. Without it in the
	// bundle, a restored install has every app's ciphertext and nothing to
	// decrypt it with — which looks like a successful restore until an app
	// starts (R-212).
	SecretsKeyPath string

	// TokenKeyPath is core's API token key file (core/tokenkey). API tokens
	// are stored as HMAC-SHA-256 under it, so a restore without it holds every
	// token's digest and can check none of them (R-063, R-212). Empty leaves it
	// out, which only a test does: every install has one.
	TokenKeyPath string

	// UploadDir is where uploaded source archives are kept
	// (source.DefaultUploadDir). Each goes into the bundle and is put back by
	// a restore (O-37). Empty leaves them out, which only a test does.
	UploadDir string

	// State supplies everything the bundle records about the install.
	State StateSource

	// Version and SchemaVersion go into the manifest so a restore can refuse a
	// bundle it cannot make sense of, rather than half-applying it.
	Version       string
	SchemaVersion uint

	// WorkDir is where the bundle is assembled before being encrypted and
	// streamed out. A DR bundle does not fit in memory.
	WorkDir string

	// Regrant re-applies the database's grant policy after a restore. Nil
	// skips it. pg_restore recreates every table and function with the
	// owner's default privileges, which hand the application role UPDATE
	// and DELETE on every month of the audit log until something revokes
	// them (R-027).
	Regrant func(ctx context.Context) error
}

Service builds, verifies and restores DR bundles (Sequence D, R-212–R-216).

func (*Service) Create

func (s *Service) Create(ctx context.Context, id string, req CreateRequest) (Created, error)

Create assembles, encrypts and stores a DR bundle.

Order matters and is Sequence D's: everything is gathered and written to the destination first, and only then does the caller record the row. A row written before the object exists claims a bundle during exactly the window when a disaster is most likely to interrupt.

func (*Service) CreateForApp

func (s *Service) CreateForApp(ctx context.Context, id string, req AppCreateRequest) (Created, error)

CreateForApp backs up one app's data and spec.

func (*Service) Discard

func (s *Service) Discard(ctx context.Context, adapterRef, objectName string) error

Discard removes a stored bundle, for retention (R-211).

Refused when the destination owns retention or holds the object immutably: deleting what the store has already locked fails every time, and the failure looks like a Pando bug rather than the policy it is.

func (*Service) Restore

func (s *Service) Restore(ctx context.Context, req RestoreRequest) (RestoreResult, error)

Restore decrypts, verifies, and only then applies (Sequence D, R-215).

The ordering is the requirement. Everything that can reject the bundle happens before anything is written: decrypt, then verify against the manifest, then check schema compatibility, then confirm intent. A bundle that fails any of those leaves the target install completely untouched — not mostly untouched.

That is why the bundle is staged to disk first. Verifying a stream and then applying it would need the stream twice, and re-reading from a remote destination means the bytes verified and the bytes applied are not provably the same bytes.

func (*Service) RestoreApp

func (s *Service) RestoreApp(ctx context.Context, req AppRestoreRequest) (AppRestoreResult, error)

RestoreApp puts one app's data back, in place (R-206).

In place is the whole of R-206: a backup restores to an app recreated from the same spec, matched by Pando's own identity for it. Backups are not portable to arbitrary apps, because Pando cannot know what is inside a volume and promising portable restore means promising semantics it cannot verify.

Same order as the DR path and for the same reason: decrypt, verify, confirm, and only then write. A bundle that fails any of those leaves the app's data exactly as it was.

func (*Service) Verify

func (s *Service) Verify(ctx context.Context, adapterRef, objectName string, passphrase secret.Value) (Verified, error)

Verify decrypts and checks a stored bundle without applying it (R-216).

Touches nothing. That is the property Sequence D asserts and the reason this is a separate call from Restore rather than a flag on it: a flag is a thing someone passes wrongly, and the wrong value here overwrites an install.

func (*Service) VerifyApp

func (s *Service) VerifyApp(ctx context.Context, adapterRef, objectName string) (Verified, error)

VerifyApp checks a per-app backup, which unlocks with the install's own key.

type ServiceRef

type ServiceRef struct {
	ServiceID  string
	AppID      string
	AdapterRef string
	Handle     string
}

ServiceRef names one provisioned service and the adapter that owns it.

type StateSource

type StateSource interface {
	// Counts are object counts for the manifest: apps, users, grants, specs,
	// volumes. Checksums prove the bytes arrived; counts prove the bytes
	// describe the install the operator thinks they are restoring.
	Counts(ctx context.Context) (map[string]int, error)

	// AdapterConfigs and HostPolicy are exported verbatim.
	AdapterConfigs(ctx context.Context) ([]byte, error)
	HostPolicy(ctx context.Context) ([]byte, error)

	// VolumesToSnapshot lists every live volume with the runtime that holds it.
	VolumesToSnapshot(ctx context.Context) ([]VolumeRef, error)

	// ServicesToSnapshot lists every provisioned service with the adapter that
	// holds it (R-131).
	ServicesToSnapshot(ctx context.Context) ([]ServiceRef, error)
}

StateSource is what the service needs from the state store.

An interface rather than the concrete store so the service can be tested without Postgres, and so this package does not import state — which would make the dependency run both ways once state records the backup.

type Verified

type Verified struct {
	Manifest Manifest

	// Found is every entry actually present, by name.
	Found map[string]Entry
}

Verified is the result of checking a bundle.

func Verify

func Verify(r io.Reader) (Verified, error)

Verify reads a decrypted bundle and checks it against its own manifest (R-215, R-216).

Reads the whole stream and checksums every entry. Returns an error naming what is wrong — a missing entry, a size mismatch, a checksum mismatch, an unknown format version — rather than a boolean, because an operator deciding whether to restore needs to know which.

Touches nothing. That is the property Sequence D asserts: a bad bundle is rejected with the target install completely untouched, not mostly untouched.

type VolumeRef

type VolumeRef struct {
	// AppID names the app the volume belongs to. A volume ID is unique only
	// within its app — two apps may both keep one called "data" — so a
	// bundle holding every app's volumes names each entry by both.
	AppID      string
	VolumeID   string
	AdapterRef string
	Handle     string
}

VolumeRef names one volume and the adapter that can snapshot it.

type Writer

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

Writer assembles a bundle.

Two passes are impossible — the payload is streamed to a destination that may be remote — so the manifest cannot list checksums it has not computed yet. The resolution: a Writer builds the payload into a buffer-like sink supplied by the caller, recording each entry as it goes, and Finish writes the manifest last. Verify therefore reads the manifest from the END of a bundle, and the cost is that a verify reads the whole bundle. That cost is the point: a verify that did not read every byte could not have checked every checksum.

func NewWriter

func NewWriter(w io.Writer, kind, pandoVersion string, schemaVersion uint) *Writer

NewWriter starts a bundle written into w.

func (*Writer) Add

func (b *Writer) Add(name string, size int64, r io.Reader) error

Add copies one file into the bundle, checksumming as it goes.

Size must be known up front because tar requires it in the header. A caller that cannot say how large something is must buffer it first — and for a pg_dump that means to a temporary file, not to memory.

func (*Writer) Count

func (b *Writer) Count(object string, n int)

Count records an object count for the manifest.

func (*Writer) Finish

func (b *Writer) Finish() (Manifest, error)

Finish writes the manifest and closes the archive.

Jump to

Keyboard shortcuts

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