A lightweight backup daemon that schedules and runs restic jobs on a Linux server. It reads a YAML config, fires jobs on cron schedules, retries on failure, and handles periodic maintenance (pruning old snapshots, checking repository integrity). Runs as a systemd service.
The binary reads config.yaml on startup, registers every job with a 6-field
cron scheduler, then sits in the background waiting. When a job fires, it
acquires an exclusive file lock (so concurrent runs of the same job are
impossible), calls restic backup, and retries up to the configured number
of times if something goes wrong. A separate maintenance job runs on its own
schedule and calls restic forget --prune and restic check.
All output from restic is streamed line-by-line to a structured log file.
config.yaml
└── scheduler (robfig/cron)
├── job: restic backup <path> ← retries, file lock
├── job: restic backup <path> ← retries, file lock
└── maintenance: forget + check
| Language | Go 1.26 |
| Scheduler | robfig/cron v3 |
| Backup engine | restic (external binary) |
| Remote storage | Backblaze B2 (or any restic backend) |
| Logging | log/slog (structured text) |
| Process locking | syscall.Flock |
| Service manager | systemd |
- Go 1.26+
resticinstalled- A restic repository already initialized
version: 3
restic:
binary: "/usr/bin/restic" # path to the restic binary
repository: "b2:bucket:path" # restic repository URL
logging:
level: "info" # debug | info | warn | error
max_size_mb: 1 # optional, default 1
max_backups: 3 # optional, default 3
retention:
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
maintenance:
schedule: "0 50 4 * * *" # runs at 04:50 every day
forget: true # run restic forget --prune
check: true # run restic check
jobs:
- name: "my-backup"
type: "directory"
schedule: "0 0 3 * * *" # runs at 03:00 every day
retry:
max_attempts: 2
delay: "1m"
tags: ["my-tag"]
directory:
path: "/data/my-dir"
excludes: ["tmp", "cache"]Schedules use 6-field cron format:
second minute hour day-of-month month day-of-week.
RESTIC_REPOSITORY="b2:bucket:path"
RESTIC_PASSWORD="restic-repo-password"
B2_ACCOUNT_ID="backblaze-account-id"
B2_ACCOUNT_KEY="backblaze-application-key"
HOME="/home/username"git clone https://github.com/felipe-heredia/backup-orchestrator
cd backup-orchestrator
# Create required runtime directories
make dev-setup
# Create your secrets file
cp config/.env.example config/.env
# edit config/.env with real credentials
# Validate config and exit
go run ./cmd/orchestrator --validate
# Run a single job immediately without writing anything
go run ./cmd/orchestrator --dry-run --run-now "my-backup"
# Run the daemon
go run ./cmd/orchestrator# 1. Make sure config/.env exists with real credentials
# 2. Build and install (copies binary, config, and service file)
make install
# 3. Start it
systemctl start backup-orchestrator
systemctl status backup-orchestrator
# Follow the logs
journalctl -u backup-orchestrator -f
# Uninstall
make uninstallmake install copies the binary to /usr/local/bin, config files to
/etc/backup-orchestrator/, and enables the systemd unit. Edit
config/backup-orchestrator.service to change the user, paths, or security
settings before installing.
| Flag | Default | Description |
|---|---|---|
--config |
config/config.yaml |
Path to the YAML config file |
--env-file |
config/.env |
Path to the secrets file |
--log-file |
logs/backup-orchestrator.log |
Where to write logs |
--lock-dir |
backup-orchestrator.lock/ |
Directory for job lock files |
--validate |
false |
Validate config and exit |
--dry-run |
false |
Pass --dry-run to restic (no writes) |
--run-now |
"" |
Run a named job immediately and exit |