v0.17.0 — now on crates.io

Encrypted backups.
Synced everywhere.

vivo·keep wraps restic to orchestrate backups with multi-remote sync — S3, B2, and more. Credentials stay encrypted at rest with SOPS + age.

$ curl -sSf https://raw.githubusercontent.com/dantuck/vivo/main/install.sh | sh

How it works

One command.
Four phases.

1

Backup

restic snapshots your directories to a local repository

2

Check

repo integrity is verified before any remote sync

3

Forget

old snapshots are pruned by your retention policy

4

Sync

data is pushed to all configured remote backends

Features

Everything restic
was missing.

Credentials encrypted at rest

SOPS + age keeps your remote keys out of plaintext configs. Secrets are decrypted in-memory only at runtime.

Multi-remote sync

Push the same backup to S3, Backblaze B2, MinIO, and more in one run. Add as many remotes as you need.

Step control

Use --start-step to skip already-completed phases. Resume from forget, or run sync-only.

Self-updating

Run `vivo update` to pull the latest release. Background version checks notify you after each run.

Flexible sources

Back up a single directory, or point `files-from` at a list of paths. Add an exclude file to skip what you do not need.

Task chaining

Tasks can call other tasks and run shell commands post-backup. Circular references are detected and skipped.

Dry-run mode

Test your full configuration without writing a single byte. Skips remote sync, forwards --dry-run to restic. Set `dry-run true` on a task to override it per task.

Health checks

Run `vivo doctor` to verify that required tools are installed, your config and secrets are valid, and all remotes are reachable. Run `vivo doctor --fix` to auto-install any missing dependencies, including FUSE for `vivo mount`.

Interactive TUI

Run `vivo manage` to open a full-screen config manager. Add, edit, and delete tasks, remotes, and call chains interactively. Set the default task with a keystroke — no manual KDL editing required.

Browse backups as files

Run `vivo mount` to explore any local or remote backup repo as a FUSE filesystem. Navigate snapshots, find individual files, and restore without touching restic directly.

Schedules itself

Run `vivo schedule add` and vivo installs a daily launchd agent, systemd timer or cron entry for you, with the right PATH, your config and key locations, and a log file. No hand-written plist.

Knows when it is broken

Run `vivo status` for the newest snapshot, the last sync to each remote, and your schedule. It exits non-zero when something is stale or failing. Add `on-failure` and `on-success` hooks to a task to send yourself a notification.

One-command restore

Run `vivo restore` to pick a task, snapshot and destination, or restore just one path. `vivo snapshots` lists what you have. No FUSE needed, and restores never overwrite your live files by default.

Rebuild a lost machine

Every remote also keeps a small recovery copy of your encrypted secrets and config. On a new machine, restore your age key and run `vivo recover` to get your setup back, then `vivo restore` for your files.

Changelog

What's new.

v0.17.02026-10-16

Features

  • add schedule, status, snapshots, restore and recover commands
v0.16.22026-10-16

Bug Fixes

  • **tui:** keep text and selection readable on dark themes
v0.16.12026-10-16

Bug Fixes

  • **update:** skip releases without a binary for this platform
v0.16.02026-10-16

Features

  • **setup:** guide first-run setup through remotes, credentials and a first backup

Bug Fixes

  • **build:** replace unmaintained knuffel with the kdl crate
v0.15.12026-09-06

Bug Fixes

  • expand leading ~ to $HOME in repo/directory paths

Configuration

KDL config.
Human-readable.

Configure backups in a clean KDL file. Set your repo, directories to back up, retention policy, and remote destinations — all in one place.

Secrets are stored separately in a SOPS-encrypted YAML file and injected as environment variables at runtime — never written to disk in plaintext.

Default config path ~/.config/vivo/backup.kdl
Default secrets path ~/.config/vivo/secrets.yaml
backup.kdl
// default task to run
default-task "backup"

tasks {
    task "backup" {
        backup {
            repo "$HOME/.local/share/restic/main"
            directory "$HOME"
            exclude-file "$HOME/.config/vivo/excludes"

            retention {
                daily   7
                weekly  5
                monthly 12
                yearly  2
            }

            remote "s3:https://s3.amazonaws.com/my-bucket" {
                credentials "aws"
            }
            remote "b2:my-bucket:restic/main" {
                credentials "b2"
            }
        }

        command "notify-send 'Backup complete'"
    }
}

Remote Backends

Your data.
Your clouds.

S3-compatible

AWS S3, MinIO, or any S3-compatible endpoint. Uses restic copy for fast, verified transfer. Initialize the remote repo once before first sync — `vivo remote add` and `vivo remote test` offer to do it for you.

