#!/usr/bin/env bash set -Eeuo pipefail usage() { cat <<'USAGE' Usage: fedora-backup-home DESTINATION [options] Rsync your home folder to an external drive or mounted network share. Dry-run is the default. Add --run when the preview looks right. Examples: fedora-backup-home /run/media/$USER/BackupDrive fedora-backup-home /run/media/$USER/BackupDrive --run fedora-backup-home /mnt/nas/backups --run --delete Options: --run Actually copy files. Without this, rsync runs in dry-run mode. --delete Delete files in the backup that no longer exist in your home folder. -h, --help Show this help. USAGE } log() { printf '\n\033[1m==> %s\033[0m\n' "$*"; } die() { printf '\033[31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } [[ $# -gt 0 ]] || { usage; exit 1; } DEST_ROOT="" RUN=false DELETE=false while [[ $# -gt 0 ]]; do case "$1" in --run) RUN=true ;; --delete) DELETE=true ;; -h|--help) usage; exit 0 ;; -*) die "Unknown option: $1" ;; *) if [[ -z "$DEST_ROOT" ]]; then DEST_ROOT="$1"; else die "Unexpected extra argument: $1"; fi ;; esac shift done [[ -n "$DEST_ROOT" ]] || die "Missing DESTINATION." command -v rsync >/dev/null 2>&1 || die "rsync is not installed. Run: sudo dnf5 install rsync" [[ -d "$DEST_ROOT" ]] || die "Destination does not exist: $DEST_ROOT" HOST="$(hostname -s 2>/dev/null || hostname)" DEST="$DEST_ROOT/${HOST}-${USER}-home" mkdir -p "$DEST" # Prevent accidental recursive backup if destination is inside $HOME. case "$(realpath -m "$DEST")" in "$(realpath -m "$HOME")"/*) die "Destination is inside your home folder; choose an external path." ;; esac RSYNC_OPTS=(-aAXH --human-readable --info=progress2) [[ "$RUN" == "false" ]] && RSYNC_OPTS+=(--dry-run --itemize-changes) [[ "$DELETE" == "true" ]] && RSYNC_OPTS+=(--delete) EXCLUDES=( --exclude='/.cache/' --exclude='/.local/share/Trash/' --exclude='/.var/app/*/cache/' --exclude='/Downloads/*.iso' --exclude='/Downloads/*.img' --exclude='/Downloads/*.qcow2' --exclude='/.npm/_cacache/' --exclude='/.cargo/registry/' --exclude='/.cargo/git/' --exclude='/.gradle/caches/' --exclude='/**/node_modules/' --exclude='/**/.venv/' ) log "Backing up $HOME to $DEST" if [[ "$RUN" == "false" ]]; then printf 'Mode: DRY RUN. Add --run to actually copy files.\n' else printf 'Mode: REAL RUN.\n' fi [[ "$DELETE" == "true" ]] && printf 'Delete mode: enabled.\n' rsync "${RSYNC_OPTS[@]}" "${EXCLUDES[@]}" "$HOME/" "$DEST/" log "Done" if [[ "$RUN" == "false" ]]; then printf 'Review the dry-run above, then rerun with --run when ready.\n' fi