Backups
that answer
back.

Valvet is a control plane for restic — one hub that enrolls your nodes, runs plans on a schedule, and gives you a single monospace dashboard where every snapshot, byte, and failure is queryable, not inferred.

ssh ops@hub · valvet online
valvet nodes ls
NAME OS STATUS LAST SEEN
db-01 linux online 12s ago
web-01 linux online 18s ago
nas-01 linux online 4s ago
ws-mac-02 darwin stale 41m ago
 
valvet plans run pg-nightly --now
→ dispatched to db-01 over websocket
snapshot 8c4a2f1 · 12.8 GB → 3.1 GB · 4m 08s
1 of 1 succeeded · dedup 4.1×
 
Hub
1single binary
Axum + embedded UI + SQLite (WAL)
Port to expose
4865
nodes dial the hub; no inbound on agents
Backup engine
resticauto-managed
AES-256, deduplicated, content-addressed
restic 0.17+/ Local · SFTP · S3 · B2 · REST Server/ Linux amd64 · arm64/ SQLite WAL/ WebSocket · SSE/ JWT · argon2id/ MIT licensed
01 Why Valvet Three things, done seriously

Most backup tools are a cron job and a prayer.

Valvet is the opposite. It treats every backup as a first-class record, every node as a citizen of the fleet, and every failure as a named, addressable event. You don’t remember what ran last night — you query it.

01 / Hub + nodes

Enroll once.
Schedule anywhere.

Nodes register with a one-time token and hold a WebSocket back to the hub. Create a plan in the UI or API and the hub dispatches it over that socket — no SSH, no inbound ports on the node.

02 / Honest numbers

Every byte
accounted for.

Real repository stats from restic stats --mode raw-data: stored size after dedup and compression, file count, snapshot count. Refreshed after every successful backup.

03 / Quiet UI

Dense,
not busy.

A monospace dashboard that reads like a log file. Status is a dot, not a pill. Live job progress streamed over SSE. Browse a snapshot, download a file, restore to any node — without leaving the browser.

The premise
Backups shouldn’t be art. They’re a schedule, a destination, a retention policy, and a proof-of-integrity. Valvet makes that legible.
02 Plans Describe the backup, let the node run it
Declarative · Scheduled · Auditable

A plan is a contract, not a script.

Pick a node, a repository, the paths to back up, a cron schedule, a retention policy, optional pre/post hooks. That’s a plan. The hub schedules it; the node runs it; every run is recorded as an addressable event.

  • Cron-scheduled. Standard 5-field cron, or trigger Run Now from the UI.
  • Hooked. Pre-hooks for database dumps, post-hooks for notifications or cleanup.
  • Retention-aware. restic forget runs after every backup with your keep-last/daily/weekly/monthly/yearly policy.
plans / pg-nightly.yaml enabled
name: pg-nightly
node: db-01
repository: backups-s3
paths:
- /var/lib/postgresql/data
- /etc/postgresql
excludes:
- **/*.log
pre_hook: pg_dump -Fc -f /backup/pg.dump
schedule: "0 3 * * *" # 03:00 daily
retention:
keep_daily: 14 · keep_weekly: 8
keep_monthly: 12 · keep_yearly: 3
last run 4m 08s · next run in 7h 14m
03 Dashboard Read it like a log, click through for the why
dashboard / live 03:14:08 UTC
pg-nightly · db-0162.3%
28,411 / 45,609 files 12.8 / 20.6 GB 3m 14s left
pg-nightlysuccess · 12m ago
web-assetsrunning · 3m ago
home-weeklywarning · 1h ago
logs-rotateerror · 3h ago
etc-hourlysuccess · 4h ago
jobs · 7d peak 184 · avg 62
Live progress · Browsable snapshots

Answers at a glance. Proof on demand.

Job progress streams over Server-Sent Events — files counted, bytes written, ETA updated without a page refresh. Click any snapshot to walk its file tree in the browser, download a single file, or restore a whole directory to the same node or another one.

  • Live job stream. SSE from the hub pushes progress the moment the node reports it.
  • Snapshot browser. Walk the tree, download a file, restore a folder.
  • Failure forensics. stderr, exit code, the exact file path — not a support ticket.
04 How it works From zero to scheduled in four minutes

Four steps.
No magic, no daemons you can’t read.

Step 01

Run the hub.

One Rust binary with the React UI embedded and SQLite baked in. Start it under Docker or systemd, then visit /setup to create the admin account.

docker run -d -p 4865:4865 \
  -v valvet-data:/data \
  ghcr.io/lindstrm/valvet
→ listening on :4865
Step 02

Enroll a node.

From Nodes → Add Node in the UI, generate a one-time token. Pipe the install script on the target machine; it pulls the agent, enrolls it, and registers a systemd service. The node dials out — no inbound ports required.

curl -fsSL http://hub:4865/install.sh \
  | sh -s -- \
  --hub http://hub:4865 \
  --token <enrollment-token> \
  --name "db-01"
→ enrolled · websocket connected
Step 03

Add a repository.

Point at any restic-compatible backend: a local path, SFTP, S3, B2, or a REST Server. Valvet stores the repository password and the backend credentials; the repository is initialized the first time a plan runs against it.

name: backups-s3
type: s3
path: s3:s3.amazonaws.com/my-backups
env: AWS_ACCESS_KEY_ID, AWS_SECRET...
→ repository ready
Step 04

Create a plan.

Pick a node, a repository, the paths, a cron schedule, a retention policy. The first run fires on the next cron tick — or hit Run Now and watch progress stream in live.

plan: pg-nightly
schedule: 0 3 * * *
retention: keep_daily 14, keep_weekly 8
→ scheduled
   next run in 7h 14m
05 Compare Honest table, no stars

vs. the status quo.

  Valvet Bare restic + cron Enterprise suite
Fleet-wide view every node, one dashboard ssh + grep yes, behind a sales call
Self-hosted single binary + SQLite trivially "on-prem edition" SKU
Restic-native format snapshots stay portable native proprietary format
Dedup & encryption restic-native, auditable restic-native black box
Failure forensics stderr, exit code, file path scroll the mail support ticket
API-first same REST API the UI uses none SOAP, sometimes
Price free · MIT licensed free per TB, compounding

Stop guessing
what ran last night.

Run the hub on one machine. Enroll one node. Schedule one plan. Read the result on a screen that respects you.

/
 navigate  open ESC close