#!/bin/bash # # Weekly restic maintenance for both repos (Sunday, offset from the # nightly backups — prune takes an EXCLUSIVE lock). Per repo: # - unlock: clear locks left by crashed runs # - prune: --max-unused 10% (our dead-data rate is ~0.1–0.4 GiB/day # vs ~1 T repos, so pruning can skip repacking for months) and # --max-repack-size 4G (bounds any single Sunday's rewrite to # ~10-15 min even after a mass deletion; the rest defers) # - check: structure every week, plus a rotating 1/52 data subset — # a full verification of every byte once a year # # Usage: restic-maintenance.sh [-r [,...]] set -u REPOS="s3:s3.sbg.io.cloud.ovh.net/restic-data,s3:s3.sbg.io.cloud.ovh.net/restic-incus" LOCKFILE=/run/lock/restic-maintenance.lock ENVFILE=/root/.restic-env RESTIC=/usr/local/bin/restic # cron PATH lacks /usr/local/bin while getopts r: flag; do case "${flag}" in r) REPOS=${OPTARG};; *) echo "Usage: $0 [-r [,...]]" >&2; exit 2;; esac done [ -r "$ENVFILE" ] || { echo "env file $ENVFILE not readable" >&2; exit 2; } . "$ENVFILE" exec 9>"$LOCKFILE" if ! flock -n 9; then echo "another restic-maintenance run holds $LOCKFILE, aborting" >&2 exit 1 fi log() { echo "[$(date '+%F %T')] $*"; } # rotate the read-data subset weekly: full coverage once a year WEEK=$(( ($(date +%s) / 604800) % 52 + 1 )) rc=0 for repo in $(printf '%s' "$REPOS" | tr ',' ' '); do log "maintenance: $repo" $RESTIC -r "$repo" unlock || rc=1 $RESTIC -r "$repo" prune --max-unused 10% --max-repack-size 4G --pack-size 64 || rc=1 $RESTIC -r "$repo" check || rc=1 log "check --read-data-subset=$WEEK/52" $RESTIC -r "$repo" check --read-data-subset="$WEEK/52" || rc=1 done log "done (rc=$rc)" exit $rc