Skip to content

Instantly share code, notes, and snippets.

@Daij-Djan
Created September 1, 2026 15:58
Show Gist options
  • Select an option

  • Save Daij-Djan/9aeba540071de0b8da6fc4a29116070e to your computer and use it in GitHub Desktop.

Select an option

Save Daij-Djan/9aeba540071de0b8da6fc4a29116070e to your computer and use it in GitHub Desktop.
dropbox-backup.sh - script to backup dropbox via rclone
#!/usr/bin/env bash
set -euo pipefail
# ── dropbox-backup ───────────────────────────────────────────────────────────
# Ad-hoc, on-demand backup of the ENTIRE Dropbox account to the LaCie drive.
#
# Pulls straight from the Dropbox cloud over the API via rclone. It does NOT
# read, touch, or depend on the local Dropbox sync folder
# (~/Library/CloudStorage/Dropbox), so it can't disturb normal Dropbox sync,
# and it also captures the ~4k files that are currently "online only".
#
# Layout created on the drive:
# /Volumes/LaCie/Dropbox-Backup/current/ exact mirror of Dropbox now
# /Volumes/LaCie/Dropbox-Backup/_archive/<stamp>/ files that were deleted or
# replaced, kept per run
# /Volumes/LaCie/Dropbox-Backup/logs/<stamp>.log full rclone log per run
#
# Nothing is ever permanently deleted from the drive: a file removed or changed
# in Dropbox is moved into that run's _archive/ folder before the mirror is
# updated.
#
# Usage:
# dropbox-backup real run
# dropbox-backup --dry-run show what would change, transfer nothing
# dropbox-backup --help
# Extra arguments are passed straight through to `rclone sync`
# (e.g. --dry-run, --bwlimit 20M, --verbose).
# ────────────────────────────────────────────────────────────────────────────
REMOTE="dropbox:"
MOUNT="/Volumes/LaCie"
DEST="$MOUNT/Dropbox-Backup"
MIRROR="$DEST/current"
STAMP="$(date +%Y-%m-%d_%H%M%S)"
ARCHIVE="$DEST/_archive/$STAMP"
LOGDIR="$DEST/logs"
LOG="$LOGDIR/$STAMP.log"
c_red=$'\033[31m'; c_grn=$'\033[32m'; c_cyn=$'\033[36m'; c_off=$'\033[0m'
info() { printf '%s▸ %s%s\n' "$c_cyn" "$*" "$c_off"; }
die() { printf '\n%s✗ %s%s\n' "$c_red" "$*" "$c_off" >&2; exit 1; }
case "${1:-}" in
-h|--help) sed -n '4,27p' "$0" | sed 's/^#\{1,\} \{0,1\}//'; exit 0 ;;
esac
# Create and announce the log file first, before any checks, so it exists
# on disk immediately and you can start tailing it right away.
mkdir -p "$LOGDIR"
: > "$LOG"
info "Log: $LOG"
info "Tail it with: tail -f \"$LOG\""
echo
# ── preflight ───────────────────────────────────────────────────────────────
command -v rclone >/dev/null || die "rclone not installed — run: brew install rclone"
rclone listremotes | grep -qx "$REMOTE" \
|| die "rclone remote '$REMOTE' is not set up. Run once: rclone config create dropbox dropbox"
[[ -d "$MOUNT" ]] && mount | grep -q " on $MOUNT " \
|| die "LaCie drive is not mounted at $MOUNT — plug it in / unlock it and retry"
[[ -w "$MOUNT" ]] || die "$MOUNT is not writable"
info "Checking the Dropbox remote is reachable…"
rclone lsd "$REMOTE" --max-depth 1 >/dev/null \
|| die "Can't reach Dropbox. Re-authorise with: rclone config reconnect $REMOTE"
mkdir -p "$MIRROR" "$LOGDIR"
avail_gb="$(df -g "$MOUNT" | awk 'NR==2 {print $4}')"
[[ -n "${avail_gb:-}" && "$avail_gb" -lt 60 ]] \
&& info "WARNING: only ${avail_gb} GB free on the LaCie"
# ── run ─────────────────────────────────────────────────────────────────────
progress=(--stats=30s --stats-one-line)
[[ -t 1 ]] && progress=(--progress)
info "Source : $REMOTE"
info "Mirror : $MIRROR"
info "Archive: $ARCHIVE (only created if something is removed/changed)"
echo
set +e
rclone sync "$REMOTE" "$MIRROR" \
--backup-dir "$ARCHIVE" \
--create-empty-src-dirs \
--fast-list \
--modify-window 2s \
--transfers 8 \
--checkers 16 \
--retries 5 \
--low-level-retries 10 \
--exclude ".DS_Store" \
--exclude ".dropbox" \
--exclude ".dropbox.attr" \
--log-file "$LOG" \
--log-level INFO \
"${progress[@]}" \
"$@"
rc=$?
set -e
# Remove the archive dir again if this run didn't move anything into it.
[[ -d "$ARCHIVE" ]] && rmdir "$ARCHIVE" 2>/dev/null && \
info "Nothing was deleted or replaced this run."
echo
if [[ $rc -eq 0 ]]; then
size="$(du -sh "$MIRROR" 2>/dev/null | cut -f1)"
printf '%s✓ Backup complete.%s Mirror is %s on the LaCie.\n' "$c_grn" "$c_off" "${size:-?}"
[[ -d "$ARCHIVE" ]] && printf ' Archived this run: %s (%s)\n' \
"$(du -sh "$ARCHIVE" 2>/dev/null | cut -f1)" "$ARCHIVE"
else
die "rclone exited with code $rc — see $LOG"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment