#!/usr/bin/env bash set -euo pipefail #################### CONFIG #################### LUKS_SOURCE="/dev/disk/by-uuid/bdd2a016-b6db-493d-ad0a-e8dd8a93dfbd" MAPPER_NAME="usb_safe" MOUNTPOINT="/mnt/usb_safe" RAW_DEVICE="" # Source vault (docs/vault) SOURCE_VAULT_MOUNT="$HOME/docs/vault" SOURCE_VAULT_IMAGE="$HOME/secure/containers/work-vault.img" SOURCE_VAULT_MAPPER="vault" # Tomb SECRETS_TOMB_FILE="$HOME/.secrets.tomb" SECRETS_TOMB_KEY="$HOME/.secrets.tomb.key" SECRETS_TOMB_MOUNT="/media/.secrets" BACKUP_ITEMS=( "$HOME/.abook" "$HOME/.config" "$HOME/docs" "$HOME/git" "$HOME/.password-store" "$HOME/secure" "$HOME/.password.tomb" "$HOME/.password.tomb.key" "$HOME/.secrets.tomb" "$HOME/.secrets.tomb.key" "/media/.secrets" "$HOME/.bitmonero" "$HOME/.electrum" "$HOME/.zotero-data" ) 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}"; } require_mount() { local path="$1" local label="$2" [[ -e "$path" ]] || die "${label} path missing: ${path}" mountpoint -q "$path" || die "${label} is not mounted: ${path}" } OPENED_SOURCE_VAULT=0 OPENED_SECRETS_TOMB=0 ensure_source_vault_open() { if mountpoint -q "${SOURCE_VAULT_MOUNT}"; then echo ">>> Source vault already mounted" return fi echo ">>> Opening source vault..." mkdir -p "${SOURCE_VAULT_MOUNT}" if [[ ! -e "/dev/mapper/${SOURCE_VAULT_MAPPER}" ]]; then sudo cryptsetup open "${SOURCE_VAULT_IMAGE}" "${SOURCE_VAULT_MAPPER}" fi sudo mount "/dev/mapper/${SOURCE_VAULT_MAPPER}" "${SOURCE_VAULT_MOUNT}" OPENED_SOURCE_VAULT=1 } ensure_secrets_tomb_open() { if mountpoint -q "${SECRETS_TOMB_MOUNT}"; then echo ">>> Secrets tomb already mounted" return fi echo ">>> Opening secrets tomb..." tomb open "${SECRETS_TOMB_FILE}" -k "${SECRETS_TOMB_KEY}" "${SECRETS_TOMB_MOUNT}" OPENED_SECRETS_TOMB=1 } cleanup() { set +e if is_mounted; then echo ">>> Unmounting backup drive..." sudo umount "${MOUNTPOINT}" || true fi if is_mapper_open; then echo ">>> Closing backup mapper..." sudo cryptsetup close "${MAPPER_NAME}" || true fi if [[ "${OPENED_SECRETS_TOMB}" -eq 1 ]]; then echo ">>> Closing secrets tomb..." tomb close "${SECRETS_TOMB_MOUNT}" || true fi if [[ "${OPENED_SOURCE_VAULT}" -eq 1 ]]; then echo ">>> Closing source vault..." sudo umount "${SOURCE_VAULT_MOUNT}" || true sudo cryptsetup close "${SOURCE_VAULT_MAPPER}" || true fi if [[ -n "${RAW_DEVICE}" ]]; then udisksctl power-off -b "${RAW_DEVICE}" >/dev/null 2>&1 || true fi } trap cleanup EXIT #################### OPEN DESTINATION #################### echo ">>> Ensuring backup mountpoint exists..." sudo mkdir -p "${MOUNTPOINT}" if is_mounted; then echo ">>> Backup drive already mounted at ${MOUNTPOINT}" elif is_mapper_open; then echo ">>> Backup mapper already open, mounting..." sudo mount "/dev/mapper/${MAPPER_NAME}" "${MOUNTPOINT}" else echo ">>> Opening backup LUKS..." sudo cryptsetup open "${LUKS_SOURCE}" "${MAPPER_NAME}" echo ">>> Mounting backup filesystem..." sudo mount "/dev/mapper/${MAPPER_NAME}" "${MOUNTPOINT}" fi #################### OPEN SOURCES #################### ensure_source_vault_open ensure_secrets_tomb_open require_mount "${SOURCE_VAULT_MOUNT}" "Vault" require_mount "${SECRETS_TOMB_MOUNT}" "Secrets" #################### BACKUP #################### echo "[$(date)] Backup start" >> "$LOGFILE" ANY_WARNING=0 for SRC in "${BACKUP_ITEMS[@]}"; do [[ -e "${SRC}" ]] || die "Missing: ${SRC}" NAME="$(basename "${SRC}")" DEST="${MOUNTPOINT}/${NAME}" echo ">>> ${SRC} -> ${DEST}" echo "[$(date)] rsync: ${SRC} -> ${DEST}" >> "$LOGFILE" set +e if [[ -d "${SRC}" ]]; then rsync "${RSYNC_OPTS[@]}" "${SRC}/" "${DEST}/" 2>>"$LOGFILE" else 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 "[$(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 if [[ "${ANY_WARNING}" -eq 1 ]]; then echo ">>> Backup completed WITH WARNINGS" echo "[$(date)] Backup completed WITH WARNINGS" >> "$LOGFILE" else echo ">>> Backup completed OK" echo "[$(date)] Backup completed OK" >> "$LOGFILE" fi echo ">>> Done" sync