Skip to main content
Version: 3.1

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:

  1. Source path: /path/to/your/project/backup/backups/.
  2. File pattern: full-backup-*.tar and full-backup-*.tar.sha256.
  3. Post-action: after a successful job, run touch <source-file>.transferred on 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, Borg prune, 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:

  1. Restore one full-backup tar from your tool's repo onto the spare host (restic restore --target /tmp ...).
  2. Verify the sha256 against the sidecar.
  3. Run bash operaide-outer-restore.sh /tmp/full-backup-...tar.
  4. Verify the data.
  5. 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 transfer and eventually flips to transport-stuck. Decide between .transferred sidecar 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.sh is, 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.sh is 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.