Documentation
¶
Overview ¶
Package backup produces and manages owner-run backups: one tar per backup holding the core dump, one dump per plugin schema, the portability export and a manifest. Records live on disk (a sidecar next to each archive), so listing and deleting work with the database down and survive a restore.
Index ¶
- Constants
- Variables
- func ErrorLine(out []byte) string
- func KeepFromEnv() (manual, preUpdate int)
- func NewID(t time.Time, kind Kind) string
- func OSExec(ctx context.Context, env []string, name string, args ...string) ([]byte, error)
- func TryLock(dir string) (func(), error)
- func ValidID(id string) bool
- func WaitLock(ctx context.Context, dir string, wait time.Duration) (func(), error)
- type Engine
- func (e *Engine) Begin(ctx context.Context, req Request) (Info, func(ctx context.Context) (Info, error), error)
- func (e *Engine) Create(ctx context.Context, req Request) (Info, error)
- func (e *Engine) Delete(id string) error
- func (e *Engine) FreeDisk() uint64
- func (e *Engine) Get(id string) (Info, error)
- func (e *Engine) List() ([]Info, error)
- func (e *Engine) MediaDirAbs() string
- func (e *Engine) Open(id string) (*os.File, Info, error)
- type ExecFunc
- type Info
- type Kind
- type Options
- type Plugin
- type Querier
- type RefusalError
- type Request
- type RestoreOptions
- type RestorePlan
- type RestoreRequest
- type State
- type Store
- type TrustIDs
Constants ¶
const ( EnvKeep = "HATAMOTO_BACKUP_KEEP" EnvKeepPreUpdate = "HATAMOTO_BACKUP_KEEP_PRE_UPDATE" )
Environment variables that set how many ok backups of a kind are kept.
const DefaultWait = 10 * time.Minute
DefaultWait is how long a pre-update backup waits for a running one.
Variables ¶
var ErrBusy = errors.New("another backup is running")
ErrBusy means another backup holds the lock.
var ErrNotFound = errors.New("backup not found")
ErrNotFound is returned for an unknown or invalid id, and for a download of a backup that is not ok.
var ErrRunning = errors.New("backup is running")
ErrRunning is returned when deleting a backup that is still being made.
Functions ¶
func ErrorLine ¶
ErrorLine is what to show the owner of a failed pg_dump: the first "pg_dump: error:" line (plus a directly following detail line), because the last lines are often the LOCK TABLE query that follows it. Without one, the last non-empty line.
func KeepFromEnv ¶
func KeepFromEnv() (manual, preUpdate int)
KeepFromEnv reads the retention counts (manual, pre-update); a missing or invalid value gives the default of 5 and 2.
func TryLock ¶
TryLock takes the one-backup-at-a-time lock on dir without waiting. It is a flock, so a process that dies frees it.
Types ¶
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine takes backups and manages the ones on disk.
func New ¶
New builds an Engine. A data dir that cannot be created shows up as an error from every call that needs it.
func (*Engine) Begin ¶
func (e *Engine) Begin(ctx context.Context, req Request) (Info, func(ctx context.Context) (Info, error), error)
Begin takes the lock and writes the running record before it returns, so a caller can answer "started" (or ErrBusy) at once and do the work later. The returned function does the work and releases the lock; it must be called exactly once. Pass it a lifecycle context, never a request context.
func (*Engine) Create ¶
Create takes a backup and returns its record. A manual request while another backup runs gives ErrBusy at once; a pre-update request waits.
func (*Engine) MediaDirAbs ¶
MediaDirAbs is the absolute path of the media dir the content export reads.
type ExecFunc ¶
ExecFunc runs a program and returns its combined output. env is added to the process environment.
type Info ¶
type Info struct {
ID string `json:"id"`
Kind Kind `json:"kind"`
Target string `json:"target,omitempty"`
State State `json:"state"`
Step string `json:"step,omitempty"`
Percent int `json:"percent"`
Error string `json:"error,omitempty"`
Created time.Time `json:"created"`
Size int64 `json:"size,omitempty"`
Parts []string `json:"parts,omitempty"`
Plugins []string `json:"plugins,omitempty"`
// MediaMissing counts media files the database references that were not
// on disk; the backup is still OK, it just lacks them.
MediaMissing int `json:"media_missing,omitempty"`
}
Info is the sidecar record of one backup.
type Options ¶
type Options struct {
Data string // the data dir; backups live in $Data/backups
MediaDir string // upload dir of the content export; empty means ./uploads
DatabaseURL string
Pool *pgxpool.Pool // nil means the database is not reachable
Run ExecFunc // nil means OSExec
// Free reports the bytes writable under dir; nil skips the space check.
Free func(dir string) (uint64, error)
Now func() time.Time
Log *slog.Logger
Version string // hatamoto version, recorded in the manifest
KeepManual int
KeepPreUpdate int
// SecretsDir holds secrets.env; the file is added to a backup when present.
SecretsDir string
// ConfigPath is the operator's env file, stored verbatim; its path is
// recorded in the manifest as an absolute path. Empty means no
// config part; a Request.ConfigPath wins.
ConfigPath string
// Trust gives the key IDs recorded in the manifest; nil means the IDs
// embedded in this binary.
Trust func() (TrustIDs, error)
}
Options configure an Engine.
type Plugin ¶
type Plugin struct {
Name string // display name, e.g. foo-bar
Schema string // plugin_foo_bar
Role string
}
Plugin is one plugin schema to dump and the role that owns it.
type Querier ¶
type Querier interface {
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
Querier is the part of a pgx pool discovery needs.
type RefusalError ¶
type RefusalError struct{ Reasons []string }
RefusalError lists every reason a restore was refused; nothing was written.
func (*RefusalError) Error ¶
func (e *RefusalError) Error() string
type Request ¶
type Request struct {
Kind Kind
Target string // the version a pre-update backup protects
Wait time.Duration // how long a pre-update backup waits for the lock; 0 means DefaultWait
// ConfigPath overrides Options.ConfigPath for this backup.
ConfigPath string
}
Request asks for one backup.
type RestoreOptions ¶
type RestoreOptions struct {
Data string // the data dir; an id source is $Data/backups/<id>.tar
MediaDir string // must be empty or missing
SecretsDir string // secrets.env is written here
DatabaseURL string // the app role on the empty target database
Version string // the running hatamoto version; must equal the backup's
Run ExecFunc
// Q reads the target database (emptiness, server major). Nil opens a pool
// from DatabaseURL for the checks.
Q Querier
ServerMajor func(ctx context.Context) (int, error)
// Trust gives the key IDs of this binary; nil means the embedded ones.
Trust func() (TrustIDs, error)
OpenPool func(ctx context.Context, url string) (*pgxpool.Pool, error)
// OpenAdmin connects with the admin URL (pointed at the target database)
// for the pre-flight checks; nil connects with pgx.
OpenAdmin func(ctx context.Context, url string) (Querier, func(), error)
Log *slog.Logger
// Getenv reads the ambient environment for the libpq overrides check; nil
// means os.Getenv.
Getenv func(string) string
}
RestoreOptions say where a backup is restored to. Everything is checked against the archive before anything is written.
type RestorePlan ¶
type RestorePlan struct {
Backup string
Checks []string
Would []string
Warnings []string
DryRun bool
Done bool // the steps ran: Print says "done", not "would"
}
RestorePlan is what a restore checked and does (or would do).
func Restore ¶
func Restore(ctx context.Context, o RestoreOptions, req RestoreRequest) (*RestorePlan, error)
Restore checks a backup against this machine and, unless it is a dry run, restores it. It is not an Engine method: Engine.New creates directories, and a dry run writes nothing.
func (*RestorePlan) Print ¶
func (p *RestorePlan) Print(w io.Writer)
Print writes the plan in the shape of the restore command output.
type RestoreRequest ¶
type RestoreRequest struct {
Source string // a backup id or the path of a .tar
AdminURL string // superuser DSN for the role bootstrap
ConfigOut string // where to write the recorded config; empty means the config is not written
DryRun bool
}
RestoreRequest is one restore.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes the records in $DATA/backups. It needs no database.
func NewStore ¶
NewStore opens (and creates) $data/backups, mode 0700: the archives hold the whole database.
func (*Store) Delete ¶
Delete removes the archive, then the sidecar. A backup that is really running is refused; an interrupted one reads as failed and can go.
func (*Store) Get ¶
Get returns one record. A sidecar that says running while nobody holds the lock is reported (and rewritten) as failed: the process died.
func (*Store) List ¶
List returns every backup, newest first. Legacy pre-*.dump files and anything hidden are not backups of this kind and are never touched.
type TrustIDs ¶
type TrustIDs struct {
Release []string `json:"release,omitempty"`
Manifest []string `json:"manifest,omitempty"`
Env []string `json:"env,omitempty"`
}
TrustIDs are the key IDs a backup records: the release and manifest keys embedded in the binary that made it, and the operator's own keys.