s3:https://s3.amazonaws.com/my-bucket
Initialize once restic init --repo s3:https://s3.amazonaws.com/my-bucket

RustFS

Self-hosted S3-compatible object storage via the rustfs: prefix — perfect for home labs and private NAS setups. The bucket is created automatically on first sync, and no remote restic init is needed. Set mc-max-workers and mc-limit-upload per remote to throttle uploads and avoid overwhelming your server.

rustfs:http://nas:9000/bucket

Backblaze B2

Cost-effective cold storage via the b2 CLI. Uses b2 sync with automatic replacement of stale files. Credentials are imported automatically and re-authorized on failure. No remote restic repo initialization needed.

b2:my-bucket:restic/main

Recovery copy. After each sync, vivo also keeps your encrypted secrets.yaml and an age-encrypted backup.kdl next to each repository (in a sibling .vivo-recovery folder, or a -vivo-recovery bucket for a RustFS bucket root). They are only re-uploaded when they change, and a Backblaze B2 repo needs a folder in its bucket (b2:bucket:folder) to have one. Opt a remote out with recovery false.

Quick Start

Up and running
in six steps.

Initialize vivo

A guided setup: checks prerequisites, creates your age key, sets the restic password, adds and tests an off-site remote, then offers a dry-run and a first backup. Steps 2–4 below are the same pieces, available on their own. Not on a terminal? It writes a static starter config instead.

$ vivo init

Add a backup task

Create a named task with its restic repo and the directory to back up. Or use vivo manage for an interactive TUI.

$ vivo task add --name main --repo ~/.local/share/restic/main --dir ~
$ vivo manage

Add remote destinations

Attach one or more remotes to a task — S3, RustFS, or Backblaze B2. Run vivo remote add with no flags for a guided flow, then test every remote any time.

$ vivo secrets import-s3 --profile rustfs
$ vivo remote add --task main --url "rustfs:http://nas:9000/bucket" --credentials rustfs
$ vivo remote test
$ vivo remote list

Add your secrets

Set your restic password and any additional remote credentials, encrypted with SOPS + age.

$ vivo secrets edit

Run a backup

Dry-run first to verify your setup, then run the real backup.

$ vivo --dry-run
$ vivo

vivo exits non-zero if the backup fails or any remote fails to sync, so schedulers can alert on it.

Automate it and check in

Schedule a daily backup, see at a glance whether it is working, and get files back when you need them.

$ vivo schedule add
$ vivo status
$ vivo restore

Scheduling

One command.
Backed up every day.

vivo schedule add installs the right scheduler for your system and takes care of the details that make hand-written jobs fail: the full path to vivo, a PATH that finds restic, sops and your sync tools, your custom config and key locations, and a log file. A Mac that is asleep at backup time runs it on wake. vivo exits non-zero on any failure, and vivo status tells you whether the last run actually worked.

$ vivo schedule add             # daily at 02:00, or --at 03:30
$ vivo schedule status          # is it installed, and when does it run?
$ vivo status                   # latest snapshot and last sync per remote
$ vivo schedule remove

What vivo installs for you:

macOS (launchd)

~/Library/LaunchAgents/dev.vivo.backup.plist
<key>ProgramArguments</key>
<array><string>/usr/local/bin/vivo</string></array>
<key>EnvironmentVariables</key>
<dict><key>PATH</key>
  <string>/opt/homebrew/bin:/usr/bin:/bin</string>
  <!-- config, secrets, age key, state dir -->
</dict>
<key>StartCalendarInterval</key>
<dict><key>Hour</key><integer>2</integer>
  <key>Minute</key><integer>0</integer></dict>

Linux (systemd timer)

~/.config/systemd/user/vivo.{service,timer}
# vivo.service
[Service]
Type=oneshot
Environment="PATH=..."
ExecStart=/home/you/.local/bin/vivo

# vivo.timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

cron

your crontab (no systemd)
# >>> vivo backup >>>
0 2 * * * PATH='...' '/usr/local/bin/vivo' \
  >> '~/.local/state/vivo/vivo.log' 2>&1
# <<< vivo backup <<<

Install

Get vivo.

One-line install

Linux and macOS. Downloads the latest binary from GitHub Releases and verifies the SHA256 checksum.

$ curl -sSf https://raw.githubusercontent.com/dantuck/vivo/main/install.sh | sh

From Cargo

Requires Rust installed. Builds from source and installs to your Cargo bin directory.

$ cargo install vivo

Build from source

Clone from Codeberg or GitHub and build manually.

$ git clone https://codeberg.org/tuck/vivo
cd vivo
cargo build --release
cp target/release/vivo /usr/local/bin/

Already installed? Run vivo update to upgrade to the latest release, or vivo update --dry-run to preview without applying.