Bring Your Own Backup Tool
Use restic, Borg, Bacula, Veeam, or any other backup agent to ship the bundles off-site. The Operaide backup script writes the tar locally and stops; your tool watches <project>/backup/backups/ and handles transport, retention, and verification on its own schedule.
This pattern fits when your tool already has a runtime (daemon, agent, sidecar) or when corporate policy mandates a specific backup product. It uses two cron jobs: one for the Operaide backup, one for your tool.
When to pick this pattern
- Your organization standardizes on one backup product across hosts.
- You want deduplication, encryption, or compression beyond what a plain tar provides.
- Your tool already has a job scheduler and you do not want a second mechanism.
- You need point-in-time recovery, browseable snapshots, or chain-of-trust signing.
If none of those apply, the transfer.sh hook is simpler. Reach for BYO when you have a real reason.
Prerequisites
- Backup setup already enabled on the Operaide host. See Enable Backups first.
- Your tool installed on the Operaide host (or on a bastion that mounts the backup directory read-only).
- A repository or destination already configured in your tool.
Step 1: leave transfer.sh as the no-op stub
Open <project>/backup/transfer.sh. Either leave the seeded stub untouched (it does nothing) or replace its body with an explicit no-op for clarity:
#!/usr/bin/env bash
# transfer.sh — intentionally empty.
# Off-site transport runs on its own schedule via <your tool>.
exit 0
The Operaide backup script invokes this on every run; an exit 0 finishes immediately and does not interfere with your tool.
Step 2: configure your tool to watch backup/backups/
The exact configuration depends on the tool. Two examples:
Example: restic
Initialize a restic repository (one-time):
sudo restic -r <your-restic-repo> init
Schedule a cron job that runs after the Operaide backup. If Operaide runs at 03:00, run restic at 04:00 to give the bundle time to land:
0 4 * * * /usr/local/bin/operaide-restic-backup.sh >> /var/log/operaide-restic.log 2>&1
/usr/local/bin/operaide-restic-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
export RESTIC_REPOSITORY="<your-restic-repo>"
export RESTIC_PASSWORD_FILE=/root/.restic-password
PROJECT="/path/to/your/project"
# 1. Push every untransferred tar into restic.
for tar in "$PROJECT"/backup/backups/full-backup-*.tar; do
[ -f "$tar" ] || continue
[ -f "$tar.transferred" ] && continue # already shipped
restic backup "$tar" "$tar.sha256"
touch "$tar.transferred"
done
# 2. Apply retention. Keep last 30 daily, 12 monthly snapshots.
restic forget --keep-daily 30 --keep-monthly 12 --prune
The script touches <tar>.transferred after restic confirms the snapshot. The Operaide UI sees the sidecar and marks the bundle as transferred.
Example: Borg
Same shape, different commands:
#!/usr/bin/env bash
set -euo pipefail
export BORG_REPO="user@backup-host:/srv/operaide-borg"
export BORG_PASSPHRASE_FILE=/root/.borg-passphrase
PROJECT="/path/to/your/project"
for tar in "$PROJECT"/backup/backups/full-backup-*.tar; do
[ -f "$tar" ] || continue
[ -f "$tar.transferred" ] && continue
borg create "::operaide-$(basename "$tar")" "$tar" "$tar.sha256"
touch "$tar.transferred"
done
borg prune --keep-daily 30 --keep-monthly 12
Example: managed agent (Veeam, Bacula, others)
Most managed agents work in "watch a directory" mode. Configure the agent to:
- Source path:
/path/to/your/project/backup/backups/. - File pattern:
full-backup-*.tarandfull-backup-*.tar.sha256. - Post-action: after a successful job, run
touch <source-file>.transferredon the Operaide host.
If your agent cannot run a post-action against the Operaide host, configure it to delete the source file after a successful copy. Local deletion is the alternative "transferred" signal.
Step 3: signal "transferred"
The Operaide UI and the transport-stuck health check both watch the filesystem for one of two signals next to each full-backup-*.tar:
- A sibling marker file
<tar>.transferred(any content, empty is fine), or - The tar itself is gone.
Whichever your tool can produce reliably, use it. Without one, the bundle starts showing as transport-stuck on the Backups page after 7 days.
Step 4: confirm the transferred badge
After your tool's first successful run, refresh System Admin -> Backups. The Outer-block card should show Transferred (green check). If it shows Pending transfer, neither signal made it onto disk.
Step 5: separate retention
You now have two retention policies:
- Local on the Operaide host:
OPERAIDE_BACKUP_MIN_FREE_BYTES(default 5 GiB). The inner-backup script evicts the oldest local tar when free space drops below the threshold, but it refuses to evict the only remaining tar. - Remote in your tool: restic
forget, Borgprune, Veeam policy, etc.
Set both. Local eviction protects the Operaide host's disk; remote retention protects against cost growth and mistakes on a single tar.
Step 6: drill the restore path through your tool
The standard Recovery Drill assumes the bundle is reachable directly. With BYO, the drill must include retrieving the bundle from your tool's repository:
- Restore one full-backup tar from your tool's repo onto the spare host (
restic restore --target /tmp ...). - Verify the sha256 against the sidecar.
- Run
bash operaide-outer-restore.sh /tmp/full-backup-...tar. - Verify the data.
- Tear down.
If your tool's restore step is fiddly, the drill is also where the operator learns it. Document the exact commands in your operations runbook.
Common mistakes
- No "transferred" signal. Bundles look like they were shipped, but the UI keeps showing
Pending transferand eventually flips totransport-stuck. Decide between.transferredsidecar and source-deletion before you finish the job. - Race between Operaide cron and your tool. If your tool fires while Operaide is still writing the tar, it picks up a partial file. Keep the schedules at least 30 minutes apart, or have your tool check for
<tar>.sha256(only present after the tar is finalized) before processing. - Same credentials in two places. Restic/Borg passphrases and repository URLs end up in the cron script. They are NOT in the bundle (
transfer.shis, but the BYO cron script is somewhere else). Treat both with the same care. - No retention on either side. Local fills up, remote fills up. Set both, separately.
- Drill skips your tool. A working
operaide-outer-restore.shis half the drill. The other half is "I can pull the bundle out of restic in under 10 minutes during an incident." Practice that too.