Storage backends
Rivet v0.4 separates what a snapshot is (a versioned, reviewable tree of
manifest + files) from where it lives, per
ADR-0002. Git stays the reviewable default;
this is the reference for the other three: local/NAS (fs), the long tail of
cloud/NAS providers (rclone), and deduplicated backup history (restic).
Choosing a backend
Section titled “Choosing a backend”| Backend | Best for | Extra dependency | Encryption |
|---|---|---|---|
git (default) |
Reviewing every change in a diff before it happens | none | none (plaintext, reviewable by design) |
fs |
A NAS share already mounted over SMB/NFS, or a second local disk | none | Rivet-side age, on by default |
rclone |
S3, Backblaze B2, Google Drive, Dropbox, SFTP, WebDAV, and ~70 other providers | rclone, already configured |
Rivet-side age, on by default |
restic |
Deduplicated, incremental backup history without Git | restic |
restic’s own (Rivet’s encrypt flag does not apply) |
You can configure more than one — snapshot writes to every configured
backend (fan-out); restore reads from the first configured one, or a
specific one via --backend <id>.
Setting one up
Section titled “Setting one up”Each backend is a repeatable [[backend]] block in the snapshot
repository’s rivet.toml, added with rivet init --backend <id> ...:
# Local/mounted NAS share, encrypted by defaultrivet init --repo ~/rivet-snapshots --backend fs --path /mnt/nas/rivet-snapshots
# rclone remote (configure the remote itself with `rclone config` first)rivet init --repo ~/rivet-snapshots --backend rclone --remote b2:my-bucket/rivet
# restic repository, password from an env var you already setrivet init --repo ~/rivet-snapshots --backend restic \ --restic-repo sftp:nas:/backups/rivet --password-env RESTIC_PASSWORD
# Skip Rivet-side encryption for fs/rclone (e.g. you're already using# `rclone crypt`, or the destination is already encrypted at rest)rivet init --repo ~/rivet-snapshots --backend fs --path /mnt/nas/rivet-snapshots --no-encryptRun init --backend again with a different --backend/target to add a
second destination — rivet.toml accumulates one [[backend]] entry per
call. If no [[backend]] is ever added, Rivet behaves exactly as it did
through v0.3: Git only.
rivet snapshot # writes to every configured backendrivet restore # reads from the first configured onerivet restore --backend restic # reads from a specific oneAn unavailable optional tool (rclone/restic not installed, or not
configured) only breaks the backend that needs it — a snapshot to your other
configured destinations, or to Git, still succeeds. This is the same
availability-check pattern snap/flatpak have used since v0.1.
Encryption at rest
Section titled “Encryption at rest”fs and rclone wrap every blob they write — tracked file/AppImage/hook
content, manifest.toml, and the readable packages/ lists — with
age before it touches disk, by default
(encrypt = true; pass --no-encrypt to skip it). Directory structure and
file names stay in cleartext; only each blob’s bytes become opaque.
The first init --backend fs --encrypt (or rclone) generates an age
X25519 keypair:
- The public recipient (
age1...) is stored inrivet.toml. - The private identity is written to
~/.config/rivet/backends/<id>.age(mode 600) — never anywhere under the backend’s own storage path. Losing this file means losing access to that backend’s encrypted snapshots; back it up somewhere separate from the snapshots themselves.
Running init --backend fs --encrypt again reuses the existing identity at
that path rather than generating a new one and silently orphaning anything
already encrypted to the old key.
restic owns its own repository encryption end to end — Rivet’s encrypt
flag has no effect there. The repository password is never written to
rivet.toml in plaintext: point --password-env <VAR> at an environment
variable you already set (Rivet reads it from its own environment and
forwards only the value to restic), or --password-file <path> at a file
restic reads directly.
v0.5 note: this is a single-recipient, no-rotation primitive by design (ADR-0002 §4) — multi-recipient support and key rotation are the v0.5 secrets vault’s job (ADR-0003), built over this same
ageprimitive rather than a second crypto path.
How each backend stores a snapshot
Section titled “How each backend stores a snapshot”fs:<path>/snapshots/<utc-timestamp>/(manifest.toml+files/appimages/+hooks/+packages/), one directory per snapshot. Change-detection compares a SHA-256 hash of the whole tree against the latest snapshot’s and skips the write when nothing changed — the same “commit only on change” behaviour as Git.
rclone: identical shape, staged locally through the samefslogic (including encryption) and pushed withrclone sync/rclone copy. Change-detection fetches only the remote’s small hash file rather than downloading a whole snapshot to compare.restic: onerestic backupper snapshot, taggedrivet-hash:<sha256>so a repeat snapshot with nothing changed is detected and skipped — restic itself always creates a new snapshot entry perbackupcall regardless of content (dedup happens at the storage layer, not the snapshot-count layer), so Rivet’s own tag is what restores the “only on change” behaviour.
Testing hermetically
Section titled “Testing hermetically”rclone’s :local: connection string and a throwaway local restic
repository need no cloud credentials or rclone.conf entry, so CI installs
both and runs the full round-trip suite (cargo test -- --include-ignored)
against them on every push — see .github/workflows/ci.yml.