aboutsummaryrefslogtreecommitdiff
path: root/usb-backup
blob: 8c81b69ff794f768025402de592091de66126b10 (plain) (blame)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env bash
set -euo pipefail

#################### CONFIG ####################

# LUKS2 container UUID (LUKS header UUID)
LUKS_SOURCE="/dev/disk/by-uuid/bdd2a016-b6db-493d-ad0a-e8dd8a93dfbd"

# Name for /dev/mapper/<name>
MAPPER_NAME="usb_safe"

# Mountpoint for decrypted filesystem
MOUNTPOINT="/mnt/usb_safe"

# Optional underlying block device for power-off (set "" to disable)
RAW_DEVICE=""

# Items to mirror from $HOME to the encrypted drive (files and directories)
BACKUP_ITEMS=(
  "$HOME/.abook"
  "$HOME/.config"
  "$HOME/Documents"
  "$HOME/dotfiles"
  "$HOME/.password-store"
  "$HOME/secure"
  "$HOME/vimwiki"
  "$HOME/.password.tomb"
  "$HOME/.secrets.tomb"
)

RSYNC_OPTS=(
  -a
  --delete
  --no-perms
  --no-owner
  --no-group
  --no-xattrs
  --omit-dir-times
  --progress
)

LOGFILE="$HOME/backup_flash_rsync.log"

#################### HELPERS ####################

die() { echo "ERROR: $*" >&2; exit 1; }

is_mapper_open() { [[ -e "/dev/mapper/${MAPPER_NAME}" ]]; }
is_mounted()     { mountpoint -q "${MOUNTPOINT}"; }

busy_report() {
  echo ">>> Busy report for ${MOUNTPOINT}:"
  echo ">>> lsof:"
  sudo lsof +f -- "${MOUNTPOINT}" || true
  echo ">>> fuser:"
  sudo fuser -vm "${MOUNTPOINT}" || true
}

cleanup() {
  set +e

  if is_mounted; then
    echo ">>> Unmounting ${MOUNTPOINT}..."
    sudo umount "${MOUNTPOINT}" || {
      echo ">>> Unmount failed (device busy)."
      busy_report
      echo ">>> Last resort: sudo umount -l '${MOUNTPOINT}'"
    }
  fi

  if is_mapper_open; then
    echo ">>> Closing LUKS mapper ${MAPPER_NAME}..."
    sudo cryptsetup close "${MAPPER_NAME}" || true
  fi

  if [[ -n "${RAW_DEVICE}" ]]; then
    echo ">>> Powering off ${RAW_DEVICE}..."
    udisksctl power-off -b "${RAW_DEVICE}" >/dev/null 2>&1 || true
  fi
}

trap cleanup EXIT

#################### OPEN + MOUNT ####################

echo ">>> Checking LUKS source exists: ${LUKS_SOURCE}"
[[ -e "${LUKS_SOURCE}" ]] || die "LUKS source not found: ${LUKS_SOURCE}"

echo ">>> Ensuring mountpoint exists: ${MOUNTPOINT}"
sudo mkdir -p "${MOUNTPOINT}"

is_mounted     && die "Mountpoint already in use: ${MOUNTPOINT}"
is_mapper_open && die "Mapper already open: /dev/mapper/${MAPPER_NAME}"

echo ">>> Opening LUKS2 container (prompt for passphrase)..."
sudo cryptsetup open --type luks2 "${LUKS_SOURCE}" "${MAPPER_NAME}"

echo ">>> Mounting decrypted filesystem..."
sudo mount "/dev/mapper/${MAPPER_NAME}" "${MOUNTPOINT}"

echo ">>> Mounted at ${MOUNTPOINT}"
echo ">>> Logging rsync output to: ${LOGFILE}"
echo "[$(date)] Backup run started" >> "${LOGFILE}"

#################### RSYNC LOOP ####################

ANY_WARNING=0

for SRC in "${BACKUP_ITEMS[@]}"; do
  [[ -e "${SRC}" ]] || die "Missing source item: ${SRC}"

  NAME="$(basename "${SRC}")"
  DEST="${MOUNTPOINT}/${NAME}"

  echo ">>> rsync: ${SRC} -> ${DEST}"
  echo "[$(date)] rsync: ${SRC} -> ${DEST}" >> "${LOGFILE}"

  set +e
  if [[ -d "${SRC}" ]]; then
    rsync "${RSYNC_OPTS[@]}" "${SRC}/" "${DEST}/" 2>> "${LOGFILE}"
  else
    # file
    rsync "${RSYNC_OPTS[@]}" "${SRC}" "${DEST}" 2>> "${LOGFILE}"
  fi
  RS=$?
  set -e

  if [[ "${RS}" -eq 0 ]]; then
    echo ">>> rsync OK: ${SRC}"
    echo "[$(date)] rsync OK: ${SRC}" >> "${LOGFILE}"
  elif [[ "${RS}" -eq 23 || "${RS}" -eq 24 ]]; then
    echo ">>> WARNING: rsync non-critical issues (code ${RS}) for: ${SRC}"
    echo ">>> See ${LOGFILE}."
    echo "[$(date)] WARNING: rsync code ${RS} for: ${SRC}" >> "${LOGFILE}"
    ANY_WARNING=1
  else
    echo "[$(date)] ERROR: rsync failed code ${RS} for: ${SRC}" >> "${LOGFILE}"
    exit "${RS}"
  fi
done

echo ">>> Backup loop finished."
if [[ "${ANY_WARNING}" -eq 1 ]]; then
  echo ">>> Backup completed WITH WARNINGS. See ${LOGFILE}."
  echo "[$(date)] Backup completed WITH WARNINGS" >> "${LOGFILE}"
else
  echo ">>> Backup completed OK."
  echo "[$(date)] Backup completed OK" >> "${LOGFILE}"
fi

echo ">>> Syncing..."
sync

echo ">>> Done. (Auto-cleanup will unmount + close mapper.)"