Explore Vestal

Documentation

Everything you need, on one page

From zero to a verified check in about five minutes. The agents are plain bash: read them before you run them, they're short on purpose.

Quickstart

  1. Create an account and a check

    Sign up (email + password, nothing else), then create a check from the dashboard: give it a name, pick your backup tool, and set how often it should report. For a nightly backup, an expected interval of 1 day with a 2 hour grace period is a good default. You'll get a unique ping URL for that check.

  2. Download the agent for your tool

    Each agent is a single bash script with no dependencies beyond curl and your backup tool itself. It does not take backups and never sees your data. It verifies an existing repository and reports the verdict.

  3. Add one line after your backup command

    # your existing job, unchanged:
    restic backup /srv/data
    
    # new: verify the repository, then report to Vestal:
    RESTIC_REPOSITORY=/path/to/repo \
    VESTAL_PING_URL=https://vestalapp.com/ping/<your-token> \
    bash vestal-restic.sh

    That's it. If verification passes, Vestal records a check-in. If it fails, or the script crashes, or the job never runs at all, you get alerted on every channel you've configured.

Agent: restic

Runs restic check against your repository. Set VESTAL_READ_DATA_SUBSET to also re-read a percentage of actual pack data: slower, but it's the part that catches silent bit-rot that a metadata-only check cannot see. Written against restic 0.17+ conventions. If you haven't restore-tested a restic repo by hand before, this walkthrough covers what actually goes wrong and how to check for it yourself first.

VariableRequiredMeaning
RESTIC_REPOSITORYyesyour repository, as restic already expects
RESTIC_PASSWORDyesor RESTIC_PASSWORD_FILE / RESTIC_PASSWORD_COMMAND, as usual
VESTAL_PING_URLyesthe check's ping URL from the dashboard
VESTAL_READ_DATA_SUBSETnoe.g. 5%, re-read and verify that share of real data each run
RESTIC_REPOSITORY=sftp:backup@nas:/srv/restic-repo \
RESTIC_PASSWORD_FILE=/root/.restic-pass \
VESTAL_READ_DATA_SUBSET=5% \
VESTAL_PING_URL=https://vestalapp.com/ping/<token> \
bash vestal-restic.sh

Agent: BorgBackup

Runs borg check. Unlike restic, Borg's --verify-data has no percentage knob: it decrypts and CRC-checks every data block or none, so VESTAL_VERIFY_DATA is a plain on/off switch. Written against Borg 1.4.x. See how this compares to restic and Kopia's own verify commands here.

VariableRequiredMeaning
BORG_REPOyesyour repository, as Borg already expects
BORG_PASSPHRASEyesor BORG_PASSCOMMAND / BORG_PASSPHRASE_FD
VESTAL_PING_URLyesthe check's ping URL
VESTAL_VERIFY_DATAnoany non-empty value adds --verify-data (full data verification, all-or-nothing)

Agent: Kopia

Runs kopia content verify, and with VESTAL_VERIFY_PERCENT set, a full verify that downloads and checksums that share of real data plus kopia snapshot verify. The agent always uses a fresh cache directory: in our own pre-release testing, a warm local cache reported a deliberately-corrupted repository as healthy. That's not a theoretical concern; the agent exists in its current form because of it.

VariableRequiredMeaning
KOPIA_CONFIG_PATHyespath to the kopia config file for the repository connection
VESTAL_PING_URLyesthe check's ping URL
VESTAL_VERIFY_PERCENTnoe.g. 10, download and checksum that percentage of file data

Agent: Duplicati

Runs duplicati-cli test with --full-remote-verification and retries disabled. Duplicati's default retry behavior turns real corruption into a multi-minute hang instead of a fast failure. With retries off, a broken backend produces an immediate Hash mismatch and an immediate alert.

VariableRequiredMeaning
VESTAL_DUPLICATI_TARGETyesthe backup's storage URL, e.g. file:///mnt/backups/work
VESTAL_DUPLICATI_PASSPHRASEyesthe backup's encryption passphrase
VESTAL_PING_URLyesthe check's ping URL
VESTAL_DUPLICATI_CLInopath to the CLI if it isn't on $PATH
VESTAL_VERIFY_SAMPLESnohow many remote file samples to download and verify (default 1)

Ping API

Using a tool without a ready-made agent? The ping API is three URLs. The token is a 192-bit bearer secret, so treat the URL like a password.

EndpointMeaning
GET|POST /ping/<token>success: verification passed
GET|POST /ping/<token>/starta run has begun (optional, enables duration tracking)
GET|POST /ping/<token>/failexplicit failure: alerts immediately, no waiting for the deadline

Optional form/query parameters on any of them: message (short text, e.g. a log tail) and duration_ms. Pings are rate-limited to 30 per minute per token.

# minimal integration for any tool, in two lines:
my-backup-tool verify --repo /srv/repo \
  && curl -fsS https://vestalapp.com/ping/<token> \
  || curl -fsS https://vestalapp.com/ping/<token>/fail

REST API

Everything the dashboard does is available with your API key (shown once at signup, revocable any time): Authorization: Bearer vs_…. Keys are stored server-side only as SHA-256 hashes.

EndpointWhat it does
GET /api/checkslist your checks, each with its ping URL
POST /api/checkscreate a check (name, backend, expected_interval_seconds, grace_period_seconds)
DELETE /api/checks/:iddelete a check and its ping history
GET /api/alert-channelslist alert channels
POST /api/alert-channelsadd one (kind: discord | slack | email | webhook, target)
DELETE /api/alert-channels/:idremove one
curl -s https://vestalapp.com/api/checks \
  -H "Authorization: Bearer vs_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"nas-nightly","backend":"restic","expected_interval_seconds":86400,"grace_period_seconds":7200}' \
  -X POST

The docs end here. The setup takes five minutes.

Set up your first verified check in about five minutes. No card, no trial clock, no sales call.