Documentation
¶
Index ¶
- Constants
- Variables
- func DirUsage(dir string) (count int, total int64, err error)
- func FormatSize(bytes int64) string
- func ParseByteSize(s string) (int64, error)
- type BackupResult
- type ComposeProject
- type DrillOptions
- type DrillReport
- type DrillResult
- type HealthCheck
- type ListEntry
- type Manifest
- type Mount
- type PruneResult
- type RefusedMount
- type RestoreOptions
- type RestoreResult
- type RetentionConfig
- type ServiceInfo
Constants ¶
const ( DefaultBootTimeout = 60 * time.Second DefaultHealthTimeout = 30 * time.Second )
Variables ¶
var HealthChecks = map[string]HealthCheck{ "nginx-proxy-manager": { Path: "/", ExpectCodes: []int{200, 301}, ContainerPort: "81", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "vaultwarden": { Path: "/alive", ExpectCodes: []int{200}, ContainerPort: "80", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "uptime-kuma": { Path: "/", ExpectCodes: []int{200}, ContainerPort: "3001", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "pi-hole": { Path: "/admin", ExpectCodes: []int{200, 301}, ContainerPort: "80", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "gitea": { Path: "/", ExpectCodes: []int{200}, ContainerPort: "3000", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "jellyfin": { Path: "/health", ExpectCodes: []int{200}, ContainerPort: "8096", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "plex": { Path: "/web", ExpectCodes: []int{200, 301, 302}, ContainerPort: "32400", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "portainer": { Path: "/", ExpectCodes: []int{200, 301, 302}, ContainerPort: "9000", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "homepage": { Path: "/", ExpectCodes: []int{200}, ContainerPort: "3000", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, "adguard-home": { Path: "/", ExpectCodes: []int{200, 302}, ContainerPort: "3000", BootTimeout: DefaultBootTimeout, HealthTimeout: DefaultHealthTimeout, }, }
HealthChecks maps app names to their health check configuration. Apps are based on the install.Registry definitions.
Functions ¶
func DirUsage ¶ added in v0.24.0
DirUsage reports how many archives dir holds and what they total.
This is the half of the picture `doctor` was missing: it already checks that a backup is recent, which says nothing about whether the directory has been growing without a bound since the day it was created.
func FormatSize ¶ added in v0.24.0
FormatSize renders a byte count the way homebutler shows sizes.
func ParseByteSize ¶ added in v0.24.0
ParseByteSize reads a size written the way an operator writes one.
Both conventions are accepted for the same reason `df` and every dashboard disagree about them: "20GB" and "20GiB" both mean what the person typing meant, and refusing one of them teaches nothing.
Types ¶
type BackupResult ¶
type BackupResult struct {
Archive string `json:"archive"`
Services []string `json:"services"`
Volumes int `json:"volumes"`
Size string `json:"size"`
// Pruned is what retention removed after this backup was written, and is
// absent when retention is not configured — which is the default.
Pruned *PruneResult `json:"pruned,omitempty"`
}
BackupResult is returned after a successful backup.
func Run ¶
func Run(backupDir, service string, retention RetentionConfig) (*BackupResult, error)
Run performs a backup of all (or filtered) Docker services.
Retention is applied after the archive is written and only if writing it succeeded. Pruning before, or on the way out of a failure, would delete history to make room for a backup that does not exist.
type ComposeProject ¶
type ComposeProject struct {
Name string `json:"Name"`
Status string `json:"Status"`
ConfigFile string `json:"ConfigFiles"`
}
ComposeProject represents a docker compose project from `docker compose ls`.
type DrillOptions ¶ added in v0.13.0
type DrillOptions struct {
BackupDir string // directory containing backup archives
Archive string // explicit archive path (overrides BackupDir lookup)
}
DrillOptions configures a drill run.
type DrillReport ¶ added in v0.13.0
type DrillReport struct {
Results []DrillResult `json:"results"`
Total int `json:"total"`
Passed int `json:"passed"`
Failed int `json:"failed"`
}
DrillReport holds the aggregated results of drilling multiple apps.
func RunDrillAll ¶ added in v0.13.0
func RunDrillAll(opts DrillOptions) (*DrillReport, error)
RunDrillAll executes a backup drill for every app found in the backup that has a defined health check.
func (*DrillReport) String ¶ added in v0.13.0
func (r *DrillReport) String() string
String returns human-readable output for a drill report.
type DrillResult ¶ added in v0.13.0
type DrillResult struct {
App string `json:"app"`
Archive string `json:"archive"`
Size string `json:"size"`
FileCount int `json:"file_count"`
Integrity bool `json:"integrity"`
Booted bool `json:"booted"`
BootSeconds int `json:"boot_seconds"`
HealthStatus int `json:"health_status"`
HealthPort string `json:"health_port"`
Passed bool `json:"passed"`
Error string `json:"error,omitempty"`
Logs string `json:"logs,omitempty"`
TotalSeconds int `json:"total_seconds"`
}
DrillResult holds the outcome of drilling a single app.
func RunDrill ¶ added in v0.13.0
func RunDrill(appName string, opts DrillOptions) (result *DrillResult, err error)
RunDrill executes a backup drill for a single app. Returns (nil, error) if the drill cannot start at all. Returns (*DrillResult, nil) once the drill runs — check result.Passed.
func (*DrillResult) String ¶ added in v0.13.0
func (r *DrillResult) String() string
String returns human-readable output for a single drill result.
type HealthCheck ¶ added in v0.13.0
type HealthCheck struct {
Path string // HTTP path to check (e.g. "/", "/health")
ExpectCodes []int // acceptable HTTP status codes
ContainerPort string // container-side port to map
BootTimeout time.Duration // max wait for container to start
HealthTimeout time.Duration // max wait for health endpoint to respond
}
HealthCheck defines how to verify that a restored app is working.
type ListEntry ¶
type ListEntry struct {
Name string `json:"name"`
Path string `json:"path"`
Size string `json:"size"`
CreatedAt string `json:"created_at"`
}
ListEntry represents a single backup in the list.
type Manifest ¶
type Manifest struct {
Version string `json:"version"`
CreatedAt string `json:"created_at"`
Services []ServiceInfo `json:"services"`
}
Manifest describes a backup archive.
type Mount ¶
type Mount struct {
Type string `json:"type"` // "volume" or "bind"
Name string `json:"name"` // volume name or host path
Source string `json:"source"` // host path
Destination string `json:"destination"` // container path
}
Mount represents a Docker volume or bind mount.
type PruneResult ¶ added in v0.24.0
type PruneResult struct {
Removed []string `json:"removed,omitempty"`
Freed int64 `json:"freed_bytes,omitempty"`
Kept int `json:"kept"`
KeptSum int64 `json:"kept_bytes"`
}
PruneResult describes what a prune removed.
func Prune ¶ added in v0.24.0
func Prune(dir string, cfg RetentionConfig) (*PruneResult, error)
Prune deletes the oldest archives until dir is inside cfg.
The newest archive is never deleted, whatever the limits say — including when it exceeds MaxBytes by itself. A limit that empties the directory has misunderstood what it was asked to bound, and the operator is left with nothing to restore from.
type RefusedMount ¶ added in v0.22.1
type RefusedMount struct {
Service string `json:"service"`
Type string `json:"type"`
Target string `json:"target"`
Reason string `json:"reason"`
}
RefusedMount records one mount that was declined, and why.
type RestoreOptions ¶ added in v0.22.1
type RestoreOptions struct {
// Service restores only the named service when set.
Service string
// AllowBind lists host paths the operator has explicitly permitted as
// bind-mount targets. Empty means no bind mount is restored.
AllowBind []string
}
RestoreOptions carries what the operator asked for, as distinct from what the archive declares. Every filesystem target restore writes to has to be traceable to this struct rather than to manifest.json.
type RestoreResult ¶
type RestoreResult struct {
Archive string `json:"archive"`
Services []string `json:"services"`
Volumes int `json:"volumes"`
// Refused lists mounts the archive asked for and restore declined to
// perform. A refusal is never silent: an operator who sees fewer volumes
// than expected must be able to find out why from the result alone.
Refused []RefusedMount `json:"refused,omitempty"`
}
RestoreResult is returned after a successful restore.
func Restore ¶
func Restore(archivePath string, opts RestoreOptions) (*RestoreResult, error)
Restore extracts an archive and restores volumes.
Paths declared in manifest.json are attacker-controlled whenever the archive came from somewhere else, which is the normal case for a tool built around portable backups. Nothing in the manifest selects a filesystem target on its own: volume names must look like volume names, and bind targets must have been named by the operator in opts.AllowBind.
type RetentionConfig ¶ added in v0.24.0
type RetentionConfig struct {
// MaxArchives is how many archives to keep, newest first. Zero, the
// default, keeps every archive.
MaxArchives int `yaml:"max_archives,omitempty" json:"max_archives"`
// MaxBytes caps the total size of the directory, as a size with a unit —
// "500MB", "20GB". Empty, the default, means no cap.
//
// Count alone is not a disk guarantee, and a disk guarantee is what someone
// running backup from cron actually wants: ten archives can be 200MB or
// 200GB depending on what was running when they were taken.
MaxBytes string `yaml:"max_bytes,omitempty" json:"max_bytes"`
}
RetentionConfig bounds how much backup history is kept on disk.
Both limits default to unlimited, which is the opposite of the incident directory and is deliberate. A pruned incident costs some history; a pruned backup can be the only remaining copy of data that no longer exists anywhere else. Deleting that is not a default anyone chose, so retention is something an operator turns on. What homebutler does without being asked is report the size, through `doctor`.
func (RetentionConfig) IsZero ¶ added in v0.24.0
func (r RetentionConfig) IsZero() bool
IsZero reports whether retention is unconfigured, so callers can skip the work and say nothing rather than reporting a prune that removed nothing.