restic — Interfaces#
Interface catalog#
Backend#
- Package:
github.com/restic/restic/internal/backend - File:
internal/backend/backend.go:19 - Methods:
Properties() Properties Hasher() hash.Hash Remove(ctx context.Context, h Handle) error Close() error Save(ctx context.Context, h Handle, rd RewindReader) error Load(ctx context.Context, h Handle, length int, offset int64, fn func(rd io.Reader) error) error Stat(ctx context.Context, h Handle) (FileInfo, error) List(ctx context.Context, t FileType, fn func(FileInfo) error) error IsNotExist(err error) bool IsPermanentError(err error) bool Delete(ctx context.Context) error Warmup(ctx context.Context, h []Handle) ([]Handle, error) WarmupWait(ctx context.Context, h []Handle) error - Purpose: Raw object-storage contract. Abstracts all durable persistence: save a named binary blob, load it back (with offset support for partial reads), enumerate blobs by file type, remove them. The
Loadcallback design (fn func(io.Reader) error) allows retrying reads without re-allocating — callers may be invoked multiple times. - Implementations:
local,sftp,rest,s3,azure,gs(GCS),b2,swift,rclonestorage drivers;cache.Backend,retry.Backend,logger.Backend,sema.Backend,limiter.Backend,dryrun.Backenddecorator wrappers;mock.Backend,mem.Backend(testing). - Design quality: Well-segregated for its role. 13 methods is on the larger side, but each method corresponds to a distinct storage primitive. The
Warmup/WarmupWaitpair is notably forward-looking (supporting cold-to-hot storage tiering like AWS Glacier). TheIsPermanentError/IsNotExisterror classification methods allow the retry decorator to make intelligent decisions without inspecting implementation-specific error types. Follows ISP at the use-site by extracting smaller sub-interfaces (Lister,LoaderUnpacked) for components that only need a subset.
Repository#
- Package:
github.com/restic/restic/internal/restic - File:
internal/restic/repository.go:18 - Methods:
Connections() uint Config() Config PackSize() uint Key() *crypto.Key LoadIndex(ctx context.Context, p TerminalCounterFactory) error LookupBlob(t BlobType, id ID) []PackedBlob LookupBlobSize(t BlobType, id ID) (size uint, exists bool) NewAssociatedBlobSet() AssociatedBlobSet ListBlobs(ctx context.Context, fn func(PackedBlob)) error ListPacksFromIndex(ctx context.Context, packs IDSet) <-chan PackBlobs ListPack(ctx context.Context, id ID, packSize int64) (entries []Blob, hdrSize uint32, err error) LoadBlob(ctx context.Context, t BlobType, id ID, buf []byte) ([]byte, error) LoadBlobsFromPack(ctx context.Context, packID ID, blobs []Blob, handleBlobFn func(blob BlobHandle, buf []byte, err error) error) error WithBlobUploader(ctx context.Context, fn func(ctx context.Context, uploader BlobSaverWithAsync) error) error List(ctx context.Context, t FileType, fn func(ID, int64) error) error LoadRaw(ctx context.Context, t FileType, id ID) (data []byte, err error) LoadUnpacked(ctx context.Context, t FileType, id ID) (data []byte, err error) SaveUnpacked(ctx context.Context, t WriteableFileType, buf []byte) (ID, error) RemoveUnpacked(ctx context.Context, t WriteableFileType, id ID) error StartWarmup(ctx context.Context, packs IDSet) (WarmupJob, error) - Purpose: The complete high-level repository contract: encrypted and deduplicated content-addressable storage. Defined in the domain package — this is the dependency inversion anchor. All operations (backup, restore, check, prune) program against this interface, not the concrete
repository.Repositorystruct. Exposes blob-level operations (content-addressed chunks) and file-level operations (snapshots, indexes, keys, locks). - Implementations:
internal/repository.Repository(the sole production implementation). Test implementations ininternal/repository/testing.go. - Design quality: At ~20 methods, this is the most complex interface in the project. The breadth is justified — it is the central system abstraction, and splitting it further would just create artificial seams. The
WithBlobUploadermethod is notable: it uses a callback-based API to manage lifetime of an upload session (workers are started before fn is called, stopped and index is flushed after fn returns). This prevents misuse where a caller might forget to finalize an upload session.
Unwrapper#
- Package:
github.com/restic/restic/internal/backend - File:
internal/backend/backend.go:104 - Methods:
Unwrap() Backend - Purpose: Allows traversal of the backend decorator stack. The generic
AsBackend[B Backend](b Backend) Bfunction uses this to find a specific concrete type anywhere in the chain (e.g., to locate thecache.Backendfor cache invalidation after a successful write). Without this, the decorator chain would be opaque to the composition root. - Implementations: Every decorator wrapper:
cache.Backend,retry.Backend,logger.Backend,sema.Backend,limiter.Backend,dryrun.Backend. - Design quality: Elegant. A single-method interface with a companion generic function. The pattern appears at the
backendlayer (not theresticdomain layer), keeping decorator-stack introspection out of domain concerns. The use of generics (AsBackend[B]) avoids the need for a type-switch cascade.
BlobSaver, BlobSaverAsync, BlobSaverWithAsync, BlobLoader#
- Package:
github.com/restic/restic/internal/restic - File:
internal/restic/repository.go:160–181 - Methods:
// BlobSaver SaveBlob(ctx context.Context, tpe BlobType, buf []byte, id ID, storeDuplicate bool) (newID ID, known bool, sizeInRepo int, err error) // BlobSaverAsync SaveBlobAsync(ctx context.Context, tpe BlobType, buf []byte, id ID, storeDuplicate bool, cb func(newID ID, known bool, sizeInRepo int, err error)) // BlobSaverWithAsync = BlobSaver + BlobSaverAsync (composite) // BlobLoader LoadBlob(context.Context, BlobType, ID, []byte) ([]byte, error) - Purpose: Narrow capability interfaces for passing to workers.
archiver.FileSaverandarchiver.TreeSaverreceive aBlobSaverWithAsync(not the fullRepository) for their upload work. This enforces that upload workers can only save blobs — they cannot query the index, modify snapshots, or call other repository methods. The async variant enables fire-and-forget uploads with a callback for completion notification, enabling pipeline parallelism in the archiver. - Implementations:
internal/repository.Repositorysatisfies all three. Mock implementations in archiver tests. - Design quality: Excellent application of the Interface Segregation Principle. The archiver’s
FileSavergoroutines genuinely do not needLoadIndexorListBlobs. TheBlobSaverAsynccallback design also sidesteps channel complexity for communicating results from upload workers back to the tree assembler.
Factory#
- Package:
github.com/restic/restic/internal/backend/location - File:
internal/backend/location/registry.go:32 - Methods:
Scheme() string ParseConfig(s string) (interface{}, error) StripPassword(s string) string Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (backend.Backend, error) Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (backend.Backend, error) - Purpose: Plugin factory for registering and instantiating storage backends by URL scheme. The
Registrymaps scheme strings ("s3","sftp","rest", etc.) toFactoryimplementations.ParseConfigconverts a URL string into a driver-specific config struct;Open/Createinstantiate theBackendfrom that config.StripPasswordsanitizes URLs for logging. - Implementations: Each storage driver provides a factory via
NewHTTPBackendFactoryorNewLimitedBackendFactorygeneric constructors, registered viainit()side-effects ininternal/backend/all. The genericgenericBackendFactory[C, T]struct bridges the type-erased interface with typed driver configs. - Design quality: The use of
interface{}for the config parameter is a necessary type erasure at the registry boundary — the registry cannot know each driver’s config type. The generic constructors (NewHTTPBackendFactory[C, T]) restore type safety at the driver registration site. Theinterface{}leakage is minimal (only inFactory, not in any driver code directly).
FS and File#
- Package:
github.com/restic/restic/internal/fs - File:
internal/fs/interface.go:10–52 - Methods (
FS):OpenFile(name string, flag int, metadataOnly bool) (File, error) Lstat(name string) (*ExtendedFileInfo, error) Join(elem ...string) string Separator() string Abs(path string) (string, error) Clean(path string) string VolumeName(path string) string IsAbs(path string) bool Dir(path string) string Base(path string) string - Methods (
File):MakeReadable() error Read(p []byte) (n int, err error) // io.Reader Close() error // io.Closer Readdirnames(n int) ([]string, error) Stat() (*ExtendedFileInfo, error) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*data.Node, error) - Purpose: Filesystem abstraction enabling backup of virtual or OS filesystems. The critical feature is the
metadataOnlyflag onOpenFile: when set, the implementation can return aFileobject without actually opening/reading the file — metadata (stat, xattr) is accessible but reading data requires callingMakeReadable(). This enables efficient pre-scan passes (gathering file counts/sizes without data reads) and supports platforms where some file types cannot be opened normally. - Implementations:
fs.OSFS(wraps the real OS),fs.VirtualFS(for testing with in-memory trees),fs.ModeFilter(wraps another FS, filtering by file mode). - Design quality:
Fileembedsio.Readerandio.Closer, honoring stdlib interfaces. TheToNode()method onFileis a domain-layer coupling (returns*data.Node) — it bleeds domain types into the filesystem abstraction. However, sincefsis only consumed by the archiver anddata.Nodeis the canonical file-metadata type, this is pragmatic rather than problematic.
Layout#
- Package:
github.com/restic/restic/internal/backend/layout - File:
internal/backend/layout/layout.go:8 - Methods:
Filename(backend.Handle) string Dirname(backend.Handle) string Basedir(backend.FileType) (dir string, subdirs bool) Paths() []string Name() string - Purpose: Strategy interface for computing filesystem paths from backend handles. Allows the
localandsftpbackends to support multiple on-disk layouts (default restic layout, old legacy layout, S3-style flat layout). The layout is selected at repository open time and passed to the storage driver — the driver then delegates all path computation to the layout. - Implementations:
layout.DefaultLayout(directory fan-out:<type>/<first-2-hex-chars>/<full-hash>),layout.S3LegacyLayout(flat),layout.RESTLayout. - Design quality: Clean strategy pattern. 5 methods, all path-computation related. No stateful behavior. Well-segregated.
ui.Terminal#
- Package:
github.com/restic/restic/internal/ui - File:
internal/ui/terminal.go:10 - Methods:
Print(line string) Error(line string) SetStatus(lines []string) CanUpdateStatus() bool InputRaw() io.ReadCloser InputIsTerminal() bool ReadPassword(ctx context.Context, prompt string) (string, error) OutputWriter() io.Writer OutputRaw() io.Writer OutputIsTerminal() bool - Purpose: Abstracts all terminal I/O: status lines (in-place update), log output, error output, and interactive password prompting. Allows the same command code to work against a real
termstatus.Terminal(with ANSI escape sequences) and against a plain line-oriented writer (for--jsonmode, non-TTY environments, and tests). - Implementations:
termstatus.Terminal(full ANSI terminal), a plainui.StdioTerminalused in JSON/non-interactive mode. - Design quality: Broad (10 methods) but cohesive — all methods relate to the terminal I/O contract. The separation of
Print/Error(for immediate lines) fromSetStatus(for in-place updateable lines) fromOutputWriter(for raw concurrent-safe writing) covers the full range of output modes restic needs without leaking implementation details of the ANSI terminal.
Interface patterns#
- Size distribution: Ranges from 1 method (
Unwrapper) to ~20 methods (Repository). Most interfaces have 3–10 methods. The project heavily applies ISP: large interfaces likeRepositoryare broken into small capability subsets (Lister,LoaderUnpacked,BlobSaver,BlobLoader) for use by specific callers. - Embedding: Extensively used for interface composition.
ListerLoaderUnpackedembedsLister+LoaderUnpacked.BlobSaverWithAsyncembedsBlobSaver+BlobSaverAsync.Unpacked[FT]embedsListerLoaderUnpacked+SaverUnpacked[FT]+RemoverUnpacked[FT].FreezeBackendembedsBackend.Fileembedsio.Reader+io.Closer. - Generics:
SaverUnpacked[FT FileTypes],RemoverUnpacked[FT FileTypes],Unpacked[FT FileTypes]use type constraints to restrict file types toFileType | WriteableFileType— a rare but effective use of Go generics to prevent callingSaveUnpackedwith an incorrect file type at compile time.AsBackend[B Backend]is a generic function for type-safe decorator-stack traversal. - Implicit satisfaction: Interfaces are defined predominantly by consumers in the domain package (
internal/restic). Drivers ininternal/backend/*satisfyBackendwithout importing from the domain package — the dependency arrow points inward. This is a textbook application of the Go interface/dependency rule. - stdlib interfaces used:
io.Readerandio.Closerembedded infs.File.hash.Hashreturned byBackend.Hasher().io.ReadCloserandio.Writerinui.Terminal. TheRewindReaderinterface (returned by various helpers) extendsio.Reader.
Key abstractions#
1. backend.Backend — The storage primitive#
The foundational contract. Every persistence operation goes through it. Its 13 methods map directly to the operations any object-store must support. Its error-classification methods (IsPermanentError, IsNotExist) are what enable the retry.Backend decorator to retry transient failures without knowing which storage system it wraps.
2. restic.Repository — The encryption and deduplication gateway#
The central domain contract. All high-level operations (archiving, restoring, checking, pruning) program against this interface. By placing it in the innermost package, restic ensures that no core business logic can accidentally bypass encryption or the content-addressed index.
3. restic.BlobSaver / BlobSaverAsync — Minimum-capability upload contract#
The narrow interfaces passed to archiver workers. Demonstrate mature ISP application: workers receive only the one capability they need, making the data flow more auditable and the test surface smaller.
4. backend.Unwrapper + AsBackend[B] — Decorator-stack introspection#
A single-method interface combined with a generic traversal function. Solves the practical problem that the composition root needs to locate specific decorators (e.g., cache) after they’ve been buried in the stack — without requiring the stack to expose a typed accessor for every possible decorator type.
5. fs.FS / fs.File — Virtual filesystem abstraction#
Enables the archiver to work against real OS filesystems, in-memory test fixtures, and filtered views using the same code. The metadataOnly flag on OpenFile is the key innovation: it allows a fast metadata-only pre-scan pass before committing to a full data read, which is important for large backup sets.
Interface-driven extensibility#
restic’s extensibility is almost entirely interface-driven:
Storage backends: Any type satisfying backend.Backend can be registered as a storage driver via the location.Factory interface + Registry. The backend/all package uses blank-import init() side-effects to auto-register all built-in drivers. Third-party backends could be added by importing additional packages.
Backend decorators: The backend.Backend + backend.Unwrapper pair defines the decorator composability contract. Any cross-cutting concern (rate limiting, caching, observability, dry-run simulation) can be implemented as a wrapping Backend without modifying the drivers. The explicit composition in global.wrapBackend means the decorator order is auditable.
Filesystem sources: The fs.FS interface allows the archiver to back up sources other than the real OS filesystem. This is used in integration tests (in-memory VirtualFS) and could support future features like container filesystem snapshotting.
Repository migrations: The internal/migrations package defines a Migration interface for applying structural changes to existing repositories. Each migration is independently implemented and registered, allowing the restic migrate command to list and apply them.
Layout strategies: The layout.Layout interface allows local and SFTP backends to support multiple on-disk directory organizations (current default, S3-legacy, REST-style flat) via strategy injection at open time.
The architecture ensures that all extension points flow through interfaces defined in the domain package (internal/restic) or the backend abstraction layer (internal/backend), never through concrete type imports. This makes the system legible: adding a new storage backend requires touching only the driver package and the all registration package.