#!/usr/bin/env bash # offload-backup.sh — back up /mnt/data directly to a USB drive using restic. # # Run on the Pi (see homey-offload-backup in the dev shell). # Scans for plugged-in USB partitions, lets you pick one, mounts it if needed, # initialises a restic repo on it, and runs a backup of all service data dirs. # # The restic password is read from the sops-managed secret at runtime; # no S3 credentials are needed — this is a direct local backup. # # Usage: sudo bash offload-backup.sh set -euo pipefail REPO_NAME="homey-backup" DATA_DIR="/mnt/data" PASSWORD_FILE="/run/secrets/restic/password" BACKUP_PATHS=( "$DATA_DIR/openldap" "$DATA_DIR/authelia" "$DATA_DIR/gitea" "$DATA_DIR/nextcloud" "$DATA_DIR/jellyfin" "$DATA_DIR/transmission" ) EXCLUDE_ARGS=( --exclude "$DATA_DIR/nextcloud/db" --exclude "$DATA_DIR/restic-cache" ) # --------------------------------------------------------------------------- # Find USB partitions # --------------------------------------------------------------------------- echo "Scanning for USB drives..." mapfile -t USB_PARTS < <( lsblk -o NAME,SIZE,TRAN,LABEL,MOUNTPOINT -rn \ | awk '$3 == "usb" && $2 != "" {print $1, $2, $4, $5}' ) if [ "${#USB_PARTS[@]}" -eq 0 ]; then echo "No USB partitions found. Plug in a USB drive and try again." >&2 exit 1 fi echo "" echo "Available USB partitions:" for i in "${!USB_PARTS[@]}"; do read -r dev size label mount <<< "${USB_PARTS[$i]}" label="${label:-(no label)}" mount="${mount:-(not mounted)}" printf " [%d] /dev/%s %s label=%s mount=%s\n" \ "$((i + 1))" "$dev" "$size" "$label" "$mount" done echo "" read -rp "Select a partition [1-${#USB_PARTS[@]}]: " CHOICE if ! [[ "$CHOICE" =~ ^[0-9]+$ ]] \ || [ "$CHOICE" -lt 1 ] \ || [ "$CHOICE" -gt "${#USB_PARTS[@]}" ]; then echo "Invalid selection." >&2 exit 1 fi read -r SELECTED_DEV _ _ EXISTING_MOUNT <<< "${USB_PARTS[$((CHOICE - 1))]}" SELECTED_DEV="/dev/$SELECTED_DEV" # --------------------------------------------------------------------------- # Mount if needed # --------------------------------------------------------------------------- MOUNTED_HERE=false MOUNT_DIR="" if [ -n "$EXISTING_MOUNT" ]; then MOUNT_DIR="$EXISTING_MOUNT" echo "Using existing mount at $MOUNT_DIR" else MOUNT_DIR=$(mktemp -d) echo "Mounting $SELECTED_DEV at $MOUNT_DIR..." mount "$SELECTED_DEV" "$MOUNT_DIR" MOUNTED_HERE=true fi cleanup() { if [ "$MOUNTED_HERE" = true ] && [ -n "$MOUNT_DIR" ]; then echo "Unmounting $MOUNT_DIR..." umount "$MOUNT_DIR" rmdir "$MOUNT_DIR" fi } trap cleanup EXIT # --------------------------------------------------------------------------- # Initialise restic repo if this is the first run # --------------------------------------------------------------------------- REPO="$MOUNT_DIR/$REPO_NAME" if ! restic -r "$REPO" --password-file "$PASSWORD_FILE" snapshots &>/dev/null 2>&1; then echo "Initialising restic repository at $REPO..." restic -r "$REPO" --password-file "$PASSWORD_FILE" init fi # --------------------------------------------------------------------------- # Run the backup # --------------------------------------------------------------------------- echo "" echo "Backing up to $REPO..." restic -r "$REPO" \ --password-file "$PASSWORD_FILE" \ --cache-dir "$DATA_DIR/restic-cache" \ backup \ "${BACKUP_PATHS[@]}" \ "${EXCLUDE_ARGS[@]}" echo "" echo "Snapshots on this drive:" restic -r "$REPO" --password-file "$PASSWORD_FILE" snapshots