appbackup

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package appbackup is the app-data backup ENGINE: it produces encrypted snapshots of an app's stateful data — a logical database dump (pg_dump / mysqldump / mongodump) or a raw data-volume tar — by running a THROWAWAY sidecar container (`docker run --rm …`) on the write plane, piping its stdout through gzip and the tamper-evident AES-256-GCM stream from internal/backup, to a sink (a local .mbk file and/or an off-box object store).

Security posture:

  • A sidecar is a fresh one-shot container that removes itself — NEVER an exec into a running container (Mooring does not give shells into live containers).
  • Database credentials are passed via a 0600 --env-file, NEVER on the argv (argv is world-visible in `ps`). The file lives in a 0700 dir and is removed after the run.
  • All container mutation rides the existing write-plane Runner (the §0 RAM gate + the one-docker-child semaphore); this package only builds STATIC argv (no shell).
  • The plaintext dump never touches disk unencrypted — it streams sidecar→gzip→Encrypt→sink.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LocalFile

func LocalFile(dir, id string) (*os.File, string, error)

LocalFile opens a 0600 ciphertext sink under dir/<id>.mbk, returning the file and its path. The caller writes the Produce result into it and catalogues it.

func RestoreVolume

func RestoreVolume(ctx context.Context, r StdinRunner, volume, image string, key []byte, src io.Reader, tmpDir string, onLine func(string)) error

RestoreVolume restores a volume snapshot IN PLACE: it overwrites the volume's contents with the snapshot. DESTRUCTIVE — the caller MUST stop every container using the volume first and gate the action.

The archive is decrypted+AUTHENTICATED IN FULL to a transient 0600 file (under tmpDir) BEFORE the tar sidecar touches the volume. This matters because backup.Decrypt is a chunked stream: were we to pipe it straight into `tar`, a corrupt/truncated archive past the first 64 KiB chunk would already have extracted the leading chunks into the live volume before the defect surfaced. Verifying the whole archive first makes "a bad archive never clobbers the volume" actually true. Only then is the verified plaintext gunzipped into a throwaway `docker run -i --rm -v <volume>:/data <image> tar -x -C /data` sidecar.

Types

type Kind

type Kind string

Kind is the backup producer type.

const (
	KindPostgres Kind = "postgres" // pg_dump (custom format); password via PGPASSWORD env-file
	KindMySQL    Kind = "mysql"    // mysqldump (also mariadb); password via MYSQL_PWD env-file
	KindVolume   Kind = "volume"   // tar of a named data volume — the universal fallback for

)

type Options

type Options struct {
	EnvFileDir string
	// Gzip compresses the plaintext before encryption (dumps compress well). Default true;
	// a volume that is already compressed data still tars fine, just with less gain.
	NoGzip bool
	OnErr  func(string) // sidecar stderr sink (best-effort logging); may be nil
}

EnvFileDir is where the transient 0600 credential env-file is written. It MUST be a Mooring-owned 0700 directory. The file is removed as soon as the sidecar exits.

type Result

type Result struct {
	Ciphertext int64  // bytes written to the sink (the .mbk size)
	SHA256     string // sha256 of the ciphertext (integrity of the stored blob)
}

Result is the outcome of a successful Produce.

func Produce

func Produce(ctx context.Context, r StreamRunner, spec Spec, key []byte, dst io.Writer, opts Options) (Result, error)

Produce runs the target's sidecar and streams sidecar-stdout → gzip → backup.Encrypt → dst. The caller MUST already hold the one-docker-child semaphore (Produce uses RunStreamHeld). key is the 32-byte master key. dst is the ciphertext sink (a local file and/or an S3 multi-writer). Returns the ciphertext size + sha256. The plaintext is never written to disk.

type Spec

type Spec struct {
	Kind Kind

	// Image is the sidecar image, digest- or tag-pinned by the caller. For a DB dump it is
	// normally the DB service's OWN image (so the client tool version matches the server);
	// for a volume tar it is a minimal helper (e.g. a pinned busybox/alpine) that has `tar`.
	Image string

	// Network is the app's compose network the sidecar joins to reach the DB service
	// (e.g. "<project>_default"). Empty for a volume tar (no network needed).
	Network string

	// DB-dump fields.
	Host     string // the DB service name on the compose network (e.g. "db")
	Port     int    // 0 → engine default
	User     string
	DBName   string
	Password string // resolved from the secret store by the caller; passed via 0600 env-file

	// Volume-tar field.
	Volume string // the named data volume to snapshot (read-only mount)
}

Spec describes one backup target. Exactly the fields for its Kind are used.

type StdinRunner

type StdinRunner interface {
	RunStreamStdinHeld(ctx context.Context, argv []string, stdin io.Reader, onLine func(string)) error
}

StdinRunner runs a static-argv docker command feeding stdin to the child (satisfied by dockerexec.Runner.RunStreamStdinHeld); the caller holds the one-docker-child semaphore.

type StreamRunner

type StreamRunner interface {
	RunStreamHeld(ctx context.Context, argv []string, stdout io.Writer, onErrLine func(string)) error
}

StreamRunner runs a static-argv `docker` command, streaming its raw stdout to w and its stderr line-by-line to onErrLine. It is satisfied by dockerexec.Runner (RunStreamHeld — the caller holds the one-docker-child semaphore); a fake implements it in tests.

Jump to

Keyboard shortcuts

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