ADR-0002 — Storage backends
- Status: Accepted (2026-07-24, Opus review)
- Related: ROADMAP v0.4, CODE_REVIEW M2
- Acceptance note: Confirmed. For v0.2 only
GitBackendis implemented, but theBackendtrait (withread_snapshottaking aSnapshotRefthat carries an optional revision) is defined now so the--revisionfix and later backends are additive. fs/rclone/restic land in v0.4.
Context
Section titled “Context”The Charter names Git as the first storage backend, implying others. The user
wants snapshots to go to “various places / NAS / cloud providers.” Today
snapshot.rs calls the git executable directly throughout — repository init,
staging, commit, push, force-push, delete, history — so storage location and the
snapshot workflow are fused.
We need to separate what a snapshot is (a versioned, reviewable tree of manifest + files) from where it lives.
Decision
Section titled “Decision”Define a Backend trait in rivet-core and move all git calls behind a
GitBackend in rivet-backend. Add further backends as separate implementors.
pub trait Backend { fn id(&self) -> &'static str; // "git", "fs", "rclone", "restic" fn init(&self, cfg: &BackendConfig) -> Result<()>; fn write_snapshot(&self, snap: &SnapshotTree, msg: &str) -> Result<SnapshotId>; fn read_snapshot(&self, id: &SnapshotRef) -> Result<SnapshotTree>; // supports --revision fn list(&self) -> Result<Vec<SnapshotMeta>>; // history fn push(&self) -> Result<()>; // no-op for local fs}Backends shipped by v0.4:
GitBackend— the reviewable default; unchanged behaviour, keepsghprivate-remote automation.SnapshotRefgains Git-ref support → fixes H3.FsBackend— a plain local or already-mounted directory. This is the NAS answer when the share is mounted (SMB/NFS); no extra dependency.RcloneBackend— delegates torclonefor the long tail of cloud/NAS: S3, Backblaze B2, Google Drive, Dropbox, SFTP, WebDAV, SMB. One dependency, ~70 providers, matches Rivet’s “delegate to proven tools” principle.ResticBackend— deduplicated, encrypted, incremental history for users who want backup semantics without Git.
Config lives in a [backend] (and repeatable [[backend]]) block in
rivet.toml; rivet init --backend <id> scaffolds it. Multiple destinations
are allowed (fan-out). Non-Git backends get encryption at rest: restic is
native; fs/rclone wrap the snapshot tree with age (ADR-0003 primitive) before
it leaves the machine.
Alternatives considered
Section titled “Alternatives considered”- Git-only, tell users to
git remoteto a NAS. Doesn’t cover object stores or consumer clouds, and forces Git even where the user wants plain files. Rejected — the user explicitly asked for NAS/cloud choice. - Reimplement S3/SFTP clients in-tree. Large surface, security-sensitive,
redundant with
rclone/restic. Rejected. - Only restic. Great for backup, poor for the “reviewable in a Git diff” property that makes Rivet auditable. Keep Git as default, restic as an option.
Consequences
Section titled “Consequences”- Positive: storage is a user choice; NAS/cloud covered with minimal new code;
--revisionfalls out ofread_snapshot. - Positive: whole-snapshot encryption at rest is available independently of the per-secret vault.
- Cost: an abstraction over heterogeneous history models (Git commits vs
restic snapshots vs fs timestamps);
SnapshotId/SnapshotRefmust be general. - Risk:
rclone/resticbecome optional runtime dependencies — handled the same way assnap/flatpaktoday (availability check → notice, never a hard failure of unrelated backends).