- Shell 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| hooks | ||
| .gitignore | ||
| AGENTS.md | ||
| backrest | ||
| backup.example.json | ||
| backup.schema.json | ||
| lib.sh | ||
| README.md | ||
| SYSTEMD_TIMER.txt | ||
backrest
A JSON-driven backup runner wrapping restic (incremental, deduplicated snapshots) and rsync (mirror copies) under a single config file.
Dependencies
jq— JSON parsingcheck-jsonschema— config validationrestic— fortype: resticprofilesrsync— fortype: rsyncprofilesrclone— fortype: s3profilessqlite3— fortype: sqliteprofiles
sudo apt install jq restic rsync rclone sqlite3
pip install check-jsonschema
Quick start
cp backup.example.json backup.json
# edit backup.json with your paths and profiles
./backrest --validate # check config syntax
./backrest --dry-run # preview what will run
./backrest # run all enabled profiles
Editor autocomplete
backup.json contains a $schema key pointing to backup.schema.json. This gives JSON schema validation and autocomplete in editors with LSP support (e.g. Neovim's jsonls, VSCode). The $schema field is ignored by check-jsonschema, so it's safe to keep.
Configuration
backup.json has four sections:
paths
Named placeholders substituted anywhere with ${name}:
"paths": {
"fast": "/mnt/cloud-drive",
"storage": "/mnt/backup-drive",
"home": "/home/you"
}
global
Settings shared across all profiles:
| Key | Description |
|---|---|
restic_password_file |
Path to file containing the restic repo password |
restic_password_command |
Command that prints the password (alternative) |
restic_password_autogen |
Generate a random password into ~/.config/backrest/restic-password on first run |
check |
Default integrity check: full, quick, or never |
healthchecks_url |
Healthchecks.io ping URL (optional) |
profiles
Each key is a profile name. Supported backends:
restic
Incremental, deduplicated snapshots. Requires a restic repository (auto-initialized on first run).
"photos": {
"type": "restic",
"src": "${fast}/media/photos",
"repo": "${storage}/backrest/photos",
"retention": "keep-daily 7 keep-weekly 4 keep-monthly 6",
"exclude": ["*.tmp", "cache/"],
"check": "quick",
"prehook": "hooks/dump-db.sh"
}
After each backup, restic runs the configured retention policy (forget --prune) followed by an integrity check.
rsync
A local mirror copy. Supports an optional remote_dest for off-site replication.
"music": {
"type": "rsync",
"src": "${fast}/media/music",
"dest": "${storage}/media/music",
"remote_dest": "user@offsite:/backups/music"
}
s3
A mirror sync to a cloud bucket via rclone sync. The destination uses an rclone remote name (configured with rclone config), e.g. myremote:bucket/path.
"photos-cloud": {
"type": "s3",
"src": "${fast}/media/photos",
"dest": "backup-bucket:backups/photos"
}
Note: rclone sync mirrors the source — files removed locally are deleted remotely.
sqlite
Versioned snapshots of a SQLite database using sqlite3's online backup API (safe while the DB is in use, handles WAL mode). The consistent snapshot is backed up into a restic repo, so you get restic's deduplication and battle-tested forget retention policies.
"myapp-db": {
"type": "sqlite",
"src": "/srv/myapp/data.db",
"repo": "${storage}/backrest/db/myapp",
"retention": "keep-daily 7 keep-weekly 4 keep-monthly 6"
}
retention is a standard restic forget policy (keep-last, keep-daily, keep-weekly, keep-monthly, keep-yearly). Defaults to keep-last 10.
Profile fields
| Field | Type | Description |
|---|---|---|
enabled |
bool | When false, skipped during "run all". Can still be run explicitly. Defaults to true. |
type |
string | restic, rsync, s3, or sqlite |
src |
string | Source path. Supports ${path} placeholders and host:path remote references. For sqlite, the path to the .db file. |
repo |
string | (restic/sqlite) Path to the restic repository. Created automatically. |
dest |
string | (rsync/s3) Local destination directory (rsync) or rclone remote path like remote:bucket/path (s3). |
remote_dest |
string | (rsync) Optional second destination, e.g. user@host:/path. |
retention |
string | (restic/sqlite) Restic forget policy. For sqlite: keep-last, keep-daily, keep-weekly, keep-monthly, keep-yearly. Defaults to keep-last 10. |
exclude |
array | Glob patterns to skip. |
prehook |
string | Script to run before backup (e.g. database dump, stop container). |
posthook |
string | Script to run after backup, even on failure (e.g. restart container). |
check |
string | Per-profile integrity check override (full, quick, never). |
Usage
./backrest run all enabled profiles
./backrest photos run a single profile
./backrest --dry-run preview what would run
./backrest --validate validate config against schema
./backrest --config /path/to/backup.json use a different config
./backrest --progress show rsync progress in terminal
Hooks
Pre/post hooks are executable scripts that run around each profile. They source hooks/.env for secrets (git-ignored).
Copy hooks/.env.example to hooks/.env and fill in real values.
Example pre-hook that dumps a database before backup:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$SCRIPT_DIR/lib.sh"
load_env_file "$SCRIPT_DIR/hooks/.env"
docker exec myapp pg_dump -U postgres > /tmp/backup.sql
Scheduling
Add a crontab entry:
0 2 * * * /path/to/backrest >> /var/log/backrest.log 2>&1
Or see SYSTEMD_TIMER.txt for systemd timer setup.
Logs
Logs are written to the directory set in log_dir (defaults to ~/.local/log/backrest). Old logs are cleaned up based on log_retention_days.
Password setup
restic needs a repository password. Either generate one manually:
mkdir -p ~/.config/backrest
chmod 700 ~/.config/backrest
openssl rand -base64 32 | tr -d '\n' > ~/.config/backrest/restic-password
chmod 600 ~/.config/backrest/restic-password
Then set restic_password_file in global.
Or set "restic_password_autogen": true in global and backrest generates the password into ~/.config/backrest/restic-password on first run (reusing it on later runs). Note: existing restic repos can only be decrypted with the password they were created with, so don't switch methods on an existing repo.
Healthchecks.io
Set healthchecks_url in global to your check URL. backrest pings:
<url>/startat the beginning<url>on success<url>/failon failure