summaryrefslogtreecommitdiff
path: root/scripts/home-backup
blob: 007967f7a50fd073ec4b53c7efd08e9f469c0ee9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env bash
# Backup the home directory to a remote SSH location using rsync.
#
# Excludes large, regenerable, or already-replicated data (caches, trash,
# build artifacts, git checkouts, syncthing, media libraries, VM images,
# etc.). See the EXCLUDES array below for the full list.
#
# Usage:
#   home-backup                 # Run the backup (real run)
#   home-backup -n              # Dry run (show what would transfer)
#   home-backup -d user@host:/path/backup   # Override destination
#
# The destination can also be set via the BACKUP_DEST environment variable.

set -euo pipefail

declare -r PROGRAM=${0##*/}

# Remote SSH destination. Defaults to the encrypted ZFS dataset on f2
# (zdata/enc/earth-backup, inherits aes-256-gcm encryption from zdata/enc).
# Override with the BACKUP_DEST env var or the -d flag.
declare DEST=${BACKUP_DEST:-"paul@f2.lan:/data/enc/earth-backup"}

# Source directory (trailing slash matters for rsync: copy contents of $HOME).
declare -r SRC="$HOME/"

declare DRY_RUN=""

# Paths/patterns to exclude from the backup. Patterns are relative to $HOME.
declare -ra EXCLUDES=(
  # --- Explicitly excluded by request ---
  "/git/"
  "/syncthing/"
  "/Pictures/"
  "/Music/"
  "/.gradle/"
  "/.thunderbird/"

  # --- Caches / trash / regenerable data ---
  "/.cache/"
  "/.local/share/Trash/"
  "/.local/share/baloo/"
  "/.local/share/containers/"
  "/.local/share/gnome-boxes/"
  "/.npm/"
  "/go/"
  "/.cargo/"
  "/.bun/"
  "/.android/"
  "/.config/google-chrome/"

  # --- Common regenerable junk anywhere in the tree ---
  "node_modules/"
  ".cache/"
  "*.tmp"
  "/.var/app/*/cache/"

  # --- Runtime / volatile ---
  "/.gvfs/"
  "/.dbus/"
)

log() {
  printf '%s\n' "$*"
}

error() {
  log "$*" >&2
}

usage() {
  cat <<EOF
Usage: $PROGRAM [-n] [-d DEST]

  -n           Dry run (show what would be transferred, transfer nothing)
  -d DEST      Remote SSH destination (user@host:/path), overrides BACKUP_DEST
  -h           Show this help

Destination may also be set via the BACKUP_DEST environment variable
or by editing the DEST variable at the top of this script.
EOF
}

main() {
  while getopts ':nd:h' opt; do
    case "$opt" in
      n) DRY_RUN="--dry-run" ;;
      d) DEST="$OPTARG" ;;
      h) usage; exit 0 ;;
      :) error "Option -$OPTARG requires an argument."; usage; exit 2 ;;
      ?) error "Unknown option: -$OPTARG"; usage; exit 2 ;;
    esac
  done

  if [[ "$DEST" == CHANGEME_* || -z "$DEST" ]]; then
    error "No backup destination set."
    error "Set BACKUP_DEST, pass -d user@host:/path, or edit DEST in $PROGRAM."
    exit 1
  fi

  # Build the rsync exclude arguments.
  local -a exclude_args=()
  local pattern
  for pattern in "${EXCLUDES[@]}"; do
    exclude_args+=("--exclude=$pattern")
  done

  log "Source:      $SRC"
  log "Destination: $DEST"
  [[ -n "$DRY_RUN" ]] && log "Mode:        DRY RUN (no data transferred)"
  log ""

  # rsync flags:
  #   -a  archive (recursive, perms, times, symlinks, etc.)
  #   -H  preserve hard links
  #   -A  preserve ACLs
  #   -X  preserve extended attributes
  #   --numeric-ids        don't map uid/gid by name on the remote
  #   --partial --progress resumable transfers with progress output
  #   --delete             mirror: remove remote files that no longer exist locally
  #   --delete-excluded    also remove previously-backed-up excluded paths
  #   --human-readable     human-readable sizes
  #   -e ssh               transport over SSH
  rsync \
    -aHAX \
    --numeric-ids \
    --partial --progress \
    --delete --delete-excluded \
    --human-readable \
    ${DRY_RUN:+$DRY_RUN} \
    -e ssh \
    "${exclude_args[@]}" \
    "$SRC" \
    "$DEST"

  log ""
  log "Backup complete."
}

main "$@"